@@ -33,78 +33,170 @@ Global | |||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||
Debug_Unicode|Win32 = Debug_Unicode|Win32 | |||
Debug_Unicode|x64 = Debug_Unicode|x64 | |||
Debug|Win32 = Debug|Win32 | |||
Debug|x64 = Debug|x64 | |||
Release_Unicode|Win32 = Release_Unicode|Win32 | |||
Release_Unicode|x64 = Release_Unicode|x64 | |||
Release|Win32 = Release|Win32 | |||
Release|x64 = Release|x64 | |||
EndGlobalSection | |||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | |||
{5266973A-47AE-481C-BD4B-06E5DB08A99B}.Debug_Unicode|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{5266973A-47AE-481C-BD4B-06E5DB08A99B}.Debug_Unicode|Win32.Build.0 = Debug_Unicode|Win32 | |||
{5266973A-47AE-481C-BD4B-06E5DB08A99B}.Debug_Unicode|x64.ActiveCfg = Debug_Unicode|Win32 | |||
{5266973A-47AE-481C-BD4B-06E5DB08A99B}.Debug|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{5266973A-47AE-481C-BD4B-06E5DB08A99B}.Debug|Win32.Build.0 = Debug_Unicode|Win32 | |||
{5266973A-47AE-481C-BD4B-06E5DB08A99B}.Debug|x64.ActiveCfg = Release_Unicode|Win32 | |||
{5266973A-47AE-481C-BD4B-06E5DB08A99B}.Debug|x64.Build.0 = Release_Unicode|Win32 | |||
{5266973A-47AE-481C-BD4B-06E5DB08A99B}.Release_Unicode|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{5266973A-47AE-481C-BD4B-06E5DB08A99B}.Release_Unicode|Win32.Build.0 = Release_Unicode|Win32 | |||
{5266973A-47AE-481C-BD4B-06E5DB08A99B}.Release_Unicode|x64.ActiveCfg = Release_Unicode|Win32 | |||
{5266973A-47AE-481C-BD4B-06E5DB08A99B}.Release|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{5266973A-47AE-481C-BD4B-06E5DB08A99B}.Release|Win32.Build.0 = Release_Unicode|Win32 | |||
{5266973A-47AE-481C-BD4B-06E5DB08A99B}.Release|x64.ActiveCfg = Release_Unicode|Win32 | |||
{5266973A-47AE-481C-BD4B-06E5DB08A99B}.Release|x64.Build.0 = Release_Unicode|Win32 | |||
{CCD2D328-1912-4FC4-91B5-2333A7EF6EB7}.Debug_Unicode|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{CCD2D328-1912-4FC4-91B5-2333A7EF6EB7}.Debug_Unicode|Win32.Build.0 = Debug_Unicode|Win32 | |||
{CCD2D328-1912-4FC4-91B5-2333A7EF6EB7}.Debug_Unicode|x64.ActiveCfg = Debug_Unicode|Win32 | |||
{CCD2D328-1912-4FC4-91B5-2333A7EF6EB7}.Debug|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{CCD2D328-1912-4FC4-91B5-2333A7EF6EB7}.Debug|Win32.Build.0 = Debug_Unicode|Win32 | |||
{CCD2D328-1912-4FC4-91B5-2333A7EF6EB7}.Debug|x64.ActiveCfg = Release_Unicode|Win32 | |||
{CCD2D328-1912-4FC4-91B5-2333A7EF6EB7}.Debug|x64.Build.0 = Release_Unicode|Win32 | |||
{CCD2D328-1912-4FC4-91B5-2333A7EF6EB7}.Release_Unicode|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{CCD2D328-1912-4FC4-91B5-2333A7EF6EB7}.Release_Unicode|Win32.Build.0 = Release_Unicode|Win32 | |||
{CCD2D328-1912-4FC4-91B5-2333A7EF6EB7}.Release_Unicode|x64.ActiveCfg = Release_Unicode|Win32 | |||
{CCD2D328-1912-4FC4-91B5-2333A7EF6EB7}.Release|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{CCD2D328-1912-4FC4-91B5-2333A7EF6EB7}.Release|Win32.Build.0 = Release_Unicode|Win32 | |||
{CCD2D328-1912-4FC4-91B5-2333A7EF6EB7}.Release|x64.ActiveCfg = Release_Unicode|Win32 | |||
{CCD2D328-1912-4FC4-91B5-2333A7EF6EB7}.Release|x64.Build.0 = Release_Unicode|Win32 | |||
{51FD2060-F4F9-4982-8474-473541D4FCF8}.Debug_Unicode|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{51FD2060-F4F9-4982-8474-473541D4FCF8}.Debug_Unicode|Win32.Build.0 = Debug_Unicode|Win32 | |||
{51FD2060-F4F9-4982-8474-473541D4FCF8}.Debug_Unicode|x64.ActiveCfg = Debug_Unicode|Win32 | |||
{51FD2060-F4F9-4982-8474-473541D4FCF8}.Debug|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{51FD2060-F4F9-4982-8474-473541D4FCF8}.Debug|Win32.Build.0 = Debug_Unicode|Win32 | |||
{51FD2060-F4F9-4982-8474-473541D4FCF8}.Debug|x64.ActiveCfg = Release_Unicode|Win32 | |||
{51FD2060-F4F9-4982-8474-473541D4FCF8}.Debug|x64.Build.0 = Release_Unicode|Win32 | |||
{51FD2060-F4F9-4982-8474-473541D4FCF8}.Release_Unicode|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{51FD2060-F4F9-4982-8474-473541D4FCF8}.Release_Unicode|Win32.Build.0 = Release_Unicode|Win32 | |||
{51FD2060-F4F9-4982-8474-473541D4FCF8}.Release_Unicode|x64.ActiveCfg = Release_Unicode|Win32 | |||
{51FD2060-F4F9-4982-8474-473541D4FCF8}.Release|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{51FD2060-F4F9-4982-8474-473541D4FCF8}.Release|Win32.Build.0 = Release_Unicode|Win32 | |||
{51FD2060-F4F9-4982-8474-473541D4FCF8}.Release|x64.ActiveCfg = Release_Unicode|Win32 | |||
{51FD2060-F4F9-4982-8474-473541D4FCF8}.Release|x64.Build.0 = Release_Unicode|Win32 | |||
{C38EABE4-E7E3-437A-8ECF-97A8626227D0}.Debug_Unicode|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{C38EABE4-E7E3-437A-8ECF-97A8626227D0}.Debug_Unicode|Win32.Build.0 = Debug_Unicode|Win32 | |||
{C38EABE4-E7E3-437A-8ECF-97A8626227D0}.Debug_Unicode|x64.ActiveCfg = Debug_Unicode|Win32 | |||
{C38EABE4-E7E3-437A-8ECF-97A8626227D0}.Debug|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{C38EABE4-E7E3-437A-8ECF-97A8626227D0}.Debug|Win32.Build.0 = Debug_Unicode|Win32 | |||
{C38EABE4-E7E3-437A-8ECF-97A8626227D0}.Debug|x64.ActiveCfg = Release_Unicode|Win32 | |||
{C38EABE4-E7E3-437A-8ECF-97A8626227D0}.Debug|x64.Build.0 = Release_Unicode|Win32 | |||
{C38EABE4-E7E3-437A-8ECF-97A8626227D0}.Release_Unicode|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{C38EABE4-E7E3-437A-8ECF-97A8626227D0}.Release_Unicode|Win32.Build.0 = Release_Unicode|Win32 | |||
{C38EABE4-E7E3-437A-8ECF-97A8626227D0}.Release_Unicode|x64.ActiveCfg = Release_Unicode|Win32 | |||
{C38EABE4-E7E3-437A-8ECF-97A8626227D0}.Release|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{C38EABE4-E7E3-437A-8ECF-97A8626227D0}.Release|Win32.Build.0 = Release_Unicode|Win32 | |||
{C38EABE4-E7E3-437A-8ECF-97A8626227D0}.Release|x64.ActiveCfg = Release_Unicode|Win32 | |||
{C38EABE4-E7E3-437A-8ECF-97A8626227D0}.Release|x64.Build.0 = Release_Unicode|Win32 | |||
{DA531A23-506A-4643-BA47-B77542C5F41C}.Debug_Unicode|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{DA531A23-506A-4643-BA47-B77542C5F41C}.Debug_Unicode|Win32.Build.0 = Debug_Unicode|Win32 | |||
{DA531A23-506A-4643-BA47-B77542C5F41C}.Debug_Unicode|x64.ActiveCfg = Debug_Unicode|Win32 | |||
{DA531A23-506A-4643-BA47-B77542C5F41C}.Debug|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{DA531A23-506A-4643-BA47-B77542C5F41C}.Debug|Win32.Build.0 = Debug_Unicode|Win32 | |||
{DA531A23-506A-4643-BA47-B77542C5F41C}.Debug|x64.ActiveCfg = Release_Unicode|Win32 | |||
{DA531A23-506A-4643-BA47-B77542C5F41C}.Debug|x64.Build.0 = Release_Unicode|Win32 | |||
{DA531A23-506A-4643-BA47-B77542C5F41C}.Release_Unicode|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{DA531A23-506A-4643-BA47-B77542C5F41C}.Release_Unicode|Win32.Build.0 = Release_Unicode|Win32 | |||
{DA531A23-506A-4643-BA47-B77542C5F41C}.Release_Unicode|x64.ActiveCfg = Release_Unicode|Win32 | |||
{DA531A23-506A-4643-BA47-B77542C5F41C}.Release|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{DA531A23-506A-4643-BA47-B77542C5F41C}.Release|Win32.Build.0 = Release_Unicode|Win32 | |||
{DA531A23-506A-4643-BA47-B77542C5F41C}.Release|x64.ActiveCfg = Release_Unicode|Win32 | |||
{DA531A23-506A-4643-BA47-B77542C5F41C}.Release|x64.Build.0 = Release_Unicode|Win32 | |||
{74DE8924-75DC-4444-AB26-B687F71BD778}.Debug_Unicode|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{74DE8924-75DC-4444-AB26-B687F71BD778}.Debug_Unicode|Win32.Build.0 = Debug_Unicode|Win32 | |||
{74DE8924-75DC-4444-AB26-B687F71BD778}.Debug_Unicode|x64.ActiveCfg = Debug_Unicode|Win32 | |||
{74DE8924-75DC-4444-AB26-B687F71BD778}.Debug|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{74DE8924-75DC-4444-AB26-B687F71BD778}.Debug|Win32.Build.0 = Debug_Unicode|Win32 | |||
{74DE8924-75DC-4444-AB26-B687F71BD778}.Debug|x64.ActiveCfg = Release_Unicode|Win32 | |||
{74DE8924-75DC-4444-AB26-B687F71BD778}.Debug|x64.Build.0 = Release_Unicode|Win32 | |||
{74DE8924-75DC-4444-AB26-B687F71BD778}.Release_Unicode|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{74DE8924-75DC-4444-AB26-B687F71BD778}.Release_Unicode|Win32.Build.0 = Release_Unicode|Win32 | |||
{74DE8924-75DC-4444-AB26-B687F71BD778}.Release_Unicode|x64.ActiveCfg = Release_Unicode|Win32 | |||
{74DE8924-75DC-4444-AB26-B687F71BD778}.Release|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{74DE8924-75DC-4444-AB26-B687F71BD778}.Release|Win32.Build.0 = Release_Unicode|Win32 | |||
{74DE8924-75DC-4444-AB26-B687F71BD778}.Release|x64.ActiveCfg = Release_Unicode|Win32 | |||
{74DE8924-75DC-4444-AB26-B687F71BD778}.Release|x64.Build.0 = Release_Unicode|Win32 | |||
{C323A106-B50D-48CB-A353-CFBE5308F2A5}.Debug_Unicode|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{C323A106-B50D-48CB-A353-CFBE5308F2A5}.Debug_Unicode|Win32.Build.0 = Debug_Unicode|Win32 | |||
{C323A106-B50D-48CB-A353-CFBE5308F2A5}.Debug_Unicode|x64.ActiveCfg = Debug_Unicode|Win32 | |||
{C323A106-B50D-48CB-A353-CFBE5308F2A5}.Debug|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{C323A106-B50D-48CB-A353-CFBE5308F2A5}.Debug|Win32.Build.0 = Debug_Unicode|Win32 | |||
{C323A106-B50D-48CB-A353-CFBE5308F2A5}.Debug|x64.ActiveCfg = Release_Unicode|Win32 | |||
{C323A106-B50D-48CB-A353-CFBE5308F2A5}.Debug|x64.Build.0 = Release_Unicode|Win32 | |||
{C323A106-B50D-48CB-A353-CFBE5308F2A5}.Release_Unicode|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{C323A106-B50D-48CB-A353-CFBE5308F2A5}.Release_Unicode|Win32.Build.0 = Release_Unicode|Win32 | |||
{C323A106-B50D-48CB-A353-CFBE5308F2A5}.Release_Unicode|x64.ActiveCfg = Release_Unicode|Win32 | |||
{C323A106-B50D-48CB-A353-CFBE5308F2A5}.Release|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{C323A106-B50D-48CB-A353-CFBE5308F2A5}.Release|Win32.Build.0 = Release_Unicode|Win32 | |||
{C323A106-B50D-48CB-A353-CFBE5308F2A5}.Release|x64.ActiveCfg = Release_Unicode|Win32 | |||
{C323A106-B50D-48CB-A353-CFBE5308F2A5}.Release|x64.Build.0 = Release_Unicode|Win32 | |||
{660F9BCF-23A3-425A-9BDC-40D7AFBC8DFB}.Debug_Unicode|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{660F9BCF-23A3-425A-9BDC-40D7AFBC8DFB}.Debug_Unicode|Win32.Build.0 = Debug_Unicode|Win32 | |||
{660F9BCF-23A3-425A-9BDC-40D7AFBC8DFB}.Debug_Unicode|x64.ActiveCfg = Debug_Unicode|Win32 | |||
{660F9BCF-23A3-425A-9BDC-40D7AFBC8DFB}.Debug|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{660F9BCF-23A3-425A-9BDC-40D7AFBC8DFB}.Debug|Win32.Build.0 = Debug_Unicode|Win32 | |||
{660F9BCF-23A3-425A-9BDC-40D7AFBC8DFB}.Debug|x64.ActiveCfg = Release_Unicode|Win32 | |||
{660F9BCF-23A3-425A-9BDC-40D7AFBC8DFB}.Debug|x64.Build.0 = Release_Unicode|Win32 | |||
{660F9BCF-23A3-425A-9BDC-40D7AFBC8DFB}.Release_Unicode|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{660F9BCF-23A3-425A-9BDC-40D7AFBC8DFB}.Release_Unicode|Win32.Build.0 = Release_Unicode|Win32 | |||
{660F9BCF-23A3-425A-9BDC-40D7AFBC8DFB}.Release_Unicode|x64.ActiveCfg = Release_Unicode|Win32 | |||
{660F9BCF-23A3-425A-9BDC-40D7AFBC8DFB}.Release|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{660F9BCF-23A3-425A-9BDC-40D7AFBC8DFB}.Release|Win32.Build.0 = Release_Unicode|Win32 | |||
{660F9BCF-23A3-425A-9BDC-40D7AFBC8DFB}.Release|x64.ActiveCfg = Release_Unicode|Win32 | |||
{660F9BCF-23A3-425A-9BDC-40D7AFBC8DFB}.Release|x64.Build.0 = Release_Unicode|Win32 | |||
{60710DEB-8538-4CB6-8ABA-3A143E451C21}.Debug_Unicode|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{60710DEB-8538-4CB6-8ABA-3A143E451C21}.Debug_Unicode|Win32.Build.0 = Debug_Unicode|Win32 | |||
{60710DEB-8538-4CB6-8ABA-3A143E451C21}.Debug_Unicode|x64.ActiveCfg = Debug_Unicode|Win32 | |||
{60710DEB-8538-4CB6-8ABA-3A143E451C21}.Debug|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{60710DEB-8538-4CB6-8ABA-3A143E451C21}.Debug|Win32.Build.0 = Debug_Unicode|Win32 | |||
{60710DEB-8538-4CB6-8ABA-3A143E451C21}.Debug|x64.ActiveCfg = Release_Unicode|Win32 | |||
{60710DEB-8538-4CB6-8ABA-3A143E451C21}.Debug|x64.Build.0 = Release_Unicode|Win32 | |||
{60710DEB-8538-4CB6-8ABA-3A143E451C21}.Release_Unicode|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{60710DEB-8538-4CB6-8ABA-3A143E451C21}.Release_Unicode|Win32.Build.0 = Release_Unicode|Win32 | |||
{60710DEB-8538-4CB6-8ABA-3A143E451C21}.Release_Unicode|x64.ActiveCfg = Release_Unicode|Win32 | |||
{60710DEB-8538-4CB6-8ABA-3A143E451C21}.Release|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{60710DEB-8538-4CB6-8ABA-3A143E451C21}.Release|Win32.Build.0 = Release_Unicode|Win32 | |||
{60710DEB-8538-4CB6-8ABA-3A143E451C21}.Release|x64.ActiveCfg = Release_Unicode|Win32 | |||
{60710DEB-8538-4CB6-8ABA-3A143E451C21}.Release|x64.Build.0 = Release_Unicode|Win32 | |||
{B4766ADF-5AA6-4ADF-9354-4F889FFF028E}.Debug_Unicode|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{B4766ADF-5AA6-4ADF-9354-4F889FFF028E}.Debug_Unicode|Win32.Build.0 = Debug_Unicode|Win32 | |||
{B4766ADF-5AA6-4ADF-9354-4F889FFF028E}.Debug_Unicode|x64.ActiveCfg = Debug_Unicode|Win32 | |||
{B4766ADF-5AA6-4ADF-9354-4F889FFF028E}.Debug|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{B4766ADF-5AA6-4ADF-9354-4F889FFF028E}.Debug|Win32.Build.0 = Debug_Unicode|Win32 | |||
{B4766ADF-5AA6-4ADF-9354-4F889FFF028E}.Debug|x64.ActiveCfg = Release_Unicode|Win32 | |||
{B4766ADF-5AA6-4ADF-9354-4F889FFF028E}.Debug|x64.Build.0 = Release_Unicode|Win32 | |||
{B4766ADF-5AA6-4ADF-9354-4F889FFF028E}.Release_Unicode|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{B4766ADF-5AA6-4ADF-9354-4F889FFF028E}.Release_Unicode|Win32.Build.0 = Release_Unicode|Win32 | |||
{B4766ADF-5AA6-4ADF-9354-4F889FFF028E}.Release_Unicode|x64.ActiveCfg = Release_Unicode|Win32 | |||
{B4766ADF-5AA6-4ADF-9354-4F889FFF028E}.Release|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{B4766ADF-5AA6-4ADF-9354-4F889FFF028E}.Release|Win32.Build.0 = Release_Unicode|Win32 | |||
{B4766ADF-5AA6-4ADF-9354-4F889FFF028E}.Release|x64.ActiveCfg = Release_Unicode|Win32 | |||
{B4766ADF-5AA6-4ADF-9354-4F889FFF028E}.Release|x64.Build.0 = Release_Unicode|Win32 | |||
{A24A269E-D5C3-4D96-B5D3-A254D91F617D}.Debug_Unicode|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{A24A269E-D5C3-4D96-B5D3-A254D91F617D}.Debug_Unicode|Win32.Build.0 = Debug_Unicode|Win32 | |||
{A24A269E-D5C3-4D96-B5D3-A254D91F617D}.Debug_Unicode|x64.ActiveCfg = Debug_Unicode|x64 | |||
{A24A269E-D5C3-4D96-B5D3-A254D91F617D}.Debug_Unicode|x64.Build.0 = Debug_Unicode|x64 | |||
{A24A269E-D5C3-4D96-B5D3-A254D91F617D}.Debug|Win32.ActiveCfg = Debug_Unicode|Win32 | |||
{A24A269E-D5C3-4D96-B5D3-A254D91F617D}.Debug|Win32.Build.0 = Debug_Unicode|Win32 | |||
{A24A269E-D5C3-4D96-B5D3-A254D91F617D}.Debug|x64.ActiveCfg = Debug_Unicode|x64 | |||
{A24A269E-D5C3-4D96-B5D3-A254D91F617D}.Debug|x64.Build.0 = Debug_Unicode|x64 | |||
{A24A269E-D5C3-4D96-B5D3-A254D91F617D}.Release_Unicode|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{A24A269E-D5C3-4D96-B5D3-A254D91F617D}.Release_Unicode|Win32.Build.0 = Release_Unicode|Win32 | |||
{A24A269E-D5C3-4D96-B5D3-A254D91F617D}.Release_Unicode|x64.ActiveCfg = Release_Unicode|x64 | |||
{A24A269E-D5C3-4D96-B5D3-A254D91F617D}.Release_Unicode|x64.Build.0 = Release_Unicode|x64 | |||
{A24A269E-D5C3-4D96-B5D3-A254D91F617D}.Release|Win32.ActiveCfg = Release_Unicode|Win32 | |||
{A24A269E-D5C3-4D96-B5D3-A254D91F617D}.Release|Win32.Build.0 = Release_Unicode|Win32 | |||
{A24A269E-D5C3-4D96-B5D3-A254D91F617D}.Release|x64.ActiveCfg = Release_Unicode|x64 | |||
{A24A269E-D5C3-4D96-B5D3-A254D91F617D}.Release|x64.Build.0 = Release_Unicode|x64 | |||
EndGlobalSection | |||
GlobalSection(SolutionProperties) = preSolution | |||
HideSolutionNode = FALSE | |||
@@ -111,6 +111,7 @@ bool CServiceUnits::InitializeService() | |||
WORD wServicePort = m_InitParameter.m_wServicePort; | |||
if (m_TCPNetworkEngine->SetServiceParameter(wServicePort, wMaxConnect, szCompilation) == false) return false; | |||
// | |||
return true; | |||
} | |||
@@ -0,0 +1,144 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 2015-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#ifndef CPP_REDIS_BUILDERS_ARRAY_BUILDER_HPP_ | |||
#define CPP_REDIS_BUILDERS_ARRAY_BUILDER_HPP_ | |||
#include <cpp_redis/builders/builder_iface.hpp> | |||
#include <cpp_redis/builders/integer_builder.hpp> | |||
#include <cpp_redis/core/reply.hpp> | |||
namespace cpp_redis { | |||
namespace builders { | |||
/** | |||
* builder to build redis array replies | |||
* | |||
*/ | |||
class array_builder : public builder_iface { | |||
public: | |||
/** | |||
* ctor | |||
* | |||
*/ | |||
array_builder(); | |||
/** | |||
* dtor | |||
* | |||
*/ | |||
~array_builder() override = default; | |||
/** | |||
* copy ctor | |||
* | |||
*/ | |||
array_builder(const array_builder &) = delete; | |||
/** | |||
* assignment operator | |||
* | |||
*/ | |||
array_builder &operator=(const array_builder &) = delete; | |||
public: | |||
/** | |||
* take data as parameter which is consumed to build the reply | |||
* every bytes used to build the reply must be removed from the buffer passed as parameter | |||
* | |||
* @param data data to be consumed | |||
* @return current instance | |||
* | |||
*/ | |||
builder_iface &operator<<(std::string &data) override; | |||
/** | |||
* @return whether the reply could be built | |||
* | |||
*/ | |||
bool reply_ready() const override; | |||
/** | |||
* @return reply object | |||
* | |||
*/ | |||
reply get_reply() const override; | |||
private: | |||
/** | |||
* take data as parameter which is consumed to determine array size | |||
* every bytes used to build size is removed from the buffer passed as parameter | |||
* | |||
* @param buffer data to be consumer | |||
* @return true if the size could be found | |||
* | |||
*/ | |||
bool fetch_array_size(std::string &buffer); | |||
/** | |||
* take data as parameter which is consumed to build an array row | |||
* every bytes used to build row is removed from the buffer passed as parameter | |||
* | |||
* @param buffer data to be consumer | |||
* @return true if the row could be built | |||
* | |||
*/ | |||
bool build_row(std::string &buffer); | |||
private: | |||
/** | |||
* builder used to fetch the array size | |||
* | |||
*/ | |||
integer_builder m_int_builder; | |||
/** | |||
* built array size | |||
* | |||
*/ | |||
uint64_t m_array_size; | |||
/** | |||
* current builder used to build current row | |||
* | |||
*/ | |||
std::unique_ptr<builder_iface> m_current_builder; | |||
/** | |||
* whether the reply is ready or not | |||
* | |||
*/ | |||
bool m_reply_ready; | |||
/** | |||
* reply to be built (or built) | |||
* | |||
*/ | |||
reply m_reply; | |||
}; | |||
} // namespace builders | |||
} // namespace cpp_redis | |||
#endif |
@@ -0,0 +1,69 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 2015-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#ifndef CPP_REDIS_BUILDERS_BUILDER_IFACE_HPP_ | |||
#define CPP_REDIS_BUILDERS_BUILDER_IFACE_HPP_ | |||
#include <memory> | |||
#include <string> | |||
#include <cpp_redis/core/reply.hpp> | |||
namespace cpp_redis { | |||
namespace builders { | |||
/** | |||
* @brief interface inherited by all builders | |||
*/ | |||
class builder_iface { | |||
public: | |||
virtual ~builder_iface() = default; | |||
/** | |||
* take data as parameter which is consumed to build the reply | |||
* every bytes used to build the reply must be removed from the buffer passed as parameter | |||
* | |||
* @param data data to be consumed | |||
* @return current instance | |||
* | |||
*/ | |||
virtual builder_iface &operator<<(std::string &data) = 0; | |||
/** | |||
* @return whether the reply could be built | |||
* | |||
*/ | |||
virtual bool reply_ready() const = 0; | |||
/** | |||
* @return reply object | |||
* | |||
*/ | |||
virtual reply get_reply() const = 0; | |||
}; | |||
} // namespace builders | |||
} // namespace cpp_redis | |||
#endif |
@@ -0,0 +1,48 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 2015-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <memory> | |||
#include <cpp_redis/builders/builder_iface.hpp> | |||
namespace cpp_redis { | |||
namespace builders { | |||
/** | |||
* create a builder corresponding to the given id | |||
* * + for simple strings | |||
* * - for errors | |||
* * : for integers | |||
* * $ for bulk strings | |||
* * * for arrays | |||
* | |||
* @param id char that determines which builder to return | |||
* @return new builder instance depending on id value | |||
*/ | |||
std::unique_ptr<builder_iface> create_builder(char id); | |||
} // namespace builders | |||
} // namespace cpp_redis |
@@ -0,0 +1,117 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 2015-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <cpp_redis/builders/builder_iface.hpp> | |||
#include <cpp_redis/builders/integer_builder.hpp> | |||
#include <cpp_redis/core/reply.hpp> | |||
namespace cpp_redis { | |||
namespace builders { | |||
//! | |||
//! builder to build redis bulk string replies | |||
//! | |||
class bulk_string_builder : public builder_iface { | |||
public: | |||
//! ctor | |||
bulk_string_builder(); | |||
//! dtor | |||
~bulk_string_builder() override = default; | |||
//! copy ctor | |||
bulk_string_builder(const bulk_string_builder&) = delete; | |||
//! assignment operator | |||
bulk_string_builder& operator=(const bulk_string_builder&) = delete; | |||
public: | |||
//! | |||
//! take data as parameter which is consumed to build the reply | |||
//! every bytes used to build the reply must be removed from the buffer passed as parameter | |||
//! | |||
//! @param data data to be consumed | |||
//! @return current instance | |||
//! | |||
builder_iface& operator<<(std::string& data) override; | |||
//! | |||
//! @return whether the reply could be built | |||
//! | |||
bool reply_ready() const override; | |||
//! | |||
//! @return reply object | |||
//! | |||
reply get_reply() const override; | |||
//! | |||
//! @return the parsed bulk string | |||
//! | |||
const std::string& get_bulk_string() const; | |||
//! | |||
//! @return whether the bulk string is null | |||
//! | |||
bool is_null() const; | |||
private: | |||
void build_reply(); | |||
bool fetch_size(std::string& str); | |||
void fetch_str(std::string& str); | |||
private: | |||
//! | |||
//! builder used to get bulk string size | |||
//! | |||
integer_builder m_int_builder; | |||
//! | |||
//! bulk string size | |||
//! | |||
int m_str_size; | |||
//! | |||
//! bulk string | |||
//! | |||
std::string m_str; | |||
//! | |||
//! whether the bulk string is null | |||
//! | |||
bool m_is_null; | |||
//! | |||
//! whether the reply is ready or not | |||
//! | |||
bool m_reply_ready; | |||
//! | |||
//! reply to be built | |||
//! | |||
reply m_reply; | |||
}; | |||
} // namespace builders | |||
} // namespace cpp_redis |
@@ -0,0 +1,106 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 2015-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <cpp_redis/builders/builder_iface.hpp> | |||
#include <cpp_redis/builders/simple_string_builder.hpp> | |||
#include <cpp_redis/core/reply.hpp> | |||
namespace cpp_redis { | |||
namespace builders { | |||
/** | |||
* builder to build redis error replies | |||
* | |||
*/ | |||
class error_builder : public builder_iface { | |||
public: | |||
/** | |||
* ctor | |||
* | |||
*/ | |||
error_builder() = default; | |||
/** | |||
* dtor | |||
* | |||
*/ | |||
~error_builder() override = default; | |||
/** | |||
* copy ctor | |||
* | |||
*/ | |||
error_builder(const error_builder&) = delete; | |||
/** | |||
* assignment operator | |||
* | |||
*/ | |||
error_builder& operator=(const error_builder&) = delete; | |||
public: | |||
/** | |||
* take data as parameter which is consumed to build the reply | |||
* every bytes used to build the reply must be removed from the buffer passed as parameter | |||
* | |||
* @param data data to be consumed | |||
* @return current instance | |||
* | |||
*/ | |||
builder_iface& operator<<(std::string& data) override; | |||
/** | |||
* @return whether the reply could be built | |||
* | |||
*/ | |||
bool reply_ready() const override; | |||
/** | |||
* @return reply object | |||
* | |||
*/ | |||
reply get_reply() const override; | |||
/** | |||
* @return the parsed error | |||
* | |||
*/ | |||
const std::string& get_error() const; | |||
private: | |||
/** | |||
* builder used to parse the error | |||
* | |||
*/ | |||
simple_string_builder m_string_builder; | |||
/** | |||
* reply to be built | |||
* | |||
*/ | |||
reply m_reply; | |||
}; | |||
} // namespace builders | |||
} // namespace cpp_redis |
@@ -0,0 +1,119 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 2015-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <cpp_redis/builders/builder_iface.hpp> | |||
#include <cpp_redis/core/reply.hpp> | |||
#include <cstdint> | |||
namespace cpp_redis { | |||
namespace builders { | |||
/** | |||
* builder to build redis integer replies | |||
* | |||
*/ | |||
class integer_builder : public builder_iface { | |||
public: | |||
/** | |||
* ctor | |||
* | |||
*/ | |||
integer_builder(); | |||
/** | |||
* dtor | |||
* | |||
*/ | |||
~integer_builder() override = default; | |||
/** | |||
* copy ctor | |||
* | |||
*/ | |||
integer_builder(const integer_builder&) = delete; | |||
/** | |||
* assignment operator | |||
* | |||
*/ | |||
integer_builder& operator=(const integer_builder&) = delete; | |||
public: | |||
/** | |||
* take data as parameter which is consumed to build the reply | |||
* every bytes used to build the reply must be removed from the buffer passed as parameter | |||
* | |||
* @param data data to be consumed | |||
* @return current instance | |||
* | |||
*/ | |||
builder_iface& operator<<(std::string& data) override; | |||
/** | |||
* @return whether the reply could be built | |||
* | |||
*/ | |||
bool reply_ready() const override; | |||
/** | |||
* @return reply object | |||
* | |||
*/ | |||
reply get_reply() const override; | |||
/** | |||
* @return the parsed integer | |||
* | |||
*/ | |||
int64_t get_integer() const; | |||
private: | |||
/** | |||
* parsed number | |||
* | |||
*/ | |||
int64_t m_nbr; | |||
/** | |||
* -1 for negative number, 1 otherwise | |||
* | |||
*/ | |||
int64_t m_negative_multiplicator; | |||
/** | |||
* whether the reply is ready or not | |||
* | |||
*/ | |||
bool m_reply_ready; | |||
/** | |||
* reply to be built | |||
* | |||
*/ | |||
reply m_reply; | |||
}; | |||
} // namespace builders | |||
} // namespace cpp_redis |
@@ -0,0 +1,139 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 2015-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <deque> | |||
#include <memory> | |||
#include <stdexcept> | |||
#include <string> | |||
#include <cpp_redis/builders/builder_iface.hpp> | |||
#include <cpp_redis/core/reply.hpp> | |||
namespace cpp_redis { | |||
namespace builders { | |||
/** | |||
* class coordinating the several builders and the builder factory to build all the replies returned by redis server | |||
* | |||
*/ | |||
class reply_builder { | |||
public: | |||
/** | |||
* ctor | |||
* | |||
*/ | |||
reply_builder(); | |||
/** | |||
* dtor | |||
* | |||
*/ | |||
~reply_builder() = default; | |||
/** | |||
* copy ctor | |||
* | |||
*/ | |||
reply_builder(const reply_builder&) = delete; | |||
/** | |||
* assignment operator | |||
* | |||
*/ | |||
reply_builder& operator=(const reply_builder&) = delete; | |||
public: | |||
/** | |||
* add data to reply builder | |||
* data is used to build replies that can be retrieved with get_front later on if reply_available returns true | |||
* | |||
* @param data data to be used for building replies | |||
* @return current instance | |||
* | |||
*/ | |||
reply_builder& operator<<(const std::string& data); | |||
/** | |||
* similar as get_front, store reply in the passed parameter | |||
* | |||
* @param reply reference to the reply object where to store the first available reply | |||
* | |||
*/ | |||
void operator>>(reply& reply); | |||
/** | |||
* @return the first available reply | |||
* | |||
*/ | |||
const reply& get_front() const; | |||
/** | |||
* pop the first available reply | |||
* | |||
*/ | |||
void pop_front(); | |||
/** | |||
* @return whether a reply is available | |||
* | |||
*/ | |||
bool reply_available() const; | |||
/** | |||
* reset the reply builder to its initial state (clear internal buffer and stages) | |||
* | |||
*/ | |||
void reset(); | |||
private: | |||
/** | |||
* build reply using m_buffer content | |||
* | |||
* @return whether the reply has been fully built or not | |||
* | |||
*/ | |||
bool build_reply(); | |||
private: | |||
/** | |||
* buffer to be used to build data | |||
* | |||
*/ | |||
std::string m_buffer; | |||
/** | |||
* current builder used to build current reply | |||
* | |||
*/ | |||
std::unique_ptr<builder_iface> m_builder; | |||
/** | |||
* queue of available (built) replies | |||
* | |||
*/ | |||
std::deque<reply> m_available_replies; | |||
}; | |||
} // namespace builders | |||
} // namespace cpp_redis |
@@ -0,0 +1,113 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 2015-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <string> | |||
#include <cpp_redis/builders/builder_iface.hpp> | |||
#include <cpp_redis/core/reply.hpp> | |||
namespace cpp_redis { | |||
namespace builders { | |||
/** | |||
* builder to build redis simple string replies | |||
* | |||
*/ | |||
class simple_string_builder : public builder_iface { | |||
public: | |||
/** | |||
* ctor | |||
* | |||
*/ | |||
simple_string_builder(); | |||
/** | |||
* dtor | |||
* | |||
*/ | |||
~simple_string_builder() override = default; | |||
/** | |||
* copy ctor | |||
* | |||
*/ | |||
simple_string_builder(const simple_string_builder&) = delete; | |||
/** | |||
* assignment operator | |||
* | |||
*/ | |||
simple_string_builder& operator=(const simple_string_builder&) = delete; | |||
public: | |||
/** | |||
* take data as parameter which is consumed to build the reply | |||
* every bytes used to build the reply must be removed from the buffer passed as parameter | |||
* | |||
* @param data data to be consumed | |||
* @return current instance | |||
* | |||
*/ | |||
builder_iface& operator<<(std::string& data) override; | |||
/** | |||
* @return whether the reply could be built | |||
* | |||
*/ | |||
bool reply_ready() const override; | |||
/** | |||
* @return reply object | |||
* | |||
*/ | |||
reply get_reply() const override; | |||
/** | |||
* @return the parsed simple string | |||
* | |||
*/ | |||
const std::string& get_simple_string() const; | |||
private: | |||
/** | |||
* parsed simple string | |||
* | |||
*/ | |||
std::string m_str; | |||
/** | |||
* whether the reply is ready or not | |||
* | |||
*/ | |||
bool m_reply_ready; | |||
/** | |||
* reply to be built | |||
* | |||
*/ | |||
reply m_reply; | |||
}; | |||
} // namespace builders | |||
} // namespace cpp_redis |
@@ -0,0 +1,134 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 11/27/18 nick. <nbatkins@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE.#ifndef CPP_REDIS_CONSUMER_HPP | |||
#ifndef CPP_REDIS_CONSUMER_HPP | |||
#define CPP_REDIS_CONSUMER_HPP | |||
#include <string> | |||
#include <cpp_redis/core/client.hpp> | |||
#include <cpp_redis/misc/dispatch_queue.hpp> | |||
namespace cpp_redis { | |||
using defer = std::shared_ptr<void>; | |||
/** | |||
* reply callback called whenever a reply is received | |||
* takes as parameter the received reply | |||
*/ | |||
typedef dispatch_callback_t consumer_callback_t; | |||
typedef client::reply_callback_t reply_callback_t; | |||
typedef struct consumer_callback_container { | |||
consumer_callback_t consumer_callback; | |||
acknowledgement_callback_t acknowledgement_callback; | |||
} consumer_callback_container_t; | |||
typedef struct consumer_reply { | |||
std::string group_id; | |||
xstream_reply_t reply; | |||
} consumer_reply_t; | |||
class consumer_client_container { | |||
public: | |||
consumer_client_container(); | |||
client ack_client; | |||
client poll_client; | |||
}; | |||
typedef consumer_client_container consumer_client_container_t; | |||
typedef std::unique_ptr<consumer_client_container_t> client_container_ptr_t; | |||
typedef std::multimap<std::string, consumer_callback_container_t> consumer_callbacks_t; | |||
//typedef std::map<std::string, consumer_callback_container_t> consumer_callbacks_t; | |||
class consumer { | |||
public: | |||
explicit consumer(std::string stream, std::string consumer, | |||
size_t max_concurrency = std::thread::hardware_concurrency()); | |||
consumer &subscribe(const std::string &group, | |||
const consumer_callback_t &consumer_callback, | |||
const acknowledgement_callback_t &acknowledgement_callback = nullptr); | |||
/** | |||
* @brief Connect to redis server | |||
* @param host host to be connected to | |||
* @param port port to be connected to | |||
* @param connect_callback connect handler to be called on connect events (may be null) | |||
* @param timeout_ms maximum time to connect | |||
* @param max_reconnects maximum attempts of reconnection if connection dropped | |||
* @param reconnect_interval_ms time between two attempts of reconnection | |||
*/ | |||
void connect( | |||
const std::string &host = "127.0.0.1", | |||
std::size_t port = 6379, | |||
const connect_callback_t &connect_callback = nullptr, | |||
std::uint32_t timeout_ms = 0, | |||
std::int32_t max_reconnects = 0, | |||
std::uint32_t reconnect_interval_ms = 0); | |||
void auth(const std::string &password, | |||
const reply_callback_t &reply_callback = nullptr); | |||
/* | |||
* commit pipelined transaction | |||
* that is, send to the network all commands pipelined by calling send() / subscribe() / ... | |||
* | |||
* @return current instance | |||
*/ | |||
consumer &commit(); | |||
void dispatch_changed_handler(size_t size); | |||
private: | |||
void poll(); | |||
private: | |||
std::string m_stream; | |||
std::string m_name; | |||
std::string m_read_id; | |||
int m_block_sec; | |||
size_t m_max_concurrency; | |||
int m_read_count; | |||
client_container_ptr_t m_client; | |||
consumer_callbacks_t m_callbacks; | |||
std::mutex m_callbacks_mutex; | |||
dispatch_queue_ptr_t m_dispatch_queue; | |||
std::atomic_bool dispatch_queue_full{false}; | |||
std::condition_variable dispatch_queue_changed; | |||
std::mutex dispatch_queue_changed_mutex; | |||
bool is_ready = false; | |||
std::atomic_bool m_should_read_pending{true}; | |||
}; | |||
} // namespace cpp_redis | |||
#endif //CPP_REDIS_CONSUMER_HPP |
@@ -0,0 +1,288 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 2015-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <iostream> | |||
#include <string> | |||
#include <vector> | |||
#include <cpp_redis/misc/optional.hpp> | |||
#include <cstdint> | |||
namespace cpp_redis { | |||
/** | |||
* cpp_redis::reply is the class that wraps Redis server replies. | |||
* That is, cpp_redis::reply objects are passed as parameters of commands callbacks and contain the server's response. | |||
* | |||
*/ | |||
class reply { | |||
public: | |||
#define __CPP_REDIS_REPLY_ERR 0 | |||
#define __CPP_REDIS_REPLY_BULK 1 | |||
#define __CPP_REDIS_REPLY_SIMPLE 2 | |||
#define __CPP_REDIS_REPLY_NULL 3 | |||
#define __CPP_REDIS_REPLY_INT 4 | |||
#define __CPP_REDIS_REPLY_ARRAY 5 | |||
/** | |||
* type of reply, based on redis server standard replies | |||
* | |||
*/ | |||
enum class type { | |||
error = __CPP_REDIS_REPLY_ERR, | |||
bulk_string = __CPP_REDIS_REPLY_BULK, | |||
simple_string = __CPP_REDIS_REPLY_SIMPLE, | |||
null = __CPP_REDIS_REPLY_NULL, | |||
integer = __CPP_REDIS_REPLY_INT, | |||
array = __CPP_REDIS_REPLY_ARRAY | |||
}; | |||
/** | |||
* specific type of replies for string-based replies | |||
* | |||
*/ | |||
enum class string_type { | |||
error = __CPP_REDIS_REPLY_ERR, | |||
bulk_string = __CPP_REDIS_REPLY_BULK, | |||
simple_string = __CPP_REDIS_REPLY_SIMPLE | |||
}; | |||
public: | |||
/** | |||
* default ctor (set a null reply) | |||
* | |||
*/ | |||
reply(); | |||
/** | |||
* ctor for string values | |||
* | |||
* @param value string value | |||
* @param reply_type of string reply | |||
* | |||
*/ | |||
reply(const std::string &value, string_type reply_type); | |||
/** | |||
* ctor for int values | |||
* | |||
* @param value integer value | |||
* | |||
*/ | |||
explicit reply(int64_t value); | |||
/** | |||
* ctor for array values | |||
* | |||
* @param rows array reply | |||
* @return current instance | |||
* | |||
*/ | |||
explicit reply(const std::vector<reply> &rows); | |||
/** | |||
* dtor | |||
* | |||
*/ | |||
~reply() = default; | |||
/** | |||
* copy ctor | |||
* | |||
*/ | |||
reply(const reply &) = default; | |||
/** | |||
* assignment operator | |||
* | |||
*/ | |||
reply &operator=(const reply &) = default; | |||
/** | |||
* move ctor | |||
* | |||
*/ | |||
reply(reply &&) noexcept; | |||
/** | |||
* move assignment operator | |||
* | |||
*/ | |||
reply &operator=(reply &&) noexcept; | |||
public: | |||
/** | |||
* @return whether the reply is an array | |||
* | |||
*/ | |||
bool is_array() const; | |||
/** | |||
* @return whether the reply is a string (simple, bulk, error) | |||
* | |||
*/ | |||
bool is_string() const; | |||
/** | |||
* @return whether the reply is a simple string | |||
* | |||
*/ | |||
bool is_simple_string() const; | |||
/** | |||
* @return whether the reply is a bulk string | |||
* | |||
*/ | |||
bool is_bulk_string() const; | |||
/** | |||
* @return whether the reply is an error | |||
* | |||
*/ | |||
bool is_error() const; | |||
/** | |||
* @return whether the reply is an integer | |||
* | |||
*/ | |||
bool is_integer() const; | |||
/** | |||
* @return whether the reply is null | |||
* | |||
*/ | |||
bool is_null() const; | |||
public: | |||
/** | |||
* @return true if function is not an error | |||
* | |||
*/ | |||
bool ok() const; | |||
/** | |||
* @return true if function is an error | |||
* | |||
*/ | |||
bool ko() const; | |||
/** | |||
* convenience implicit conversion, same as !is_null() / ok() | |||
* | |||
*/ | |||
explicit operator bool() const; | |||
public: | |||
optional_t<int64_t> try_get_int() const; | |||
public: | |||
/** | |||
* @return the underlying error | |||
* | |||
*/ | |||
const std::string &error() const; | |||
/** | |||
* @return the underlying array | |||
* | |||
*/ | |||
const std::vector<reply> &as_array() const; | |||
/** | |||
* @return the underlying string | |||
* | |||
*/ | |||
const std::string &as_string() const; | |||
/** | |||
* @return the underlying integer | |||
* | |||
*/ | |||
int64_t as_integer() const; | |||
public: | |||
/** | |||
* set reply as null | |||
* | |||
*/ | |||
void set(); | |||
/** | |||
* set a string reply | |||
* | |||
* @param value string value | |||
* @param reply_type of string reply | |||
* | |||
*/ | |||
void set(const std::string &value, string_type reply_type); | |||
/** | |||
* set an integer reply | |||
* | |||
* @param value integer value | |||
* | |||
*/ | |||
void set(int64_t value); | |||
/** | |||
* set an array reply | |||
* | |||
* @param rows array reply | |||
* | |||
*/ | |||
void set(const std::vector<reply> &rows); | |||
/** | |||
* for array replies, add a new row to the reply | |||
* | |||
* @param reply new row to be appended | |||
* @return current instance | |||
* | |||
*/ | |||
reply &operator<<(const reply &reply); | |||
public: | |||
/** | |||
* @return reply type | |||
* | |||
*/ | |||
type get_type() const; | |||
private: | |||
type m_type; | |||
std::vector<cpp_redis::reply> m_rows; | |||
std::string m_str_val; | |||
int64_t m_int_val; | |||
}; | |||
typedef reply reply_t; | |||
} // namespace cpp_redis | |||
/** | |||
* support for output | |||
* | |||
*/ | |||
std::ostream &operator<<(std::ostream &os, const cpp_redis::reply_t &reply); |
@@ -0,0 +1,420 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 2015-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <atomic> | |||
#include <condition_variable> | |||
#include <queue> | |||
#include <utility> | |||
#include <vector> | |||
#include <cpp_redis/misc/logger.hpp> | |||
#include <cpp_redis/network/redis_connection.hpp> | |||
namespace cpp_redis { | |||
/** | |||
* cpp_redis::sentinel is the class providing sentinel configuration. | |||
* It is meant to be used for sending sentinel-related commands to the remote server and receiving its replies. | |||
* It is also meant to be used with cpp_redis::client and cpp_redis::subscriber for high availability (automatic failover if reconnection is enabled). | |||
* | |||
*/ | |||
class sentinel { | |||
public: | |||
/** | |||
* ctor & dtor | |||
* | |||
*/ | |||
#ifndef __CPP_REDIS_USE_CUSTOM_TCP_CLIENT | |||
/** | |||
* default ctor | |||
* | |||
*/ | |||
sentinel(); | |||
#endif /* __CPP_REDIS_USE_CUSTOM_TCP_CLIENT */ | |||
/** | |||
* custom ctor to specify custom tcp_client | |||
* | |||
* @param tcp_client tcp client to be used for network communications | |||
* | |||
*/ | |||
explicit sentinel(const std::shared_ptr<network::tcp_client_iface> &tcp_client); | |||
/** | |||
* dtor | |||
* | |||
*/ | |||
~sentinel(); | |||
/** | |||
* copy ctor | |||
* | |||
*/ | |||
sentinel(const sentinel &) = delete; | |||
/** | |||
* assignment operator | |||
* | |||
*/ | |||
sentinel &operator=(const sentinel &) = delete; | |||
public: | |||
/** | |||
* callback to be called whenever a reply has been received | |||
* | |||
*/ | |||
typedef std::function<void(reply &)> reply_callback_t; | |||
/** | |||
* send the given command | |||
* the command is actually pipelined and only buffered, so nothing is sent to the network | |||
* please call commit() to flush the buffer | |||
* | |||
* @param sentinel_cmd command to be sent | |||
* @param callback callback to be called when reply is received for this command | |||
* @return current instance | |||
* | |||
*/ | |||
sentinel &send(const std::vector<std::string> &sentinel_cmd, const reply_callback_t &callback = nullptr); | |||
/** | |||
* commit pipelined transaction | |||
* that is, send to the network all commands pipelined by calling send() | |||
* | |||
* @return current instance | |||
* | |||
*/ | |||
sentinel &commit(); | |||
/** | |||
* same as commit(), but synchronous | |||
* will block until all pending commands have been sent and that a reply has been received for each of them and all underlying callbacks completed | |||
* | |||
* @return current instance | |||
* | |||
*/ | |||
sentinel &sync_commit(); | |||
/** | |||
* same as sync_commit, but with a timeout | |||
* will simply block until it completes or timeout expires | |||
* | |||
* @return current instance | |||
* | |||
*/ | |||
template<class Rep, class Period> | |||
sentinel & | |||
sync_commit(const std::chrono::duration<Rep, Period> &timeout) { | |||
try_commit(); | |||
std::unique_lock<std::mutex> lock_callback(m_callbacks_mutex); | |||
__CPP_REDIS_LOG(debug, "cpp_redis::sentinel waiting for callbacks to complete"); | |||
if (!m_sync_condvar.wait_for(lock_callback, timeout, [=] { | |||
return m_callbacks_running == 0 && m_callbacks.empty(); | |||
})) { | |||
__CPP_REDIS_LOG(debug, "cpp_redis::sentinel finished waiting for callback"); | |||
} else { | |||
__CPP_REDIS_LOG(debug, "cpp_redis::sentinel timed out waiting for callback"); | |||
} | |||
return *this; | |||
} | |||
public: | |||
/** | |||
* add a sentinel definition. Required for connect() or get_master_addr_by_name() when autoconnect is enabled. | |||
* | |||
* @param host sentinel host | |||
* @param port sentinel port | |||
* @param timeout_ms maximum time to connect | |||
* @return current instance | |||
* | |||
*/ | |||
sentinel &add_sentinel(const std::string &host, std::size_t port, std::uint32_t timeout_ms = 0); | |||
/** | |||
* clear all existing sentinels. | |||
* | |||
*/ | |||
void clear_sentinels(); | |||
public: | |||
/** | |||
* disconnect from redis server | |||
* | |||
* @param wait_for_removal when sets to true, disconnect blocks until the underlying TCP client has been effectively removed from the io_service and that all the underlying callbacks have completed. | |||
* | |||
*/ | |||
void disconnect(bool wait_for_removal = false); | |||
/** | |||
* @return whether we are connected to the redis server or not | |||
* | |||
*/ | |||
bool is_connected(); | |||
/** | |||
* handlers called whenever disconnection occurred | |||
* function takes the sentinel current instance as parameter | |||
* | |||
*/ | |||
typedef std::function<void(sentinel &)> sentinel_disconnect_handler_t; | |||
/** | |||
* Connect to 1st active sentinel we find. Requires add_sentinel() to be called first | |||
* will use timeout set for each added sentinel independently | |||
* | |||
* @param disconnect_handler handler to be called whenever disconnection occurs | |||
* | |||
*/ | |||
void connect_sentinel(const sentinel_disconnect_handler_t &disconnect_handler = nullptr); | |||
/** | |||
* Connect to named sentinel | |||
* | |||
* @param host host to be connected to | |||
* @param port port to be connected to | |||
* @param timeout_ms maximum time to connect | |||
* @param disconnect_handler handler to be called whenever disconnection occurs | |||
* | |||
*/ | |||
void connect( | |||
const std::string &host, | |||
std::size_t port, | |||
const sentinel_disconnect_handler_t &disconnect_handler = nullptr, | |||
std::uint32_t timeout_ms = 0); | |||
/** | |||
* Used to find the current redis master by asking one or more sentinels. Use high availability. | |||
* Handles connect() and disconnect() automatically when autoconnect=true | |||
* This method is synchronous. No need to call sync_commit() or process a reply callback. | |||
* Call add_sentinel() before using when autoconnect==true | |||
* | |||
* @param name sentinel name | |||
* @param host sentinel host | |||
* @param port sentinel port | |||
* @param autoconnect autoconnect we loop through and connect/disconnect as necessary to sentinels that were added using add_sentinel(). | |||
* Otherwise we rely on the call to connect to a sentinel before calling this method. | |||
* @return true if a master was found and fills in host and port output parameters, false otherwise | |||
*/ | |||
bool get_master_addr_by_name( | |||
const std::string &name, | |||
std::string &host, | |||
std::size_t &port, | |||
bool autoconnect = true); | |||
public: | |||
sentinel &ckquorum(const std::string &name, const reply_callback_t &reply_callback = nullptr); | |||
sentinel &failover(const std::string &name, const reply_callback_t &reply_callback = nullptr); | |||
sentinel &flushconfig(const reply_callback_t &reply_callback = nullptr); | |||
sentinel &master(const std::string &name, const reply_callback_t &reply_callback = nullptr); | |||
sentinel &masters(const reply_callback_t &reply_callback = nullptr); | |||
sentinel &monitor(const std::string &name, const std::string &ip, std::size_t port, std::size_t quorum, | |||
const reply_callback_t &reply_callback = nullptr); | |||
sentinel &ping(const reply_callback_t &reply_callback = nullptr); | |||
sentinel &remove(const std::string &name, const reply_callback_t &reply_callback = nullptr); | |||
sentinel &reset(const std::string &pattern, const reply_callback_t &reply_callback = nullptr); | |||
sentinel &sentinels(const std::string &name, const reply_callback_t &reply_callback = nullptr); | |||
sentinel &set(const std::string &name, const std::string &option, const std::string &value, | |||
const reply_callback_t &reply_callback = nullptr); | |||
sentinel &slaves(const std::string &name, const reply_callback_t &reply_callback = nullptr); | |||
public: | |||
/** | |||
* store informations related to a sentinel | |||
* typically, host, port and connection timeout | |||
* | |||
*/ | |||
class sentinel_def { | |||
public: | |||
/** | |||
* ctor | |||
* | |||
*/ | |||
sentinel_def(std::string host, std::size_t port, std::uint32_t timeout_ms) | |||
: m_host(std::move(host)), m_port(port), m_timeout_ms(timeout_ms) {} | |||
/** | |||
* dtor | |||
* | |||
*/ | |||
~sentinel_def() = default; | |||
public: | |||
/** | |||
* @return sentinel host | |||
* | |||
*/ | |||
const std::string & | |||
get_host() const { return m_host; } | |||
/** | |||
* @return sentinel port | |||
* | |||
*/ | |||
size_t | |||
get_port() const { return m_port; } | |||
/** | |||
* @return timeout for sentinel | |||
* | |||
*/ | |||
std::uint32_t | |||
get_timeout_ms() const { return m_timeout_ms; } | |||
/** | |||
* set connect timeout for sentinel in ms | |||
* @param timeout_ms new value | |||
* | |||
*/ | |||
void | |||
set_timeout_ms(std::uint32_t timeout_ms) { m_timeout_ms = timeout_ms; } | |||
private: | |||
/** | |||
* sentinel host | |||
* | |||
*/ | |||
std::string m_host; | |||
/** | |||
* sentinel port | |||
* | |||
*/ | |||
std::size_t m_port; | |||
/** | |||
* connect timeout config | |||
* | |||
*/ | |||
std::uint32_t m_timeout_ms; | |||
}; | |||
public: | |||
/** | |||
* @return sentinels | |||
* | |||
*/ | |||
const std::vector<sentinel_def> &get_sentinels() const; | |||
/** | |||
* @return sentinels (non-const version) | |||
* | |||
*/ | |||
std::vector<sentinel_def> &get_sentinels(); | |||
private: | |||
/** | |||
* redis connection receive handler, triggered whenever a reply has been read by the redis connection | |||
* | |||
* @param connection redis_connection instance | |||
* @param reply parsed reply | |||
* | |||
*/ | |||
void connection_receive_handler(network::redis_connection &connection, reply &reply); | |||
/** | |||
* redis_connection disconnection handler, triggered whenever a disconnection occurred | |||
* | |||
* @param connection redis_connection instance | |||
* | |||
*/ | |||
void connection_disconnect_handler(network::redis_connection &connection); | |||
/** | |||
* Call the user-defined disconnection handler | |||
* | |||
*/ | |||
void call_disconnect_handler(); | |||
/** | |||
* reset the queue of pending callbacks | |||
* | |||
*/ | |||
void clear_callbacks(); | |||
/** | |||
* try to commit the pending pipelined | |||
* if client is disconnected, will throw an exception and clear all pending callbacks (call clear_callbacks()) | |||
* | |||
*/ | |||
void try_commit(); | |||
private: | |||
/** | |||
* A pool of 1 or more sentinels we ask to determine which redis server is the master. | |||
* | |||
*/ | |||
std::vector<sentinel_def> m_sentinels; | |||
/** | |||
* tcp client for redis sentinel connection | |||
* | |||
*/ | |||
network::redis_connection m_client; | |||
/** | |||
* queue of callback to process | |||
* | |||
*/ | |||
std::queue<reply_callback_t> m_callbacks; | |||
/** | |||
* user defined disconnection handler to be called on disconnection | |||
* | |||
*/ | |||
sentinel_disconnect_handler_t m_disconnect_handler; | |||
/** | |||
* callbacks thread safety | |||
* | |||
*/ | |||
std::mutex m_callbacks_mutex; | |||
/** | |||
* condvar for callbacks updates | |||
* | |||
*/ | |||
std::condition_variable m_sync_condvar; | |||
/** | |||
* number of callbacks currently being running | |||
* | |||
*/ | |||
std::atomic<unsigned int> m_callbacks_running; | |||
}; | |||
} // namespace cpp_redis |
@@ -0,0 +1,523 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 2015-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <atomic> | |||
#include <functional> | |||
#include <map> | |||
#include <mutex> | |||
#include <string> | |||
#include <cpp_redis/core/sentinel.hpp> | |||
#include <cpp_redis/core/types.hpp> | |||
#include <cpp_redis/network/redis_connection.hpp> | |||
#include <cpp_redis/network/tcp_client_iface.hpp> | |||
namespace cpp_redis { | |||
/** | |||
* The cpp_redis::subscriber is meant to be used for PUB/SUB communication with the Redis server. | |||
* Please do not use cpp_redis::client to subscribe to some Redis channels as: | |||
* * the behavior is undefined | |||
* * cpp_redis::client is not meant for that | |||
* | |||
*/ | |||
class subscriber { | |||
public: | |||
#ifndef __CPP_REDIS_USE_CUSTOM_TCP_CLIENT | |||
/** | |||
* ctor | |||
* | |||
*/ | |||
subscriber(); | |||
#endif /* __CPP_REDIS_USE_CUSTOM_TCP_CLIENT */ | |||
/** | |||
* custom ctor to specify custom tcp_client | |||
* | |||
* @param tcp_client tcp client to be used for network communications | |||
* | |||
*/ | |||
explicit subscriber(const std::shared_ptr<network::tcp_client_iface> &tcp_client); | |||
/** | |||
* dtor | |||
* | |||
*/ | |||
~subscriber(); | |||
/** | |||
* copy ctor | |||
* | |||
*/ | |||
subscriber(const subscriber &) = delete; | |||
/** | |||
* assignment operator | |||
* | |||
*/ | |||
subscriber &operator=(const subscriber &) = delete; | |||
public: | |||
/** | |||
* @brief Connect to redis server | |||
* @param host host to be connected to | |||
* @param port port to be connected to | |||
* @param connect_callback connect handler to be called on connect events (may be null) | |||
* @param timeout_ms maximum time to connect | |||
* @param max_reconnects maximum attempts of reconnection if connection dropped | |||
* @param reconnect_interval_ms time between two attempts of reconnection | |||
* | |||
*/ | |||
void connect( | |||
const std::string &host = "127.0.0.1", | |||
std::size_t port = 6379, | |||
const connect_callback_t &connect_callback = nullptr, | |||
std::uint32_t timeout_ms = 0, | |||
std::int32_t max_reconnects = 0, | |||
std::uint32_t reconnect_interval_ms = 0); | |||
/** | |||
* @brief Connect to redis server | |||
* @param name sentinel name | |||
* @param connect_callback connect handler to be called on connect events (may be null) | |||
* @param timeout_ms maximum time to connect | |||
* @param max_reconnects maximum attempts of reconnection if connection dropped | |||
* @param reconnect_interval_ms time between two attempts of reconnection | |||
* | |||
*/ | |||
void connect( | |||
const std::string &name, | |||
const connect_callback_t &connect_callback = nullptr, | |||
std::uint32_t timeout_ms = 0, | |||
std::int32_t max_reconnects = 0, | |||
std::uint32_t reconnect_interval_ms = 0); | |||
/** | |||
* @brief determines client connectivity | |||
* @return whether we are connected to the redis server | |||
* | |||
*/ | |||
bool is_connected() const; | |||
/** | |||
* @brief disconnect from redis server | |||
* @param wait_for_removal when set to true, disconnect blocks until the underlying TCP client has been effectively removed from the io_service and that all the underlying callbacks have completed. | |||
* | |||
*/ | |||
void disconnect(bool wait_for_removal = false); | |||
/** | |||
* @brief determines if reconnect is in progress | |||
* @return whether an attempt to reconnect is in progress | |||
* | |||
*/ | |||
bool is_reconnecting() const; | |||
/** | |||
* @brief stop any reconnect in progress | |||
* | |||
*/ | |||
void cancel_reconnect(); | |||
public: | |||
/** | |||
* @brief reply callback called whenever a reply is received, takes as parameter the received reply | |||
* | |||
*/ | |||
typedef std::function<void(reply &)> reply_callback_t; | |||
/** | |||
* @brief ability to authenticate on the redis server if necessary | |||
* this method should not be called repeatedly as the storage of reply_callback is NOT thread safe (only one reply callback is stored for the subscriber client) | |||
* calling repeatedly auth() is undefined concerning the execution of the associated callbacks | |||
* @param password password to be used for authentication | |||
* @param reply_callback callback to be called on auth completion (nullable) | |||
* @return current instance | |||
* | |||
*/ | |||
subscriber &auth(const std::string &password, const reply_callback_t &reply_callback = nullptr); | |||
/** | |||
* @brief Set the label for the connection on the Redis server via the CLIENT SETNAME command. | |||
* This is useful for monitoring and managing connections on the server side of things. | |||
* @param name - string to label the connection with on the server side | |||
* @param reply_callback callback to be called on auth completion (nullable) | |||
* @return current instance | |||
* | |||
*/ | |||
subscriber& client_setname(const std::string& name, const reply_callback_t& reply_callback = nullptr); | |||
/** | |||
* subscribe callback, called whenever a new message is published on a subscribed channel | |||
* takes as parameter the channel and the message | |||
* | |||
*/ | |||
typedef std::function<void(const std::string &, const std::string &)> subscribe_callback_t; | |||
/** | |||
* Subscribes to the given channel and: | |||
* * calls acknowledgement_callback once the server has acknowledged about the subscription. | |||
* * calls subscribe_callback each time a message is published on this channel. | |||
* The command is not effectively sent immediately but stored in an internal buffer until commit() is called. | |||
* | |||
* @param channel channel to subscribe | |||
* @param callback callback to be called whenever a message is received for this channel | |||
* @param acknowledgement_callback callback to be called on subscription completion (nullable) | |||
* @return current instance | |||
*/ | |||
//! | |||
subscriber &subscribe(const std::string &channel, const subscribe_callback_t &callback, | |||
const acknowledgement_callback_t &acknowledgement_callback = nullptr); | |||
/** | |||
* PSubscribes to the given channel and: | |||
* * calls acknowledgement_callback once the server has acknowledged about the subscription. | |||
* * calls subscribe_callback each time a message is published on this channel. | |||
* The command is not effectively sent immediately but stored in an internal buffer until commit() is called. | |||
* | |||
* @param pattern pattern to psubscribe | |||
* @param callback callback to be called whenever a message is received for this pattern | |||
* @param acknowledgement_callback callback to be called on subscription completion (nullable) | |||
* @return current instance | |||
*/ | |||
//! | |||
subscriber &psubscribe(const std::string &pattern, const subscribe_callback_t &callback, | |||
const acknowledgement_callback_t &acknowledgement_callback = nullptr); | |||
/** | |||
* unsubscribe from the given channel | |||
* The command is not effectively sent immediately, but stored inside an internal buffer until commit() is called. | |||
* | |||
* @param channel channel to unsubscribe from | |||
* @return current instance | |||
* | |||
*/ | |||
subscriber &unsubscribe(const std::string &channel); | |||
/** | |||
* punsubscribe from the given pattern | |||
* The command is not effectively sent immediately, but stored inside an internal buffer until commit() is called. | |||
* | |||
* @param pattern pattern to punsubscribe from | |||
* @return current instance | |||
* | |||
*/ | |||
subscriber &punsubscribe(const std::string &pattern); | |||
/** | |||
* commit pipelined transaction | |||
* that is, send to the network all commands pipelined by calling send() / subscribe() / ... | |||
* | |||
* @return current instance | |||
* | |||
*/ | |||
subscriber &commit(); | |||
public: | |||
/** | |||
* add a sentinel definition. Required for connect() or get_master_addr_by_name() when autoconnect is enabled. | |||
* | |||
* @param host sentinel host | |||
* @param port sentinel port | |||
* @param timeout_ms maximum time to connect | |||
* | |||
*/ | |||
void add_sentinel(const std::string &host, std::size_t port, std::uint32_t timeout_ms = 0); | |||
/** | |||
* retrieve sentinel for current client | |||
* | |||
* @return sentinel associated to current client | |||
* | |||
*/ | |||
const sentinel &get_sentinel() const; | |||
/** | |||
* retrieve sentinel for current client | |||
* non-const version | |||
* | |||
* @return sentinel associated to current client | |||
* | |||
*/ | |||
sentinel &get_sentinel(); | |||
/** | |||
* clear all existing sentinels. | |||
* | |||
*/ | |||
void clear_sentinels(); | |||
private: | |||
/** | |||
* struct to hold callbacks (sub and ack) for a given channel or pattern | |||
* | |||
*/ | |||
struct callback_holder { | |||
subscribe_callback_t subscribe_callback; | |||
acknowledgement_callback_t acknowledgement_callback; | |||
}; | |||
private: | |||
/** | |||
* redis connection receive handler, triggered whenever a reply has been read by the redis connection | |||
* | |||
* @param connection redis_connection instance | |||
* @param reply parsed reply | |||
* | |||
*/ | |||
void connection_receive_handler(network::redis_connection &connection, reply &reply); | |||
/** | |||
* redis_connection disconnection handler, triggered whenever a disconnection occurred | |||
* | |||
* @param connection redis_connection instance | |||
* | |||
*/ | |||
void connection_disconnection_handler(network::redis_connection &connection); | |||
/** | |||
* trigger the ack callback for matching channel/pattern | |||
* check if reply is valid | |||
* | |||
* @param reply received reply | |||
* | |||
*/ | |||
void handle_acknowledgement_reply(const std::vector<reply> &reply); | |||
/** | |||
* trigger the sub callback for all matching channels/patterns | |||
* check if reply is valid | |||
* | |||
* @param reply received reply | |||
* | |||
*/ | |||
void handle_subscribe_reply(const std::vector<reply> &reply); | |||
/** | |||
* trigger the sub callback for all matching channels/patterns | |||
* check if reply is valid | |||
* | |||
* @param reply received reply | |||
* | |||
*/ | |||
void handle_psubscribe_reply(const std::vector<reply> &reply); | |||
/** | |||
* find channel or pattern that is associated to the reply and call its ack callback | |||
* | |||
* @param channel channel or pattern that caused the issuance of this reply | |||
* @param channels list of channels or patterns to be searched for the received channel | |||
* @param channels_mtx channels or patterns mtx to be locked for race condition | |||
* @param nb_chans redis server ack reply | |||
* | |||
*/ | |||
void | |||
call_acknowledgement_callback(const std::string &channel, const std::map<std::string, callback_holder> &channels, | |||
std::mutex &channels_mtx, int64_t nb_chans); | |||
private: | |||
/** | |||
* reconnect to the previously connected host | |||
* automatically re authenticate and resubscribe to subscribed channel in case of success | |||
* | |||
*/ | |||
void reconnect(); | |||
/** | |||
* re authenticate to redis server based on previously used password | |||
* | |||
*/ | |||
void re_auth(); | |||
/** | |||
* re send CLIENT SETNAME to redis server based on previously used name | |||
* | |||
*/ | |||
void re_client_setname(void); | |||
/** | |||
* resubscribe (sub and psub) to previously subscribed channels/patterns | |||
* | |||
*/ | |||
void re_subscribe(); | |||
/** | |||
* @return whether a reconnection attempt should be performed | |||
* | |||
*/ | |||
bool should_reconnect() const; | |||
/** | |||
* sleep between two reconnect attempts if necessary | |||
* | |||
*/ | |||
void sleep_before_next_reconnect_attempt(); | |||
/** | |||
* clear all subscriptions (dirty way, no unsub/punsub commands send: mostly used for cleaning in disconnection condition) | |||
* | |||
*/ | |||
void clear_subscriptions(); | |||
private: | |||
/** | |||
* unprotected sub | |||
* same as subscribe, but without any mutex lock | |||
* | |||
* @param channel channel to subscribe | |||
* @param callback callback to be called whenever a message is received for this channel | |||
* @param acknowledgement_callback callback to be called on subscription completion (nullable) | |||
* | |||
*/ | |||
void unprotected_subscribe(const std::string &channel, const subscribe_callback_t &callback, | |||
const acknowledgement_callback_t &acknowledgement_callback); | |||
/** | |||
* unprotected psub | |||
* same as psubscribe, but without any mutex lock | |||
* | |||
* @param pattern pattern to psubscribe | |||
* @param callback callback to be called whenever a message is received for this pattern | |||
* @param acknowledgement_callback callback to be called on subscription completion (nullable) | |||
* | |||
*/ | |||
void unprotected_psubscribe(const std::string &pattern, const subscribe_callback_t &callback, | |||
const acknowledgement_callback_t &acknowledgement_callback); | |||
private: | |||
/** | |||
* server we are connected to | |||
* | |||
*/ | |||
std::string m_redis_server; | |||
/** | |||
* port we are connected to | |||
* | |||
*/ | |||
std::size_t m_redis_port = 0; | |||
/** | |||
* master name (if we are using sentinel) we are connected to | |||
* | |||
*/ | |||
std::string m_master_name; | |||
/** | |||
* password used to authenticate | |||
* | |||
*/ | |||
std::string m_password; | |||
/** | |||
* name to use with CLIENT SETNAME | |||
* | |||
*/ | |||
std::string m_client_name; | |||
/** | |||
* tcp client for redis connection | |||
* | |||
*/ | |||
network::redis_connection m_client; | |||
/** | |||
* redis sentinel | |||
* | |||
*/ | |||
cpp_redis::sentinel m_sentinel; | |||
/** | |||
* max time to connect | |||
* | |||
*/ | |||
std::uint32_t m_connect_timeout_ms = 0; | |||
/** | |||
* max number of reconnection attempts | |||
* | |||
*/ | |||
std::int32_t m_max_reconnects = 0; | |||
/** | |||
* current number of attempts to reconnect | |||
* | |||
*/ | |||
std::int32_t m_current_reconnect_attempts = 0; | |||
/** | |||
* time between two reconnection attempts | |||
* | |||
*/ | |||
std::uint32_t m_reconnect_interval_ms = 0; | |||
/** | |||
* reconnection status | |||
* | |||
*/ | |||
std::atomic_bool m_reconnecting; | |||
/** | |||
* to force cancel reconnection | |||
* | |||
*/ | |||
std::atomic_bool m_cancel; | |||
/** | |||
* subscribed channels and their associated channels | |||
* | |||
*/ | |||
std::map<std::string, callback_holder> m_subscribed_channels; | |||
/** | |||
* psubscribed channels and their associated channels | |||
* | |||
*/ | |||
std::map<std::string, callback_holder> m_psubscribed_channels; | |||
/** | |||
* connect handler | |||
* | |||
*/ | |||
connect_callback_t m_connect_callback; | |||
/** | |||
* sub chans thread safety | |||
* | |||
*/ | |||
std::mutex m_psubscribed_channels_mutex; | |||
/** | |||
* psub chans thread safety | |||
* | |||
*/ | |||
std::mutex m_subscribed_channels_mutex; | |||
/** | |||
* auth reply callback | |||
* | |||
*/ | |||
reply_callback_t m_auth_reply_callback; | |||
/** | |||
* client setname reply callback | |||
* | |||
*/ | |||
reply_callback_t m_client_setname_reply_callback; | |||
}; | |||
} // namespace cpp_redis |
@@ -0,0 +1,183 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 2015-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#ifndef CPP_REDIS_CORE_TYPES_HPP | |||
#define CPP_REDIS_CORE_TYPES_HPP | |||
#include <string> | |||
#include <vector> | |||
#include <map> | |||
#include <ctime> | |||
#include <chrono> | |||
#include <cpp_redis/core/reply.hpp> | |||
#include <functional> | |||
#include <cpp_redis/impl/types.hpp> | |||
namespace cpp_redis { | |||
typedef std::int64_t ms; | |||
/** | |||
* @brief first array is the session name, second is ids | |||
* | |||
*/ | |||
typedef std::pair<std::vector<std::string>, std::vector<std::string>> streams_t; | |||
/** | |||
* @brief Options | |||
*/ | |||
typedef struct xread_options { | |||
streams_t Streams; | |||
std::int64_t Count; | |||
std::int64_t Block; | |||
} xread_options_t; | |||
typedef struct xreadgroup_options { | |||
std::string Group; | |||
std::string Consumer; | |||
streams_t Streams; | |||
std::int64_t Count; | |||
std::int64_t Block; | |||
bool NoAck; | |||
} xreadgroup_options_t; | |||
typedef struct range_options { | |||
std::string Start; | |||
std::string Stop; | |||
std::int64_t Count; | |||
} range_options_t; | |||
typedef struct xclaim_options { | |||
std::int64_t Idle; | |||
std::time_t *Time; | |||
std::int64_t RetryCount; | |||
bool Force; | |||
bool JustId; | |||
} xclaim_options_t; | |||
typedef struct xpending_options { | |||
range_options_t Range; | |||
std::string Consumer; | |||
} xpending_options_t; | |||
/** | |||
* @brief Replies | |||
*/ | |||
class xmessage : public message_type { | |||
public: | |||
xmessage(); | |||
explicit xmessage(const reply_t &data); | |||
friend std::ostream &operator<<(std::ostream &os, const xmessage &xm); | |||
}; | |||
typedef xmessage xmessage_t; | |||
class xstream { | |||
public: | |||
explicit xstream(const reply_t &data); | |||
friend std::ostream &operator<<(std::ostream &os, const xstream &xs); | |||
std::string Stream; | |||
std::vector<xmessage_t> Messages; | |||
}; | |||
typedef xstream xstream_t; | |||
class xinfo_reply { | |||
public: | |||
explicit xinfo_reply(const cpp_redis::reply &data); | |||
std::int64_t Length; | |||
std::int64_t RadixTreeKeys; | |||
std::int64_t RadixTreeNodes; | |||
std::int64_t Groups; | |||
std::string LastGeneratedId; | |||
xmessage_t FirstEntry; | |||
xmessage_t LastEntry; | |||
}; | |||
class xstream_reply : public std::vector<xstream_t> { | |||
public: | |||
explicit xstream_reply(const reply_t &data); | |||
friend std::ostream &operator<<(std::ostream &os, const xstream_reply &xs); | |||
bool is_null() const { | |||
if (empty()) | |||
return true; | |||
for (auto &v : *this) { | |||
if (v.Messages.empty()) | |||
return true; | |||
} | |||
return false; | |||
} | |||
}; | |||
typedef xstream_reply xstream_reply_t; | |||
/** | |||
* @brief Callbacks | |||
*/ | |||
/** | |||
* acknowledgment callback called whenever a subscribe completes | |||
* takes as parameter the int returned by the redis server (usually the number of channels you are subscribed to) | |||
* | |||
*/ | |||
typedef std::function<void(const int64_t &)> acknowledgement_callback_t; | |||
/** | |||
* high availability (re)connection states | |||
* * dropped: connection has dropped | |||
* * start: attempt of connection has started | |||
* * sleeping: sleep between two attempts | |||
* * ok: connected | |||
* * failed: failed to connect | |||
* * lookup failed: failed to retrieve master sentinel | |||
* * stopped: stop to try to reconnect | |||
* | |||
*/ | |||
enum class connect_state { | |||
dropped, | |||
start, | |||
sleeping, | |||
ok, | |||
failed, | |||
lookup_failed, | |||
stopped | |||
}; | |||
/** | |||
* connect handler, called whenever a new connection even occurred | |||
* | |||
*/ | |||
typedef std::function<void(const std::string &host, std::size_t port, connect_state status)> connect_callback_t; | |||
typedef std::function<void(const cpp_redis::message_type&)> message_callback_t; | |||
} // namespace cpp_redis | |||
#endif //CPP_REDIS_TYPES_HPP |
@@ -0,0 +1,42 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 2015-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#ifndef __CPP_REDIS_ | |||
#define __CPP_REDIS_ | |||
#ifdef _WIN32 | |||
#pragma comment( lib, "ws2_32.lib") | |||
#endif /* _WIN32 */ | |||
#include <cpp_redis/core/client.hpp> | |||
#include <cpp_redis/core/consumer.hpp> | |||
#include <cpp_redis/core/subscriber.hpp> | |||
#include <cpp_redis/core/reply.hpp> | |||
#include <cpp_redis/misc/error.hpp> | |||
#include <cpp_redis/misc/logger.hpp> | |||
#include <cpp_redis/core/types.hpp> | |||
#endif | |||
#ifndef __CPP_REDIS_USE_CUSTOM_TCP_CLIENT | |||
#include <cpp_redis/network/tcp_client.hpp> | |||
#endif /* __CPP_REDIS_USE_CUSTOM_TCP_CLIENT */ |
@@ -0,0 +1,41 @@ | |||
// | |||
// Created by nick on 11/22/18. | |||
// | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 2015-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#ifndef CPP_REDIS_GENERATE_RAND_HPP | |||
#define CPP_REDIS_GENERATE_RAND_HPP | |||
#include <random> | |||
#include <string> | |||
namespace cpp_redis { | |||
inline std::string generate_rand() { | |||
std::mt19937 rng; | |||
rng.seed(std::random_device()()); | |||
std::uniform_int_distribution<std::mt19937::result_type> dist6(1,6); // distribution in range [1, 6] | |||
return std::to_string(dist6(rng)); | |||
} | |||
} | |||
#endif //CPP_REDIS_GENERATE_RAND_HPP |
@@ -0,0 +1,130 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 2015-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <type_traits> | |||
namespace cpp_redis { | |||
namespace helpers { | |||
/** | |||
* type traits to return last element of a variadic list | |||
* | |||
*/ | |||
template <typename T, typename... Args> | |||
struct back { | |||
/** | |||
* last type of variadic list | |||
* | |||
*/ | |||
using type = typename back<Args...>::type; | |||
}; | |||
/** | |||
* type traits to return last element of a variadic list | |||
* | |||
*/ | |||
template <typename T> | |||
struct back<T> { | |||
/** | |||
* templated type | |||
* | |||
*/ | |||
using type = T; | |||
}; | |||
/** | |||
* type traits to return front element of a variadic list | |||
* | |||
*/ | |||
template <typename T, typename... Ts> | |||
struct front { | |||
/** | |||
* front type of variadic list | |||
* | |||
*/ | |||
using type = T; | |||
}; | |||
/** | |||
* type traits to check if type is present in variadic list | |||
* | |||
*/ | |||
template <typename T1, typename T2, typename... Ts> | |||
struct is_type_present { | |||
/** | |||
* true if T1 is present in remaining types of variadic list | |||
* false otherwise | |||
* | |||
*/ | |||
static constexpr bool value = std::is_same<T1, T2>::value | |||
? true | |||
: is_type_present<T1, Ts...>::value; | |||
}; | |||
/** | |||
* type traits to check if type is present in variadic list | |||
* | |||
*/ | |||
template <typename T1, typename T2> | |||
struct is_type_present<T1, T2> { | |||
/** | |||
* true if T1 and T2 are the same | |||
* false otherwise | |||
* | |||
*/ | |||
static constexpr bool value = std::is_same<T1, T2>::value; | |||
}; | |||
/** | |||
* type traits to check if type is not present in variadic list | |||
* | |||
*/ | |||
template <typename T, typename... Args> | |||
struct is_different_types { | |||
/** | |||
* true if T is not in remaining types of variadic list | |||
* false otherwise | |||
* | |||
*/ | |||
static constexpr bool value = is_type_present<T, Args...>::value | |||
? false | |||
: is_different_types<Args...>::value; | |||
}; | |||
/** | |||
* type traits to check if type is not present in variadic list | |||
* | |||
*/ | |||
template <typename T1> | |||
struct is_different_types<T1> { | |||
/** | |||
* true | |||
* | |||
*/ | |||
static constexpr bool value = true; | |||
}; | |||
} // namespace helpers | |||
} // namespace cpp_redis |
@@ -0,0 +1,131 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 2015-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#include <functional> | |||
#include <iostream> | |||
namespace cpp_redis { | |||
template <typename T> | |||
typename std::enable_if<std::is_same<T, client::client_type>::value>::type | |||
client::client_kill_unpack_arg(std::vector<std::string>& redis_cmd, reply_callback_t&, client_type type) { | |||
redis_cmd.emplace_back("TYPE"); | |||
std::string type_string; | |||
switch (type) { | |||
case client_type::normal: type_string = "normal"; break; | |||
case client_type::master: type_string = "master"; break; | |||
case client_type::pubsub: type_string = "pubsub"; break; | |||
case client_type::slave: type_string = "slave"; break; | |||
} | |||
redis_cmd.emplace_back(type_string); | |||
} | |||
template <typename T> | |||
typename std::enable_if<std::is_same<T, bool>::value>::type | |||
client::client_kill_unpack_arg(std::vector<std::string>& redis_cmd, reply_callback_t&, bool skip) { | |||
redis_cmd.emplace_back("SKIPME"); | |||
redis_cmd.emplace_back(skip ? "yes" : "no"); | |||
} | |||
template <typename T> | |||
typename std::enable_if<std::is_integral<T>::value>::type | |||
client::client_kill_unpack_arg(std::vector<std::string>& redis_cmd, reply_callback_t&, uint64_t id) { | |||
redis_cmd.emplace_back("ID"); | |||
redis_cmd.emplace_back(std::to_string(id)); | |||
} | |||
template <typename T> | |||
typename std::enable_if<std::is_class<T>::value>::type | |||
client::client_kill_unpack_arg(std::vector<std::string>&, reply_callback_t& reply_callback, const T& cb) { | |||
reply_callback = cb; | |||
} | |||
template <typename T, typename... Ts> | |||
void | |||
client::client_kill_impl(std::vector<std::string>& redis_cmd, reply_callback_t& reply, const T& arg, const Ts&... args) { | |||
static_assert(!std::is_class<T>::value, "Reply callback should be in the end of the argument list"); | |||
client_kill_unpack_arg<T>(redis_cmd, reply, arg); | |||
client_kill_impl(redis_cmd, reply, args...); | |||
} | |||
template <typename T> | |||
void | |||
client::client_kill_impl(std::vector<std::string>& redis_cmd, reply_callback_t& reply, const T& arg) { | |||
client_kill_unpack_arg<T>(redis_cmd, reply, arg); | |||
} | |||
template <typename T, typename... Ts> | |||
inline client& | |||
client::client_kill(const T& arg, const Ts&... args) { | |||
static_assert(helpers::is_different_types<T, Ts...>::value, "Should only have one distinct value per filter type"); | |||
static_assert(!(std::is_class<T>::value && std::is_same<T, typename helpers::back<T, Ts...>::type>::value), "Should have at least one filter"); | |||
std::vector<std::string> redis_cmd({"CLIENT", "KILL"}); | |||
reply_callback_t reply_cb = nullptr; | |||
client_kill_impl<T, Ts...>(redis_cmd, reply_cb, arg, args...); | |||
return send(redis_cmd, reply_cb); | |||
} | |||
template <typename T, typename... Ts> | |||
inline client& | |||
client::client_kill(const std::string& host, int port, const T& arg, const Ts&... args) { | |||
static_assert(helpers::is_different_types<T, Ts...>::value, "Should only have one distinct value per filter type"); | |||
std::vector<std::string> redis_cmd({"CLIENT", "KILL"}); | |||
//! If we have other type than lambda, then it's a filter | |||
if (!std::is_class<T>::value) { | |||
redis_cmd.emplace_back("ADDR"); | |||
} | |||
redis_cmd.emplace_back(host + ":" + std::to_string(port)); | |||
reply_callback_t reply_cb = nullptr; | |||
client_kill_impl<T, Ts...>(redis_cmd, reply_cb, arg, args...); | |||
return send(redis_cmd, reply_cb); | |||
} | |||
inline client& | |||
client::client_kill(const std::string& host, int port) { | |||
return client_kill(host, port, reply_callback_t(nullptr)); | |||
} | |||
template <typename... Ts> | |||
inline client& | |||
client::client_kill(const char* host, int port, const Ts&... args) { | |||
return client_kill(std::string(host), port, args...); | |||
} | |||
template <typename T, typename... Ts> | |||
std::future<reply> | |||
client::client_kill_future(const T arg, const Ts... args) { | |||
//! gcc 4.8 doesn't handle variadic template capture arguments (appears in 4.9) | |||
//! so std::bind should capture all arguments because of the compiler. | |||
return exec_cmd(std::bind([this](T arg, Ts... args, const reply_callback_t& cb) -> client& { | |||
return client_kill(arg, args..., cb); | |||
}, | |||
arg, args..., std::placeholders::_1)); | |||
} | |||
} // namespace cpp_redis |
@@ -0,0 +1,157 @@ | |||
#include <utility> | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 11/27/18 nick. <nbatkins@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE.#ifndef CPP_REDIS_TYPES_HPP | |||
#ifndef CPP_REDIS_IMPL_TYPES_HPP | |||
#define CPP_REDIS_IMPL_TYPES_HPP | |||
#include <cpp_redis/core/reply.hpp> | |||
#include <cpp_redis/misc/convert.hpp> | |||
#include <cpp_redis/misc/logger.hpp> | |||
#include <cpp_redis/misc/optional.hpp> | |||
#include <sstream> | |||
#include <string> | |||
#include <vector> | |||
#include <map> | |||
namespace cpp_redis { | |||
class serializer_type { | |||
public: | |||
inline serializer_type() {} | |||
virtual ~serializer_type() {} | |||
/** | |||
* @return the underlying string | |||
* | |||
*/ | |||
virtual const std::string& as_string() const = 0; | |||
/** | |||
* @return the underlying integer | |||
* | |||
*/ | |||
virtual optional_t<int64_t> try_get_int() const = 0; | |||
protected: | |||
std::string m_str_val; | |||
}; | |||
typedef std::shared_ptr<serializer_type> serializer_ptr_t; | |||
template <typename T> | |||
class message_impl { | |||
public: | |||
virtual ~message_impl() {} | |||
virtual const std::string get_id() const = 0; | |||
virtual const message_impl& set_id(std::string id) = 0; | |||
virtual T find(std::string key) const = 0; | |||
virtual const message_impl& push(std::string key, T value) = 0; | |||
virtual const message_impl& push(std::vector<std::pair<std::string, T>> values) = 0; | |||
virtual const message_impl& push(typename std::vector<T>::const_iterator ptr_begin, | |||
typename std::vector<T>::const_iterator ptr_end) = 0; | |||
virtual const std::multimap<std::string, T>& get_values() const = 0; | |||
protected: | |||
std::string m_id; | |||
std::multimap<std::string, T> m_values; | |||
}; | |||
class message_type : public message_impl<reply_t> { | |||
public: | |||
inline const std::string | |||
get_id() const override { return m_id; }; | |||
inline const message_type& | |||
set_id(std::string id) override { | |||
m_id = id; | |||
return *this; | |||
} | |||
inline reply_t | |||
find(std::string key) const override { | |||
auto it = m_values.find(key); | |||
if (it != m_values.end()) | |||
return it->second; | |||
else | |||
throw "value not found"; | |||
}; | |||
inline message_type& | |||
push(std::string key, reply_t value) override { | |||
m_values.insert({key, std::move(value)}); | |||
return *this; | |||
} | |||
inline message_type& | |||
push(std::vector<std::pair<std::string, reply_t>> values) override { | |||
for (auto& v : values) { | |||
m_values.insert({v.first, std::move(v.second)}); | |||
} | |||
return *this; | |||
} | |||
inline message_type& | |||
push(std::vector<reply_t>::const_iterator ptr_begin, | |||
std::vector<reply_t>::const_iterator ptr_end) override { | |||
std::string key; | |||
size_t i = 2; | |||
for (auto pb = ptr_begin; pb != ptr_end; pb++) { | |||
if (i % 2 == 0) { | |||
key = pb->as_string(); | |||
} | |||
else { | |||
m_values.insert({key, *pb}); | |||
} | |||
} | |||
return *this; | |||
} | |||
inline const std::multimap<std::string, reply_t>& | |||
get_values() const override { | |||
return m_values; | |||
}; | |||
inline std::multimap<std::string, std::string> | |||
get_str_values() const { | |||
std::multimap<std::string, std::string> ret; | |||
for (auto& v : m_values) { | |||
std::stringstream s; | |||
s << v.second; | |||
ret.insert({v.first, s.str()}); | |||
} | |||
return ret; | |||
}; | |||
}; | |||
} // namespace cpp_redis | |||
#endif //CPP_REDIS_TYPES_HPP |
@@ -0,0 +1,48 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 11/27/18 nick. <nbatkins@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE.#ifndef CPP_REDIS_CONVERT_HPP | |||
#ifndef CPP_REDIS_CONVERT_HPP | |||
#define CPP_REDIS_CONVERT_HPP | |||
#include <sstream> | |||
#include <cpp_redis/misc/optional.hpp> | |||
namespace cpp_redis { | |||
class try_convert { | |||
public: | |||
template <class T> | |||
static enableIf<std::is_convertible<T, std::string>::value, optional_t<int64_t> > to_int(T value) { | |||
try { | |||
std::stringstream stream(value); | |||
int64_t x; | |||
stream >> x; | |||
return optional_t<int64_t>(x); | |||
} catch (std::exception &exc) { | |||
return {}; | |||
} | |||
} | |||
}; | |||
} | |||
#endif //CPP_REDIS_CONVERT_HPP |
@@ -0,0 +1,10 @@ | |||
#pragma once | |||
#if defined(__GNUC__) || defined(__clang__) | |||
#define DEPRECATED __attribute__((deprecated)) | |||
#elif defined(_MSC_VER) | |||
#define DEPRECATED __declspec(deprecated) | |||
#else | |||
#pragma message("WARNING: You need to implement DEPRECATED for this compiler") | |||
#define DEPRECATED | |||
#endif |
@@ -0,0 +1,91 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 11/27/18 nick. <nbatkins@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE.#ifndef CPP_REDIS_CONVERT_HPP | |||
// | |||
// Code modified from https://github.com/embeddedartistry/embedded-resources/blob/master/examples/cpp/dispatch.cpp | |||
// | |||
#ifndef CPP_REDIS_DISPATCH_QUEUE_HPP | |||
#define CPP_REDIS_DISPATCH_QUEUE_HPP | |||
#include <thread> | |||
#include <functional> | |||
#include <vector> | |||
#include <cstdint> | |||
#include <cstdio> | |||
#include <queue> | |||
#include <mutex> | |||
#include <string> | |||
#include <condition_variable> | |||
#include <cpp_redis/impl/types.hpp> | |||
namespace cpp_redis { | |||
typedef std::multimap<std::string, std::string> consumer_response_t; | |||
typedef std::function<consumer_response_t(const cpp_redis::message_type&)> dispatch_callback_t; | |||
typedef std::function<void(size_t size)> notify_callback_t; | |||
typedef struct dispatch_callback_collection { | |||
dispatch_callback_t callback; | |||
message_type message; | |||
} dispatch_callback_collection_t; | |||
class dispatch_queue { | |||
public: | |||
explicit dispatch_queue(std::string name, const notify_callback_t ¬ify_callback, size_t thread_cnt = 1); | |||
~dispatch_queue(); | |||
// dispatch and copy | |||
void dispatch(const cpp_redis::message_type& message, const dispatch_callback_t& op); | |||
// dispatch and move | |||
void dispatch(const cpp_redis::message_type& message, dispatch_callback_t&& op); | |||
// Deleted operations | |||
dispatch_queue(const dispatch_queue& rhs) = delete; | |||
dispatch_queue& operator=(const dispatch_queue& rhs) = delete; | |||
dispatch_queue(dispatch_queue&& rhs) = delete; | |||
dispatch_queue& operator=(dispatch_queue&& rhs) = delete; | |||
size_t size(); | |||
private: | |||
std::string m_name; | |||
std::mutex m_threads_lock; | |||
mutable std::vector<std::thread> m_threads; | |||
std::mutex m_mq_mutex; | |||
std::queue<dispatch_callback_collection_t> m_mq; | |||
std::condition_variable m_cv; | |||
bool m_quit = false; | |||
notify_callback_t notify_handler; | |||
void dispatch_thread_handler(); | |||
}; | |||
typedef dispatch_queue dispatch_queue_t; | |||
typedef std::unique_ptr<dispatch_queue> dispatch_queue_ptr_t; | |||
} | |||
#endif //CPP_REDIS_DISPATCH_QUEUE_HPP |
@@ -0,0 +1,56 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 2015-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <stdexcept> | |||
#include <string> | |||
namespace cpp_redis { | |||
/** | |||
* specialized runtime_error used for cpp_redis error | |||
* | |||
*/ | |||
class redis_error : public std::runtime_error { | |||
public: | |||
using std::runtime_error::runtime_error; | |||
using std::runtime_error::what; | |||
/** | |||
* ctor (string) | |||
* | |||
*/ | |||
explicit redis_error(const std::string& err) | |||
: std::runtime_error(err.c_str()) { | |||
} | |||
/** | |||
* ctor(char*) | |||
* | |||
*/ | |||
explicit redis_error(const char* err) | |||
: std::runtime_error(err) { | |||
} | |||
}; | |||
} // namespace cpp_redis |
@@ -0,0 +1,258 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 2015-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <memory> | |||
#include <mutex> | |||
#include <string> | |||
namespace cpp_redis { | |||
/** | |||
* logger_iface | |||
* should be inherited by any class intended to be used for logging | |||
* | |||
*/ | |||
class logger_iface { | |||
public: | |||
/** | |||
* ctor | |||
* | |||
*/ | |||
logger_iface() = default; | |||
/** | |||
* dtor | |||
* | |||
*/ | |||
virtual ~logger_iface() = default; | |||
/** | |||
* copy ctor | |||
* | |||
*/ | |||
logger_iface(const logger_iface&) = default; | |||
/** | |||
* assignment operator | |||
* | |||
*/ | |||
logger_iface& operator=(const logger_iface&) = default; | |||
public: | |||
/** | |||
* debug logging | |||
* | |||
* @param msg message to be logged | |||
* @param file file from which the message is coming | |||
* @param line line in the file of the message | |||
* | |||
*/ | |||
virtual void debug(const std::string& msg, const std::string& file, std::size_t line) = 0; | |||
/** | |||
* info logging | |||
* | |||
* @param msg message to be logged | |||
* @param file file from which the message is coming | |||
* @param line line in the file of the message | |||
* | |||
*/ | |||
virtual void info(const std::string& msg, const std::string& file, std::size_t line) = 0; | |||
/** | |||
* warn logging | |||
* | |||
* @param msg message to be logged | |||
* @param file file from which the message is coming | |||
* @param line line in the file of the message | |||
* | |||
*/ | |||
virtual void warn(const std::string& msg, const std::string& file, std::size_t line) = 0; | |||
/** | |||
* error logging | |||
* | |||
* @param msg message to be logged | |||
* @param file file from which the message is coming | |||
* @param line line in the file of the message | |||
* | |||
*/ | |||
virtual void error(const std::string& msg, const std::string& file, std::size_t line) = 0; | |||
}; | |||
/** | |||
* default logger class provided by the library | |||
* | |||
*/ | |||
class logger : public logger_iface { | |||
public: | |||
/** | |||
* log level | |||
* | |||
*/ | |||
enum class log_level { | |||
error = 0, | |||
warn = 1, | |||
info = 2, | |||
debug = 3 | |||
}; | |||
public: | |||
/** | |||
* ctor | |||
* | |||
*/ | |||
explicit logger(log_level level = log_level::info); | |||
/** | |||
* dtor | |||
* | |||
*/ | |||
~logger() override = default; | |||
/** | |||
* copy ctor | |||
* | |||
*/ | |||
logger(const logger&) = default; | |||
/** | |||
* assignment operator | |||
* | |||
*/ | |||
logger& operator=(const logger&) = default; | |||
public: | |||
/** | |||
* debug logging | |||
* | |||
* @param msg message to be logged | |||
* @param file file from which the message is coming | |||
* @param line line in the file of the message | |||
* | |||
*/ | |||
void debug(const std::string& msg, const std::string& file, std::size_t line) override; | |||
/** | |||
* info logging | |||
* | |||
* @param msg message to be logged | |||
* @param file file from which the message is coming | |||
* @param line line in the file of the message | |||
* | |||
*/ | |||
void info(const std::string& msg, const std::string& file, std::size_t line) override; | |||
/** | |||
* warn logging | |||
* | |||
* @param msg message to be logged | |||
* @param file file from which the message is coming | |||
* @param line line in the file of the message | |||
* | |||
*/ | |||
void warn(const std::string& msg, const std::string& file, std::size_t line) override; | |||
/** | |||
* error logging | |||
* | |||
* @param msg message to be logged | |||
* @param file file from which the message is coming | |||
* @param line line in the file of the message | |||
* | |||
*/ | |||
void error(const std::string& msg, const std::string& file, std::size_t line) override; | |||
private: | |||
/** | |||
* current log level in use | |||
* | |||
*/ | |||
log_level m_level; | |||
/** | |||
* mutex used to serialize logs in multi-threaded environment | |||
* | |||
*/ | |||
std::mutex m_mutex; | |||
}; | |||
/** | |||
* variable containing the current logger | |||
* by default, not set (no logs) | |||
* | |||
*/ | |||
extern std::unique_ptr<logger_iface> active_logger; | |||
/** | |||
* debug logging | |||
* convenience function used internally to call the logger | |||
* | |||
* @param msg message to be logged | |||
* @param file file from which the message is coming | |||
* @param line line in the file of the message | |||
* | |||
*/ | |||
void debug(const std::string& msg, const std::string& file, std::size_t line); | |||
/** | |||
* info logging | |||
* convenience function used internally to call the logger | |||
* | |||
* @param msg message to be logged | |||
* @param file file from which the message is coming | |||
* @param line line in the file of the message | |||
* | |||
*/ | |||
void info(const std::string& msg, const std::string& file, std::size_t line); | |||
/** | |||
* warn logging | |||
* convenience function used internally to call the logger | |||
* | |||
* @param msg message to be logged | |||
* @param file file from which the message is coming | |||
* @param line line in the file of the message | |||
* | |||
*/ | |||
void warn(const std::string& msg, const std::string& file, std::size_t line); | |||
/** | |||
* error logging | |||
* convenience function used internally to call the logger | |||
* | |||
* @param msg message to be logged | |||
* @param file file from which the message is coming | |||
* @param line line in the file of the message | |||
* | |||
*/ | |||
void error(const std::string& msg, const std::string& file, std::size_t line); | |||
/** | |||
* convenience macro to log with file and line information | |||
* | |||
*/ | |||
#ifdef __CPP_REDIS_LOGGING_ENABLED | |||
#define __CPP_REDIS_LOG(level, msg) cpp_redis::level(msg, __FILE__, __LINE__); | |||
#else | |||
#define __CPP_REDIS_LOG(level, msg) | |||
#endif /* __CPP_REDIS_LOGGING_ENABLED */ | |||
} // namespace cpp_redis |
@@ -0,0 +1,31 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 2015-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#if _WIN32 | |||
#define __CPP_REDIS_LENGTH(size) static_cast<unsigned int>(size) // for Windows, convert size to `unsigned int` | |||
#else /* _WIN32 */ | |||
#define __CPP_REDIS_LENGTH(size) size // for Unix, keep size as `size_t` | |||
#endif /* _WIN32 */ | |||
#define __CPP_REDIS_PRINT(...) printf(__VA_ARGS__) |
@@ -0,0 +1,76 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 11/27/18 nick. <nbatkins@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE.#ifndef CPP_REDIS_OPTIONAL_HPP | |||
#ifndef CPP_REDIS_OPTIONAL_HPP | |||
#define CPP_REDIS_OPTIONAL_HPP | |||
#include <string> | |||
// Prefer std optional | |||
#if __cplusplus >= 201703L | |||
#include <optional> | |||
namespace cpp_redis { | |||
template <class T> | |||
using optional_t = std::optional<T>; | |||
template <int I, class T> | |||
using enableIf = typename std::enable_if<I, T>::type; | |||
#else | |||
#include <cpp_redis/misc/logger.hpp> | |||
namespace cpp_redis { | |||
template <int I, class T> | |||
using enableIf = typename std::enable_if<I, T>::type; | |||
template <class T> | |||
struct optional { | |||
optional(T value) : m_value(value) {} | |||
// optional<T>& | |||
// operator()(T value) { | |||
// m_value = value; | |||
// return *this; | |||
// } | |||
T m_value; | |||
template <class U> | |||
enableIf<std::is_convertible<U, T>::value, T> | |||
value_or(U&& v) const { | |||
__CPP_REDIS_LOG(1, "value_or(U&& v)\n") | |||
return std::forward<U>(v); | |||
} | |||
template <class F> | |||
auto | |||
value_or(F&& action) const -> decltype(action()) { | |||
return action(); | |||
} | |||
}; | |||
template <class T> | |||
using optional_t = optional<T>; | |||
#endif | |||
} // namespace cpp_redis | |||
#endif //CPP_REDIS_OPTIONAL_HPP |
@@ -0,0 +1,219 @@ | |||
// The MIT License (MIT) | |||
// | |||
// Copyright (c) 2015-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <functional> | |||
#include <memory> | |||
#include <mutex> | |||
#include <string> | |||
#include <vector> | |||
#include <cpp_redis/builders/reply_builder.hpp> | |||
#include <cpp_redis/network/tcp_client_iface.hpp> | |||
#ifndef __CPP_REDIS_READ_SIZE | |||
#define __CPP_REDIS_READ_SIZE 4096 | |||
#endif /* __CPP_REDIS_READ_SIZE */ | |||
namespace cpp_redis { | |||
namespace network { | |||
/** | |||
* tcp connection wrapper handling redis protocol | |||
* | |||
*/ | |||
class redis_connection { | |||
public: | |||
#ifndef __CPP_REDIS_USE_CUSTOM_TCP_CLIENT | |||
/** | |||
* ctor | |||
* | |||
*/ | |||
redis_connection(); | |||
#endif /* __CPP_REDIS_USE_CUSTOM_TCP_CLIENT */ | |||
/** | |||
* ctor allowing to specify custom tcp client (default ctor uses the default tacopie tcp client) | |||
* | |||
* @param tcp_client tcp client to be used for network communications | |||
* | |||
*/ | |||
explicit redis_connection(const std::shared_ptr<tcp_client_iface> &tcp_client); | |||
/** | |||
* dtor | |||
* | |||
*/ | |||
~redis_connection(); | |||
/** | |||
* copy ctor | |||
* | |||
*/ | |||
redis_connection(const redis_connection &) = delete; | |||
/** | |||
* assignment operator | |||
* | |||
*/ | |||
redis_connection &operator=(const redis_connection &) = delete; | |||
public: | |||
/** | |||
* disconnection handler takes as parameter the instance of the redis_connection | |||
* | |||
*/ | |||
typedef std::function<void(redis_connection &)> disconnection_handler_t; | |||
/** | |||
* reply handler takes as parameter the instance of the redis_connection and the built reply | |||
* | |||
*/ | |||
typedef std::function<void(redis_connection &, reply &)> reply_callback_t; | |||
/** | |||
* connect to the given host and port, and set both disconnection and reply callbacks | |||
* | |||
* @param host host to be connected to | |||
* @param port port to be connected to | |||
* @param disconnection_handler handler to be called in case of disconnection | |||
* @param reply_callback handler to be called once a reply is ready | |||
* @param timeout_ms max time to connect (in ms) | |||
* | |||
*/ | |||
void connect( | |||
const std::string &host = "127.0.0.1", | |||
std::size_t port = 6379, | |||
const disconnection_handler_t &disconnection_handler = nullptr, | |||
const reply_callback_t &reply_callback = nullptr, | |||
std::uint32_t timeout_ms = 0); | |||
/** | |||
* disconnect from redis server | |||
* | |||
* @param wait_for_removal when sets to true, disconnect blocks until the underlying TCP client has been effectively removed from the io_service and that all the underlying callbacks have completed. | |||
* | |||
*/ | |||
void disconnect(bool wait_for_removal = false); | |||
/** | |||
* @return whether we are connected to the redis server or not | |||
* | |||
*/ | |||
bool is_connected() const; | |||
/** | |||
* send the given command | |||
* the command is actually pipelined and only buffered, so nothing is sent to the network | |||
* please call commit() to flush the buffer | |||
* | |||
* @param redis_cmd command to be sent | |||
* @return current instance | |||
* | |||
*/ | |||
redis_connection &send(const std::vector<std::string> &redis_cmd); | |||
/** | |||
* commit pipelined transaction | |||
* that is, send to the network all commands pipelined by calling send() | |||
* | |||
* @return current instance | |||
* | |||
*/ | |||
redis_connection &commit(); | |||
private: | |||
/** | |||
* tcp_client receive handler | |||
* called by the tcp_client whenever a read has completed | |||
* | |||
* @param result read result | |||
* | |||
*/ | |||
void tcp_client_receive_handler(const tcp_client_iface::read_result &result); | |||
/** | |||
* tcp_client disconnection handler | |||
* called by the tcp_client whenever a disconnection occurred | |||
* | |||
*/ | |||
void tcp_client_disconnection_handler(); | |||
/** | |||
* transform a user command to a redis command using the redis protocol format | |||
* for example, transform {"GET", "HELLO"} to something like "*2\r\n+GET\r\n+HELLO\r\n" | |||
* | |||
*/ | |||
std::string build_command(const std::vector<std::string> &redis_cmd); | |||
private: | |||
/** | |||
* simply call the disconnection handler (does nothing if disconnection handler is set to null) | |||
* | |||
*/ | |||
void call_disconnection_handler(); | |||
private: | |||
/** | |||
* tcp client for redis connection | |||
* | |||
*/ | |||
std::shared_ptr<cpp_redis::network::tcp_client_iface> m_client; | |||
/** | |||
* reply callback called whenever a reply has been read | |||
* | |||
*/ | |||
reply_callback_t m_reply_callback; | |||
/** | |||
* disconnection handler whenever a disconnection occurred | |||
* | |||
*/ | |||
disconnection_handler_t m_disconnection_handler; | |||
/** | |||
* reply builder used to build replies | |||
* | |||
*/ | |||
builders::reply_builder m_builder; | |||
/** | |||
* internal buffer used for pipelining (commands are buffered here and flushed to the tcp client when commit is called) | |||
* | |||
*/ | |||
std::string m_buffer; | |||
/** | |||
* protect internal buffer against race conditions | |||
* | |||
*/ | |||
std::mutex m_buffer_mutex; | |||
}; | |||
} // namespace network | |||
} // namespace cpp_redis |
@@ -0,0 +1,128 @@ | |||
// MIT License | |||
// | |||
// Copyright (c) 2016-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <cpp_redis/misc/error.hpp> | |||
#include <cpp_redis/network/tcp_client_iface.hpp> | |||
#include <tacopie/tacopie> | |||
namespace cpp_redis { | |||
namespace network { | |||
/** | |||
* implementation of the tcp_client_iface based on tacopie networking library | |||
* | |||
*/ | |||
class tcp_client : public tcp_client_iface { | |||
public: | |||
/** | |||
* ctor | |||
* | |||
*/ | |||
tcp_client() = default; | |||
/** | |||
* dtor | |||
* | |||
*/ | |||
~tcp_client() override = default; | |||
public: | |||
/** | |||
* start the tcp client | |||
* | |||
* @param addr host to be connected to | |||
* @param port port to be connected to | |||
* @param timeout_ms max time to connect in ms | |||
* | |||
*/ | |||
void connect(const std::string& addr, std::uint32_t port, std::uint32_t timeout_ms) override; | |||
/** | |||
* stop the tcp client | |||
* | |||
* @param wait_for_removal when sets to true, disconnect blocks until the underlying TCP client has been effectively removed from the io_service and that all the underlying callbacks have completed. | |||
* | |||
*/ | |||
void disconnect(bool wait_for_removal = false) override; | |||
/** | |||
* @return whether the client is currently connected or not | |||
* | |||
*/ | |||
bool is_connected() const override; | |||
/** | |||
* set number of io service workers for the io service monitoring this tcp connection | |||
* | |||
* @param nb_threads number of threads to be assigned | |||
* | |||
*/ | |||
void set_nb_workers(std::size_t nb_threads); | |||
public: | |||
/** | |||
* async read operation | |||
* | |||
* @param request information about what should be read and what should be done after completion | |||
* | |||
*/ | |||
void async_read(read_request& request) override; | |||
/** | |||
* async write operation | |||
* | |||
* @param request information about what should be written and what should be done after completion | |||
* | |||
*/ | |||
void async_write(write_request& request) override; | |||
public: | |||
/** | |||
* set on disconnection handler | |||
* | |||
* @param disconnection_handler handler to be called in case of a disconnection | |||
* | |||
*/ | |||
void set_on_disconnection_handler(const disconnection_handler_t& disconnection_handler) override; | |||
private: | |||
/** | |||
* tcp client for redis connection | |||
* | |||
*/ | |||
tacopie::tcp_client m_client; | |||
}; | |||
/** | |||
* set the number of workers to be assigned for the default io service | |||
* | |||
* @param nb_threads the number of threads to be assigned | |||
* | |||
*/ | |||
void set_default_nb_workers(std::size_t nb_threads); | |||
} // namespace network | |||
} // namespace cpp_redis |
@@ -0,0 +1,200 @@ | |||
// MIT License | |||
// | |||
// Copyright (c) 2016-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <cstdint> | |||
#include <functional> | |||
#include <string> | |||
#include <vector> | |||
namespace cpp_redis { | |||
namespace network { | |||
/** | |||
* interface defining how tcp client should be implemented to be used inside cpp_redis | |||
* | |||
*/ | |||
class tcp_client_iface { | |||
public: | |||
/** | |||
* ctor | |||
* | |||
*/ | |||
tcp_client_iface() = default; | |||
/** | |||
* dtor | |||
* | |||
*/ | |||
virtual ~tcp_client_iface() = default; | |||
public: | |||
/** | |||
* start the tcp client | |||
* | |||
* @param addr host to be connected to | |||
* @param port port to be connected to | |||
* @param timeout_ms max time to connect in ms | |||
* | |||
*/ | |||
virtual void connect(const std::string& addr, std::uint32_t port, std::uint32_t timeout_ms = 0) = 0; | |||
/** | |||
* stop the tcp client | |||
* | |||
* @param wait_for_removal when sets to true, disconnect blocks until the underlying TCP client has been effectively removed from the io_service and that all the underlying callbacks have completed. | |||
* | |||
*/ | |||
virtual void disconnect(bool wait_for_removal = false) = 0; | |||
/** | |||
* @return whether the client is currently connected or not | |||
* | |||
*/ | |||
virtual bool is_connected() const = 0; | |||
public: | |||
/** | |||
* structure to store read requests result | |||
* | |||
*/ | |||
struct read_result { | |||
/** | |||
* whether the operation succeeded or not | |||
* | |||
*/ | |||
bool success; | |||
/** | |||
* read bytes | |||
* | |||
*/ | |||
std::vector<char> buffer; | |||
}; | |||
/** | |||
* structure to store write requests result | |||
* | |||
*/ | |||
struct write_result { | |||
/** | |||
* whether the operation succeeded or not | |||
* | |||
*/ | |||
bool success; | |||
/** | |||
* number of bytes written | |||
* | |||
*/ | |||
std::size_t size; | |||
}; | |||
public: | |||
/** | |||
* async read completion callbacks | |||
* function taking read_result as a parameter | |||
* | |||
*/ | |||
typedef std::function<void(read_result&)> async_read_callback_t; | |||
/** | |||
* async write completion callbacks | |||
* function taking write_result as a parameter | |||
* | |||
*/ | |||
typedef std::function<void(write_result&)> async_write_callback_t; | |||
public: | |||
/** | |||
* structure to store read requests information | |||
* | |||
*/ | |||
struct read_request { | |||
/** | |||
* number of bytes to read | |||
* | |||
*/ | |||
std::size_t size; | |||
/** | |||
* callback to be called on operation completion | |||
* | |||
*/ | |||
async_read_callback_t async_read_callback; | |||
}; | |||
/** | |||
* structure to store write requests information | |||
* | |||
*/ | |||
struct write_request { | |||
/** | |||
* bytes to write | |||
* | |||
*/ | |||
std::vector<char> buffer; | |||
/** | |||
* callback to be called on operation completion | |||
* | |||
*/ | |||
async_write_callback_t async_write_callback; | |||
}; | |||
public: | |||
/** | |||
* async read operation | |||
* | |||
* @param request information about what should be read and what should be done after completion | |||
* | |||
*/ | |||
virtual void async_read(read_request& request) = 0; | |||
/** | |||
* async write operation | |||
* | |||
* @param request information about what should be written and what should be done after completion | |||
* | |||
*/ | |||
virtual void async_write(write_request& request) = 0; | |||
public: | |||
/** | |||
* disconnection handler | |||
* | |||
*/ | |||
typedef std::function<void()> disconnection_handler_t; | |||
/** | |||
* set on disconnection handler | |||
* | |||
* @param disconnection_handler handler to be called in case of a disconnection | |||
* | |||
*/ | |||
virtual void set_on_disconnection_handler(const disconnection_handler_t& disconnection_handler) = 0; | |||
}; | |||
} // namespace network | |||
} // namespace cpp_redis |
@@ -0,0 +1,265 @@ | |||
// MIT License | |||
// | |||
// Copyright (c) 2016-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <atomic> | |||
#include <condition_variable> | |||
#include <functional> | |||
#include <memory> | |||
#include <mutex> | |||
#include <thread> | |||
#include <unordered_map> | |||
#include <vector> | |||
#ifdef _WIN32 | |||
#include <winsock2.h> | |||
#else | |||
#include <sys/select.h> | |||
#endif /* _WIN32 */ | |||
#include <tacopie/network/self_pipe.hpp> | |||
#include <tacopie/network/tcp_socket.hpp> | |||
#include <tacopie/utils/thread_pool.hpp> | |||
#ifndef __TACOPIE_IO_SERVICE_NB_WORKERS | |||
#define __TACOPIE_IO_SERVICE_NB_WORKERS 1 | |||
#endif /* __TACOPIE_IO_SERVICE_NB_WORKERS */ | |||
namespace tacopie { | |||
//! | |||
//! service that operates IO Handling. | |||
//! It polls sockets for input and output, processes read and write operations and calls the appropriate callbacks. | |||
//! | |||
class io_service { | |||
public: | |||
//! | |||
//! ctor | |||
//! | |||
io_service(void); | |||
//! dtor | |||
~io_service(void); | |||
//! copy ctor | |||
io_service(const io_service&) = delete; | |||
//! assignment operator | |||
io_service& operator=(const io_service&) = delete; | |||
public: | |||
//! | |||
//! reset number of io_service workers assigned to this io_service | |||
//! this can be safely called at runtime, even if the io_service is currently running | |||
//! it can be useful if you need to re-adjust the number of workers | |||
//! | |||
//! \param nb_threads number of workers | |||
//! | |||
void set_nb_workers(std::size_t nb_threads); | |||
public: | |||
//! callback handler typedef | |||
//! called on new socket event if register to io_service | |||
typedef std::function<void(fd_t)> event_callback_t; | |||
//! | |||
//! track socket | |||
//! add socket to io_service tracking for read/write operation | |||
//! socket is polled only if read or write callback is defined | |||
//! | |||
//! \param socket socket to be tracked | |||
//! \param rd_callback callback to be executed on read event | |||
//! \param wr_callback callback to be executed on write event | |||
//! | |||
void track(const tcp_socket& socket, const event_callback_t& rd_callback = nullptr, const event_callback_t& wr_callback = nullptr); | |||
//! | |||
//! update the read callback | |||
//! if socket is not tracked yet, track it | |||
//! | |||
//! \param socket socket to be tracked | |||
//! \param event_callback callback to be executed on read event | |||
//! | |||
void set_rd_callback(const tcp_socket& socket, const event_callback_t& event_callback); | |||
//! | |||
//! update the write callback | |||
//! if socket is not tracked yet, track it | |||
//! | |||
//! \param socket socket to be tracked | |||
//! \param event_callback callback to be executed on write event | |||
//! | |||
void set_wr_callback(const tcp_socket& socket, const event_callback_t& event_callback); | |||
//! | |||
//! remove socket from io_service tracking | |||
//! socket is marked for untracking and will effectively be removed asynchronously from tracking once | |||
//! * poll wakes up | |||
//! * no callback are being executed for that socket | |||
//! | |||
//! re-adding track while socket is pending for untrack is fine and will simply cancel the untrack operation | |||
//! | |||
//! \param socket socket to be untracked | |||
//! | |||
void untrack(const tcp_socket& socket); | |||
//! | |||
//! wait until the socket has been effectively removed | |||
//! basically wait until all pending callbacks are executed | |||
//! | |||
//! \param socket socket to wait for | |||
//! | |||
void wait_for_removal(const tcp_socket& socket); | |||
private: | |||
//! | |||
//! struct tracked_socket | |||
//! contains information about what a current socket is tracking | |||
//! * rd_callback: callback to be executed on read availability | |||
//! * is_executing_rd_callback: whether the rd callback is currently being executed or not | |||
//! * wr_callback: callback to be executed on write availability | |||
//! * is_executing_wr_callback: whether the wr callback is currently being executed or not | |||
//! * marked_for_untrack: whether the socket is marked for being untrack (that is, will be untracked whenever all the callback completed their execution) | |||
//! | |||
//! | |||
struct tracked_socket { | |||
//! ctor | |||
tracked_socket(void) | |||
: rd_callback(nullptr) | |||
, wr_callback(nullptr) {} | |||
//! rd event | |||
event_callback_t rd_callback; | |||
std::atomic<bool> is_executing_rd_callback = ATOMIC_VAR_INIT(false); | |||
//! wr event | |||
event_callback_t wr_callback; | |||
std::atomic<bool> is_executing_wr_callback = ATOMIC_VAR_INIT(false); | |||
//! marked for untrack | |||
std::atomic<bool> marked_for_untrack = ATOMIC_VAR_INIT(false); | |||
}; | |||
private: | |||
//! | |||
//! poll worker function | |||
//! main loop of the background thread in charge of the io_service in charge of polling fds | |||
//! | |||
void poll(void); | |||
//! | |||
//! init m_poll_fds_info | |||
//! simply initialize m_polled_fds variable based on m_tracked_sockets information | |||
//! | |||
//! \return maximum fd value polled | |||
//! | |||
int init_poll_fds_info(void); | |||
//! | |||
//! process poll detected events | |||
//! called whenever select/poll completed to check read and write availablity | |||
//! | |||
void process_events(void); | |||
//! | |||
//! process read event reported by select/poll for a given socket | |||
//! | |||
//! \param fd fd for which a read event has been reported | |||
//! \param socket tracked_socket associated to the given fd | |||
//! | |||
void process_rd_event(const fd_t& fd, tracked_socket& socket); | |||
//! | |||
//! process write event reported by select/poll for a given socket | |||
//! | |||
//! \param fd fd for which a write event has been reported | |||
//! \param socket tracked_socket associated to the given fd | |||
//! | |||
void process_wr_event(const fd_t& fd, tracked_socket& socket); | |||
private: | |||
//! | |||
//! tracked sockets | |||
//! | |||
std::unordered_map<fd_t, tracked_socket> m_tracked_sockets; | |||
//! | |||
//! whether the worker should stop or not | |||
//! | |||
std::atomic<bool> m_should_stop; | |||
//! | |||
//! poll thread | |||
//! | |||
std::thread m_poll_worker; | |||
//! | |||
//! callback workers | |||
//! | |||
utils::thread_pool m_callback_workers; | |||
//! | |||
//! thread safety | |||
//! | |||
std::mutex m_tracked_sockets_mtx; | |||
//! | |||
//! data structure given to select (list of fds to poll) | |||
//! | |||
std::vector<fd_t> m_polled_fds; | |||
//! | |||
//! data structure given to select (list of fds to poll for read) | |||
//! | |||
fd_set m_rd_set; | |||
//! | |||
//! data structure given to select (list of fds to poll for write) | |||
//! | |||
fd_set m_wr_set; | |||
//! | |||
//! condition variable to wait on removal | |||
//! | |||
std::condition_variable m_wait_for_removal_condvar; | |||
//! | |||
//! fd associated to the pipe used to wake up the poll call | |||
//! | |||
tacopie::self_pipe m_notifier; | |||
}; | |||
//! | |||
//! default io_service getter & setter | |||
//! | |||
//! \return shared_ptr to the default instance of the io_service | |||
//! | |||
const std::shared_ptr<io_service>& get_default_io_service(void); | |||
//! | |||
//! set the default io_service to be returned by get_default_io_service | |||
//! | |||
//! \param service the service to be used as the default io_service instance | |||
//! | |||
void set_default_io_service(const std::shared_ptr<io_service>& service); | |||
} // namespace tacopie |
@@ -0,0 +1,90 @@ | |||
// MIT License | |||
// | |||
// Copyright (c) 2016-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <tacopie/utils/typedefs.hpp> | |||
namespace tacopie { | |||
//! | |||
//! used to force poll to wake up | |||
//! simply make poll watch for read events on one side of the pipe and write to the other side | |||
//! | |||
class self_pipe { | |||
public: | |||
//! ctor | |||
self_pipe(void); | |||
//! dtor | |||
~self_pipe(void); | |||
//! copy ctor | |||
self_pipe(const self_pipe&) = delete; | |||
//! assignment operator | |||
self_pipe& operator=(const self_pipe&) = delete; | |||
public: | |||
//! | |||
//! \return the read fd of the pipe | |||
//! | |||
fd_t get_read_fd(void) const; | |||
//! | |||
//! \return the write fd of the pipe | |||
//! | |||
fd_t get_write_fd(void) const; | |||
//! | |||
//! notify the self pipe (basically write to the pipe) | |||
//! | |||
void notify(void); | |||
//! | |||
//! clear the pipe (basically read from the pipe) | |||
//! | |||
void clr_buffer(void); | |||
private: | |||
#ifdef _WIN32 | |||
//! | |||
//! socket fd | |||
//! | |||
fd_t m_fd; | |||
//! | |||
//! socket information | |||
//! | |||
struct sockaddr m_addr; | |||
//! | |||
//! socket information (addr len) | |||
//! | |||
int m_addr_len; | |||
#else | |||
//! | |||
//! pipe file descriptors | |||
//! | |||
fd_t m_fds[2]; | |||
#endif /* _WIN32 */ | |||
}; | |||
} // namespace tacopie |
@@ -0,0 +1,330 @@ | |||
// MIT License | |||
// | |||
// Copyright (c) 2016-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <atomic> | |||
#include <cstdint> | |||
#include <mutex> | |||
#include <queue> | |||
#include <string> | |||
#include <tacopie/network/io_service.hpp> | |||
#include <tacopie/network/tcp_socket.hpp> | |||
#include <tacopie/utils/typedefs.hpp> | |||
namespace tacopie { | |||
//! | |||
//! tacopie::tcp_server is the class providing TCP Client features. | |||
//! The tcp_client works entirely asynchronously | |||
//! | |||
class tcp_client { | |||
public: | |||
//! ctor & dtor | |||
tcp_client(void); | |||
~tcp_client(void); | |||
//! | |||
//! custom ctor | |||
//! build socket from existing socket | |||
//! | |||
//! \param socket tcp_socket instance to be used for building the client (socket will be moved) | |||
//! | |||
explicit tcp_client(tcp_socket&& socket); | |||
//! copy ctor | |||
tcp_client(const tcp_client&) = delete; | |||
//! assignment operator | |||
tcp_client& operator=(const tcp_client&) = delete; | |||
public: | |||
//! | |||
//! comparison operator | |||
//! | |||
//! \return true when the underlying sockets are the same (same file descriptor and socket type). | |||
//! | |||
bool operator==(const tcp_client& rhs) const; | |||
//! | |||
//! comparison operator | |||
//! | |||
//! \return true when the underlying sockets are different (different file descriptor or socket type). | |||
//! | |||
bool operator!=(const tcp_client& rhs) const; | |||
public: | |||
//! | |||
//! \return the hostname associated with the underlying socket. | |||
//! | |||
const std::string& get_host(void) const; | |||
//! | |||
//! \return the port associated with the underlying socket. | |||
//! | |||
std::uint32_t get_port(void) const; | |||
public: | |||
//! | |||
//! Connect the socket to the remote server. | |||
//! | |||
//! \param host Hostname of the target server | |||
//! \param port Port of the target server | |||
//! \param timeout_msecs maximum time to connect (will block until connect succeed or timeout expire). 0 will block undefinitely. If timeout expires, connection fails | |||
//! | |||
void connect(const std::string& host, std::uint32_t port, std::uint32_t timeout_msecs = 0); | |||
//! | |||
//! Disconnect the tcp_client if it was currently connected. | |||
//! | |||
//! \param wait_for_removal When sets to true, disconnect blocks until the underlying TCP client has been effectively removed from the io_service and that all the underlying callbacks have completed. | |||
//! | |||
void disconnect(bool wait_for_removal = false); | |||
//! | |||
//! \return whether the client is currently connected or not | |||
//! | |||
bool is_connected(void) const; | |||
private: | |||
//! | |||
//! Call the user-defined disconnection handler | |||
//! | |||
void call_disconnection_handler(void); | |||
public: | |||
//! | |||
//! structure to store read requests result | |||
//! * success: Whether the read operation has succeeded or not. If false, the client has been disconnected | |||
//! * buffer: Vector containing the read bytes | |||
//! | |||
struct read_result { | |||
//! | |||
//! whether the operation succeeeded or not | |||
//! | |||
bool success; | |||
//! | |||
//! read bytes | |||
//! | |||
std::vector<char> buffer; | |||
}; | |||
//! | |||
//! structure to store write requests result | |||
//! * success: Whether the write operation has succeeded or not. If false, the client has been disconnected | |||
//! * size: Number of bytes written | |||
//! | |||
struct write_result { | |||
//! | |||
//! whether the operation succeeeded or not | |||
//! | |||
bool success; | |||
//! | |||
//! number of bytes written | |||
//! | |||
std::size_t size; | |||
}; | |||
public: | |||
//! | |||
//! callback to be called on async read completion | |||
//! takes the read_result as a parameter | |||
//! | |||
typedef std::function<void(read_result&)> async_read_callback_t; | |||
//! | |||
//! callback to be called on async write completion | |||
//! takes the write_result as a parameter | |||
//! | |||
typedef std::function<void(write_result&)> async_write_callback_t; | |||
public: | |||
//! | |||
//! structure to store read requests information | |||
//! * size: Number of bytes to read | |||
//! * async_read_callback: Callback to be called on a read operation completion, even though the operation read less bytes than requested. | |||
//! | |||
struct read_request { | |||
//! | |||
//! number of bytes to read | |||
//! | |||
std::size_t size; | |||
//! | |||
//! callback to be executed on read operation completion | |||
//! | |||
async_read_callback_t async_read_callback; | |||
}; | |||
//! | |||
//! structure to store write requests information | |||
//! * buffer: Bytes to be written | |||
//! * async_write_callback: Callback to be called on a write operation completion, even though the operation wrote less bytes than requested. | |||
//! | |||
struct write_request { | |||
//! | |||
//! bytes to write | |||
//! | |||
std::vector<char> buffer; | |||
//! | |||
//! callback to be executed on write operation completion | |||
//! | |||
async_write_callback_t async_write_callback; | |||
}; | |||
public: | |||
//! | |||
//! async read operation | |||
//! | |||
//! \param request read request information | |||
//! | |||
void async_read(const read_request& request); | |||
//! | |||
//! async write operation | |||
//! | |||
//! \param request write request information | |||
//! | |||
void async_write(const write_request& request); | |||
public: | |||
//! | |||
//! \return underlying tcp_socket (non-const version) | |||
//! | |||
tacopie::tcp_socket& get_socket(void); | |||
//! | |||
//! \return underlying tcp_socket (const version) | |||
//! | |||
const tacopie::tcp_socket& get_socket(void) const; | |||
public: | |||
//! | |||
//! \return io service monitoring this tcp connection | |||
//! | |||
const std::shared_ptr<tacopie::io_service>& get_io_service(void) const; | |||
public: | |||
//! | |||
//! disconnection handle | |||
//! called whenever a disconnection occured | |||
//! | |||
//! | |||
typedef std::function<void()> disconnection_handler_t; | |||
//! | |||
//! set on disconnection handler | |||
//! | |||
//! \param disconnection_handler the handler to be called on disconnection | |||
//! | |||
void set_on_disconnection_handler(const disconnection_handler_t& disconnection_handler); | |||
private: | |||
//! | |||
//! io service read callback | |||
//! called by the io service whenever the socket is readable | |||
//! | |||
//! \param fd file description of the socket for which the read is available | |||
//! | |||
void on_read_available(fd_t fd); | |||
//! | |||
//! io service write callback | |||
//! called by the io service whenever the socket is writable | |||
//! | |||
//! \param fd file description of the socket for which the write is available | |||
//! | |||
void on_write_available(fd_t fd); | |||
private: | |||
//! | |||
//! Clear pending read requests (basically empty the queue of read requests) | |||
//! | |||
void clear_read_requests(void); | |||
//! | |||
//! Clear pending write requests (basically empty the queue of write requests) | |||
//! | |||
void clear_write_requests(void); | |||
private: | |||
//! | |||
//! process read operations when available | |||
//! basically called whenever on_read_available is called and try to read from the socket | |||
//! handle possible case of failure and fill in the result | |||
//! | |||
//! \param result result of the read operation | |||
//! \return the callback to be executed (set in the read request) on read completion (may be null) | |||
//! | |||
async_read_callback_t process_read(read_result& result); | |||
//! | |||
//! process write operations when available | |||
//! basically called whenever on_write_available is called and try to write to the socket | |||
//! handle possible case of failure and fill in the result | |||
//! | |||
//! \param result result of the write operation | |||
//! \return the callback to be executed (set in the write request) on read completion (may be null) | |||
//! | |||
async_write_callback_t process_write(write_result& result); | |||
private: | |||
//! | |||
//! store io_service | |||
//! prevent deletion of io_service before the tcp_client itself | |||
//! | |||
std::shared_ptr<io_service> m_io_service; | |||
//! | |||
//! client socket | |||
//! | |||
tacopie::tcp_socket m_socket; | |||
//! | |||
//! whether the client is currently connected or not | |||
//! | |||
std::atomic<bool> m_is_connected = ATOMIC_VAR_INIT(false); | |||
//! | |||
//! read requests | |||
//! | |||
std::queue<read_request> m_read_requests; | |||
//! | |||
//! write requests | |||
//! | |||
std::queue<write_request> m_write_requests; | |||
//! | |||
//! read requests thread safety | |||
//! | |||
std::mutex m_read_requests_mtx; | |||
//! | |||
//! write requests thread safety | |||
//! | |||
std::mutex m_write_requests_mtx; | |||
//! | |||
//! disconnection handler | |||
//! | |||
disconnection_handler_t m_disconnection_handler; | |||
}; | |||
} // namespace tacopie |
@@ -0,0 +1,175 @@ | |||
// MIT License | |||
// | |||
// Copyright (c) 2016-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <atomic> | |||
#include <cstdint> | |||
#include <list> | |||
#include <memory> | |||
#include <mutex> | |||
#include <string> | |||
#include <tacopie/network/io_service.hpp> | |||
#include <tacopie/network/tcp_client.hpp> | |||
#include <tacopie/network/tcp_socket.hpp> | |||
#include <tacopie/utils/typedefs.hpp> | |||
#define __TACOPIE_CONNECTION_QUEUE_SIZE 1024 | |||
namespace tacopie { | |||
//! | |||
//! tacopie::tcp_server is the class providing TCP Server features. | |||
//! The tcp_server works entirely asynchronously, waiting for the io_service to notify whenever a new client wished to connect. | |||
//! | |||
class tcp_server { | |||
public: | |||
//! ctor | |||
tcp_server(void); | |||
//! dtor | |||
~tcp_server(void); | |||
//! copy ctor | |||
tcp_server(const tcp_server&) = delete; | |||
//! assignment operator | |||
tcp_server& operator=(const tcp_server&) = delete; | |||
public: | |||
//! | |||
//! comparison operator | |||
//! | |||
//! \return true when the underlying sockets are the same (same file descriptor and socket type). | |||
//! | |||
bool operator==(const tcp_server& rhs) const; | |||
//! | |||
//! comparison operator | |||
//! | |||
//! \return true when the underlying sockets are different (different file descriptor or socket type). | |||
//! | |||
bool operator!=(const tcp_server& rhs) const; | |||
public: | |||
//! | |||
//! callback called whenever a new client is connecting to the server | |||
//! | |||
//! Takes as parameter a shared pointer to the tcp_client that wishes to connect | |||
//! Returning true means connection is handled by tcp_client wrapper and nothing will be done by tcp_server. Returning false means connection is handled by tcp_server, will be stored in an internal list and tcp_client disconection_handler overriden. | |||
//! | |||
typedef std::function<bool(const std::shared_ptr<tcp_client>&)> on_new_connection_callback_t; | |||
//! | |||
//! Start the tcp_server at the given host and port. | |||
//! | |||
//! \param host hostname to be connected to | |||
//! \param port port to be connected to | |||
//! \param callback callback to be called on new connections (may be null, connections are then handled automatically by the tcp_server object) | |||
//! | |||
void start(const std::string& host, std::uint32_t port, const on_new_connection_callback_t& callback = nullptr); | |||
//! | |||
//! Disconnect the tcp_server if it was currently running. | |||
//! | |||
//! \param wait_for_removal When sets to true, disconnect blocks until the underlying TCP server has been effectively removed from the io_service and that all the underlying callbacks have completed. | |||
//! \param recursive_wait_for_removal When sets to true and wait_for_removal is also set to true, blocks until all the underlying TCP client connected to the TCP server have been effectively removed from the io_service and that all the underlying callbacks have completed. | |||
//! | |||
void stop(bool wait_for_removal = false, bool recursive_wait_for_removal = true); | |||
//! | |||
//! \return whether the server is currently running or not | |||
///! | |||
bool is_running(void) const; | |||
public: | |||
//! | |||
//! \return the tacopie::tcp_socket associated to the server. (non-const version) | |||
//! | |||
tcp_socket& get_socket(void); | |||
//! | |||
//! \return the tacopie::tcp_socket associated to the server. (const version) | |||
//! | |||
const tcp_socket& get_socket(void) const; | |||
public: | |||
//! | |||
//! \return io service monitoring this tcp connection | |||
//! | |||
const std::shared_ptr<tacopie::io_service>& get_io_service(void) const; | |||
public: | |||
//! | |||
//! \return the list of tacopie::tcp_client connected to the server. | |||
//! | |||
const std::list<std::shared_ptr<tacopie::tcp_client>>& get_clients(void) const; | |||
private: | |||
//! | |||
//! io service read callback | |||
//! | |||
//! \param fd socket that triggered the read callback | |||
//! | |||
void on_read_available(fd_t fd); | |||
//! | |||
//! client disconnected | |||
//! called whenever a client disconnected from the tcp_server | |||
//! | |||
//! \param client disconnected client | |||
//! | |||
void on_client_disconnected(const std::shared_ptr<tcp_client>& client); | |||
private: | |||
//! | |||
//! store io_service | |||
//! prevent deletion of io_service before the tcp_server itself | |||
//! | |||
std::shared_ptr<io_service> m_io_service; | |||
//1 | |||
//! server socket | |||
//! | |||
tacopie::tcp_socket m_socket; | |||
//! | |||
//! whether the server is currently running or not | |||
//! | |||
std::atomic<bool> m_is_running = ATOMIC_VAR_INIT(false); | |||
//! | |||
//! clients | |||
//! | |||
std::list<std::shared_ptr<tacopie::tcp_client>> m_clients; | |||
//! | |||
//! clients thread safety | |||
//! | |||
std::mutex m_clients_mtx; | |||
//! | |||
//! on new connection callback | |||
//! | |||
on_new_connection_callback_t m_on_new_connection_callback; | |||
}; | |||
} // namespace tacopie |
@@ -0,0 +1,223 @@ | |||
// MIT License | |||
// | |||
// Copyright (c) 2016-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <cstdint> | |||
#include <string> | |||
#include <vector> | |||
#include <tacopie/utils/typedefs.hpp> | |||
namespace tacopie { | |||
//! | |||
//! tacopie::tcp_socket is the class providing low-level TCP socket features. | |||
//! The tcp_socket provides a simple but convenient abstraction to unix and windows sockets. | |||
//! It also provides a socket type checker to ensure that server-only operations are only processable on server sockets, and client-only operations are only processable on client sockets. | |||
//! | |||
class tcp_socket { | |||
public: | |||
//! | |||
//! possible types of a TCP socket, either a client or a server | |||
//! type is used to prevent the used of client specific operations on a server socket (and vice-versa) | |||
//! | |||
//! UNKNOWN is used when socket type could not be determined for now | |||
//! | |||
enum class type { | |||
CLIENT, | |||
SERVER, | |||
UNKNOWN | |||
}; | |||
public: | |||
//! ctor | |||
tcp_socket(void); | |||
//! dtor | |||
~tcp_socket(void) = default; | |||
//! | |||
//! custom ctor | |||
//! build socket from existing file descriptor | |||
//! | |||
//! \param fd fd of the raw socket that will be used to init the tcp_socket object | |||
//! \param host host associated to the socket | |||
//! \param port port associated to the socket | |||
//! \param t type of the socket (client or server) | |||
tcp_socket(fd_t fd, const std::string& host, std::uint32_t port, type t); | |||
//! move ctor | |||
tcp_socket(tcp_socket&&); | |||
//! copy ctor | |||
tcp_socket(const tcp_socket&) = delete; | |||
//! assignment operator | |||
tcp_socket& operator=(const tcp_socket&) = delete; | |||
public: | |||
//! | |||
//! comparison operator | |||
//! | |||
//! \return true when the underlying sockets are the same (same file descriptor and socket type). | |||
//! | |||
bool operator==(const tcp_socket& rhs) const; | |||
//! | |||
//! comparison operator | |||
//! | |||
//! \return true when the underlying sockets are different (different file descriptor or socket type). | |||
//! | |||
bool operator!=(const tcp_socket& rhs) const; | |||
public: | |||
//! | |||
//! Read data synchronously from the underlying socket. | |||
//! The socket must be of type client to process this operation. If the type of the socket is unknown, the socket type will be set to client. | |||
//! | |||
//! \param size_to_read Number of bytes to read (might read less than requested) | |||
//! \return Returns the read bytes | |||
//! | |||
std::vector<char> recv(std::size_t size_to_read); | |||
//! | |||
//! Send data synchronously to the underlying socket. | |||
//! The socket must be of type client to process this operation. If the type of the socket is unknown, the socket type will be set to client. | |||
//! | |||
//! \param data Buffer containing bytes to be written | |||
//! \param size_to_write Number of bytes to send | |||
//! \return Returns the number of bytes that were effectively sent. | |||
//! | |||
std::size_t send(const std::vector<char>& data, std::size_t size_to_write); | |||
//! | |||
//! Connect the socket to the remote server. | |||
//! The socket must be of type client to process this operation. If the type of the socket is unknown, the socket type will be set to client. | |||
//! | |||
//! \param host Hostname of the target server | |||
//! \param port Port of the target server | |||
//! \param timeout_msecs maximum time to connect (will block until connect succeed or timeout expire). 0 will block undefinitely. If timeout expires, connection fails | |||
//! | |||
void connect(const std::string& host, std::uint32_t port, std::uint32_t timeout_msecs = 0); | |||
//! | |||
//! Binds the socket to the given host and port. | |||
//! The socket must be of type server to process this operation. If the type of the socket is unknown, the socket type will be set to server. | |||
//! | |||
//! \param host Hostname to be bind to | |||
//! \param port Port to be bind to | |||
//! | |||
void bind(const std::string& host, std::uint32_t port); | |||
//! | |||
//! Make the socket listen for incoming connections. | |||
//! Socket must be of type server to process this operation. If the type of the socket is unknown, the socket type will be set to server. | |||
//! | |||
//! \param max_connection_queue Size of the queue for incoming connections to be processed by the server | |||
//! | |||
void listen(std::size_t max_connection_queue); | |||
//! | |||
//! Accept a new incoming connection. | |||
//! The socket must be of type server to process this operation. If the type of the socket is unknown, the socket type will be set to server. | |||
//! | |||
//! \return Return the tcp_socket associated to the newly accepted connection. | |||
//! | |||
tcp_socket accept(void); | |||
//! | |||
//! Close the underlying socket. | |||
//! | |||
void close(void); | |||
public: | |||
//! | |||
//! \return the hostname associated with the underlying socket. | |||
//! | |||
const std::string& get_host(void) const; | |||
//! | |||
//! \return the port associated with the underlying socket. | |||
//! | |||
std::uint32_t get_port(void) const; | |||
//! | |||
//! \return the type associated with the underlying socket. | |||
//! | |||
type get_type(void) const; | |||
//! | |||
//! set type, should be used if some operations determining socket type | |||
//! have been done on the behalf of the tcp_socket instance | |||
//! | |||
//! \param t type of the socket | |||
//! | |||
void set_type(type t); | |||
//! | |||
//! direct access to the underlying fd | |||
//! | |||
//! \return underlying socket fd | |||
//! | |||
fd_t get_fd(void) const; | |||
public: | |||
//! | |||
//! \return whether the host is IPV6 | |||
//! | |||
bool is_ipv6(void) const; | |||
private: | |||
//! | |||
//! create a new socket if no socket has been initialized yet | |||
//! | |||
void create_socket_if_necessary(void); | |||
//! | |||
//! check whether the current socket has an approriate type for that kind of operation | |||
//! if current type is UNKNOWN, update internal type with given type | |||
//! | |||
//! \param t expected type of our socket to process the operation | |||
//! | |||
void check_or_set_type(type t); | |||
private: | |||
//! | |||
//! fd associated to the socket | |||
//! | |||
fd_t m_fd; | |||
//! | |||
//! socket hostname information | |||
//! | |||
std::string m_host; | |||
//! | |||
//! socket port information | |||
//! | |||
std::uint32_t m_port; | |||
//! | |||
//! type of the socket | |||
//! | |||
type m_type; | |||
}; | |||
} // namespace tacopie |
@@ -0,0 +1,40 @@ | |||
// MIT License | |||
// | |||
// Copyright (c) 2016-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#ifdef _WIN32 | |||
#pragma comment( lib, "ws2_32.lib") | |||
#endif /* _WIN32 */ | |||
//! utils | |||
#include <tacopie/utils/error.hpp> | |||
#include <tacopie/utils/logger.hpp> | |||
#include <tacopie/utils/typedefs.hpp> | |||
//! network | |||
#include <tacopie/network/io_service.hpp> | |||
#include <tacopie/network/tcp_server.hpp> | |||
#include <tacopie/network/tcp_socket.hpp> | |||
//! utils | |||
#include <tacopie/utils/thread_pool.hpp> |
@@ -0,0 +1,78 @@ | |||
// MIT License | |||
// | |||
// Copyright (c) 2016-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <cstdint> | |||
#include <stdexcept> | |||
#include <string> | |||
#include <tacopie/utils/logger.hpp> | |||
namespace tacopie { | |||
//! | |||
//! specialized runtime_error used for tacopie error | |||
//! | |||
class tacopie_error : public std::runtime_error { | |||
public: | |||
//! ctor | |||
tacopie_error(const std::string& what, const std::string& file, std::size_t line); | |||
//! assignment operator | |||
~tacopie_error(void) = default; | |||
//! copy ctor | |||
tacopie_error(const tacopie_error&) = default; | |||
//! assignment operator | |||
tacopie_error& operator=(const tacopie_error&) = default; | |||
public: | |||
//! | |||
//! \return file in which error occured | |||
//! | |||
const std::string& get_file(void) const; | |||
//! | |||
//! \return line at which the error occured | |||
//! | |||
std::size_t get_line(void) const; | |||
private: | |||
//! | |||
//! file location of the error | |||
//! | |||
std::string m_file; | |||
//! | |||
//! line location of the error | |||
//! | |||
std::size_t m_line; | |||
}; | |||
} // namespace tacopie | |||
//! macro for convenience | |||
#define __TACOPIE_THROW(level, what) \ | |||
{ \ | |||
__TACOPIE_LOG(level, (what)); \ | |||
throw tacopie::tacopie_error((what), __FILE__, __LINE__); \ | |||
} |
@@ -0,0 +1,215 @@ | |||
// MIT License | |||
// | |||
// Copyright (c) 2016-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <memory> | |||
#include <mutex> | |||
#include <string> | |||
namespace tacopie { | |||
//! | |||
//! logger_iface | |||
//! should be inherited by any class intended to be used for logging | |||
//! | |||
class logger_iface { | |||
public: | |||
//! ctor | |||
logger_iface(void) = default; | |||
//! dtor | |||
virtual ~logger_iface(void) = default; | |||
//! copy ctor | |||
logger_iface(const logger_iface&) = default; | |||
//! assignment operator | |||
logger_iface& operator=(const logger_iface&) = default; | |||
public: | |||
//! | |||
//! debug logging | |||
//! | |||
//! \param msg message to be logged | |||
//! \param file file from which the message is coming | |||
//! \param line line in the file of the message | |||
//! | |||
virtual void debug(const std::string& msg, const std::string& file, std::size_t line) = 0; | |||
//! | |||
//! info logging | |||
//! | |||
//! \param msg message to be logged | |||
//! \param file file from which the message is coming | |||
//! \param line line in the file of the message | |||
//! | |||
virtual void info(const std::string& msg, const std::string& file, std::size_t line) = 0; | |||
//! | |||
//! warn logging | |||
//! | |||
//! \param msg message to be logged | |||
//! \param file file from which the message is coming | |||
//! \param line line in the file of the message | |||
//! | |||
virtual void warn(const std::string& msg, const std::string& file, std::size_t line) = 0; | |||
//! | |||
//! error logging | |||
//! | |||
//! \param msg message to be logged | |||
//! \param file file from which the message is coming | |||
//! \param line line in the file of the message | |||
//! | |||
virtual void error(const std::string& msg, const std::string& file, std::size_t line) = 0; | |||
}; | |||
//! | |||
//! default logger class provided by the library | |||
//! | |||
class logger : public logger_iface { | |||
public: | |||
//! | |||
//! log level | |||
//! | |||
enum class log_level { | |||
error = 0, | |||
warn = 1, | |||
info = 2, | |||
debug = 3 | |||
}; | |||
public: | |||
//! ctor | |||
logger(log_level level = log_level::info); | |||
//! dtor | |||
~logger(void) = default; | |||
//! copy ctor | |||
logger(const logger&) = default; | |||
//! assignment operator | |||
logger& operator=(const logger&) = default; | |||
public: | |||
//! | |||
//! debug logging | |||
//! | |||
//! \param msg message to be logged | |||
//! \param file file from which the message is coming | |||
//! \param line line in the file of the message | |||
//! | |||
void debug(const std::string& msg, const std::string& file, std::size_t line); | |||
//! | |||
//! info logging | |||
//! | |||
//! \param msg message to be logged | |||
//! \param file file from which the message is coming | |||
//! \param line line in the file of the message | |||
//! | |||
void info(const std::string& msg, const std::string& file, std::size_t line); | |||
//! | |||
//! warn logging | |||
//! | |||
//! \param msg message to be logged | |||
//! \param file file from which the message is coming | |||
//! \param line line in the file of the message | |||
//! | |||
void warn(const std::string& msg, const std::string& file, std::size_t line); | |||
//! | |||
//! error logging | |||
//! | |||
//! \param msg message to be logged | |||
//! \param file file from which the message is coming | |||
//! \param line line in the file of the message | |||
//! | |||
void error(const std::string& msg, const std::string& file, std::size_t line); | |||
private: | |||
//! | |||
//! current log level in use | |||
//! | |||
log_level m_level; | |||
//! | |||
//! mutex used to serialize logs in multithreaded environment | |||
//! | |||
std::mutex m_mutex; | |||
}; | |||
//! | |||
//! variable containing the current logger | |||
//! by default, not set (no logs) | |||
//! | |||
extern std::unique_ptr<logger_iface> active_logger; | |||
//! | |||
//! debug logging | |||
//! convenience function used internaly to call the logger | |||
//! | |||
//! \param msg message to be logged | |||
//! \param file file from which the message is coming | |||
//! \param line line in the file of the message | |||
//! | |||
void debug(const std::string& msg, const std::string& file, std::size_t line); | |||
//! | |||
//! info logging | |||
//! convenience function used internaly to call the logger | |||
//! | |||
//! \param msg message to be logged | |||
//! \param file file from which the message is coming | |||
//! \param line line in the file of the message | |||
//! | |||
void info(const std::string& msg, const std::string& file, std::size_t line); | |||
//! | |||
//! warn logging | |||
//! convenience function used internaly to call the logger | |||
//! | |||
//! \param msg message to be logged | |||
//! \param file file from which the message is coming | |||
//! \param line line in the file of the message | |||
//! | |||
void warn(const std::string& msg, const std::string& file, std::size_t line); | |||
//! | |||
//! error logging | |||
//! convenience function used internaly to call the logger | |||
//! | |||
//! \param msg message to be logged | |||
//! \param file file from which the message is coming | |||
//! \param line line in the file of the message | |||
//! | |||
void error(const std::string& msg, const std::string& file, std::size_t line); | |||
//! | |||
//! convenience macro to log with file and line information | |||
//! | |||
#ifdef __TACOPIE_LOGGING_ENABLED | |||
#define __TACOPIE_LOG(level, msg) tacopie::level(msg, __FILE__, __LINE__); | |||
#else | |||
#define __TACOPIE_LOG(level, msg) | |||
#endif /* __TACOPIE_LOGGING_ENABLED */ | |||
} // namespace tacopie |
@@ -0,0 +1,169 @@ | |||
// MIT License | |||
// | |||
// Copyright (c) 2016-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#include <atomic> | |||
#include <condition_variable> | |||
#include <functional> | |||
#include <list> | |||
#include <mutex> | |||
#include <queue> | |||
#include <thread> | |||
#include <vector> | |||
namespace tacopie { | |||
namespace utils { | |||
//! | |||
//! basic thread pool used to push async tasks from the io_service | |||
//! | |||
class thread_pool { | |||
public: | |||
//! | |||
//! ctor | |||
//! created the worker thread that start working immediately | |||
//! | |||
//! \param nb_threads number of threads to start the thread pool | |||
//! | |||
explicit thread_pool(std::size_t nb_threads); | |||
//! dtor | |||
~thread_pool(void); | |||
//! copy ctor | |||
thread_pool(const thread_pool&) = delete; | |||
//! assignment operator | |||
thread_pool& operator=(const thread_pool&) = delete; | |||
public: | |||
//! | |||
//! task typedef | |||
///! simply a callable taking no parameter | |||
//! | |||
typedef std::function<void()> task_t; | |||
//! | |||
//! add tasks to thread pool | |||
//! task is enqueued and will be executed whenever all previously executed tasked have been executed (or are currently being executed) | |||
//! | |||
//! \param task task to be executed by the threadpool | |||
//! | |||
void add_task(const task_t& task); | |||
//! | |||
//! same as add_task | |||
//! | |||
//! \param task task to be executed by the threadpool | |||
//! \return current instance | |||
//! | |||
thread_pool& operator<<(const task_t& task); | |||
//! | |||
//! stop the thread pool and wait for workers completion | |||
//! if some tasks are pending, they won't be executed | |||
//! | |||
void stop(void); | |||
public: | |||
//! | |||
//! \return whether the thread_pool is running or not | |||
//! | |||
bool is_running(void) const; | |||
public: | |||
//! | |||
//! reset the number of threads working in the thread pool | |||
//! this can be safely called at runtime and can be useful if you need to adjust the number of workers | |||
//! | |||
//! this function returns immediately, but change might be applied in the background | |||
//! that is, increasing number of threads will spwan new threads directly from this function (but they may take a while to start) | |||
//! moreover, shrinking the number of threads can only be applied in the background to make sure to not stop some threads in the middle of their task | |||
//! | |||
//! changing number of workers do not affect tasks to be executed and tasks currently being executed | |||
//! | |||
//! \param nb_threads number of threads | |||
//! | |||
void set_nb_threads(std::size_t nb_threads); | |||
private: | |||
//! | |||
//! worker main loop | |||
//! | |||
void run(void); | |||
//! | |||
//! retrieve a new task | |||
//! fetch the first element in the queue, or wait if no task are available | |||
//! | |||
//! \return a pair <stopped, task> | |||
//! pair.first indicated whether the thread has been marked for stop and should return immediately | |||
//! pair.second contains the task to be executed | |||
//! | |||
std::pair<bool, task_t> fetch_task_or_stop(void); | |||
//! | |||
//! \return whether the thread should stop or not | |||
//! | |||
bool should_stop(void) const; | |||
private: | |||
//! | |||
//! threads | |||
//! | |||
std::list<std::thread> m_workers; | |||
//! | |||
//! number of threads allowed | |||
//! | |||
std::atomic<std::size_t> m_max_nb_threads = ATOMIC_VAR_INIT(0); | |||
//! | |||
//! current number of running threads | |||
//! | |||
std::atomic<std::size_t> m_nb_running_threads = ATOMIC_VAR_INIT(0); | |||
//! | |||
//! whether the thread_pool should stop or not | |||
//! | |||
std::atomic<bool> m_should_stop = ATOMIC_VAR_INIT(false); | |||
//! | |||
//! tasks | |||
//! | |||
std::queue<task_t> m_tasks; | |||
//! | |||
//! tasks thread safety | |||
//! | |||
std::mutex m_tasks_mtx; | |||
//! | |||
//! task condvar to sync on tasks changes | |||
//! | |||
std::condition_variable m_tasks_condvar; | |||
}; | |||
} // namespace utils | |||
} // namespace tacopie |
@@ -0,0 +1,46 @@ | |||
// MIT License | |||
// | |||
// Copyright (c) 2016-2017 Simon Ninon <simon.ninon@gmail.com> | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in all | |||
// copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
// SOFTWARE. | |||
#pragma once | |||
#ifdef _WIN32 | |||
#include <winsock2.h> | |||
#endif /* _WIN32 */ | |||
namespace tacopie { | |||
//! file descriptor platform type | |||
#ifdef _WIN32 | |||
typedef SOCKET fd_t; | |||
#define __TACOPIE_INVALID_FD INVALID_SOCKET | |||
#else | |||
typedef int fd_t; | |||
#define __TACOPIE_INVALID_FD -1 | |||
#endif /* _WIN32 */ | |||
//! ssize_t | |||
#if defined(_MSC_VER) | |||
#include <BaseTsd.h> | |||
typedef SSIZE_T ssize_t; | |||
#endif | |||
} //! tacopie |
@@ -0,0 +1,22 @@ | |||
#pragma once | |||
#ifdef _WIN32 | |||
#include <Winsock2.h> | |||
#include <stdexcept> | |||
class winsock_initializer { | |||
public: | |||
winsock_initializer() { | |||
//! Windows network DLL init | |||
WORD version = MAKEWORD(2, 2); | |||
WSADATA data; | |||
if (WSAStartup(version, &data) != 0) { | |||
throw std::runtime_error("WSAStartup() failure"); | |||
} | |||
} | |||
~winsock_initializer() { WSACleanup(); } | |||
}; | |||
#else | |||
class winsock_initializer {}; | |||
#endif /* _WIN32 */ |
@@ -1,8 +0,0 @@ | |||
/hiredis-test | |||
/examples/hiredis-example* | |||
/*.o | |||
/*.so | |||
/*.dylib | |||
/*.a | |||
/*.pc | |||
*.dSYM |
@@ -1,97 +0,0 @@ | |||
language: c | |||
sudo: false | |||
compiler: | |||
- gcc | |||
- clang | |||
os: | |||
- linux | |||
- osx | |||
branches: | |||
only: | |||
- staging | |||
- trying | |||
- master | |||
before_script: | |||
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi | |||
addons: | |||
apt: | |||
packages: | |||
- libc6-dbg | |||
- libc6-dev | |||
- libc6:i386 | |||
- libc6-dev-i386 | |||
- libc6-dbg:i386 | |||
- gcc-multilib | |||
- g++-multilib | |||
- valgrind | |||
env: | |||
- BITS="32" | |||
- BITS="64" | |||
script: | |||
- EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DHIREDIS_SSL:BOOL=ON"; | |||
if [ "$TRAVIS_OS_NAME" == "osx" ]; then | |||
if [ "$BITS" == "32" ]; then | |||
CFLAGS="-m32 -Werror"; | |||
CXXFLAGS="-m32 -Werror"; | |||
LDFLAGS="-m32"; | |||
EXTRA_CMAKE_OPTS=; | |||
else | |||
CFLAGS="-Werror"; | |||
CXXFLAGS="-Werror"; | |||
fi; | |||
else | |||
TEST_PREFIX="valgrind --track-origins=yes --leak-check=full"; | |||
if [ "$BITS" == "32" ]; then | |||
CFLAGS="-m32 -Werror"; | |||
CXXFLAGS="-m32 -Werror"; | |||
LDFLAGS="-m32"; | |||
EXTRA_CMAKE_OPTS=; | |||
else | |||
CFLAGS="-Werror"; | |||
CXXFLAGS="-Werror"; | |||
fi; | |||
fi; | |||
export CFLAGS CXXFLAGS LDFLAGS TEST_PREFIX EXTRA_CMAKE_OPTS | |||
- mkdir build/ && cd build/ | |||
- cmake .. ${EXTRA_CMAKE_OPTS} | |||
- make VERBOSE=1 | |||
- ctest -V | |||
matrix: | |||
include: | |||
# Windows MinGW cross compile on Linux | |||
- os: linux | |||
dist: xenial | |||
compiler: mingw | |||
addons: | |||
apt: | |||
packages: | |||
- ninja-build | |||
- gcc-mingw-w64-x86-64 | |||
- g++-mingw-w64-x86-64 | |||
script: | |||
- mkdir build && cd build | |||
- CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_BUILD_WITH_INSTALL_RPATH=on | |||
- ninja -v | |||
# Windows MSVC 2017 | |||
- os: windows | |||
compiler: msvc | |||
env: | |||
- MATRIX_EVAL="CC=cl.exe && CXX=cl.exe" | |||
before_install: | |||
- eval "${MATRIX_EVAL}" | |||
install: | |||
- choco install ninja | |||
script: | |||
- mkdir build && cd build | |||
- cmd.exe /C '"C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" amd64 && | |||
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release && | |||
ninja -v' | |||
- ctest -V |
@@ -1,199 +0,0 @@ | |||
### 1.0.0 (unreleased) | |||
**BREAKING CHANGES**: | |||
* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now | |||
protocol errors. This is consistent with the RESP specification. On 32-bit | |||
platforms, the upper bound is lowered to `SIZE_MAX`. | |||
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string | |||
User code should compare this to `size_t` values as well. If it was used to | |||
compare to other values, casting might be necessary or can be removed, if | |||
casting was applied before. | |||
### 0.x.x (unreleased) | |||
**BREAKING CHANGES**: | |||
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string | |||
User code should compare this to `size_t` values as well. | |||
If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before. | |||
* `redisReplyObjectFunctions.createArray` now takes `size_t` for its length parameter. | |||
### 0.14.0 (2018-09-25) | |||
* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b]) | |||
* Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537]) | |||
* Use string2ll from Redis w/added tests (Michael Grunder [7bef04, 60f622]) | |||
* Makefile - OSX compilation fixes (Ryan Schmidt [881fcb, 0e9af8]) | |||
* Remove redundant NULL checks (Justin Brewer [54acc8, 58e6b8]) | |||
* Fix bulk and multi-bulk length truncation (Justin Brewer [109197]) | |||
* Fix SIGSEGV in OpenBSD by checking for NULL before calling freeaddrinfo (Justin Brewer [546d94]) | |||
* Several POSIX compatibility fixes (Justin Brewer [bbeab8, 49bbaa, d1c1b6]) | |||
* Makefile - Compatibility fixes (Dimitri Vorobiev [3238cf, 12a9d1]) | |||
* Makefile - Fix make install on FreeBSD (Zach Shipko [a2ef2b]) | |||
* Makefile - don't assume $(INSTALL) is cp (Igor Gnatenko [725a96]) | |||
* Separate side-effect causing function from assert and small cleanup (amallia [b46413, 3c3234]) | |||
* Don't send negative values to `__redisAsyncCommand` (Frederik Deweerdt [706129]) | |||
* Fix leak if setsockopt fails (Frederik Deweerdt [e21c9c]) | |||
* Fix libevent leak (zfz [515228]) | |||
* Clean up GCC warning (Ichito Nagata [2ec774]) | |||
* Keep track of errno in `__redisSetErrorFromErrno()` as snprintf may use it (Jin Qing [25cd88]) | |||
* Solaris compilation fix (Donald Whyte [41b07d]) | |||
* Reorder linker arguments when building examples (Tustfarm-heart [06eedd]) | |||
* Keep track of subscriptions in case of rapid subscribe/unsubscribe (Hyungjin Kim [073dc8, be76c5, d46999]) | |||
* libuv use after free fix (Paul Scott [cbb956]) | |||
* Properly close socket fd on reconnect attempt (WSL [64d1ec]) | |||
* Skip valgrind in OSX tests (Jan-Erik Rediger [9deb78]) | |||
* Various updates for Travis testing OSX (Ted Nyman [fa3774, 16a459, bc0ea5]) | |||
* Update libevent (Chris Xin [386802]) | |||
* Change sds.h for building in C++ projects (Ali Volkan ATLI [f5b32e]) | |||
* Use proper format specifier in redisFormatSdsCommandArgv (Paulino Huerta, Jan-Erik Rediger [360a06, 8655a6]) | |||
* Better handling of NULL reply in example code (Jan-Erik Rediger [1b8ed3]) | |||
* Prevent overflow when formatting an error (Jan-Erik Rediger [0335cb]) | |||
* Compatibility fix for strerror_r (Tom Lee [bb1747]) | |||
* Properly detect integer parse/overflow errors (Justin Brewer [93421f]) | |||
* Adds CI for Windows and cygwin fixes (owent, [6c53d6, 6c3e40]) | |||
* Catch a buffer overflow when formatting the error message | |||
* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13 | |||
* Fix warnings, when compiled with -Wshadow | |||
* Make hiredis compile in Cygwin on Windows, now CI-tested | |||
* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now | |||
protocol errors. This is consistent with the RESP specification. On 32-bit | |||
platforms, the upper bound is lowered to `SIZE_MAX`. | |||
* Remove backwards compatibility macro's | |||
This removes the following old function aliases, use the new name now: | |||
| Old | New | | |||
| --------------------------- | ---------------------- | | |||
| redisReplyReaderCreate | redisReaderCreate | | |||
| redisReplyReaderCreate | redisReaderCreate | | |||
| redisReplyReaderFree | redisReaderFree | | |||
| redisReplyReaderFeed | redisReaderFeed | | |||
| redisReplyReaderGetReply | redisReaderGetReply | | |||
| redisReplyReaderSetPrivdata | redisReaderSetPrivdata | | |||
| redisReplyReaderGetObject | redisReaderGetObject | | |||
| redisReplyReaderGetError | redisReaderGetError | | |||
* The `DEBUG` variable in the Makefile was renamed to `DEBUG_FLAGS` | |||
Previously it broke some builds for people that had `DEBUG` set to some arbitrary value, | |||
due to debugging other software. | |||
By renaming we avoid unintentional name clashes. | |||
Simply rename `DEBUG` to `DEBUG_FLAGS` in your environment to make it working again. | |||
### 0.13.3 (2015-09-16) | |||
* Revert "Clear `REDIS_CONNECTED` flag when connection is closed". | |||
* Make tests pass on FreeBSD (Thanks, Giacomo Olgeni) | |||
If the `REDIS_CONNECTED` flag is cleared, | |||
the async onDisconnect callback function will never be called. | |||
This causes problems as the disconnect is never reported back to the user. | |||
### 0.13.2 (2015-08-25) | |||
* Prevent crash on pending replies in async code (Thanks, @switch-st) | |||
* Clear `REDIS_CONNECTED` flag when connection is closed (Thanks, Jerry Jacobs) | |||
* Add MacOS X addapter (Thanks, @dizzus) | |||
* Add Qt adapter (Thanks, Pietro Cerutti) | |||
* Add Ivykis adapter (Thanks, Gergely Nagy) | |||
All adapters are provided as is and are only tested where possible. | |||
### 0.13.1 (2015-05-03) | |||
This is a bug fix release. | |||
The new `reconnect` method introduced new struct members, which clashed with pre-defined names in pre-C99 code. | |||
Another commit forced C99 compilation just to make it work, but of course this is not desirable for outside projects. | |||
Other non-C99 code can now use hiredis as usual again. | |||
Sorry for the inconvenience. | |||
* Fix memory leak in async reply handling (Salvatore Sanfilippo) | |||
* Rename struct member to avoid name clash with pre-c99 code (Alex Balashov, ncopa) | |||
### 0.13.0 (2015-04-16) | |||
This release adds a minimal Windows compatibility layer. | |||
The parser, standalone since v0.12.0, can now be compiled on Windows | |||
(and thus used in other client libraries as well) | |||
* Windows compatibility layer for parser code (tzickel) | |||
* Properly escape data printed to PKGCONF file (Dan Skorupski) | |||
* Fix tests when assert() undefined (Keith Bennett, Matt Stancliff) | |||
* Implement a reconnect method for the client context, this changes the structure of `redisContext` (Aaron Bedra) | |||
### 0.12.1 (2015-01-26) | |||
* Fix `make install`: DESTDIR support, install all required files, install PKGCONF in proper location | |||
* Fix `make test` as 32 bit build on 64 bit platform | |||
### 0.12.0 (2015-01-22) | |||
* Add optional KeepAlive support | |||
* Try again on EINTR errors | |||
* Add libuv adapter | |||
* Add IPv6 support | |||
* Remove possibility of multiple close on same fd | |||
* Add ability to bind source address on connect | |||
* Add redisConnectFd() and redisFreeKeepFd() | |||
* Fix getaddrinfo() memory leak | |||
* Free string if it is unused (fixes memory leak) | |||
* Improve redisAppendCommandArgv performance 2.5x | |||
* Add support for SO_REUSEADDR | |||
* Fix redisvFormatCommand format parsing | |||
* Add GLib 2.0 adapter | |||
* Refactor reading code into read.c | |||
* Fix errno error buffers to not clobber errors | |||
* Generate pkgconf during build | |||
* Silence _BSD_SOURCE warnings | |||
* Improve digit counting for multibulk creation | |||
### 0.11.0 | |||
* Increase the maximum multi-bulk reply depth to 7. | |||
* Increase the read buffer size from 2k to 16k. | |||
* Use poll(2) instead of select(2) to support large fds (>= 1024). | |||
### 0.10.1 | |||
* Makefile overhaul. Important to check out if you override one or more | |||
variables using environment variables or via arguments to the "make" tool. | |||
* Issue #45: Fix potential memory leak for a multi bulk reply with 0 elements | |||
being created by the default reply object functions. | |||
* Issue #43: Don't crash in an asynchronous context when Redis returns an error | |||
reply after the connection has been made (this happens when the maximum | |||
number of connections is reached). | |||
### 0.10.0 | |||
* See commit log. | |||
@@ -1,29 +0,0 @@ | |||
Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> | |||
Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||
All rights reserved. | |||
Redistribution and use in source and binary forms, with or without | |||
modification, are permitted provided that the following conditions are met: | |||
* Redistributions of source code must retain the above copyright notice, | |||
this list of conditions and the following disclaimer. | |||
* Redistributions in binary form must reproduce the above copyright notice, | |||
this list of conditions and the following disclaimer in the documentation | |||
and/or other materials provided with the distribution. | |||
* Neither the name of Redis nor the names of its contributors may be used | |||
to endorse or promote products derived from this software without specific | |||
prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | |||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | |||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@@ -1,274 +0,0 @@ | |||
# Hiredis Makefile | |||
# Copyright (C) 2010-2011 Salvatore Sanfilippo <antirez at gmail dot com> | |||
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||
# This file is released under the BSD license, see the COPYING file | |||
OBJ=net.o hiredis.o sds.o async.o read.o sockcompat.o alloc.o | |||
SSL_OBJ=ssl.o | |||
EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib | |||
ifeq ($(USE_SSL),1) | |||
EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl | |||
endif | |||
TESTS=hiredis-test | |||
LIBNAME=libhiredis | |||
SSL_LIBNAME=libhiredis_ssl | |||
PKGCONFNAME=hiredis.pc | |||
SSL_PKGCONFNAME=hiredis_ssl.pc | |||
HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}') | |||
HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}') | |||
HIREDIS_PATCH=$(shell grep HIREDIS_PATCH hiredis.h | awk '{print $$3}') | |||
HIREDIS_SONAME=$(shell grep HIREDIS_SONAME hiredis.h | awk '{print $$3}') | |||
# Installation related variables and target | |||
PREFIX?=/usr/local | |||
INCLUDE_PATH?=include/hiredis | |||
LIBRARY_PATH?=lib | |||
PKGCONF_PATH?=pkgconfig | |||
INSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH) | |||
INSTALL_LIBRARY_PATH= $(DESTDIR)$(PREFIX)/$(LIBRARY_PATH) | |||
INSTALL_PKGCONF_PATH= $(INSTALL_LIBRARY_PATH)/$(PKGCONF_PATH) | |||
# redis-server configuration used for testing | |||
REDIS_PORT=56379 | |||
REDIS_SERVER=redis-server | |||
define REDIS_TEST_CONFIG | |||
daemonize yes | |||
pidfile /tmp/hiredis-test-redis.pid | |||
port $(REDIS_PORT) | |||
bind 127.0.0.1 | |||
unixsocket /tmp/hiredis-test-redis.sock | |||
endef | |||
export REDIS_TEST_CONFIG | |||
# Fallback to gcc when $CC is not in $PATH. | |||
CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc') | |||
CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++') | |||
OPTIMIZATION?=-O3 | |||
WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers | |||
DEBUG_FLAGS?= -g -ggdb | |||
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) | |||
REAL_LDFLAGS=$(LDFLAGS) | |||
DYLIBSUFFIX=so | |||
STLIBSUFFIX=a | |||
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME) | |||
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) | |||
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) | |||
SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX) | |||
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) | |||
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) | |||
SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX) | |||
STLIB_MAKE_CMD=$(AR) rcs | |||
# Platform-specific overrides | |||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') | |||
USE_SSL?=0 | |||
# This is required for test.c only | |||
ifeq ($(USE_SSL),1) | |||
CFLAGS+=-DHIREDIS_TEST_SSL | |||
endif | |||
ifeq ($(uname_S),Linux) | |||
SSL_LDFLAGS=-lssl -lcrypto | |||
else | |||
OPENSSL_PREFIX?=/usr/local/opt/openssl | |||
CFLAGS+=-I$(OPENSSL_PREFIX)/include | |||
SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto | |||
endif | |||
ifeq ($(uname_S),SunOS) | |||
REAL_LDFLAGS+= -ldl -lnsl -lsocket | |||
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS) | |||
endif | |||
ifeq ($(uname_S),Darwin) | |||
DYLIBSUFFIX=dylib | |||
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX) | |||
DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) | |||
endif | |||
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) | |||
ifeq ($(USE_SSL),1) | |||
all: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME) | |||
endif | |||
# Deps (use make dep to generate this) | |||
async.o: async.c fmacros.h alloc.h async.h hiredis.h read.h sds.h net.h dict.c dict.h win32.h async_private.h | |||
dict.o: dict.c fmacros.h alloc.h dict.h | |||
hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h alloc.h net.h async.h win32.h | |||
alloc.o: alloc.c alloc.h | |||
net.o: net.c fmacros.h net.h hiredis.h read.h sds.h alloc.h sockcompat.h win32.h | |||
read.o: read.c fmacros.h read.h sds.h win32.h | |||
sds.o: sds.c sds.h sdsalloc.h | |||
sockcompat.o: sockcompat.c sockcompat.h | |||
ssl.o: ssl.c hiredis.h read.h sds.h alloc.h async.h async_private.h | |||
test.o: test.c fmacros.h hiredis.h read.h sds.h alloc.h net.h | |||
$(DYLIBNAME): $(OBJ) | |||
$(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS) | |||
$(STLIBNAME): $(OBJ) | |||
$(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ) | |||
$(SSL_DYLIBNAME): $(SSL_OBJ) | |||
$(DYLIB_MAKE_CMD) -o $(SSL_DYLIBNAME) $(SSL_OBJ) $(REAL_LDFLAGS) $(SSL_LDFLAGS) | |||
$(SSL_STLIBNAME): $(SSL_OBJ) | |||
$(STLIB_MAKE_CMD) $(SSL_STLIBNAME) $(SSL_OBJ) | |||
dynamic: $(DYLIBNAME) | |||
static: $(STLIBNAME) | |||
ifeq ($(USE_SSL),1) | |||
dynamic: $(SSL_DYLIBNAME) | |||
static: $(SSL_STLIBNAME) | |||
endif | |||
# Binaries: | |||
hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) | |||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS) | |||
hiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME) $(SSL_STLIBNAME) | |||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS) | |||
hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) | |||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lev $(STLIBNAME) $(REAL_LDFLAGS) | |||
hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME) | |||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) $(REAL_LDFLAGS) | |||
hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME) | |||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -livykis $(STLIBNAME) $(REAL_LDFLAGS) | |||
hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME) | |||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) $(REAL_LDFLAGS) | |||
hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME) | |||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS) | |||
ifndef AE_DIR | |||
hiredis-example-ae: | |||
@echo "Please specify AE_DIR (e.g. <redis repository>/src)" | |||
@false | |||
else | |||
hiredis-example-ae: examples/example-ae.c adapters/ae.h $(STLIBNAME) | |||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(AE_DIR) $< $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o $(AE_DIR)/../deps/jemalloc/lib/libjemalloc.a -pthread $(STLIBNAME) | |||
endif | |||
ifndef LIBUV_DIR | |||
hiredis-example-libuv: | |||
@echo "Please specify LIBUV_DIR (e.g. ../libuv/)" | |||
@false | |||
else | |||
hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) | |||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS) | |||
endif | |||
ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),) | |||
hiredis-example-qt: | |||
@echo "Please specify QT_MOC, QT_INCLUDE_DIR AND QT_LIBRARY_DIR" | |||
@false | |||
else | |||
hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME) | |||
$(QT_MOC) adapters/qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \ | |||
$(CXX) -x c++ -o qt-adapter-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | |||
$(QT_MOC) examples/example-qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \ | |||
$(CXX) -x c++ -o qt-example-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | |||
$(CXX) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore -L$(QT_LIBRARY_DIR) qt-adapter-moc.o qt-example-moc.o $< -pthread $(STLIBNAME) -lQtCore | |||
endif | |||
hiredis-example: examples/example.c $(STLIBNAME) | |||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS) | |||
examples: $(EXAMPLES) | |||
TEST_LIBS = $(STLIBNAME) | |||
ifeq ($(USE_SSL),1) | |||
TEST_LIBS += $(SSL_STLIBNAME) -lssl -lcrypto -lpthread | |||
endif | |||
hiredis-test: test.o $(TEST_LIBS) | |||
hiredis-%: %.o $(STLIBNAME) | |||
$(CC) $(REAL_CFLAGS) -o $@ $< $(TEST_LIBS) $(REAL_LDFLAGS) | |||
test: hiredis-test | |||
./hiredis-test | |||
check: hiredis-test | |||
TEST_SSL=$(USE_SSL) ./test.sh | |||
.c.o: | |||
$(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $< | |||
clean: | |||
rm -rf $(DYLIBNAME) $(STLIBNAME) $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov | |||
dep: | |||
$(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c | |||
INSTALL?= cp -pPR | |||
$(PKGCONFNAME): hiredis.h | |||
@echo "Generating $@ for pkgconfig..." | |||
@echo prefix=$(PREFIX) > $@ | |||
@echo exec_prefix=\$${prefix} >> $@ | |||
@echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@ | |||
@echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@ | |||
@echo >> $@ | |||
@echo Name: hiredis >> $@ | |||
@echo Description: Minimalistic C client library for Redis. >> $@ | |||
@echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@ | |||
@echo Libs: -L\$${libdir} -lhiredis >> $@ | |||
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ | |||
$(SSL_PKGCONFNAME): hiredis.h | |||
@echo "Generating $@ for pkgconfig..." | |||
@echo prefix=$(PREFIX) > $@ | |||
@echo exec_prefix=\$${prefix} >> $@ | |||
@echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@ | |||
@echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@ | |||
@echo >> $@ | |||
@echo Name: hiredis_ssl >> $@ | |||
@echo Description: SSL Support for hiredis. >> $@ | |||
@echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@ | |||
@echo Requires: hiredis >> $@ | |||
@echo Libs: -L\$${libdir} -lhiredis_ssl >> $@ | |||
@echo Libs.private: -lssl -lcrypto >> $@ | |||
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) | |||
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH) | |||
$(INSTALL) hiredis.h async.h read.h sds.h alloc.h $(INSTALL_INCLUDE_PATH) | |||
$(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters | |||
$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME) | |||
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME) | |||
$(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH) | |||
mkdir -p $(INSTALL_PKGCONF_PATH) | |||
$(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH) | |||
32bit: | |||
@echo "" | |||
@echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386" | |||
@echo "" | |||
$(MAKE) CFLAGS="-m32" LDFLAGS="-m32" | |||
32bit-vars: | |||
$(eval CFLAGS=-m32) | |||
$(eval LDFLAGS=-m32) | |||
gprof: | |||
$(MAKE) CFLAGS="-pg" LDFLAGS="-pg" | |||
gcov: | |||
$(MAKE) CFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs" | |||
coverage: gcov | |||
make check | |||
mkdir -p tmp/lcov | |||
lcov -d . -c -o tmp/lcov/hiredis.info | |||
genhtml --legend -o tmp/lcov/report tmp/lcov/hiredis.info | |||
noopt: | |||
$(MAKE) OPTIMIZATION="" | |||
.PHONY: all test check clean dep install 32bit 32bit-vars gprof gcov noopt |
@@ -1,470 +0,0 @@ | |||
[](https://travis-ci.org/redis/hiredis) | |||
**This Readme reflects the latest changed in the master branch. See [v0.13.3](https://github.com/redis/hiredis/tree/v0.13.3) for the Readme and documentation for the latest release.** | |||
# HIREDIS | |||
Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database. | |||
It is minimalistic because it just adds minimal support for the protocol, but | |||
at the same time it uses a high level printf-alike API in order to make it | |||
much higher level than otherwise suggested by its minimal code base and the | |||
lack of explicit bindings for every Redis command. | |||
Apart from supporting sending commands and receiving replies, it comes with | |||
a reply parser that is decoupled from the I/O layer. It | |||
is a stream parser designed for easy reusability, which can for instance be used | |||
in higher level language bindings for efficient reply parsing. | |||
Hiredis only supports the binary-safe Redis protocol, so you can use it with any | |||
Redis version >= 1.2.0. | |||
The library comes with multiple APIs. There is the | |||
*synchronous API*, the *asynchronous API* and the *reply parsing API*. | |||
## Upgrading to `1.0.0` | |||
Version 1.0.0 marks a stable release of hiredis. | |||
It includes some minor breaking changes, mostly to make the exposed API more uniform and self-explanatory. | |||
It also bundles the updated `sds` library, to sync up with upstream and Redis. | |||
For most applications a recompile against the new hiredis should be enough. | |||
For code changes see the [Changelog](CHANGELOG.md). | |||
## Upgrading from `<0.9.0` | |||
Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing | |||
code using hiredis should not be a big pain. The key thing to keep in mind when | |||
upgrading is that hiredis >= 0.9.0 uses a `redisContext*` to keep state, in contrast to | |||
the stateless 0.0.1 that only has a file descriptor to work with. | |||
## Synchronous API | |||
To consume the synchronous API, there are only a few function calls that need to be introduced: | |||
```c | |||
redisContext *redisConnect(const char *ip, int port); | |||
void *redisCommand(redisContext *c, const char *format, ...); | |||
void freeReplyObject(void *reply); | |||
``` | |||
### Connecting | |||
The function `redisConnect` is used to create a so-called `redisContext`. The | |||
context is where Hiredis holds state for a connection. The `redisContext` | |||
struct has an integer `err` field that is non-zero when the connection is in | |||
an error state. The field `errstr` will contain a string with a description of | |||
the error. More information on errors can be found in the **Errors** section. | |||
After trying to connect to Redis using `redisConnect` you should | |||
check the `err` field to see if establishing the connection was successful: | |||
```c | |||
redisContext *c = redisConnect("127.0.0.1", 6379); | |||
if (c == NULL || c->err) { | |||
if (c) { | |||
printf("Error: %s\n", c->errstr); | |||
// handle error | |||
} else { | |||
printf("Can't allocate redis context\n"); | |||
} | |||
} | |||
``` | |||
*Note: A `redisContext` is not thread-safe.* | |||
### Sending commands | |||
There are several ways to issue commands to Redis. The first that will be introduced is | |||
`redisCommand`. This function takes a format similar to printf. In the simplest form, | |||
it is used like this: | |||
```c | |||
reply = redisCommand(context, "SET foo bar"); | |||
``` | |||
The specifier `%s` interpolates a string in the command, and uses `strlen` to | |||
determine the length of the string: | |||
```c | |||
reply = redisCommand(context, "SET foo %s", value); | |||
``` | |||
When you need to pass binary safe strings in a command, the `%b` specifier can be | |||
used. Together with a pointer to the string, it requires a `size_t` length argument | |||
of the string: | |||
```c | |||
reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen); | |||
``` | |||
Internally, Hiredis splits the command in different arguments and will | |||
convert it to the protocol used to communicate with Redis. | |||
One or more spaces separates arguments, so you can use the specifiers | |||
anywhere in an argument: | |||
```c | |||
reply = redisCommand(context, "SET key:%s %s", myid, value); | |||
``` | |||
### Using replies | |||
The return value of `redisCommand` holds a reply when the command was | |||
successfully executed. When an error occurs, the return value is `NULL` and | |||
the `err` field in the context will be set (see section on **Errors**). | |||
Once an error is returned the context cannot be reused and you should set up | |||
a new connection. | |||
The standard replies that `redisCommand` are of the type `redisReply`. The | |||
`type` field in the `redisReply` should be used to test what kind of reply | |||
was received: | |||
* **`REDIS_REPLY_STATUS`**: | |||
* The command replied with a status reply. The status string can be accessed using `reply->str`. | |||
The length of this string can be accessed using `reply->len`. | |||
* **`REDIS_REPLY_ERROR`**: | |||
* The command replied with an error. The error string can be accessed identical to `REDIS_REPLY_STATUS`. | |||
* **`REDIS_REPLY_INTEGER`**: | |||
* The command replied with an integer. The integer value can be accessed using the | |||
`reply->integer` field of type `long long`. | |||
* **`REDIS_REPLY_NIL`**: | |||
* The command replied with a **nil** object. There is no data to access. | |||
* **`REDIS_REPLY_STRING`**: | |||
* A bulk (string) reply. The value of the reply can be accessed using `reply->str`. | |||
The length of this string can be accessed using `reply->len`. | |||
* **`REDIS_REPLY_ARRAY`**: | |||
* A multi bulk reply. The number of elements in the multi bulk reply is stored in | |||
`reply->elements`. Every element in the multi bulk reply is a `redisReply` object as well | |||
and can be accessed via `reply->element[..index..]`. | |||
Redis may reply with nested arrays but this is fully supported. | |||
Replies should be freed using the `freeReplyObject()` function. | |||
Note that this function will take care of freeing sub-reply objects | |||
contained in arrays and nested arrays, so there is no need for the user to | |||
free the sub replies (it is actually harmful and will corrupt the memory). | |||
**Important:** the current version of hiredis (0.10.0) frees replies when the | |||
asynchronous API is used. This means you should not call `freeReplyObject` when | |||
you use this API. The reply is cleaned up by hiredis _after_ the callback | |||
returns. This behavior will probably change in future releases, so make sure to | |||
keep an eye on the changelog when upgrading (see issue #39). | |||
### Cleaning up | |||
To disconnect and free the context the following function can be used: | |||
```c | |||
void redisFree(redisContext *c); | |||
``` | |||
This function immediately closes the socket and then frees the allocations done in | |||
creating the context. | |||
### Sending commands (cont'd) | |||
Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands. | |||
It has the following prototype: | |||
```c | |||
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); | |||
``` | |||
It takes the number of arguments `argc`, an array of strings `argv` and the lengths of the | |||
arguments `argvlen`. For convenience, `argvlen` may be set to `NULL` and the function will | |||
use `strlen(3)` on every argument to determine its length. Obviously, when any of the arguments | |||
need to be binary safe, the entire array of lengths `argvlen` should be provided. | |||
The return value has the same semantic as `redisCommand`. | |||
### Pipelining | |||
To explain how Hiredis supports pipelining in a blocking connection, there needs to be | |||
understanding of the internal execution flow. | |||
When any of the functions in the `redisCommand` family is called, Hiredis first formats the | |||
command according to the Redis protocol. The formatted command is then put in the output buffer | |||
of the context. This output buffer is dynamic, so it can hold any number of commands. | |||
After the command is put in the output buffer, `redisGetReply` is called. This function has the | |||
following two execution paths: | |||
1. The input buffer is non-empty: | |||
* Try to parse a single reply from the input buffer and return it | |||
* If no reply could be parsed, continue at *2* | |||
2. The input buffer is empty: | |||
* Write the **entire** output buffer to the socket | |||
* Read from the socket until a single reply could be parsed | |||
The function `redisGetReply` is exported as part of the Hiredis API and can be used when a reply | |||
is expected on the socket. To pipeline commands, the only things that needs to be done is | |||
filling up the output buffer. For this cause, two commands can be used that are identical | |||
to the `redisCommand` family, apart from not returning a reply: | |||
```c | |||
void redisAppendCommand(redisContext *c, const char *format, ...); | |||
void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); | |||
``` | |||
After calling either function one or more times, `redisGetReply` can be used to receive the | |||
subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where | |||
the latter means an error occurred while reading a reply. Just as with the other commands, | |||
the `err` field in the context can be used to find out what the cause of this error is. | |||
The following examples shows a simple pipeline (resulting in only a single call to `write(2)` and | |||
a single call to `read(2)`): | |||
```c | |||
redisReply *reply; | |||
redisAppendCommand(context,"SET foo bar"); | |||
redisAppendCommand(context,"GET foo"); | |||
redisGetReply(context,&reply); // reply for SET | |||
freeReplyObject(reply); | |||
redisGetReply(context,&reply); // reply for GET | |||
freeReplyObject(reply); | |||
``` | |||
This API can also be used to implement a blocking subscriber: | |||
```c | |||
reply = redisCommand(context,"SUBSCRIBE foo"); | |||
freeReplyObject(reply); | |||
while(redisGetReply(context,&reply) == REDIS_OK) { | |||
// consume message | |||
freeReplyObject(reply); | |||
} | |||
``` | |||
### Errors | |||
When a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is | |||
returned. The `err` field inside the context will be non-zero and set to one of the | |||
following constants: | |||
* **`REDIS_ERR_IO`**: | |||
There was an I/O error while creating the connection, trying to write | |||
to the socket or read from the socket. If you included `errno.h` in your | |||
application, you can use the global `errno` variable to find out what is | |||
wrong. | |||
* **`REDIS_ERR_EOF`**: | |||
The server closed the connection which resulted in an empty read. | |||
* **`REDIS_ERR_PROTOCOL`**: | |||
There was an error while parsing the protocol. | |||
* **`REDIS_ERR_OTHER`**: | |||
Any other error. Currently, it is only used when a specified hostname to connect | |||
to cannot be resolved. | |||
In every case, the `errstr` field in the context will be set to hold a string representation | |||
of the error. | |||
## Asynchronous API | |||
Hiredis comes with an asynchronous API that works easily with any event library. | |||
Examples are bundled that show using Hiredis with [libev](http://software.schmorp.de/pkg/libev.html) | |||
and [libevent](http://monkey.org/~provos/libevent/). | |||
### Connecting | |||
The function `redisAsyncConnect` can be used to establish a non-blocking connection to | |||
Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field | |||
should be checked after creation to see if there were errors creating the connection. | |||
Because the connection that will be created is non-blocking, the kernel is not able to | |||
instantly return if the specified host and port is able to accept a connection. | |||
*Note: A `redisAsyncContext` is not thread-safe.* | |||
```c | |||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); | |||
if (c->err) { | |||
printf("Error: %s\n", c->errstr); | |||
// handle error | |||
} | |||
``` | |||
The asynchronous context can hold a disconnect callback function that is called when the | |||
connection is disconnected (either because of an error or per user request). This function should | |||
have the following prototype: | |||
```c | |||
void(const redisAsyncContext *c, int status); | |||
``` | |||
On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the | |||
user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err` | |||
field in the context can be accessed to find out the cause of the error. | |||
The context object is always freed after the disconnect callback fired. When a reconnect is needed, | |||
the disconnect callback is a good point to do so. | |||
Setting the disconnect callback can only be done once per context. For subsequent calls it will | |||
return `REDIS_ERR`. The function to set the disconnect callback has the following prototype: | |||
```c | |||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); | |||
``` | |||
`ac->data` may be used to pass user data to this callback, the same can be done for redisConnectCallback. | |||
### Sending commands and their callbacks | |||
In an asynchronous context, commands are automatically pipelined due to the nature of an event loop. | |||
Therefore, unlike the synchronous API, there is only a single way to send commands. | |||
Because commands are sent to Redis asynchronously, issuing a command requires a callback function | |||
that is called when the reply is received. Reply callbacks should have the following prototype: | |||
```c | |||
void(redisAsyncContext *c, void *reply, void *privdata); | |||
``` | |||
The `privdata` argument can be used to curry arbitrary data to the callback from the point where | |||
the command is initially queued for execution. | |||
The functions that can be used to issue commands in an asynchronous context are: | |||
```c | |||
int redisAsyncCommand( | |||
redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, | |||
const char *format, ...); | |||
int redisAsyncCommandArgv( | |||
redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, | |||
int argc, const char **argv, const size_t *argvlen); | |||
``` | |||
Both functions work like their blocking counterparts. The return value is `REDIS_OK` when the command | |||
was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection | |||
is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is | |||
returned on calls to the `redisAsyncCommand` family. | |||
If the reply for a command with a `NULL` callback is read, it is immediately freed. When the callback | |||
for a command is non-`NULL`, the memory is freed immediately following the callback: the reply is only | |||
valid for the duration of the callback. | |||
All pending callbacks are called with a `NULL` reply when the context encountered an error. | |||
### Disconnecting | |||
An asynchronous connection can be terminated using: | |||
```c | |||
void redisAsyncDisconnect(redisAsyncContext *ac); | |||
``` | |||
When this function is called, the connection is **not** immediately terminated. Instead, new | |||
commands are no longer accepted and the connection is only terminated when all pending commands | |||
have been written to the socket, their respective replies have been read and their respective | |||
callbacks have been executed. After this, the disconnection callback is executed with the | |||
`REDIS_OK` status and the context object is freed. | |||
### Hooking it up to event library *X* | |||
There are a few hooks that need to be set on the context object after it is created. | |||
See the `adapters/` directory for bindings to *libev* and *libevent*. | |||
## Reply parsing API | |||
Hiredis comes with a reply parsing API that makes it easy for writing higher | |||
level language bindings. | |||
The reply parsing API consists of the following functions: | |||
```c | |||
redisReader *redisReaderCreate(void); | |||
void redisReaderFree(redisReader *reader); | |||
int redisReaderFeed(redisReader *reader, const char *buf, size_t len); | |||
int redisReaderGetReply(redisReader *reader, void **reply); | |||
``` | |||
The same set of functions are used internally by hiredis when creating a | |||
normal Redis context, the above API just exposes it to the user for a direct | |||
usage. | |||
### Usage | |||
The function `redisReaderCreate` creates a `redisReader` structure that holds a | |||
buffer with unparsed data and state for the protocol parser. | |||
Incoming data -- most likely from a socket -- can be placed in the internal | |||
buffer of the `redisReader` using `redisReaderFeed`. This function will make a | |||
copy of the buffer pointed to by `buf` for `len` bytes. This data is parsed | |||
when `redisReaderGetReply` is called. This function returns an integer status | |||
and a reply object (as described above) via `void **reply`. The returned status | |||
can be either `REDIS_OK` or `REDIS_ERR`, where the latter means something went | |||
wrong (either a protocol error, or an out of memory error). | |||
The parser limits the level of nesting for multi bulk payloads to 7. If the | |||
multi bulk nesting level is higher than this, the parser returns an error. | |||
### Customizing replies | |||
The function `redisReaderGetReply` creates `redisReply` and makes the function | |||
argument `reply` point to the created `redisReply` variable. For instance, if | |||
the response of type `REDIS_REPLY_STATUS` then the `str` field of `redisReply` | |||
will hold the status as a vanilla C string. However, the functions that are | |||
responsible for creating instances of the `redisReply` can be customized by | |||
setting the `fn` field on the `redisReader` struct. This should be done | |||
immediately after creating the `redisReader`. | |||
For example, [hiredis-rb](https://github.com/pietern/hiredis-rb/blob/master/ext/hiredis_ext/reader.c) | |||
uses customized reply object functions to create Ruby objects. | |||
### Reader max buffer | |||
Both when using the Reader API directly or when using it indirectly via a | |||
normal Redis context, the redisReader structure uses a buffer in order to | |||
accumulate data from the server. | |||
Usually this buffer is destroyed when it is empty and is larger than 16 | |||
KiB in order to avoid wasting memory in unused buffers | |||
However when working with very big payloads destroying the buffer may slow | |||
down performances considerably, so it is possible to modify the max size of | |||
an idle buffer changing the value of the `maxbuf` field of the reader structure | |||
to the desired value. The special value of 0 means that there is no maximum | |||
value for an idle buffer, so the buffer will never get freed. | |||
For instance if you have a normal Redis context you can set the maximum idle | |||
buffer to zero (unlimited) just with: | |||
```c | |||
context->reader->maxbuf = 0; | |||
``` | |||
This should be done only in order to maximize performances when working with | |||
large payloads. The context should be set back to `REDIS_READER_MAX_BUF` again | |||
as soon as possible in order to prevent allocation of useless memory. | |||
## SSL/TLS Support | |||
### Building | |||
SSL/TLS support is not built by default and requires an explicit flag: | |||
make USE_SSL=1 | |||
This requires OpenSSL development package (e.g. including header files to be | |||
available. | |||
When enabled, SSL/TLS support is built into extra `libhiredis_ssl.a` and | |||
`libhiredis_ssl.so` static/dynamic libraries. This leaves the original libraries | |||
unaffected so no additional dependencies are introduced. | |||
### Using it | |||
First, you'll need to make sure you include the SSL header file: | |||
```c | |||
#include "hiredis.h" | |||
#include "hiredis_ssl.h" | |||
``` | |||
SSL can only be enabled on a `redisContext` connection after the connection has | |||
been established and before any command has been processed. For example: | |||
```c | |||
c = redisConnect('localhost', 6443); | |||
if (c == NULL || c->err) { | |||
/* Handle error and abort... */ | |||
} | |||
if (redisSecureConnection(c, | |||
"cacertbundle.crt", /* File name of trusted CA/ca bundle file */ | |||
"client_cert.pem", /* File name of client certificate file */ | |||
"client_key.pem", /* File name of client privat ekey */ | |||
"redis.mydomain.com" /* Server name to request (SNI) */ | |||
) != REDIS_OK) { | |||
printf("SSL error: %s\n", c->errstr); | |||
/* Abort... */ | |||
} | |||
``` | |||
You will also need to link against `libhiredis_ssl`, **in addition** to | |||
`libhiredis` and add `-lssl -lcrypto` to satisfy its dependencies. | |||
### OpenSSL Global State Initialization | |||
OpenSSL needs to have certain global state initialized before it can be used. | |||
Using `redisSecureConnection()` will handle this automatically on the first | |||
call. | |||
**If the calling application itself also initializes and uses OpenSSL directly, | |||
`redisSecureConnection()` must not be used.** | |||
Instead, use `redisInitiateSSL()` which also provides greater control over the | |||
configuration of the SSL connection, as the caller is responsible to create a | |||
connection context using `SSL_new()` and configure it as required. | |||
## AUTHORS | |||
Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and | |||
Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license. |
@@ -1,127 +0,0 @@ | |||
/* | |||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions are met: | |||
* | |||
* * Redistributions of source code must retain the above copyright notice, | |||
* this list of conditions and the following disclaimer. | |||
* * Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* * Neither the name of Redis nor the names of its contributors may be used | |||
* to endorse or promote products derived from this software without | |||
* specific prior written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
* POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
#ifndef __HIREDIS_AE_H__ | |||
#define __HIREDIS_AE_H__ | |||
#include <sys/types.h> | |||
#include <ae.h> | |||
#include "../hiredis.h" | |||
#include "../async.h" | |||
typedef struct redisAeEvents { | |||
redisAsyncContext *context; | |||
aeEventLoop *loop; | |||
int fd; | |||
int reading, writing; | |||
} redisAeEvents; | |||
static void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) { | |||
((void)el); ((void)fd); ((void)mask); | |||
redisAeEvents *e = (redisAeEvents*)privdata; | |||
redisAsyncHandleRead(e->context); | |||
} | |||
static void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) { | |||
((void)el); ((void)fd); ((void)mask); | |||
redisAeEvents *e = (redisAeEvents*)privdata; | |||
redisAsyncHandleWrite(e->context); | |||
} | |||
static void redisAeAddRead(void *privdata) { | |||
redisAeEvents *e = (redisAeEvents*)privdata; | |||
aeEventLoop *loop = e->loop; | |||
if (!e->reading) { | |||
e->reading = 1; | |||
aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e); | |||
} | |||
} | |||
static void redisAeDelRead(void *privdata) { | |||
redisAeEvents *e = (redisAeEvents*)privdata; | |||
aeEventLoop *loop = e->loop; | |||
if (e->reading) { | |||
e->reading = 0; | |||
aeDeleteFileEvent(loop,e->fd,AE_READABLE); | |||
} | |||
} | |||
static void redisAeAddWrite(void *privdata) { | |||
redisAeEvents *e = (redisAeEvents*)privdata; | |||
aeEventLoop *loop = e->loop; | |||
if (!e->writing) { | |||
e->writing = 1; | |||
aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e); | |||
} | |||
} | |||
static void redisAeDelWrite(void *privdata) { | |||
redisAeEvents *e = (redisAeEvents*)privdata; | |||
aeEventLoop *loop = e->loop; | |||
if (e->writing) { | |||
e->writing = 0; | |||
aeDeleteFileEvent(loop,e->fd,AE_WRITABLE); | |||
} | |||
} | |||
static void redisAeCleanup(void *privdata) { | |||
redisAeEvents *e = (redisAeEvents*)privdata; | |||
redisAeDelRead(privdata); | |||
redisAeDelWrite(privdata); | |||
free(e); | |||
} | |||
static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) { | |||
redisContext *c = &(ac->c); | |||
redisAeEvents *e; | |||
/* Nothing should be attached when something is already attached */ | |||
if (ac->ev.data != NULL) | |||
return REDIS_ERR; | |||
/* Create container for context and r/w events */ | |||
e = (redisAeEvents*)hi_malloc(sizeof(*e)); | |||
e->context = ac; | |||
e->loop = loop; | |||
e->fd = c->fd; | |||
e->reading = e->writing = 0; | |||
/* Register functions to start/stop listening for events */ | |||
ac->ev.addRead = redisAeAddRead; | |||
ac->ev.delRead = redisAeDelRead; | |||
ac->ev.addWrite = redisAeAddWrite; | |||
ac->ev.delWrite = redisAeDelWrite; | |||
ac->ev.cleanup = redisAeCleanup; | |||
ac->ev.data = e; | |||
return REDIS_OK; | |||
} | |||
#endif |
@@ -1,153 +0,0 @@ | |||
#ifndef __HIREDIS_GLIB_H__ | |||
#define __HIREDIS_GLIB_H__ | |||
#include <glib.h> | |||
#include "../hiredis.h" | |||
#include "../async.h" | |||
typedef struct | |||
{ | |||
GSource source; | |||
redisAsyncContext *ac; | |||
GPollFD poll_fd; | |||
} RedisSource; | |||
static void | |||
redis_source_add_read (gpointer data) | |||
{ | |||
RedisSource *source = (RedisSource *)data; | |||
g_return_if_fail(source); | |||
source->poll_fd.events |= G_IO_IN; | |||
g_main_context_wakeup(g_source_get_context((GSource *)data)); | |||
} | |||
static void | |||
redis_source_del_read (gpointer data) | |||
{ | |||
RedisSource *source = (RedisSource *)data; | |||
g_return_if_fail(source); | |||
source->poll_fd.events &= ~G_IO_IN; | |||
g_main_context_wakeup(g_source_get_context((GSource *)data)); | |||
} | |||
static void | |||
redis_source_add_write (gpointer data) | |||
{ | |||
RedisSource *source = (RedisSource *)data; | |||
g_return_if_fail(source); | |||
source->poll_fd.events |= G_IO_OUT; | |||
g_main_context_wakeup(g_source_get_context((GSource *)data)); | |||
} | |||
static void | |||
redis_source_del_write (gpointer data) | |||
{ | |||
RedisSource *source = (RedisSource *)data; | |||
g_return_if_fail(source); | |||
source->poll_fd.events &= ~G_IO_OUT; | |||
g_main_context_wakeup(g_source_get_context((GSource *)data)); | |||
} | |||
static void | |||
redis_source_cleanup (gpointer data) | |||
{ | |||
RedisSource *source = (RedisSource *)data; | |||
g_return_if_fail(source); | |||
redis_source_del_read(source); | |||
redis_source_del_write(source); | |||
/* | |||
* It is not our responsibility to remove ourself from the | |||
* current main loop. However, we will remove the GPollFD. | |||
*/ | |||
if (source->poll_fd.fd >= 0) { | |||
g_source_remove_poll((GSource *)data, &source->poll_fd); | |||
source->poll_fd.fd = -1; | |||
} | |||
} | |||
static gboolean | |||
redis_source_prepare (GSource *source, | |||
gint *timeout_) | |||
{ | |||
RedisSource *redis = (RedisSource *)source; | |||
*timeout_ = -1; | |||
return !!(redis->poll_fd.events & redis->poll_fd.revents); | |||
} | |||
static gboolean | |||
redis_source_check (GSource *source) | |||
{ | |||
RedisSource *redis = (RedisSource *)source; | |||
return !!(redis->poll_fd.events & redis->poll_fd.revents); | |||
} | |||
static gboolean | |||
redis_source_dispatch (GSource *source, | |||
GSourceFunc callback, | |||
gpointer user_data) | |||
{ | |||
RedisSource *redis = (RedisSource *)source; | |||
if ((redis->poll_fd.revents & G_IO_OUT)) { | |||
redisAsyncHandleWrite(redis->ac); | |||
redis->poll_fd.revents &= ~G_IO_OUT; | |||
} | |||
if ((redis->poll_fd.revents & G_IO_IN)) { | |||
redisAsyncHandleRead(redis->ac); | |||
redis->poll_fd.revents &= ~G_IO_IN; | |||
} | |||
if (callback) { | |||
return callback(user_data); | |||
} | |||
return TRUE; | |||
} | |||
static void | |||
redis_source_finalize (GSource *source) | |||
{ | |||
RedisSource *redis = (RedisSource *)source; | |||
if (redis->poll_fd.fd >= 0) { | |||
g_source_remove_poll(source, &redis->poll_fd); | |||
redis->poll_fd.fd = -1; | |||
} | |||
} | |||
static GSource * | |||
redis_source_new (redisAsyncContext *ac) | |||
{ | |||
static GSourceFuncs source_funcs = { | |||
.prepare = redis_source_prepare, | |||
.check = redis_source_check, | |||
.dispatch = redis_source_dispatch, | |||
.finalize = redis_source_finalize, | |||
}; | |||
redisContext *c = &ac->c; | |||
RedisSource *source; | |||
g_return_val_if_fail(ac != NULL, NULL); | |||
source = (RedisSource *)g_source_new(&source_funcs, sizeof *source); | |||
source->ac = ac; | |||
source->poll_fd.fd = c->fd; | |||
source->poll_fd.events = 0; | |||
source->poll_fd.revents = 0; | |||
g_source_add_poll((GSource *)source, &source->poll_fd); | |||
ac->ev.addRead = redis_source_add_read; | |||
ac->ev.delRead = redis_source_del_read; | |||
ac->ev.addWrite = redis_source_add_write; | |||
ac->ev.delWrite = redis_source_del_write; | |||
ac->ev.cleanup = redis_source_cleanup; | |||
ac->ev.data = source; | |||
return (GSource *)source; | |||
} | |||
#endif /* __HIREDIS_GLIB_H__ */ |
@@ -1,81 +0,0 @@ | |||
#ifndef __HIREDIS_IVYKIS_H__ | |||
#define __HIREDIS_IVYKIS_H__ | |||
#include <iv.h> | |||
#include "../hiredis.h" | |||
#include "../async.h" | |||
typedef struct redisIvykisEvents { | |||
redisAsyncContext *context; | |||
struct iv_fd fd; | |||
} redisIvykisEvents; | |||
static void redisIvykisReadEvent(void *arg) { | |||
redisAsyncContext *context = (redisAsyncContext *)arg; | |||
redisAsyncHandleRead(context); | |||
} | |||
static void redisIvykisWriteEvent(void *arg) { | |||
redisAsyncContext *context = (redisAsyncContext *)arg; | |||
redisAsyncHandleWrite(context); | |||
} | |||
static void redisIvykisAddRead(void *privdata) { | |||
redisIvykisEvents *e = (redisIvykisEvents*)privdata; | |||
iv_fd_set_handler_in(&e->fd, redisIvykisReadEvent); | |||
} | |||
static void redisIvykisDelRead(void *privdata) { | |||
redisIvykisEvents *e = (redisIvykisEvents*)privdata; | |||
iv_fd_set_handler_in(&e->fd, NULL); | |||
} | |||
static void redisIvykisAddWrite(void *privdata) { | |||
redisIvykisEvents *e = (redisIvykisEvents*)privdata; | |||
iv_fd_set_handler_out(&e->fd, redisIvykisWriteEvent); | |||
} | |||
static void redisIvykisDelWrite(void *privdata) { | |||
redisIvykisEvents *e = (redisIvykisEvents*)privdata; | |||
iv_fd_set_handler_out(&e->fd, NULL); | |||
} | |||
static void redisIvykisCleanup(void *privdata) { | |||
redisIvykisEvents *e = (redisIvykisEvents*)privdata; | |||
iv_fd_unregister(&e->fd); | |||
free(e); | |||
} | |||
static int redisIvykisAttach(redisAsyncContext *ac) { | |||
redisContext *c = &(ac->c); | |||
redisIvykisEvents *e; | |||
/* Nothing should be attached when something is already attached */ | |||
if (ac->ev.data != NULL) | |||
return REDIS_ERR; | |||
/* Create container for context and r/w events */ | |||
e = (redisIvykisEvents*)hi_malloc(sizeof(*e)); | |||
e->context = ac; | |||
/* Register functions to start/stop listening for events */ | |||
ac->ev.addRead = redisIvykisAddRead; | |||
ac->ev.delRead = redisIvykisDelRead; | |||
ac->ev.addWrite = redisIvykisAddWrite; | |||
ac->ev.delWrite = redisIvykisDelWrite; | |||
ac->ev.cleanup = redisIvykisCleanup; | |||
ac->ev.data = e; | |||
/* Initialize and install read/write events */ | |||
IV_FD_INIT(&e->fd); | |||
e->fd.fd = c->fd; | |||
e->fd.handler_in = redisIvykisReadEvent; | |||
e->fd.handler_out = redisIvykisWriteEvent; | |||
e->fd.handler_err = NULL; | |||
e->fd.cookie = e->context; | |||
iv_fd_register(&e->fd); | |||
return REDIS_OK; | |||
} | |||
#endif |
@@ -1,147 +0,0 @@ | |||
/* | |||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions are met: | |||
* | |||
* * Redistributions of source code must retain the above copyright notice, | |||
* this list of conditions and the following disclaimer. | |||
* * Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* * Neither the name of Redis nor the names of its contributors may be used | |||
* to endorse or promote products derived from this software without | |||
* specific prior written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
* POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
#ifndef __HIREDIS_LIBEV_H__ | |||
#define __HIREDIS_LIBEV_H__ | |||
#include <stdlib.h> | |||
#include <sys/types.h> | |||
#include <ev.h> | |||
#include "../hiredis.h" | |||
#include "../async.h" | |||
typedef struct redisLibevEvents { | |||
redisAsyncContext *context; | |||
struct ev_loop *loop; | |||
int reading, writing; | |||
ev_io rev, wev; | |||
} redisLibevEvents; | |||
static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) { | |||
#if EV_MULTIPLICITY | |||
((void)loop); | |||
#endif | |||
((void)revents); | |||
redisLibevEvents *e = (redisLibevEvents*)watcher->data; | |||
redisAsyncHandleRead(e->context); | |||
} | |||
static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) { | |||
#if EV_MULTIPLICITY | |||
((void)loop); | |||
#endif | |||
((void)revents); | |||
redisLibevEvents *e = (redisLibevEvents*)watcher->data; | |||
redisAsyncHandleWrite(e->context); | |||
} | |||
static void redisLibevAddRead(void *privdata) { | |||
redisLibevEvents *e = (redisLibevEvents*)privdata; | |||
struct ev_loop *loop = e->loop; | |||
((void)loop); | |||
if (!e->reading) { | |||
e->reading = 1; | |||
ev_io_start(EV_A_ &e->rev); | |||
} | |||
} | |||
static void redisLibevDelRead(void *privdata) { | |||
redisLibevEvents *e = (redisLibevEvents*)privdata; | |||
struct ev_loop *loop = e->loop; | |||
((void)loop); | |||
if (e->reading) { | |||
e->reading = 0; | |||
ev_io_stop(EV_A_ &e->rev); | |||
} | |||
} | |||
static void redisLibevAddWrite(void *privdata) { | |||
redisLibevEvents *e = (redisLibevEvents*)privdata; | |||
struct ev_loop *loop = e->loop; | |||
((void)loop); | |||
if (!e->writing) { | |||
e->writing = 1; | |||
ev_io_start(EV_A_ &e->wev); | |||
} | |||
} | |||
static void redisLibevDelWrite(void *privdata) { | |||
redisLibevEvents *e = (redisLibevEvents*)privdata; | |||
struct ev_loop *loop = e->loop; | |||
((void)loop); | |||
if (e->writing) { | |||
e->writing = 0; | |||
ev_io_stop(EV_A_ &e->wev); | |||
} | |||
} | |||
static void redisLibevCleanup(void *privdata) { | |||
redisLibevEvents *e = (redisLibevEvents*)privdata; | |||
redisLibevDelRead(privdata); | |||
redisLibevDelWrite(privdata); | |||
free(e); | |||
} | |||
static int redisLibevAttach(EV_P_ redisAsyncContext *ac) { | |||
redisContext *c = &(ac->c); | |||
redisLibevEvents *e; | |||
/* Nothing should be attached when something is already attached */ | |||
if (ac->ev.data != NULL) | |||
return REDIS_ERR; | |||
/* Create container for context and r/w events */ | |||
e = (redisLibevEvents*)hi_malloc(sizeof(*e)); | |||
e->context = ac; | |||
#if EV_MULTIPLICITY | |||
e->loop = loop; | |||
#else | |||
e->loop = NULL; | |||
#endif | |||
e->reading = e->writing = 0; | |||
e->rev.data = e; | |||
e->wev.data = e; | |||
/* Register functions to start/stop listening for events */ | |||
ac->ev.addRead = redisLibevAddRead; | |||
ac->ev.delRead = redisLibevDelRead; | |||
ac->ev.addWrite = redisLibevAddWrite; | |||
ac->ev.delWrite = redisLibevDelWrite; | |||
ac->ev.cleanup = redisLibevCleanup; | |||
ac->ev.data = e; | |||
/* Initialize read/write events */ | |||
ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ); | |||
ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE); | |||
return REDIS_OK; | |||
} | |||
#endif |
@@ -1,172 +0,0 @@ | |||
/* | |||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions are met: | |||
* | |||
* * Redistributions of source code must retain the above copyright notice, | |||
* this list of conditions and the following disclaimer. | |||
* * Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* * Neither the name of Redis nor the names of its contributors may be used | |||
* to endorse or promote products derived from this software without | |||
* specific prior written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
* POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
#ifndef __HIREDIS_LIBEVENT_H__ | |||
#define __HIREDIS_LIBEVENT_H__ | |||
#include <event2/event.h> | |||
#include "../hiredis.h" | |||
#include "../async.h" | |||
#define REDIS_LIBEVENT_DELETED 0x01 | |||
#define REDIS_LIBEVENT_ENTERED 0x02 | |||
typedef struct redisLibeventEvents { | |||
redisAsyncContext *context; | |||
struct event *ev; | |||
struct event_base *base; | |||
struct timeval tv; | |||
short flags; | |||
short state; | |||
} redisLibeventEvents; | |||
static void redisLibeventDestroy(redisLibeventEvents *e) { | |||
free(e); | |||
} | |||
static void redisLibeventHandler(int fd, short event, void *arg) { | |||
((void)fd); | |||
redisLibeventEvents *e = (redisLibeventEvents*)arg; | |||
e->state |= REDIS_LIBEVENT_ENTERED; | |||
#define CHECK_DELETED() if (e->state & REDIS_LIBEVENT_DELETED) {\ | |||
redisLibeventDestroy(e);\ | |||
return; \ | |||
} | |||
if ((event & EV_TIMEOUT) && (e->state & REDIS_LIBEVENT_DELETED) == 0) { | |||
redisAsyncHandleTimeout(e->context); | |||
CHECK_DELETED(); | |||
} | |||
if ((event & EV_READ) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) { | |||
redisAsyncHandleRead(e->context); | |||
CHECK_DELETED(); | |||
} | |||
if ((event & EV_WRITE) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) { | |||
redisAsyncHandleWrite(e->context); | |||
CHECK_DELETED(); | |||
} | |||
e->state &= ~REDIS_LIBEVENT_ENTERED; | |||
#undef CHECK_DELETED | |||
} | |||
static void redisLibeventUpdate(void *privdata, short flag, int isRemove) { | |||
redisLibeventEvents *e = (redisLibeventEvents *)privdata; | |||
const struct timeval *tv = e->tv.tv_sec || e->tv.tv_usec ? &e->tv : NULL; | |||
if (isRemove) { | |||
if ((e->flags & flag) == 0) { | |||
return; | |||
} else { | |||
e->flags &= ~flag; | |||
} | |||
} else { | |||
if (e->flags & flag) { | |||
return; | |||
} else { | |||
e->flags |= flag; | |||
} | |||
} | |||
event_del(e->ev); | |||
event_assign(e->ev, e->base, e->context->c.fd, e->flags | EV_PERSIST, | |||
redisLibeventHandler, privdata); | |||
event_add(e->ev, tv); | |||
} | |||
static void redisLibeventAddRead(void *privdata) { | |||
redisLibeventUpdate(privdata, EV_READ, 0); | |||
} | |||
static void redisLibeventDelRead(void *privdata) { | |||
redisLibeventUpdate(privdata, EV_READ, 1); | |||
} | |||
static void redisLibeventAddWrite(void *privdata) { | |||
redisLibeventUpdate(privdata, EV_WRITE, 0); | |||
} | |||
static void redisLibeventDelWrite(void *privdata) { | |||
redisLibeventUpdate(privdata, EV_WRITE, 1); | |||
} | |||
static void redisLibeventCleanup(void *privdata) { | |||
redisLibeventEvents *e = (redisLibeventEvents*)privdata; | |||
if (!e) { | |||
return; | |||
} | |||
event_del(e->ev); | |||
event_free(e->ev); | |||
e->ev = NULL; | |||
if (e->state & REDIS_LIBEVENT_ENTERED) { | |||
e->state |= REDIS_LIBEVENT_DELETED; | |||
} else { | |||
redisLibeventDestroy(e); | |||
} | |||
} | |||
static void redisLibeventSetTimeout(void *privdata, struct timeval tv) { | |||
redisLibeventEvents *e = (redisLibeventEvents *)privdata; | |||
short flags = e->flags; | |||
e->flags = 0; | |||
e->tv = tv; | |||
redisLibeventUpdate(e, flags, 0); | |||
} | |||
static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { | |||
redisContext *c = &(ac->c); | |||
redisLibeventEvents *e; | |||
/* Nothing should be attached when something is already attached */ | |||
if (ac->ev.data != NULL) | |||
return REDIS_ERR; | |||
/* Create container for context and r/w events */ | |||
e = (redisLibeventEvents*)hi_calloc(1, sizeof(*e)); | |||
e->context = ac; | |||
/* Register functions to start/stop listening for events */ | |||
ac->ev.addRead = redisLibeventAddRead; | |||
ac->ev.delRead = redisLibeventDelRead; | |||
ac->ev.addWrite = redisLibeventAddWrite; | |||
ac->ev.delWrite = redisLibeventDelWrite; | |||
ac->ev.cleanup = redisLibeventCleanup; | |||
ac->ev.scheduleTimer = redisLibeventSetTimeout; | |||
ac->ev.data = e; | |||
/* Initialize and install read/write events */ | |||
e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e); | |||
e->base = base; | |||
return REDIS_OK; | |||
} | |||
#endif |
@@ -1,119 +0,0 @@ | |||
#ifndef __HIREDIS_LIBUV_H__ | |||
#define __HIREDIS_LIBUV_H__ | |||
#include <stdlib.h> | |||
#include <uv.h> | |||
#include "../hiredis.h" | |||
#include "../async.h" | |||
#include <string.h> | |||
typedef struct redisLibuvEvents { | |||
redisAsyncContext* context; | |||
uv_poll_t handle; | |||
int events; | |||
} redisLibuvEvents; | |||
static void redisLibuvPoll(uv_poll_t* handle, int status, int events) { | |||
redisLibuvEvents* p = (redisLibuvEvents*)handle->data; | |||
int ev = (status ? p->events : events); | |||
if (p->context != NULL && (ev & UV_READABLE)) { | |||
redisAsyncHandleRead(p->context); | |||
} | |||
if (p->context != NULL && (ev & UV_WRITABLE)) { | |||
redisAsyncHandleWrite(p->context); | |||
} | |||
} | |||
static void redisLibuvAddRead(void *privdata) { | |||
redisLibuvEvents* p = (redisLibuvEvents*)privdata; | |||
p->events |= UV_READABLE; | |||
uv_poll_start(&p->handle, p->events, redisLibuvPoll); | |||
} | |||
static void redisLibuvDelRead(void *privdata) { | |||
redisLibuvEvents* p = (redisLibuvEvents*)privdata; | |||
p->events &= ~UV_READABLE; | |||
if (p->events) { | |||
uv_poll_start(&p->handle, p->events, redisLibuvPoll); | |||
} else { | |||
uv_poll_stop(&p->handle); | |||
} | |||
} | |||
static void redisLibuvAddWrite(void *privdata) { | |||
redisLibuvEvents* p = (redisLibuvEvents*)privdata; | |||
p->events |= UV_WRITABLE; | |||
uv_poll_start(&p->handle, p->events, redisLibuvPoll); | |||
} | |||
static void redisLibuvDelWrite(void *privdata) { | |||
redisLibuvEvents* p = (redisLibuvEvents*)privdata; | |||
p->events &= ~UV_WRITABLE; | |||
if (p->events) { | |||
uv_poll_start(&p->handle, p->events, redisLibuvPoll); | |||
} else { | |||
uv_poll_stop(&p->handle); | |||
} | |||
} | |||
static void on_close(uv_handle_t* handle) { | |||
redisLibuvEvents* p = (redisLibuvEvents*)handle->data; | |||
free(p); | |||
} | |||
static void redisLibuvCleanup(void *privdata) { | |||
redisLibuvEvents* p = (redisLibuvEvents*)privdata; | |||
p->context = NULL; // indicate that context might no longer exist | |||
uv_close((uv_handle_t*)&p->handle, on_close); | |||
} | |||
static int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) { | |||
redisContext *c = &(ac->c); | |||
if (ac->ev.data != NULL) { | |||
return REDIS_ERR; | |||
} | |||
ac->ev.addRead = redisLibuvAddRead; | |||
ac->ev.delRead = redisLibuvDelRead; | |||
ac->ev.addWrite = redisLibuvAddWrite; | |||
ac->ev.delWrite = redisLibuvDelWrite; | |||
ac->ev.cleanup = redisLibuvCleanup; | |||
redisLibuvEvents* p = (redisLibuvEvents*)malloc(sizeof(*p)); | |||
if (!p) { | |||
return REDIS_ERR; | |||
} | |||
memset(p, 0, sizeof(*p)); | |||
if (uv_poll_init(loop, &p->handle, c->fd) != 0) { | |||
return REDIS_ERR; | |||
} | |||
ac->ev.data = p; | |||
p->handle.data = p; | |||
p->context = ac; | |||
return REDIS_OK; | |||
} | |||
#endif |
@@ -1,114 +0,0 @@ | |||
// | |||
// Created by Дмитрий Бахвалов on 13.07.15. | |||
// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved. | |||
// | |||
#ifndef __HIREDIS_MACOSX_H__ | |||
#define __HIREDIS_MACOSX_H__ | |||
#include <CoreFoundation/CoreFoundation.h> | |||
#include "../hiredis.h" | |||
#include "../async.h" | |||
typedef struct { | |||
redisAsyncContext *context; | |||
CFSocketRef socketRef; | |||
CFRunLoopSourceRef sourceRef; | |||
} RedisRunLoop; | |||
static int freeRedisRunLoop(RedisRunLoop* redisRunLoop) { | |||
if( redisRunLoop != NULL ) { | |||
if( redisRunLoop->sourceRef != NULL ) { | |||
CFRunLoopSourceInvalidate(redisRunLoop->sourceRef); | |||
CFRelease(redisRunLoop->sourceRef); | |||
} | |||
if( redisRunLoop->socketRef != NULL ) { | |||
CFSocketInvalidate(redisRunLoop->socketRef); | |||
CFRelease(redisRunLoop->socketRef); | |||
} | |||
free(redisRunLoop); | |||
} | |||
return REDIS_ERR; | |||
} | |||
static void redisMacOSAddRead(void *privdata) { | |||
RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; | |||
CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack); | |||
} | |||
static void redisMacOSDelRead(void *privdata) { | |||
RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; | |||
CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack); | |||
} | |||
static void redisMacOSAddWrite(void *privdata) { | |||
RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; | |||
CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack); | |||
} | |||
static void redisMacOSDelWrite(void *privdata) { | |||
RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; | |||
CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack); | |||
} | |||
static void redisMacOSCleanup(void *privdata) { | |||
RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; | |||
freeRedisRunLoop(redisRunLoop); | |||
} | |||
static void redisMacOSAsyncCallback(CFSocketRef __unused s, CFSocketCallBackType callbackType, CFDataRef __unused address, const void __unused *data, void *info) { | |||
redisAsyncContext* context = (redisAsyncContext*) info; | |||
switch (callbackType) { | |||
case kCFSocketReadCallBack: | |||
redisAsyncHandleRead(context); | |||
break; | |||
case kCFSocketWriteCallBack: | |||
redisAsyncHandleWrite(context); | |||
break; | |||
default: | |||
break; | |||
} | |||
} | |||
static int redisMacOSAttach(redisAsyncContext *redisAsyncCtx, CFRunLoopRef runLoop) { | |||
redisContext *redisCtx = &(redisAsyncCtx->c); | |||
/* Nothing should be attached when something is already attached */ | |||
if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR; | |||
RedisRunLoop* redisRunLoop = (RedisRunLoop*) calloc(1, sizeof(RedisRunLoop)); | |||
if( !redisRunLoop ) return REDIS_ERR; | |||
/* Setup redis stuff */ | |||
redisRunLoop->context = redisAsyncCtx; | |||
redisAsyncCtx->ev.addRead = redisMacOSAddRead; | |||
redisAsyncCtx->ev.delRead = redisMacOSDelRead; | |||
redisAsyncCtx->ev.addWrite = redisMacOSAddWrite; | |||
redisAsyncCtx->ev.delWrite = redisMacOSDelWrite; | |||
redisAsyncCtx->ev.cleanup = redisMacOSCleanup; | |||
redisAsyncCtx->ev.data = redisRunLoop; | |||
/* Initialize and install read/write events */ | |||
CFSocketContext socketCtx = { 0, redisAsyncCtx, NULL, NULL, NULL }; | |||
redisRunLoop->socketRef = CFSocketCreateWithNative(NULL, redisCtx->fd, | |||
kCFSocketReadCallBack | kCFSocketWriteCallBack, | |||
redisMacOSAsyncCallback, | |||
&socketCtx); | |||
if( !redisRunLoop->socketRef ) return freeRedisRunLoop(redisRunLoop); | |||
redisRunLoop->sourceRef = CFSocketCreateRunLoopSource(NULL, redisRunLoop->socketRef, 0); | |||
if( !redisRunLoop->sourceRef ) return freeRedisRunLoop(redisRunLoop); | |||
CFRunLoopAddSource(runLoop, redisRunLoop->sourceRef, kCFRunLoopDefaultMode); | |||
return REDIS_OK; | |||
} | |||
#endif | |||
@@ -1,135 +0,0 @@ | |||
/*- | |||
* Copyright (C) 2014 Pietro Cerutti <gahr@gahr.ch> | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions | |||
* are met: | |||
* 1. Redistributions of source code must retain the above copyright | |||
* notice, this list of conditions and the following disclaimer. | |||
* 2. Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE | |||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||
* SUCH DAMAGE. | |||
*/ | |||
#ifndef __HIREDIS_QT_H__ | |||
#define __HIREDIS_QT_H__ | |||
#include <QSocketNotifier> | |||
#include "../async.h" | |||
static void RedisQtAddRead(void *); | |||
static void RedisQtDelRead(void *); | |||
static void RedisQtAddWrite(void *); | |||
static void RedisQtDelWrite(void *); | |||
static void RedisQtCleanup(void *); | |||
class RedisQtAdapter : public QObject { | |||
Q_OBJECT | |||
friend | |||
void RedisQtAddRead(void * adapter) { | |||
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter); | |||
a->addRead(); | |||
} | |||
friend | |||
void RedisQtDelRead(void * adapter) { | |||
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter); | |||
a->delRead(); | |||
} | |||
friend | |||
void RedisQtAddWrite(void * adapter) { | |||
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter); | |||
a->addWrite(); | |||
} | |||
friend | |||
void RedisQtDelWrite(void * adapter) { | |||
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter); | |||
a->delWrite(); | |||
} | |||
friend | |||
void RedisQtCleanup(void * adapter) { | |||
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter); | |||
a->cleanup(); | |||
} | |||
public: | |||
RedisQtAdapter(QObject * parent = 0) | |||
: QObject(parent), m_ctx(0), m_read(0), m_write(0) { } | |||
~RedisQtAdapter() { | |||
if (m_ctx != 0) { | |||
m_ctx->ev.data = NULL; | |||
} | |||
} | |||
int setContext(redisAsyncContext * ac) { | |||
if (ac->ev.data != NULL) { | |||
return REDIS_ERR; | |||
} | |||
m_ctx = ac; | |||
m_ctx->ev.data = this; | |||
m_ctx->ev.addRead = RedisQtAddRead; | |||
m_ctx->ev.delRead = RedisQtDelRead; | |||
m_ctx->ev.addWrite = RedisQtAddWrite; | |||
m_ctx->ev.delWrite = RedisQtDelWrite; | |||
m_ctx->ev.cleanup = RedisQtCleanup; | |||
return REDIS_OK; | |||
} | |||
private: | |||
void addRead() { | |||
if (m_read) return; | |||
m_read = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Read, 0); | |||
connect(m_read, SIGNAL(activated(int)), this, SLOT(read())); | |||
} | |||
void delRead() { | |||
if (!m_read) return; | |||
delete m_read; | |||
m_read = 0; | |||
} | |||
void addWrite() { | |||
if (m_write) return; | |||
m_write = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Write, 0); | |||
connect(m_write, SIGNAL(activated(int)), this, SLOT(write())); | |||
} | |||
void delWrite() { | |||
if (!m_write) return; | |||
delete m_write; | |||
m_write = 0; | |||
} | |||
void cleanup() { | |||
delRead(); | |||
delWrite(); | |||
} | |||
private slots: | |||
void read() { redisAsyncHandleRead(m_ctx); } | |||
void write() { redisAsyncHandleWrite(m_ctx); } | |||
private: | |||
redisAsyncContext * m_ctx; | |||
QSocketNotifier * m_read; | |||
QSocketNotifier * m_write; | |||
}; | |||
#endif /* !__HIREDIS_QT_H__ */ |
@@ -1,65 +0,0 @@ | |||
/* | |||
* Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com> | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions are met: | |||
* | |||
* * Redistributions of source code must retain the above copyright notice, | |||
* this list of conditions and the following disclaimer. | |||
* * Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* * Neither the name of Redis nor the names of its contributors may be used | |||
* to endorse or promote products derived from this software without | |||
* specific prior written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
* POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
#include "fmacros.h" | |||
#include "alloc.h" | |||
#include <string.h> | |||
void *hi_malloc(size_t size) { | |||
void *ptr = malloc(size); | |||
if (ptr == NULL) | |||
HIREDIS_OOM_HANDLER; | |||
return ptr; | |||
} | |||
void *hi_calloc(size_t nmemb, size_t size) { | |||
void *ptr = calloc(nmemb, size); | |||
if (ptr == NULL) | |||
HIREDIS_OOM_HANDLER; | |||
return ptr; | |||
} | |||
void *hi_realloc(void *ptr, size_t size) { | |||
void *newptr = realloc(ptr, size); | |||
if (newptr == NULL) | |||
HIREDIS_OOM_HANDLER; | |||
return newptr; | |||
} | |||
char *hi_strdup(const char *str) { | |||
char *newstr = strdup(str); | |||
if (newstr == NULL) | |||
HIREDIS_OOM_HANDLER; | |||
return newstr; | |||
} |
@@ -1,44 +0,0 @@ | |||
/* | |||
* Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com> | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions are met: | |||
* | |||
* * Redistributions of source code must retain the above copyright notice, | |||
* this list of conditions and the following disclaimer. | |||
* * Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* * Neither the name of Redis nor the names of its contributors may be used | |||
* to endorse or promote products derived from this software without | |||
* specific prior written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
* POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
#ifndef HIREDIS_ALLOC_H | |||
#include <stdlib.h> /* for size_t */ | |||
#ifndef HIREDIS_OOM_HANDLER | |||
#define HIREDIS_OOM_HANDLER abort() | |||
#endif | |||
void *hi_malloc(size_t size); | |||
void *hi_calloc(size_t nmemb, size_t size); | |||
void *hi_realloc(void *ptr, size_t size); | |||
char *hi_strdup(const char *str); | |||
#endif /* HIREDIS_ALLOC_H */ |
@@ -1,24 +0,0 @@ | |||
# Appveyor configuration file for CI build of hiredis on Windows (under Cygwin) | |||
environment: | |||
matrix: | |||
- CYG_BASH: C:\cygwin64\bin\bash | |||
CC: gcc | |||
- CYG_BASH: C:\cygwin\bin\bash | |||
CC: gcc | |||
CFLAGS: -m32 | |||
CXXFLAGS: -m32 | |||
LDFLAGS: -m32 | |||
clone_depth: 1 | |||
# Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail | |||
init: | |||
- git config --global core.autocrlf input | |||
# Install needed build dependencies | |||
install: | |||
- '%CYG_BASH% -lc "cygcheck -dc cygwin"' | |||
build_script: | |||
- 'echo building...' | |||
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; mkdir build && cd build && cmake .. -G \"Unix Makefiles\" && make VERBOSE=1"' |
@@ -1,767 +0,0 @@ | |||
/* | |||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> | |||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions are met: | |||
* | |||
* * Redistributions of source code must retain the above copyright notice, | |||
* this list of conditions and the following disclaimer. | |||
* * Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* * Neither the name of Redis nor the names of its contributors may be used | |||
* to endorse or promote products derived from this software without | |||
* specific prior written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
* POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
#include "fmacros.h" | |||
#include "alloc.h" | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#ifndef _MSC_VER | |||
#include <strings.h> | |||
#endif | |||
#include <assert.h> | |||
#include <ctype.h> | |||
#include <errno.h> | |||
#include "async.h" | |||
#include "net.h" | |||
#include "dict.c" | |||
#include "sds.h" | |||
#include "win32.h" | |||
#include "async_private.h" | |||
/* Forward declaration of function in hiredis.c */ | |||
int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); | |||
/* Functions managing dictionary of callbacks for pub/sub. */ | |||
static unsigned int callbackHash(const void *key) { | |||
return dictGenHashFunction((const unsigned char *)key, | |||
sdslen((const sds)key)); | |||
} | |||
static void *callbackValDup(void *privdata, const void *src) { | |||
((void) privdata); | |||
redisCallback *dup = hi_malloc(sizeof(*dup)); | |||
memcpy(dup,src,sizeof(*dup)); | |||
return dup; | |||
} | |||
static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) { | |||
int l1, l2; | |||
((void) privdata); | |||
l1 = sdslen((const sds)key1); | |||
l2 = sdslen((const sds)key2); | |||
if (l1 != l2) return 0; | |||
return memcmp(key1,key2,l1) == 0; | |||
} | |||
static void callbackKeyDestructor(void *privdata, void *key) { | |||
((void) privdata); | |||
sdsfree((sds)key); | |||
} | |||
static void callbackValDestructor(void *privdata, void *val) { | |||
((void) privdata); | |||
free(val); | |||
} | |||
static dictType callbackDict = { | |||
callbackHash, | |||
NULL, | |||
callbackValDup, | |||
callbackKeyCompare, | |||
callbackKeyDestructor, | |||
callbackValDestructor | |||
}; | |||
static redisAsyncContext *redisAsyncInitialize(redisContext *c) { | |||
redisAsyncContext *ac; | |||
ac = realloc(c,sizeof(redisAsyncContext)); | |||
if (ac == NULL) | |||
return NULL; | |||
c = &(ac->c); | |||
/* The regular connect functions will always set the flag REDIS_CONNECTED. | |||
* For the async API, we want to wait until the first write event is | |||
* received up before setting this flag, so reset it here. */ | |||
c->flags &= ~REDIS_CONNECTED; | |||
ac->err = 0; | |||
ac->errstr = NULL; | |||
ac->data = NULL; | |||
ac->ev.data = NULL; | |||
ac->ev.addRead = NULL; | |||
ac->ev.delRead = NULL; | |||
ac->ev.addWrite = NULL; | |||
ac->ev.delWrite = NULL; | |||
ac->ev.cleanup = NULL; | |||
ac->ev.scheduleTimer = NULL; | |||
ac->onConnect = NULL; | |||
ac->onDisconnect = NULL; | |||
ac->replies.head = NULL; | |||
ac->replies.tail = NULL; | |||
ac->sub.invalid.head = NULL; | |||
ac->sub.invalid.tail = NULL; | |||
ac->sub.channels = dictCreate(&callbackDict,NULL); | |||
ac->sub.patterns = dictCreate(&callbackDict,NULL); | |||
return ac; | |||
} | |||
/* We want the error field to be accessible directly instead of requiring | |||
* an indirection to the redisContext struct. */ | |||
static void __redisAsyncCopyError(redisAsyncContext *ac) { | |||
if (!ac) | |||
return; | |||
redisContext *c = &(ac->c); | |||
ac->err = c->err; | |||
ac->errstr = c->errstr; | |||
} | |||
redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) { | |||
redisOptions myOptions = *options; | |||
redisContext *c; | |||
redisAsyncContext *ac; | |||
myOptions.options |= REDIS_OPT_NONBLOCK; | |||
c = redisConnectWithOptions(&myOptions); | |||
if (c == NULL) { | |||
return NULL; | |||
} | |||
ac = redisAsyncInitialize(c); | |||
if (ac == NULL) { | |||
redisFree(c); | |||
return NULL; | |||
} | |||
__redisAsyncCopyError(ac); | |||
return ac; | |||
} | |||
redisAsyncContext *redisAsyncConnect(const char *ip, int port) { | |||
redisOptions options = {0}; | |||
REDIS_OPTIONS_SET_TCP(&options, ip, port); | |||
return redisAsyncConnectWithOptions(&options); | |||
} | |||
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, | |||
const char *source_addr) { | |||
redisOptions options = {0}; | |||
REDIS_OPTIONS_SET_TCP(&options, ip, port); | |||
options.endpoint.tcp.source_addr = source_addr; | |||
return redisAsyncConnectWithOptions(&options); | |||
} | |||
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, | |||
const char *source_addr) { | |||
redisOptions options = {0}; | |||
REDIS_OPTIONS_SET_TCP(&options, ip, port); | |||
options.options |= REDIS_OPT_REUSEADDR; | |||
options.endpoint.tcp.source_addr = source_addr; | |||
return redisAsyncConnectWithOptions(&options); | |||
} | |||
redisAsyncContext *redisAsyncConnectUnix(const char *path) { | |||
redisOptions options = {0}; | |||
REDIS_OPTIONS_SET_UNIX(&options, path); | |||
return redisAsyncConnectWithOptions(&options); | |||
} | |||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { | |||
if (ac->onConnect == NULL) { | |||
ac->onConnect = fn; | |||
/* The common way to detect an established connection is to wait for | |||
* the first write event to be fired. This assumes the related event | |||
* library functions are already set. */ | |||
_EL_ADD_WRITE(ac); | |||
return REDIS_OK; | |||
} | |||
return REDIS_ERR; | |||
} | |||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) { | |||
if (ac->onDisconnect == NULL) { | |||
ac->onDisconnect = fn; | |||
return REDIS_OK; | |||
} | |||
return REDIS_ERR; | |||
} | |||
/* Helper functions to push/shift callbacks */ | |||
static int __redisPushCallback(redisCallbackList *list, redisCallback *source) { | |||
redisCallback *cb; | |||
/* Copy callback from stack to heap */ | |||
cb = malloc(sizeof(*cb)); | |||
if (cb == NULL) | |||
return REDIS_ERR_OOM; | |||
if (source != NULL) { | |||
memcpy(cb,source,sizeof(*cb)); | |||
cb->next = NULL; | |||
} | |||
/* Store callback in list */ | |||
if (list->head == NULL) | |||
list->head = cb; | |||
if (list->tail != NULL) | |||
list->tail->next = cb; | |||
list->tail = cb; | |||
return REDIS_OK; | |||
} | |||
static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) { | |||
redisCallback *cb = list->head; | |||
if (cb != NULL) { | |||
list->head = cb->next; | |||
if (cb == list->tail) | |||
list->tail = NULL; | |||
/* Copy callback from heap to stack */ | |||
if (target != NULL) | |||
memcpy(target,cb,sizeof(*cb)); | |||
free(cb); | |||
return REDIS_OK; | |||
} | |||
return REDIS_ERR; | |||
} | |||
static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) { | |||
redisContext *c = &(ac->c); | |||
if (cb->fn != NULL) { | |||
c->flags |= REDIS_IN_CALLBACK; | |||
cb->fn(ac,reply,cb->privdata); | |||
c->flags &= ~REDIS_IN_CALLBACK; | |||
} | |||
} | |||
/* Helper function to free the context. */ | |||
static void __redisAsyncFree(redisAsyncContext *ac) { | |||
redisContext *c = &(ac->c); | |||
redisCallback cb; | |||
dictIterator *it; | |||
dictEntry *de; | |||
/* Execute pending callbacks with NULL reply. */ | |||
while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) | |||
__redisRunCallback(ac,&cb,NULL); | |||
/* Execute callbacks for invalid commands */ | |||
while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK) | |||
__redisRunCallback(ac,&cb,NULL); | |||
/* Run subscription callbacks callbacks with NULL reply */ | |||
it = dictGetIterator(ac->sub.channels); | |||
while ((de = dictNext(it)) != NULL) | |||
__redisRunCallback(ac,dictGetEntryVal(de),NULL); | |||
dictReleaseIterator(it); | |||
dictRelease(ac->sub.channels); | |||
it = dictGetIterator(ac->sub.patterns); | |||
while ((de = dictNext(it)) != NULL) | |||
__redisRunCallback(ac,dictGetEntryVal(de),NULL); | |||
dictReleaseIterator(it); | |||
dictRelease(ac->sub.patterns); | |||
/* Signal event lib to clean up */ | |||
_EL_CLEANUP(ac); | |||
/* Execute disconnect callback. When redisAsyncFree() initiated destroying | |||
* this context, the status will always be REDIS_OK. */ | |||
if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) { | |||
if (c->flags & REDIS_FREEING) { | |||
ac->onDisconnect(ac,REDIS_OK); | |||
} else { | |||
ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR); | |||
} | |||
} | |||
/* Cleanup self */ | |||
redisFree(c); | |||
} | |||
/* Free the async context. When this function is called from a callback, | |||
* control needs to be returned to redisProcessCallbacks() before actual | |||
* free'ing. To do so, a flag is set on the context which is picked up by | |||
* redisProcessCallbacks(). Otherwise, the context is immediately free'd. */ | |||
void redisAsyncFree(redisAsyncContext *ac) { | |||
redisContext *c = &(ac->c); | |||
c->flags |= REDIS_FREEING; | |||
if (!(c->flags & REDIS_IN_CALLBACK)) | |||
__redisAsyncFree(ac); | |||
} | |||
/* Helper function to make the disconnect happen and clean up. */ | |||
void __redisAsyncDisconnect(redisAsyncContext *ac) { | |||
redisContext *c = &(ac->c); | |||
/* Make sure error is accessible if there is any */ | |||
__redisAsyncCopyError(ac); | |||
if (ac->err == 0) { | |||
/* For clean disconnects, there should be no pending callbacks. */ | |||
int ret = __redisShiftCallback(&ac->replies,NULL); | |||
assert(ret == REDIS_ERR); | |||
} else { | |||
/* Disconnection is caused by an error, make sure that pending | |||
* callbacks cannot call new commands. */ | |||
c->flags |= REDIS_DISCONNECTING; | |||
} | |||
/* cleanup event library on disconnect. | |||
* this is safe to call multiple times */ | |||
_EL_CLEANUP(ac); | |||
/* For non-clean disconnects, __redisAsyncFree() will execute pending | |||
* callbacks with a NULL-reply. */ | |||
if (!(c->flags & REDIS_NO_AUTO_FREE)) { | |||
__redisAsyncFree(ac); | |||
} | |||
} | |||
/* Tries to do a clean disconnect from Redis, meaning it stops new commands | |||
* from being issued, but tries to flush the output buffer and execute | |||
* callbacks for all remaining replies. When this function is called from a | |||
* callback, there might be more replies and we can safely defer disconnecting | |||
* to redisProcessCallbacks(). Otherwise, we can only disconnect immediately | |||
* when there are no pending callbacks. */ | |||
void redisAsyncDisconnect(redisAsyncContext *ac) { | |||
redisContext *c = &(ac->c); | |||
c->flags |= REDIS_DISCONNECTING; | |||
/** unset the auto-free flag here, because disconnect undoes this */ | |||
c->flags &= ~REDIS_NO_AUTO_FREE; | |||
if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL) | |||
__redisAsyncDisconnect(ac); | |||
} | |||
static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) { | |||
redisContext *c = &(ac->c); | |||
dict *callbacks; | |||
redisCallback *cb; | |||
dictEntry *de; | |||
int pvariant; | |||
char *stype; | |||
sds sname; | |||
/* Custom reply functions are not supported for pub/sub. This will fail | |||
* very hard when they are used... */ | |||
if (reply->type == REDIS_REPLY_ARRAY) { | |||
assert(reply->elements >= 2); | |||
assert(reply->element[0]->type == REDIS_REPLY_STRING); | |||
stype = reply->element[0]->str; | |||
pvariant = (tolower(stype[0]) == 'p') ? 1 : 0; | |||
if (pvariant) | |||
callbacks = ac->sub.patterns; | |||
else | |||
callbacks = ac->sub.channels; | |||
/* Locate the right callback */ | |||
assert(reply->element[1]->type == REDIS_REPLY_STRING); | |||
sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len); | |||
de = dictFind(callbacks,sname); | |||
if (de != NULL) { | |||
cb = dictGetEntryVal(de); | |||
/* If this is an subscribe reply decrease pending counter. */ | |||
if (strcasecmp(stype+pvariant,"subscribe") == 0) { | |||
cb->pending_subs -= 1; | |||
} | |||
memcpy(dstcb,cb,sizeof(*dstcb)); | |||
/* If this is an unsubscribe message, remove it. */ | |||
if (strcasecmp(stype+pvariant,"unsubscribe") == 0) { | |||
if (cb->pending_subs == 0) | |||
dictDelete(callbacks,sname); | |||
/* If this was the last unsubscribe message, revert to | |||
* non-subscribe mode. */ | |||
assert(reply->element[2]->type == REDIS_REPLY_INTEGER); | |||
/* Unset subscribed flag only when no pipelined pending subscribe. */ | |||
if (reply->element[2]->integer == 0 | |||
&& dictSize(ac->sub.channels) == 0 | |||
&& dictSize(ac->sub.patterns) == 0) | |||
c->flags &= ~REDIS_SUBSCRIBED; | |||
} | |||
} | |||
sdsfree(sname); | |||
} else { | |||
/* Shift callback for invalid commands. */ | |||
__redisShiftCallback(&ac->sub.invalid,dstcb); | |||
} | |||
return REDIS_OK; | |||
} | |||
void redisProcessCallbacks(redisAsyncContext *ac) { | |||
redisContext *c = &(ac->c); | |||
redisCallback cb = {NULL, NULL, 0, NULL}; | |||
void *reply = NULL; | |||
int status; | |||
while((status = redisGetReply(c,&reply)) == REDIS_OK) { | |||
if (reply == NULL) { | |||
/* When the connection is being disconnected and there are | |||
* no more replies, this is the cue to really disconnect. */ | |||
if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0 | |||
&& ac->replies.head == NULL) { | |||
__redisAsyncDisconnect(ac); | |||
return; | |||
} | |||
/* If monitor mode, repush callback */ | |||
if(c->flags & REDIS_MONITORING) { | |||
__redisPushCallback(&ac->replies,&cb); | |||
} | |||
/* When the connection is not being disconnected, simply stop | |||
* trying to get replies and wait for the next loop tick. */ | |||
break; | |||
} | |||
/* Even if the context is subscribed, pending regular callbacks will | |||
* get a reply before pub/sub messages arrive. */ | |||
if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) { | |||
/* | |||
* A spontaneous reply in a not-subscribed context can be the error | |||
* reply that is sent when a new connection exceeds the maximum | |||
* number of allowed connections on the server side. | |||
* | |||
* This is seen as an error instead of a regular reply because the | |||
* server closes the connection after sending it. | |||
* | |||
* To prevent the error from being overwritten by an EOF error the | |||
* connection is closed here. See issue #43. | |||
* | |||
* Another possibility is that the server is loading its dataset. | |||
* In this case we also want to close the connection, and have the | |||
* user wait until the server is ready to take our request. | |||
*/ | |||
if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) { | |||
c->err = REDIS_ERR_OTHER; | |||
snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str); | |||
c->reader->fn->freeObject(reply); | |||
__redisAsyncDisconnect(ac); | |||
return; | |||
} | |||
/* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */ | |||
assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING)); | |||
if(c->flags & REDIS_SUBSCRIBED) | |||
__redisGetSubscribeCallback(ac,reply,&cb); | |||
} | |||
if (cb.fn != NULL) { | |||
__redisRunCallback(ac,&cb,reply); | |||
c->reader->fn->freeObject(reply); | |||
/* Proceed with free'ing when redisAsyncFree() was called. */ | |||
if (c->flags & REDIS_FREEING) { | |||
__redisAsyncFree(ac); | |||
return; | |||
} | |||
} else { | |||
/* No callback for this reply. This can either be a NULL callback, | |||
* or there were no callbacks to begin with. Either way, don't | |||
* abort with an error, but simply ignore it because the client | |||
* doesn't know what the server will spit out over the wire. */ | |||
c->reader->fn->freeObject(reply); | |||
} | |||
} | |||
/* Disconnect when there was an error reading the reply */ | |||
if (status != REDIS_OK) | |||
__redisAsyncDisconnect(ac); | |||
} | |||
/* Internal helper function to detect socket status the first time a read or | |||
* write event fires. When connecting was not successful, the connect callback | |||
* is called with a REDIS_ERR status and the context is free'd. */ | |||
static int __redisAsyncHandleConnect(redisAsyncContext *ac) { | |||
int completed = 0; | |||
redisContext *c = &(ac->c); | |||
if (redisCheckConnectDone(c, &completed) == REDIS_ERR) { | |||
/* Error! */ | |||
redisCheckSocketError(c); | |||
if (ac->onConnect) ac->onConnect(ac, REDIS_ERR); | |||
__redisAsyncDisconnect(ac); | |||
return REDIS_ERR; | |||
} else if (completed == 1) { | |||
/* connected! */ | |||
if (ac->onConnect) ac->onConnect(ac, REDIS_OK); | |||
c->flags |= REDIS_CONNECTED; | |||
return REDIS_OK; | |||
} else { | |||
return REDIS_OK; | |||
} | |||
} | |||
void redisAsyncRead(redisAsyncContext *ac) { | |||
redisContext *c = &(ac->c); | |||
if (redisBufferRead(c) == REDIS_ERR) { | |||
__redisAsyncDisconnect(ac); | |||
} else { | |||
/* Always re-schedule reads */ | |||
_EL_ADD_READ(ac); | |||
redisProcessCallbacks(ac); | |||
} | |||
} | |||
/* This function should be called when the socket is readable. | |||
* It processes all replies that can be read and executes their callbacks. | |||
*/ | |||
void redisAsyncHandleRead(redisAsyncContext *ac) { | |||
redisContext *c = &(ac->c); | |||
if (!(c->flags & REDIS_CONNECTED)) { | |||
/* Abort connect was not successful. */ | |||
if (__redisAsyncHandleConnect(ac) != REDIS_OK) | |||
return; | |||
/* Try again later when the context is still not connected. */ | |||
if (!(c->flags & REDIS_CONNECTED)) | |||
return; | |||
} | |||
c->funcs->async_read(ac); | |||
} | |||
void redisAsyncWrite(redisAsyncContext *ac) { | |||
redisContext *c = &(ac->c); | |||
int done = 0; | |||
if (redisBufferWrite(c,&done) == REDIS_ERR) { | |||
__redisAsyncDisconnect(ac); | |||
} else { | |||
/* Continue writing when not done, stop writing otherwise */ | |||
if (!done) | |||
_EL_ADD_WRITE(ac); | |||
else | |||
_EL_DEL_WRITE(ac); | |||
/* Always schedule reads after writes */ | |||
_EL_ADD_READ(ac); | |||
} | |||
} | |||
void redisAsyncHandleWrite(redisAsyncContext *ac) { | |||
redisContext *c = &(ac->c); | |||
if (!(c->flags & REDIS_CONNECTED)) { | |||
/* Abort connect was not successful. */ | |||
if (__redisAsyncHandleConnect(ac) != REDIS_OK) | |||
return; | |||
/* Try again later when the context is still not connected. */ | |||
if (!(c->flags & REDIS_CONNECTED)) | |||
return; | |||
} | |||
c->funcs->async_write(ac); | |||
} | |||
void __redisSetError(redisContext *c, int type, const char *str); | |||
void redisAsyncHandleTimeout(redisAsyncContext *ac) { | |||
redisContext *c = &(ac->c); | |||
redisCallback cb; | |||
if ((c->flags & REDIS_CONNECTED) && ac->replies.head == NULL) { | |||
/* Nothing to do - just an idle timeout */ | |||
return; | |||
} | |||
if (!c->err) { | |||
__redisSetError(c, REDIS_ERR_TIMEOUT, "Timeout"); | |||
} | |||
if (!(c->flags & REDIS_CONNECTED) && ac->onConnect) { | |||
ac->onConnect(ac, REDIS_ERR); | |||
} | |||
while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) { | |||
__redisRunCallback(ac, &cb, NULL); | |||
} | |||
/** | |||
* TODO: Don't automatically sever the connection, | |||
* rather, allow to ignore <x> responses before the queue is clear | |||
*/ | |||
__redisAsyncDisconnect(ac); | |||
} | |||
/* Sets a pointer to the first argument and its length starting at p. Returns | |||
* the number of bytes to skip to get to the following argument. */ | |||
static const char *nextArgument(const char *start, const char **str, size_t *len) { | |||
const char *p = start; | |||
if (p[0] != '$') { | |||
p = strchr(p,'$'); | |||
if (p == NULL) return NULL; | |||
} | |||
*len = (int)strtol(p+1,NULL,10); | |||
p = strchr(p,'\r'); | |||
assert(p); | |||
*str = p+2; | |||
return p+2+(*len)+2; | |||
} | |||
/* Helper function for the redisAsyncCommand* family of functions. Writes a | |||
* formatted command to the output buffer and registers the provided callback | |||
* function with the context. */ | |||
static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { | |||
redisContext *c = &(ac->c); | |||
redisCallback cb; | |||
struct dict *cbdict; | |||
dictEntry *de; | |||
redisCallback *existcb; | |||
int pvariant, hasnext; | |||
const char *cstr, *astr; | |||
size_t clen, alen; | |||
const char *p; | |||
sds sname; | |||
int ret; | |||
/* Don't accept new commands when the connection is about to be closed. */ | |||
if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR; | |||
/* Setup callback */ | |||
cb.fn = fn; | |||
cb.privdata = privdata; | |||
cb.pending_subs = 1; | |||
/* Find out which command will be appended. */ | |||
p = nextArgument(cmd,&cstr,&clen); | |||
assert(p != NULL); | |||
hasnext = (p[0] == '$'); | |||
pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0; | |||
cstr += pvariant; | |||
clen -= pvariant; | |||
if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) { | |||
c->flags |= REDIS_SUBSCRIBED; | |||
/* Add every channel/pattern to the list of subscription callbacks. */ | |||
while ((p = nextArgument(p,&astr,&alen)) != NULL) { | |||
sname = sdsnewlen(astr,alen); | |||
if (pvariant) | |||
cbdict = ac->sub.patterns; | |||
else | |||
cbdict = ac->sub.channels; | |||
de = dictFind(cbdict,sname); | |||
if (de != NULL) { | |||
existcb = dictGetEntryVal(de); | |||
cb.pending_subs = existcb->pending_subs + 1; | |||
} | |||
ret = dictReplace(cbdict,sname,&cb); | |||
if (ret == 0) sdsfree(sname); | |||
} | |||
} else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) { | |||
/* It is only useful to call (P)UNSUBSCRIBE when the context is | |||
* subscribed to one or more channels or patterns. */ | |||
if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR; | |||
/* (P)UNSUBSCRIBE does not have its own response: every channel or | |||
* pattern that is unsubscribed will receive a message. This means we | |||
* should not append a callback function for this command. */ | |||
} else if(strncasecmp(cstr,"monitor\r\n",9) == 0) { | |||
/* Set monitor flag and push callback */ | |||
c->flags |= REDIS_MONITORING; | |||
__redisPushCallback(&ac->replies,&cb); | |||
} else { | |||
if (c->flags & REDIS_SUBSCRIBED) | |||
/* This will likely result in an error reply, but it needs to be | |||
* received and passed to the callback. */ | |||
__redisPushCallback(&ac->sub.invalid,&cb); | |||
else | |||
__redisPushCallback(&ac->replies,&cb); | |||
} | |||
__redisAppendCommand(c,cmd,len); | |||
/* Always schedule a write when the write buffer is non-empty */ | |||
_EL_ADD_WRITE(ac); | |||
return REDIS_OK; | |||
} | |||
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) { | |||
char *cmd; | |||
int len; | |||
int status; | |||
len = redisvFormatCommand(&cmd,format,ap); | |||
/* We don't want to pass -1 or -2 to future functions as a length. */ | |||
if (len < 0) | |||
return REDIS_ERR; | |||
status = __redisAsyncCommand(ac,fn,privdata,cmd,len); | |||
free(cmd); | |||
return status; | |||
} | |||
int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) { | |||
va_list ap; | |||
int status; | |||
va_start(ap,format); | |||
status = redisvAsyncCommand(ac,fn,privdata,format,ap); | |||
va_end(ap); | |||
return status; | |||
} | |||
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { | |||
sds cmd; | |||
int len; | |||
int status; | |||
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); | |||
if (len < 0) | |||
return REDIS_ERR; | |||
status = __redisAsyncCommand(ac,fn,privdata,cmd,len); | |||
sdsfree(cmd); | |||
return status; | |||
} | |||
int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { | |||
int status = __redisAsyncCommand(ac,fn,privdata,cmd,len); | |||
return status; | |||
} | |||
void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) { | |||
if (!ac->c.timeout) { | |||
ac->c.timeout = hi_calloc(1, sizeof(tv)); | |||
} | |||
if (tv.tv_sec == ac->c.timeout->tv_sec && | |||
tv.tv_usec == ac->c.timeout->tv_usec) { | |||
return; | |||
} | |||
*ac->c.timeout = tv; | |||
} |
@@ -1,142 +0,0 @@ | |||
/* | |||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> | |||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions are met: | |||
* | |||
* * Redistributions of source code must retain the above copyright notice, | |||
* this list of conditions and the following disclaimer. | |||
* * Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* * Neither the name of Redis nor the names of its contributors may be used | |||
* to endorse or promote products derived from this software without | |||
* specific prior written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
* POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
#ifndef __HIREDIS_ASYNC_H | |||
#define __HIREDIS_ASYNC_H | |||
#include "hiredis.h" | |||
#ifdef __cplusplus | |||
extern "C" { | |||
#endif | |||
struct redisAsyncContext; /* need forward declaration of redisAsyncContext */ | |||
struct dict; /* dictionary header is included in async.c */ | |||
/* Reply callback prototype and container */ | |||
typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*); | |||
typedef struct redisCallback { | |||
struct redisCallback *next; /* simple singly linked list */ | |||
redisCallbackFn *fn; | |||
int pending_subs; | |||
void *privdata; | |||
} redisCallback; | |||
/* List of callbacks for either regular replies or pub/sub */ | |||
typedef struct redisCallbackList { | |||
redisCallback *head, *tail; | |||
} redisCallbackList; | |||
/* Connection callback prototypes */ | |||
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status); | |||
typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status); | |||
typedef void(redisTimerCallback)(void *timer, void *privdata); | |||
/* Context for an async connection to Redis */ | |||
typedef struct redisAsyncContext { | |||
/* Hold the regular context, so it can be realloc'ed. */ | |||
redisContext c; | |||
/* Setup error flags so they can be used directly. */ | |||
int err; | |||
char *errstr; | |||
/* Not used by hiredis */ | |||
void *data; | |||
/* Event library data and hooks */ | |||
struct { | |||
void *data; | |||
/* Hooks that are called when the library expects to start | |||
* reading/writing. These functions should be idempotent. */ | |||
void (*addRead)(void *privdata); | |||
void (*delRead)(void *privdata); | |||
void (*addWrite)(void *privdata); | |||
void (*delWrite)(void *privdata); | |||
void (*cleanup)(void *privdata); | |||
void (*scheduleTimer)(void *privdata, struct timeval tv); | |||
} ev; | |||
/* Called when either the connection is terminated due to an error or per | |||
* user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */ | |||
redisDisconnectCallback *onDisconnect; | |||
/* Called when the first write event was received. */ | |||
redisConnectCallback *onConnect; | |||
/* Regular command callbacks */ | |||
redisCallbackList replies; | |||
/* Address used for connect() */ | |||
struct sockaddr *saddr; | |||
size_t addrlen; | |||
/* Subscription callbacks */ | |||
struct { | |||
redisCallbackList invalid; | |||
struct dict *channels; | |||
struct dict *patterns; | |||
} sub; | |||
} redisAsyncContext; | |||
/* Functions that proxy to hiredis */ | |||
redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options); | |||
redisAsyncContext *redisAsyncConnect(const char *ip, int port); | |||
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr); | |||
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, | |||
const char *source_addr); | |||
redisAsyncContext *redisAsyncConnectUnix(const char *path); | |||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); | |||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); | |||
void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv); | |||
void redisAsyncDisconnect(redisAsyncContext *ac); | |||
void redisAsyncFree(redisAsyncContext *ac); | |||
/* Handle read/write events */ | |||
void redisAsyncHandleRead(redisAsyncContext *ac); | |||
void redisAsyncHandleWrite(redisAsyncContext *ac); | |||
void redisAsyncHandleTimeout(redisAsyncContext *ac); | |||
void redisAsyncRead(redisAsyncContext *ac); | |||
void redisAsyncWrite(redisAsyncContext *ac); | |||
/* Command functions for an async context. Write the command to the | |||
* output buffer and register the provided callback. */ | |||
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap); | |||
int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...); | |||
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); | |||
int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len); | |||
#ifdef __cplusplus | |||
} | |||
#endif | |||
#endif |
@@ -1,72 +0,0 @@ | |||
/* | |||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> | |||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions are met: | |||
* | |||
* * Redistributions of source code must retain the above copyright notice, | |||
* this list of conditions and the following disclaimer. | |||
* * Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* * Neither the name of Redis nor the names of its contributors may be used | |||
* to endorse or promote products derived from this software without | |||
* specific prior written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
* POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
#ifndef __HIREDIS_ASYNC_PRIVATE_H | |||
#define __HIREDIS_ASYNC_PRIVATE_H | |||
#define _EL_ADD_READ(ctx) \ | |||
do { \ | |||
refreshTimeout(ctx); \ | |||
if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \ | |||
} while (0) | |||
#define _EL_DEL_READ(ctx) do { \ | |||
if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \ | |||
} while(0) | |||
#define _EL_ADD_WRITE(ctx) \ | |||
do { \ | |||
refreshTimeout(ctx); \ | |||
if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \ | |||
} while (0) | |||
#define _EL_DEL_WRITE(ctx) do { \ | |||
if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \ | |||
} while(0) | |||
#define _EL_CLEANUP(ctx) do { \ | |||
if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \ | |||
ctx->ev.cleanup = NULL; \ | |||
} while(0); | |||
static inline void refreshTimeout(redisAsyncContext *ctx) { | |||
if (ctx->c.timeout && ctx->ev.scheduleTimer && | |||
(ctx->c.timeout->tv_sec || ctx->c.timeout->tv_usec)) { | |||
ctx->ev.scheduleTimer(ctx->ev.data, *ctx->c.timeout); | |||
// } else { | |||
// printf("Not scheduling timer.. (tmo=%p)\n", ctx->c.timeout); | |||
// if (ctx->c.timeout){ | |||
// printf("tv_sec: %u. tv_usec: %u\n", ctx->c.timeout->tv_sec, | |||
// ctx->c.timeout->tv_usec); | |||
// } | |||
} | |||
} | |||
void __redisAsyncDisconnect(redisAsyncContext *ac); | |||
void redisProcessCallbacks(redisAsyncContext *ac); | |||
#endif /* __HIREDIS_ASYNC_PRIVATE_H */ |
@@ -1,339 +0,0 @@ | |||
/* Hash table implementation. | |||
* | |||
* This file implements in memory hash tables with insert/del/replace/find/ | |||
* get-random-element operations. Hash tables will auto resize if needed | |||
* tables of power of two in size are used, collisions are handled by | |||
* chaining. See the source code for more information... :) | |||
* | |||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com> | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions are met: | |||
* | |||
* * Redistributions of source code must retain the above copyright notice, | |||
* this list of conditions and the following disclaimer. | |||
* * Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* * Neither the name of Redis nor the names of its contributors may be used | |||
* to endorse or promote products derived from this software without | |||
* specific prior written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
* POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
#include "fmacros.h" | |||
#include "alloc.h" | |||
#include <stdlib.h> | |||
#include <assert.h> | |||
#include <limits.h> | |||
#include "dict.h" | |||
/* -------------------------- private prototypes ---------------------------- */ | |||
static int _dictExpandIfNeeded(dict *ht); | |||
static unsigned long _dictNextPower(unsigned long size); | |||
static int _dictKeyIndex(dict *ht, const void *key); | |||
static int _dictInit(dict *ht, dictType *type, void *privDataPtr); | |||
/* -------------------------- hash functions -------------------------------- */ | |||
/* Generic hash function (a popular one from Bernstein). | |||
* I tested a few and this was the best. */ | |||
static unsigned int dictGenHashFunction(const unsigned char *buf, int len) { | |||
unsigned int hash = 5381; | |||
while (len--) | |||
hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */ | |||
return hash; | |||
} | |||
/* ----------------------------- API implementation ------------------------- */ | |||
/* Reset an hashtable already initialized with ht_init(). | |||
* NOTE: This function should only called by ht_destroy(). */ | |||
static void _dictReset(dict *ht) { | |||
ht->table = NULL; | |||
ht->size = 0; | |||
ht->sizemask = 0; | |||
ht->used = 0; | |||
} | |||
/* Create a new hash table */ | |||
static dict *dictCreate(dictType *type, void *privDataPtr) { | |||
dict *ht = hi_malloc(sizeof(*ht)); | |||
_dictInit(ht,type,privDataPtr); | |||
return ht; | |||
} | |||
/* Initialize the hash table */ | |||
static int _dictInit(dict *ht, dictType *type, void *privDataPtr) { | |||
_dictReset(ht); | |||
ht->type = type; | |||
ht->privdata = privDataPtr; | |||
return DICT_OK; | |||
} | |||
/* Expand or create the hashtable */ | |||
static int dictExpand(dict *ht, unsigned long size) { | |||
dict n; /* the new hashtable */ | |||
unsigned long realsize = _dictNextPower(size), i; | |||
/* the size is invalid if it is smaller than the number of | |||
* elements already inside the hashtable */ | |||
if (ht->used > size) | |||
return DICT_ERR; | |||
_dictInit(&n, ht->type, ht->privdata); | |||
n.size = realsize; | |||
n.sizemask = realsize-1; | |||
n.table = calloc(realsize,sizeof(dictEntry*)); | |||
/* Copy all the elements from the old to the new table: | |||
* note that if the old hash table is empty ht->size is zero, | |||
* so dictExpand just creates an hash table. */ | |||
n.used = ht->used; | |||
for (i = 0; i < ht->size && ht->used > 0; i++) { | |||
dictEntry *he, *nextHe; | |||
if (ht->table[i] == NULL) continue; | |||
/* For each hash entry on this slot... */ | |||
he = ht->table[i]; | |||
while(he) { | |||
unsigned int h; | |||
nextHe = he->next; | |||
/* Get the new element index */ | |||
h = dictHashKey(ht, he->key) & n.sizemask; | |||
he->next = n.table[h]; | |||
n.table[h] = he; | |||
ht->used--; | |||
/* Pass to the next element */ | |||
he = nextHe; | |||
} | |||
} | |||
assert(ht->used == 0); | |||
free(ht->table); | |||
/* Remap the new hashtable in the old */ | |||
*ht = n; | |||
return DICT_OK; | |||
} | |||
/* Add an element to the target hash table */ | |||
static int dictAdd(dict *ht, void *key, void *val) { | |||
int index; | |||
dictEntry *entry; | |||
/* Get the index of the new element, or -1 if | |||
* the element already exists. */ | |||
if ((index = _dictKeyIndex(ht, key)) == -1) | |||
return DICT_ERR; | |||
/* Allocates the memory and stores key */ | |||
entry = hi_malloc(sizeof(*entry)); | |||
entry->next = ht->table[index]; | |||
ht->table[index] = entry; | |||
/* Set the hash entry fields. */ | |||
dictSetHashKey(ht, entry, key); | |||
dictSetHashVal(ht, entry, val); | |||
ht->used++; | |||
return DICT_OK; | |||
} | |||
/* Add an element, discarding the old if the key already exists. | |||
* Return 1 if the key was added from scratch, 0 if there was already an | |||
* element with such key and dictReplace() just performed a value update | |||
* operation. */ | |||
static int dictReplace(dict *ht, void *key, void *val) { | |||
dictEntry *entry, auxentry; | |||
/* Try to add the element. If the key | |||
* does not exists dictAdd will succeed. */ | |||
if (dictAdd(ht, key, val) == DICT_OK) | |||
return 1; | |||
/* It already exists, get the entry */ | |||
entry = dictFind(ht, key); | |||
/* Free the old value and set the new one */ | |||
/* Set the new value and free the old one. Note that it is important | |||
* to do that in this order, as the value may just be exactly the same | |||
* as the previous one. In this context, think to reference counting, | |||
* you want to increment (set), and then decrement (free), and not the | |||
* reverse. */ | |||
auxentry = *entry; | |||
dictSetHashVal(ht, entry, val); | |||
dictFreeEntryVal(ht, &auxentry); | |||
return 0; | |||
} | |||
/* Search and remove an element */ | |||
static int dictDelete(dict *ht, const void *key) { | |||
unsigned int h; | |||
dictEntry *de, *prevde; | |||
if (ht->size == 0) | |||
return DICT_ERR; | |||
h = dictHashKey(ht, key) & ht->sizemask; | |||
de = ht->table[h]; | |||
prevde = NULL; | |||
while(de) { | |||
if (dictCompareHashKeys(ht,key,de->key)) { | |||
/* Unlink the element from the list */ | |||
if (prevde) | |||
prevde->next = de->next; | |||
else | |||
ht->table[h] = de->next; | |||
dictFreeEntryKey(ht,de); | |||
dictFreeEntryVal(ht,de); | |||
free(de); | |||
ht->used--; | |||
return DICT_OK; | |||
} | |||
prevde = de; | |||
de = de->next; | |||
} | |||
return DICT_ERR; /* not found */ | |||
} | |||
/* Destroy an entire hash table */ | |||
static int _dictClear(dict *ht) { | |||
unsigned long i; | |||
/* Free all the elements */ | |||
for (i = 0; i < ht->size && ht->used > 0; i++) { | |||
dictEntry *he, *nextHe; | |||
if ((he = ht->table[i]) == NULL) continue; | |||
while(he) { | |||
nextHe = he->next; | |||
dictFreeEntryKey(ht, he); | |||
dictFreeEntryVal(ht, he); | |||
free(he); | |||
ht->used--; | |||
he = nextHe; | |||
} | |||
} | |||
/* Free the table and the allocated cache structure */ | |||
free(ht->table); | |||
/* Re-initialize the table */ | |||
_dictReset(ht); | |||
return DICT_OK; /* never fails */ | |||
} | |||
/* Clear & Release the hash table */ | |||
static void dictRelease(dict *ht) { | |||
_dictClear(ht); | |||
free(ht); | |||
} | |||
static dictEntry *dictFind(dict *ht, const void *key) { | |||
dictEntry *he; | |||
unsigned int h; | |||
if (ht->size == 0) return NULL; | |||
h = dictHashKey(ht, key) & ht->sizemask; | |||
he = ht->table[h]; | |||
while(he) { | |||
if (dictCompareHashKeys(ht, key, he->key)) | |||
return he; | |||
he = he->next; | |||
} | |||
return NULL; | |||
} | |||
static dictIterator *dictGetIterator(dict *ht) { | |||
dictIterator *iter = hi_malloc(sizeof(*iter)); | |||
iter->ht = ht; | |||
iter->index = -1; | |||
iter->entry = NULL; | |||
iter->nextEntry = NULL; | |||
return iter; | |||
} | |||
static dictEntry *dictNext(dictIterator *iter) { | |||
while (1) { | |||
if (iter->entry == NULL) { | |||
iter->index++; | |||
if (iter->index >= | |||
(signed)iter->ht->size) break; | |||
iter->entry = iter->ht->table[iter->index]; | |||
} else { | |||
iter->entry = iter->nextEntry; | |||
} | |||
if (iter->entry) { | |||
/* We need to save the 'next' here, the iterator user | |||
* may delete the entry we are returning. */ | |||
iter->nextEntry = iter->entry->next; | |||
return iter->entry; | |||
} | |||
} | |||
return NULL; | |||
} | |||
static void dictReleaseIterator(dictIterator *iter) { | |||
free(iter); | |||
} | |||
/* ------------------------- private functions ------------------------------ */ | |||
/* Expand the hash table if needed */ | |||
static int _dictExpandIfNeeded(dict *ht) { | |||
/* If the hash table is empty expand it to the initial size, | |||
* if the table is "full" double its size. */ | |||
if (ht->size == 0) | |||
return dictExpand(ht, DICT_HT_INITIAL_SIZE); | |||
if (ht->used == ht->size) | |||
return dictExpand(ht, ht->size*2); | |||
return DICT_OK; | |||
} | |||
/* Our hash table capability is a power of two */ | |||
static unsigned long _dictNextPower(unsigned long size) { | |||
unsigned long i = DICT_HT_INITIAL_SIZE; | |||
if (size >= LONG_MAX) return LONG_MAX; | |||
while(1) { | |||
if (i >= size) | |||
return i; | |||
i *= 2; | |||
} | |||
} | |||
/* Returns the index of a free slot that can be populated with | |||
* an hash entry for the given 'key'. | |||
* If the key already exists, -1 is returned. */ | |||
static int _dictKeyIndex(dict *ht, const void *key) { | |||
unsigned int h; | |||
dictEntry *he; | |||
/* Expand the hashtable if needed */ | |||
if (_dictExpandIfNeeded(ht) == DICT_ERR) | |||
return -1; | |||
/* Compute the key hash value */ | |||
h = dictHashKey(ht, key) & ht->sizemask; | |||
/* Search if this slot does not already contain the given key */ | |||
he = ht->table[h]; | |||
while(he) { | |||
if (dictCompareHashKeys(ht, key, he->key)) | |||
return -1; | |||
he = he->next; | |||
} | |||
return h; | |||
} | |||
@@ -1,126 +0,0 @@ | |||
/* Hash table implementation. | |||
* | |||
* This file implements in memory hash tables with insert/del/replace/find/ | |||
* get-random-element operations. Hash tables will auto resize if needed | |||
* tables of power of two in size are used, collisions are handled by | |||
* chaining. See the source code for more information... :) | |||
* | |||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com> | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions are met: | |||
* | |||
* * Redistributions of source code must retain the above copyright notice, | |||
* this list of conditions and the following disclaimer. | |||
* * Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* * Neither the name of Redis nor the names of its contributors may be used | |||
* to endorse or promote products derived from this software without | |||
* specific prior written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
* POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
#ifndef __DICT_H | |||
#define __DICT_H | |||
#define DICT_OK 0 | |||
#define DICT_ERR 1 | |||
/* Unused arguments generate annoying warnings... */ | |||
#define DICT_NOTUSED(V) ((void) V) | |||
typedef struct dictEntry { | |||
void *key; | |||
void *val; | |||
struct dictEntry *next; | |||
} dictEntry; | |||
typedef struct dictType { | |||
unsigned int (*hashFunction)(const void *key); | |||
void *(*keyDup)(void *privdata, const void *key); | |||
void *(*valDup)(void *privdata, const void *obj); | |||
int (*keyCompare)(void *privdata, const void *key1, const void *key2); | |||
void (*keyDestructor)(void *privdata, void *key); | |||
void (*valDestructor)(void *privdata, void *obj); | |||
} dictType; | |||
typedef struct dict { | |||
dictEntry **table; | |||
dictType *type; | |||
unsigned long size; | |||
unsigned long sizemask; | |||
unsigned long used; | |||
void *privdata; | |||
} dict; | |||
typedef struct dictIterator { | |||
dict *ht; | |||
int index; | |||
dictEntry *entry, *nextEntry; | |||
} dictIterator; | |||
/* This is the initial size of every hash table */ | |||
#define DICT_HT_INITIAL_SIZE 4 | |||
/* ------------------------------- Macros ------------------------------------*/ | |||
#define dictFreeEntryVal(ht, entry) \ | |||
if ((ht)->type->valDestructor) \ | |||
(ht)->type->valDestructor((ht)->privdata, (entry)->val) | |||
#define dictSetHashVal(ht, entry, _val_) do { \ | |||
if ((ht)->type->valDup) \ | |||
entry->val = (ht)->type->valDup((ht)->privdata, _val_); \ | |||
else \ | |||
entry->val = (_val_); \ | |||
} while(0) | |||
#define dictFreeEntryKey(ht, entry) \ | |||
if ((ht)->type->keyDestructor) \ | |||
(ht)->type->keyDestructor((ht)->privdata, (entry)->key) | |||
#define dictSetHashKey(ht, entry, _key_) do { \ | |||
if ((ht)->type->keyDup) \ | |||
entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \ | |||
else \ | |||
entry->key = (_key_); \ | |||
} while(0) | |||
#define dictCompareHashKeys(ht, key1, key2) \ | |||
(((ht)->type->keyCompare) ? \ | |||
(ht)->type->keyCompare((ht)->privdata, key1, key2) : \ | |||
(key1) == (key2)) | |||
#define dictHashKey(ht, key) (ht)->type->hashFunction(key) | |||
#define dictGetEntryKey(he) ((he)->key) | |||
#define dictGetEntryVal(he) ((he)->val) | |||
#define dictSlots(ht) ((ht)->size) | |||
#define dictSize(ht) ((ht)->used) | |||
/* API */ | |||
static unsigned int dictGenHashFunction(const unsigned char *buf, int len); | |||
static dict *dictCreate(dictType *type, void *privDataPtr); | |||
static int dictExpand(dict *ht, unsigned long size); | |||
static int dictAdd(dict *ht, void *key, void *val); | |||
static int dictReplace(dict *ht, void *key, void *val); | |||
static int dictDelete(dict *ht, const void *key); | |||
static void dictRelease(dict *ht); | |||
static dictEntry * dictFind(dict *ht, const void *key); | |||
static dictIterator *dictGetIterator(dict *ht); | |||
static dictEntry *dictNext(dictIterator *iter); | |||
static void dictReleaseIterator(dictIterator *iter); | |||
#endif /* __DICT_H */ |
@@ -1,62 +0,0 @@ | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <signal.h> | |||
#include <hiredis.h> | |||
#include <async.h> | |||
#include <adapters/ae.h> | |||
/* Put event loop in the global scope, so it can be explicitly stopped */ | |||
static aeEventLoop *loop; | |||
void getCallback(redisAsyncContext *c, void *r, void *privdata) { | |||
redisReply *reply = r; | |||
if (reply == NULL) return; | |||
printf("argv[%s]: %s\n", (char*)privdata, reply->str); | |||
/* Disconnect after receiving the reply to GET */ | |||
redisAsyncDisconnect(c); | |||
} | |||
void connectCallback(const redisAsyncContext *c, int status) { | |||
if (status != REDIS_OK) { | |||
printf("Error: %s\n", c->errstr); | |||
aeStop(loop); | |||
return; | |||
} | |||
printf("Connected...\n"); | |||
} | |||
void disconnectCallback(const redisAsyncContext *c, int status) { | |||
if (status != REDIS_OK) { | |||
printf("Error: %s\n", c->errstr); | |||
aeStop(loop); | |||
return; | |||
} | |||
printf("Disconnected...\n"); | |||
aeStop(loop); | |||
} | |||
int main (int argc, char **argv) { | |||
signal(SIGPIPE, SIG_IGN); | |||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); | |||
if (c->err) { | |||
/* Let *c leak for now... */ | |||
printf("Error: %s\n", c->errstr); | |||
return 1; | |||
} | |||
loop = aeCreateEventLoop(64); | |||
redisAeAttach(loop, c); | |||
redisAsyncSetConnectCallback(c,connectCallback); | |||
redisAsyncSetDisconnectCallback(c,disconnectCallback); | |||
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); | |||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); | |||
aeMain(loop); | |||
return 0; | |||
} | |||
@@ -1,73 +0,0 @@ | |||
#include <stdlib.h> | |||
#include <hiredis.h> | |||
#include <async.h> | |||
#include <adapters/glib.h> | |||
static GMainLoop *mainloop; | |||
static void | |||
connect_cb (const redisAsyncContext *ac G_GNUC_UNUSED, | |||
int status) | |||
{ | |||
if (status != REDIS_OK) { | |||
g_printerr("Failed to connect: %s\n", ac->errstr); | |||
g_main_loop_quit(mainloop); | |||
} else { | |||
g_printerr("Connected...\n"); | |||
} | |||
} | |||
static void | |||
disconnect_cb (const redisAsyncContext *ac G_GNUC_UNUSED, | |||
int status) | |||
{ | |||
if (status != REDIS_OK) { | |||
g_error("Failed to disconnect: %s", ac->errstr); | |||
} else { | |||
g_printerr("Disconnected...\n"); | |||
g_main_loop_quit(mainloop); | |||
} | |||
} | |||
static void | |||
command_cb(redisAsyncContext *ac, | |||
gpointer r, | |||
gpointer user_data G_GNUC_UNUSED) | |||
{ | |||
redisReply *reply = r; | |||
if (reply) { | |||
g_print("REPLY: %s\n", reply->str); | |||
} | |||
redisAsyncDisconnect(ac); | |||
} | |||
gint | |||
main (gint argc G_GNUC_UNUSED, | |||
gchar *argv[] G_GNUC_UNUSED) | |||
{ | |||
redisAsyncContext *ac; | |||
GMainContext *context = NULL; | |||
GSource *source; | |||
ac = redisAsyncConnect("127.0.0.1", 6379); | |||
if (ac->err) { | |||
g_printerr("%s\n", ac->errstr); | |||
exit(EXIT_FAILURE); | |||
} | |||
source = redis_source_new(ac); | |||
mainloop = g_main_loop_new(context, FALSE); | |||
g_source_attach(source, context); | |||
redisAsyncSetConnectCallback(ac, connect_cb); | |||
redisAsyncSetDisconnectCallback(ac, disconnect_cb); | |||
redisAsyncCommand(ac, command_cb, NULL, "SET key 1234"); | |||
redisAsyncCommand(ac, command_cb, NULL, "GET key"); | |||
g_main_loop_run(mainloop); | |||
return EXIT_SUCCESS; | |||
} |
@@ -1,58 +0,0 @@ | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <signal.h> | |||
#include <hiredis.h> | |||
#include <async.h> | |||
#include <adapters/ivykis.h> | |||
void getCallback(redisAsyncContext *c, void *r, void *privdata) { | |||
redisReply *reply = r; | |||
if (reply == NULL) return; | |||
printf("argv[%s]: %s\n", (char*)privdata, reply->str); | |||
/* Disconnect after receiving the reply to GET */ | |||
redisAsyncDisconnect(c); | |||
} | |||
void connectCallback(const redisAsyncContext *c, int status) { | |||
if (status != REDIS_OK) { | |||
printf("Error: %s\n", c->errstr); | |||
return; | |||
} | |||
printf("Connected...\n"); | |||
} | |||
void disconnectCallback(const redisAsyncContext *c, int status) { | |||
if (status != REDIS_OK) { | |||
printf("Error: %s\n", c->errstr); | |||
return; | |||
} | |||
printf("Disconnected...\n"); | |||
} | |||
int main (int argc, char **argv) { | |||
signal(SIGPIPE, SIG_IGN); | |||
iv_init(); | |||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); | |||
if (c->err) { | |||
/* Let *c leak for now... */ | |||
printf("Error: %s\n", c->errstr); | |||
return 1; | |||
} | |||
redisIvykisAttach(c); | |||
redisAsyncSetConnectCallback(c,connectCallback); | |||
redisAsyncSetDisconnectCallback(c,disconnectCallback); | |||
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); | |||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); | |||
iv_main(); | |||
iv_deinit(); | |||
return 0; | |||
} |
@@ -1,52 +0,0 @@ | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <signal.h> | |||
#include <hiredis.h> | |||
#include <async.h> | |||
#include <adapters/libev.h> | |||
void getCallback(redisAsyncContext *c, void *r, void *privdata) { | |||
redisReply *reply = r; | |||
if (reply == NULL) return; | |||
printf("argv[%s]: %s\n", (char*)privdata, reply->str); | |||
/* Disconnect after receiving the reply to GET */ | |||
redisAsyncDisconnect(c); | |||
} | |||
void connectCallback(const redisAsyncContext *c, int status) { | |||
if (status != REDIS_OK) { | |||
printf("Error: %s\n", c->errstr); | |||
return; | |||
} | |||
printf("Connected...\n"); | |||
} | |||
void disconnectCallback(const redisAsyncContext *c, int status) { | |||
if (status != REDIS_OK) { | |||
printf("Error: %s\n", c->errstr); | |||
return; | |||
} | |||
printf("Disconnected...\n"); | |||
} | |||
int main (int argc, char **argv) { | |||
signal(SIGPIPE, SIG_IGN); | |||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); | |||
if (c->err) { | |||
/* Let *c leak for now... */ | |||
printf("Error: %s\n", c->errstr); | |||
return 1; | |||
} | |||
redisLibevAttach(EV_DEFAULT_ c); | |||
redisAsyncSetConnectCallback(c,connectCallback); | |||
redisAsyncSetDisconnectCallback(c,disconnectCallback); | |||
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); | |||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); | |||
ev_loop(EV_DEFAULT_ 0); | |||
return 0; | |||
} |
@@ -1,73 +0,0 @@ | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <signal.h> | |||
#include <hiredis.h> | |||
#include <hiredis_ssl.h> | |||
#include <async.h> | |||
#include <adapters/libevent.h> | |||
void getCallback(redisAsyncContext *c, void *r, void *privdata) { | |||
redisReply *reply = r; | |||
if (reply == NULL) return; | |||
printf("argv[%s]: %s\n", (char*)privdata, reply->str); | |||
/* Disconnect after receiving the reply to GET */ | |||
redisAsyncDisconnect(c); | |||
} | |||
void connectCallback(const redisAsyncContext *c, int status) { | |||
if (status != REDIS_OK) { | |||
printf("Error: %s\n", c->errstr); | |||
return; | |||
} | |||
printf("Connected...\n"); | |||
} | |||
void disconnectCallback(const redisAsyncContext *c, int status) { | |||
if (status != REDIS_OK) { | |||
printf("Error: %s\n", c->errstr); | |||
return; | |||
} | |||
printf("Disconnected...\n"); | |||
} | |||
int main (int argc, char **argv) { | |||
signal(SIGPIPE, SIG_IGN); | |||
struct event_base *base = event_base_new(); | |||
if (argc < 5) { | |||
fprintf(stderr, | |||
"Usage: %s <key> <host> <port> <cert> <certKey> [ca]\n", argv[0]); | |||
exit(1); | |||
} | |||
const char *value = argv[1]; | |||
size_t nvalue = strlen(value); | |||
const char *hostname = argv[2]; | |||
int port = atoi(argv[3]); | |||
const char *cert = argv[4]; | |||
const char *certKey = argv[5]; | |||
const char *caCert = argc > 5 ? argv[6] : NULL; | |||
redisAsyncContext *c = redisAsyncConnect(hostname, port); | |||
if (c->err) { | |||
/* Let *c leak for now... */ | |||
printf("Error: %s\n", c->errstr); | |||
return 1; | |||
} | |||
if (redisSecureConnection(&c->c, caCert, cert, certKey, "sni") != REDIS_OK) { | |||
printf("SSL Error!\n"); | |||
exit(1); | |||
} | |||
redisLibeventAttach(c,base); | |||
redisAsyncSetConnectCallback(c,connectCallback); | |||
redisAsyncSetDisconnectCallback(c,disconnectCallback); | |||
redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue); | |||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); | |||
event_base_dispatch(base); | |||
return 0; | |||
} |
@@ -1,64 +0,0 @@ | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <signal.h> | |||
#include <hiredis.h> | |||
#include <async.h> | |||
#include <adapters/libevent.h> | |||
void getCallback(redisAsyncContext *c, void *r, void *privdata) { | |||
redisReply *reply = r; | |||
if (reply == NULL) { | |||
if (c->errstr) { | |||
printf("errstr: %s\n", c->errstr); | |||
} | |||
return; | |||
} | |||
printf("argv[%s]: %s\n", (char*)privdata, reply->str); | |||
/* Disconnect after receiving the reply to GET */ | |||
redisAsyncDisconnect(c); | |||
} | |||
void connectCallback(const redisAsyncContext *c, int status) { | |||
if (status != REDIS_OK) { | |||
printf("Error: %s\n", c->errstr); | |||
return; | |||
} | |||
printf("Connected...\n"); | |||
} | |||
void disconnectCallback(const redisAsyncContext *c, int status) { | |||
if (status != REDIS_OK) { | |||
printf("Error: %s\n", c->errstr); | |||
return; | |||
} | |||
printf("Disconnected...\n"); | |||
} | |||
int main (int argc, char **argv) { | |||
signal(SIGPIPE, SIG_IGN); | |||
struct event_base *base = event_base_new(); | |||
redisOptions options = {0}; | |||
REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379); | |||
struct timeval tv = {0}; | |||
tv.tv_sec = 1; | |||
options.timeout = &tv; | |||
redisAsyncContext *c = redisAsyncConnectWithOptions(&options); | |||
if (c->err) { | |||
/* Let *c leak for now... */ | |||
printf("Error: %s\n", c->errstr); | |||
return 1; | |||
} | |||
redisLibeventAttach(c,base); | |||
redisAsyncSetConnectCallback(c,connectCallback); | |||
redisAsyncSetDisconnectCallback(c,disconnectCallback); | |||
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); | |||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); | |||
event_base_dispatch(base); | |||
return 0; | |||
} |
@@ -1,53 +0,0 @@ | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <signal.h> | |||
#include <hiredis.h> | |||
#include <async.h> | |||
#include <adapters/libuv.h> | |||
void getCallback(redisAsyncContext *c, void *r, void *privdata) { | |||
redisReply *reply = r; | |||
if (reply == NULL) return; | |||
printf("argv[%s]: %s\n", (char*)privdata, reply->str); | |||
/* Disconnect after receiving the reply to GET */ | |||
redisAsyncDisconnect(c); | |||
} | |||
void connectCallback(const redisAsyncContext *c, int status) { | |||
if (status != REDIS_OK) { | |||
printf("Error: %s\n", c->errstr); | |||
return; | |||
} | |||
printf("Connected...\n"); | |||
} | |||
void disconnectCallback(const redisAsyncContext *c, int status) { | |||
if (status != REDIS_OK) { | |||
printf("Error: %s\n", c->errstr); | |||
return; | |||
} | |||
printf("Disconnected...\n"); | |||
} | |||
int main (int argc, char **argv) { | |||
signal(SIGPIPE, SIG_IGN); | |||
uv_loop_t* loop = uv_default_loop(); | |||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); | |||
if (c->err) { | |||
/* Let *c leak for now... */ | |||
printf("Error: %s\n", c->errstr); | |||
return 1; | |||
} | |||
redisLibuvAttach(c,loop); | |||
redisAsyncSetConnectCallback(c,connectCallback); | |||
redisAsyncSetDisconnectCallback(c,disconnectCallback); | |||
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); | |||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); | |||
uv_run(loop, UV_RUN_DEFAULT); | |||
return 0; | |||
} |
@@ -1,66 +0,0 @@ | |||
// | |||
// Created by Дмитрий Бахвалов on 13.07.15. | |||
// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved. | |||
// | |||
#include <stdio.h> | |||
#include <hiredis.h> | |||
#include <async.h> | |||
#include <adapters/macosx.h> | |||
void getCallback(redisAsyncContext *c, void *r, void *privdata) { | |||
redisReply *reply = r; | |||
if (reply == NULL) return; | |||
printf("argv[%s]: %s\n", (char*)privdata, reply->str); | |||
/* Disconnect after receiving the reply to GET */ | |||
redisAsyncDisconnect(c); | |||
} | |||
void connectCallback(const redisAsyncContext *c, int status) { | |||
if (status != REDIS_OK) { | |||
printf("Error: %s\n", c->errstr); | |||
return; | |||
} | |||
printf("Connected...\n"); | |||
} | |||
void disconnectCallback(const redisAsyncContext *c, int status) { | |||
if (status != REDIS_OK) { | |||
printf("Error: %s\n", c->errstr); | |||
return; | |||
} | |||
CFRunLoopStop(CFRunLoopGetCurrent()); | |||
printf("Disconnected...\n"); | |||
} | |||
int main (int argc, char **argv) { | |||
signal(SIGPIPE, SIG_IGN); | |||
CFRunLoopRef loop = CFRunLoopGetCurrent(); | |||
if( !loop ) { | |||
printf("Error: Cannot get current run loop\n"); | |||
return 1; | |||
} | |||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); | |||
if (c->err) { | |||
/* Let *c leak for now... */ | |||
printf("Error: %s\n", c->errstr); | |||
return 1; | |||
} | |||
redisMacOSAttach(c, loop); | |||
redisAsyncSetConnectCallback(c,connectCallback); | |||
redisAsyncSetDisconnectCallback(c,disconnectCallback); | |||
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); | |||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); | |||
CFRunLoopRun(); | |||
return 0; | |||
} | |||
@@ -1,46 +0,0 @@ | |||
#include <iostream> | |||
using namespace std; | |||
#include <QCoreApplication> | |||
#include <QTimer> | |||
#include "example-qt.h" | |||
void getCallback(redisAsyncContext *, void * r, void * privdata) { | |||
redisReply * reply = static_cast<redisReply *>(r); | |||
ExampleQt * ex = static_cast<ExampleQt *>(privdata); | |||
if (reply == nullptr || ex == nullptr) return; | |||
cout << "key: " << reply->str << endl; | |||
ex->finish(); | |||
} | |||
void ExampleQt::run() { | |||
m_ctx = redisAsyncConnect("localhost", 6379); | |||
if (m_ctx->err) { | |||
cerr << "Error: " << m_ctx->errstr << endl; | |||
redisAsyncFree(m_ctx); | |||
emit finished(); | |||
} | |||
m_adapter.setContext(m_ctx); | |||
redisAsyncCommand(m_ctx, NULL, NULL, "SET key %s", m_value); | |||
redisAsyncCommand(m_ctx, getCallback, this, "GET key"); | |||
} | |||
int main (int argc, char **argv) { | |||
QCoreApplication app(argc, argv); | |||
ExampleQt example(argv[argc-1]); | |||
QObject::connect(&example, SIGNAL(finished()), &app, SLOT(quit())); | |||
QTimer::singleShot(0, &example, SLOT(run())); | |||
return app.exec(); | |||
} |
@@ -1,32 +0,0 @@ | |||
#ifndef __HIREDIS_EXAMPLE_QT_H | |||
#define __HIREDIS_EXAMPLE_QT_H | |||
#include <adapters/qt.h> | |||
class ExampleQt : public QObject { | |||
Q_OBJECT | |||
public: | |||
ExampleQt(const char * value, QObject * parent = 0) | |||
: QObject(parent), m_value(value) {} | |||
signals: | |||
void finished(); | |||
public slots: | |||
void run(); | |||
private: | |||
void finish() { emit finished(); } | |||
private: | |||
const char * m_value; | |||
redisAsyncContext * m_ctx; | |||
RedisQtAdapter m_adapter; | |||
friend | |||
void getCallback(redisAsyncContext *, void *, void *); | |||
}; | |||
#endif /* !__HIREDIS_EXAMPLE_QT_H */ |
@@ -1,97 +0,0 @@ | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <hiredis.h> | |||
#include <hiredis_ssl.h> | |||
int main(int argc, char **argv) { | |||
unsigned int j; | |||
redisContext *c; | |||
redisReply *reply; | |||
if (argc < 4) { | |||
printf("Usage: %s <host> <port> <cert> <key> [ca]\n", argv[0]); | |||
exit(1); | |||
} | |||
const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; | |||
int port = atoi(argv[2]); | |||
const char *cert = argv[3]; | |||
const char *key = argv[4]; | |||
const char *ca = argc > 4 ? argv[5] : NULL; | |||
struct timeval tv = { 1, 500000 }; // 1.5 seconds | |||
redisOptions options = {0}; | |||
REDIS_OPTIONS_SET_TCP(&options, hostname, port); | |||
options.timeout = &tv; | |||
c = redisConnectWithOptions(&options); | |||
if (c == NULL || c->err) { | |||
if (c) { | |||
printf("Connection error: %s\n", c->errstr); | |||
redisFree(c); | |||
} else { | |||
printf("Connection error: can't allocate redis context\n"); | |||
} | |||
exit(1); | |||
} | |||
if (redisSecureConnection(c, ca, cert, key, "sni") != REDIS_OK) { | |||
printf("Couldn't initialize SSL!\n"); | |||
printf("Error: %s\n", c->errstr); | |||
redisFree(c); | |||
exit(1); | |||
} | |||
/* PING server */ | |||
reply = redisCommand(c,"PING"); | |||
printf("PING: %s\n", reply->str); | |||
freeReplyObject(reply); | |||
/* Set a key */ | |||
reply = redisCommand(c,"SET %s %s", "foo", "hello world"); | |||
printf("SET: %s\n", reply->str); | |||
freeReplyObject(reply); | |||
/* Set a key using binary safe API */ | |||
reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5); | |||
printf("SET (binary API): %s\n", reply->str); | |||
freeReplyObject(reply); | |||
/* Try a GET and two INCR */ | |||
reply = redisCommand(c,"GET foo"); | |||
printf("GET foo: %s\n", reply->str); | |||
freeReplyObject(reply); | |||
reply = redisCommand(c,"INCR counter"); | |||
printf("INCR counter: %lld\n", reply->integer); | |||
freeReplyObject(reply); | |||
/* again ... */ | |||
reply = redisCommand(c,"INCR counter"); | |||
printf("INCR counter: %lld\n", reply->integer); | |||
freeReplyObject(reply); | |||
/* Create a list of numbers, from 0 to 9 */ | |||
reply = redisCommand(c,"DEL mylist"); | |||
freeReplyObject(reply); | |||
for (j = 0; j < 10; j++) { | |||
char buf[64]; | |||
snprintf(buf,64,"%u",j); | |||
reply = redisCommand(c,"LPUSH mylist element-%s", buf); | |||
freeReplyObject(reply); | |||
} | |||
/* Let's check what we have inside the list */ | |||
reply = redisCommand(c,"LRANGE mylist 0 -1"); | |||
if (reply->type == REDIS_REPLY_ARRAY) { | |||
for (j = 0; j < reply->elements; j++) { | |||
printf("%u) %s\n", j, reply->element[j]->str); | |||
} | |||
} | |||
freeReplyObject(reply); | |||
/* Disconnects and frees the context */ | |||
redisFree(c); | |||
return 0; | |||
} |
@@ -1,91 +0,0 @@ | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <hiredis.h> | |||
int main(int argc, char **argv) { | |||
unsigned int j, isunix = 0; | |||
redisContext *c; | |||
redisReply *reply; | |||
const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; | |||
if (argc > 2) { | |||
if (*argv[2] == 'u' || *argv[2] == 'U') { | |||
isunix = 1; | |||
/* in this case, host is the path to the unix socket */ | |||
printf("Will connect to unix socket @%s\n", hostname); | |||
} | |||
} | |||
int port = (argc > 2) ? atoi(argv[2]) : 6379; | |||
struct timeval timeout = { 1, 500000 }; // 1.5 seconds | |||
if (isunix) { | |||
c = redisConnectUnixWithTimeout(hostname, timeout); | |||
} else { | |||
c = redisConnectWithTimeout(hostname, port, timeout); | |||
} | |||
if (c == NULL || c->err) { | |||
if (c) { | |||
printf("Connection error: %s\n", c->errstr); | |||
redisFree(c); | |||
} else { | |||
printf("Connection error: can't allocate redis context\n"); | |||
} | |||
exit(1); | |||
} | |||
/* PING server */ | |||
reply = redisCommand(c,"PING"); | |||
printf("PING: %s\n", reply->str); | |||
freeReplyObject(reply); | |||
/* Set a key */ | |||
reply = redisCommand(c,"SET %s %s", "foo", "hello world"); | |||
printf("SET: %s\n", reply->str); | |||
freeReplyObject(reply); | |||
/* Set a key using binary safe API */ | |||
reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5); | |||
printf("SET (binary API): %s\n", reply->str); | |||
freeReplyObject(reply); | |||
/* Try a GET and two INCR */ | |||
reply = redisCommand(c,"GET foo"); | |||
printf("GET foo: %s\n", reply->str); | |||
freeReplyObject(reply); | |||
reply = redisCommand(c,"INCR counter"); | |||
printf("INCR counter: %lld\n", reply->integer); | |||
freeReplyObject(reply); | |||
/* again ... */ | |||
reply = redisCommand(c,"INCR counter"); | |||
printf("INCR counter: %lld\n", reply->integer); | |||
freeReplyObject(reply); | |||
/* Create a list of numbers, from 0 to 9 */ | |||
reply = redisCommand(c,"DEL mylist"); | |||
freeReplyObject(reply); | |||
for (j = 0; j < 10; j++) { | |||
char buf[64]; | |||
snprintf(buf,64,"%u",j); | |||
reply = redisCommand(c,"LPUSH mylist element-%s", buf); | |||
freeReplyObject(reply); | |||
} | |||
/* Let's check what we have inside the list */ | |||
reply = redisCommand(c,"LRANGE mylist 0 -1"); | |||
if (reply->type == REDIS_REPLY_ARRAY) { | |||
for (j = 0; j < reply->elements; j++) { | |||
printf("%u) %s\n", j, reply->element[j]->str); | |||
} | |||
} | |||
freeReplyObject(reply); | |||
/* Disconnects and frees the context */ | |||
redisFree(c); | |||
return 0; | |||
} |
@@ -1,12 +0,0 @@ | |||
#ifndef __HIREDIS_FMACRO_H | |||
#define __HIREDIS_FMACRO_H | |||
#define _XOPEN_SOURCE 600 | |||
#define _POSIX_C_SOURCE 200112L | |||
#if defined(__APPLE__) && defined(__MACH__) | |||
/* Enable TCP_KEEPALIVE */ | |||
#define _DARWIN_C_SOURCE | |||
#endif | |||
#endif |
@@ -1,298 +0,0 @@ | |||
/* | |||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> | |||
* Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>, | |||
* Jan-Erik Rediger <janerik at fnordig dot com> | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions are met: | |||
* | |||
* * Redistributions of source code must retain the above copyright notice, | |||
* this list of conditions and the following disclaimer. | |||
* * Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* * Neither the name of Redis nor the names of its contributors may be used | |||
* to endorse or promote products derived from this software without | |||
* specific prior written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
* POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
#ifndef __HIREDIS_H | |||
#define __HIREDIS_H | |||
#include "read.h" | |||
#include <stdarg.h> /* for va_list */ | |||
#ifndef _MSC_VER | |||
#include <sys/time.h> /* for struct timeval */ | |||
#else | |||
struct timeval; /* forward declaration */ | |||
#endif | |||
#include <stdint.h> /* uintXX_t, etc */ | |||
#include "sds.h" /* for sds */ | |||
#include "alloc.h" /* for allocation wrappers */ | |||
#define HIREDIS_MAJOR 0 | |||
#define HIREDIS_MINOR 14 | |||
#define HIREDIS_PATCH 0 | |||
#define HIREDIS_SONAME 0.14 | |||
/* Connection type can be blocking or non-blocking and is set in the | |||
* least significant bit of the flags field in redisContext. */ | |||
#define REDIS_BLOCK 0x1 | |||
/* Connection may be disconnected before being free'd. The second bit | |||
* in the flags field is set when the context is connected. */ | |||
#define REDIS_CONNECTED 0x2 | |||
/* The async API might try to disconnect cleanly and flush the output | |||
* buffer and read all subsequent replies before disconnecting. | |||
* This flag means no new commands can come in and the connection | |||
* should be terminated once all replies have been read. */ | |||
#define REDIS_DISCONNECTING 0x4 | |||
/* Flag specific to the async API which means that the context should be clean | |||
* up as soon as possible. */ | |||
#define REDIS_FREEING 0x8 | |||
/* Flag that is set when an async callback is executed. */ | |||
#define REDIS_IN_CALLBACK 0x10 | |||
/* Flag that is set when the async context has one or more subscriptions. */ | |||
#define REDIS_SUBSCRIBED 0x20 | |||
/* Flag that is set when monitor mode is active */ | |||
#define REDIS_MONITORING 0x40 | |||
/* Flag that is set when we should set SO_REUSEADDR before calling bind() */ | |||
#define REDIS_REUSEADDR 0x80 | |||
/** | |||
* Flag that indicates the user does not want the context to | |||
* be automatically freed upon error | |||
*/ | |||
#define REDIS_NO_AUTO_FREE 0x200 | |||
#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ | |||
/* number of times we retry to connect in the case of EADDRNOTAVAIL and | |||
* SO_REUSEADDR is being used. */ | |||
#define REDIS_CONNECT_RETRIES 10 | |||
#ifdef __cplusplus | |||
extern "C" { | |||
#endif | |||
/* This is the reply object returned by redisCommand() */ | |||
typedef struct redisReply { | |||
int type; /* REDIS_REPLY_* */ | |||
long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ | |||
double dval; /* The double when type is REDIS_REPLY_DOUBLE */ | |||
size_t len; /* Length of string */ | |||
char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING | |||
and REDIS_REPLY_DOUBLE (in additional to dval). */ | |||
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ | |||
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ | |||
} redisReply; | |||
redisReader *redisReaderCreate(void); | |||
/* Function to free the reply objects hiredis returns by default. */ | |||
void freeReplyObject(void *reply); | |||
/* Functions to format a command according to the protocol. */ | |||
int redisvFormatCommand(char **target, const char *format, va_list ap); | |||
int redisFormatCommand(char **target, const char *format, ...); | |||
int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); | |||
int redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen); | |||
void redisFreeCommand(char *cmd); | |||
void redisFreeSdsCommand(sds cmd); | |||
enum redisConnectionType { | |||
REDIS_CONN_TCP, | |||
REDIS_CONN_UNIX, | |||
REDIS_CONN_USERFD | |||
}; | |||
struct redisSsl; | |||
#define REDIS_OPT_NONBLOCK 0x01 | |||
#define REDIS_OPT_REUSEADDR 0x02 | |||
/** | |||
* Don't automatically free the async object on a connection failure, | |||
* or other implicit conditions. Only free on an explicit call to disconnect() or free() | |||
*/ | |||
#define REDIS_OPT_NOAUTOFREE 0x04 | |||
/* In Unix systems a file descriptor is a regular signed int, with -1 | |||
* representing an invalid descriptor. In Windows it is a SOCKET | |||
* (32- or 64-bit unsigned integer depending on the architecture), where | |||
* all bits set (~0) is INVALID_SOCKET. */ | |||
#ifndef _WIN32 | |||
typedef int redisFD; | |||
#define REDIS_INVALID_FD -1 | |||
#else | |||
#ifdef _WIN64 | |||
typedef unsigned long long redisFD; /* SOCKET = 64-bit UINT_PTR */ | |||
#else | |||
typedef unsigned long redisFD; /* SOCKET = 32-bit UINT_PTR */ | |||
#endif | |||
#define REDIS_INVALID_FD ((redisFD)(~0)) /* INVALID_SOCKET */ | |||
#endif | |||
typedef struct { | |||
/* | |||
* the type of connection to use. This also indicates which | |||
* `endpoint` member field to use | |||
*/ | |||
int type; | |||
/* bit field of REDIS_OPT_xxx */ | |||
int options; | |||
/* timeout value. if NULL, no timeout is used */ | |||
const struct timeval *timeout; | |||
union { | |||
/** use this field for tcp/ip connections */ | |||
struct { | |||
const char *source_addr; | |||
const char *ip; | |||
int port; | |||
} tcp; | |||
/** use this field for unix domain sockets */ | |||
const char *unix_socket; | |||
/** | |||
* use this field to have hiredis operate an already-open | |||
* file descriptor */ | |||
redisFD fd; | |||
} endpoint; | |||
} redisOptions; | |||
/** | |||
* Helper macros to initialize options to their specified fields. | |||
*/ | |||
#define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) \ | |||
(opts)->type = REDIS_CONN_TCP; \ | |||
(opts)->endpoint.tcp.ip = ip_; \ | |||
(opts)->endpoint.tcp.port = port_; | |||
#define REDIS_OPTIONS_SET_UNIX(opts, path) \ | |||
(opts)->type = REDIS_CONN_UNIX; \ | |||
(opts)->endpoint.unix_socket = path; | |||
struct redisAsyncContext; | |||
struct redisContext; | |||
typedef struct redisContextFuncs { | |||
void (*free_privdata)(void *); | |||
void (*async_read)(struct redisAsyncContext *); | |||
void (*async_write)(struct redisAsyncContext *); | |||
int (*read)(struct redisContext *, char *, size_t); | |||
int (*write)(struct redisContext *); | |||
} redisContextFuncs; | |||
/* Context for a connection to Redis */ | |||
typedef struct redisContext { | |||
const redisContextFuncs *funcs; /* Function table */ | |||
int err; /* Error flags, 0 when there is no error */ | |||
char errstr[128]; /* String representation of error when applicable */ | |||
redisFD fd; | |||
int flags; | |||
char *obuf; /* Write buffer */ | |||
redisReader *reader; /* Protocol reader */ | |||
enum redisConnectionType connection_type; | |||
struct timeval *timeout; | |||
struct { | |||
char *host; | |||
char *source_addr; | |||
int port; | |||
} tcp; | |||
struct { | |||
char *path; | |||
} unix_sock; | |||
/* For non-blocking connect */ | |||
struct sockadr *saddr; | |||
size_t addrlen; | |||
/* Additional private data for hiredis addons such as SSL */ | |||
void *privdata; | |||
} redisContext; | |||
redisContext *redisConnectWithOptions(const redisOptions *options); | |||
redisContext *redisConnect(const char *ip, int port); | |||
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv); | |||
redisContext *redisConnectNonBlock(const char *ip, int port); | |||
redisContext *redisConnectBindNonBlock(const char *ip, int port, | |||
const char *source_addr); | |||
redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, | |||
const char *source_addr); | |||
redisContext *redisConnectUnix(const char *path); | |||
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv); | |||
redisContext *redisConnectUnixNonBlock(const char *path); | |||
redisContext *redisConnectFd(redisFD fd); | |||
/** | |||
* Reconnect the given context using the saved information. | |||
* | |||
* This re-uses the exact same connect options as in the initial connection. | |||
* host, ip (or path), timeout and bind address are reused, | |||
* flags are used unmodified from the existing context. | |||
* | |||
* Returns REDIS_OK on successful connect or REDIS_ERR otherwise. | |||
*/ | |||
int redisReconnect(redisContext *c); | |||
int redisSetTimeout(redisContext *c, const struct timeval tv); | |||
int redisEnableKeepAlive(redisContext *c); | |||
void redisFree(redisContext *c); | |||
redisFD redisFreeKeepFd(redisContext *c); | |||
int redisBufferRead(redisContext *c); | |||
int redisBufferWrite(redisContext *c, int *done); | |||
/* In a blocking context, this function first checks if there are unconsumed | |||
* replies to return and returns one if so. Otherwise, it flushes the output | |||
* buffer to the socket and reads until it has a reply. In a non-blocking | |||
* context, it will return unconsumed replies until there are no more. */ | |||
int redisGetReply(redisContext *c, void **reply); | |||
int redisGetReplyFromReader(redisContext *c, void **reply); | |||
/* Write a formatted command to the output buffer. Use these functions in blocking mode | |||
* to get a pipeline of commands. */ | |||
int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len); | |||
/* Write a command to the output buffer. Use these functions in blocking mode | |||
* to get a pipeline of commands. */ | |||
int redisvAppendCommand(redisContext *c, const char *format, va_list ap); | |||
int redisAppendCommand(redisContext *c, const char *format, ...); | |||
int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); | |||
/* Issue a command to Redis. In a blocking context, it is identical to calling | |||
* redisAppendCommand, followed by redisGetReply. The function will return | |||
* NULL if there was an error in performing the request, otherwise it will | |||
* return the reply. In a non-blocking context, it is identical to calling | |||
* only redisAppendCommand and will always return NULL. */ | |||
void *redisvCommand(redisContext *c, const char *format, va_list ap); | |||
void *redisCommand(redisContext *c, const char *format, ...); | |||
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); | |||
#ifdef __cplusplus | |||
} | |||
#endif | |||
#endif |
@@ -1,11 +0,0 @@ | |||
prefix=@CMAKE_INSTALL_PREFIX@ | |||
exec_prefix=${prefix} | |||
libdir=${exec_prefix}/lib | |||
includedir=${prefix}/include | |||
pkgincludedir=${includedir}/hiredis | |||
Name: hiredis | |||
Description: Minimalistic C client library for Redis. | |||
Version: @PROJECT_VERSION@ | |||
Libs: -L${libdir} -lhiredis | |||
Cflags: -I${pkgincludedir} -D_FILE_OFFSET_BITS=64 |
@@ -1,53 +0,0 @@ | |||
/* | |||
* Copyright (c) 2019, Redis Labs | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions are met: | |||
* | |||
* * Redistributions of source code must retain the above copyright notice, | |||
* this list of conditions and the following disclaimer. | |||
* * Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* * Neither the name of Redis nor the names of its contributors may be used | |||
* to endorse or promote products derived from this software without | |||
* specific prior written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
* POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
#ifndef __HIREDIS_SSL_H | |||
#define __HIREDIS_SSL_H | |||
/* This is the underlying struct for SSL in ssl.h, which is not included to | |||
* keep build dependencies short here. | |||
*/ | |||
struct ssl_st; | |||
/** | |||
* Secure the connection using SSL. This should be done before any command is | |||
* executed on the connection. | |||
*/ | |||
int redisSecureConnection(redisContext *c, const char *capath, const char *certpath, | |||
const char *keypath, const char *servername); | |||
/** | |||
* Initiate SSL/TLS negotiation on a provided context. | |||
*/ | |||
int redisInitiateSSL(redisContext *c, struct ssl_st *ssl); | |||
#endif /* __HIREDIS_SSL_H */ |
@@ -1,12 +0,0 @@ | |||
prefix=@CMAKE_INSTALL_PREFIX@ | |||
exec_prefix=${prefix} | |||
libdir=${exec_prefix}/lib | |||
includedir=${prefix}/include | |||
pkgincludedir=${includedir}/hiredis | |||
Name: hiredis_ssl | |||
Description: SSL Support for hiredis. | |||
Version: @PROJECT_VERSION@ | |||
Requires: hiredis | |||
Libs: -L${libdir} -lhiredis_ssl | |||
Libs.private: -lssl -lcrypto |
@@ -1,571 +0,0 @@ | |||
/* Extracted from anet.c to work properly with Hiredis error reporting. | |||
* | |||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> | |||
* Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>, | |||
* Jan-Erik Rediger <janerik at fnordig dot com> | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions are met: | |||
* | |||
* * Redistributions of source code must retain the above copyright notice, | |||
* this list of conditions and the following disclaimer. | |||
* * Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* * Neither the name of Redis nor the names of its contributors may be used | |||
* to endorse or promote products derived from this software without | |||
* specific prior written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
* POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
#include "fmacros.h" | |||
#include <sys/types.h> | |||
#include <fcntl.h> | |||
#include <string.h> | |||
#include <errno.h> | |||
#include <stdarg.h> | |||
#include <stdio.h> | |||
#include <limits.h> | |||
#include <stdlib.h> | |||
#include "net.h" | |||
#include "sds.h" | |||
#include "sockcompat.h" | |||
#include "win32.h" | |||
/* Defined in hiredis.c */ | |||
void __redisSetError(redisContext *c, int type, const char *str); | |||
void redisNetClose(redisContext *c) { | |||
if (c && c->fd != REDIS_INVALID_FD) { | |||
close(c->fd); | |||
c->fd = REDIS_INVALID_FD; | |||
} | |||
} | |||
int redisNetRead(redisContext *c, char *buf, size_t bufcap) { | |||
int nread = recv(c->fd, buf, bufcap, 0); | |||
if (nread == -1) { | |||
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { | |||
/* Try again later */ | |||
return 0; | |||
} else if(errno == ETIMEDOUT && (c->flags & REDIS_BLOCK)) { | |||
/* especially in windows */ | |||
__redisSetError(c, REDIS_ERR_TIMEOUT, "recv timeout"); | |||
return -1; | |||
} else { | |||
__redisSetError(c, REDIS_ERR_IO, NULL); | |||
return -1; | |||
} | |||
} else if (nread == 0) { | |||
__redisSetError(c, REDIS_ERR_EOF, "Server closed the connection"); | |||
return -1; | |||
} else { | |||
return nread; | |||
} | |||
} | |||
int redisNetWrite(redisContext *c) { | |||
int nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0); | |||
if (nwritten < 0) { | |||
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { | |||
/* Try again later */ | |||
} else { | |||
__redisSetError(c, REDIS_ERR_IO, NULL); | |||
return -1; | |||
} | |||
} | |||
return nwritten; | |||
} | |||
static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { | |||
int errorno = errno; /* snprintf() may change errno */ | |||
char buf[128] = { 0 }; | |||
size_t len = 0; | |||
if (prefix != NULL) | |||
len = snprintf(buf,sizeof(buf),"%s: ",prefix); | |||
strerror_r(errorno, (char *)(buf + len), sizeof(buf) - len); | |||
__redisSetError(c,type,buf); | |||
} | |||
static int redisSetReuseAddr(redisContext *c) { | |||
int on = 1; | |||
if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { | |||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); | |||
redisNetClose(c); | |||
return REDIS_ERR; | |||
} | |||
return REDIS_OK; | |||
} | |||
static int redisCreateSocket(redisContext *c, int type) { | |||
redisFD s; | |||
if ((s = socket(type, SOCK_STREAM, 0)) == REDIS_INVALID_FD) { | |||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); | |||
return REDIS_ERR; | |||
} | |||
c->fd = s; | |||
if (type == AF_INET) { | |||
if (redisSetReuseAddr(c) == REDIS_ERR) { | |||
return REDIS_ERR; | |||
} | |||
} | |||
return REDIS_OK; | |||
} | |||
static int redisSetBlocking(redisContext *c, int blocking) { | |||
#ifndef _WIN32 | |||
int flags; | |||
/* Set the socket nonblocking. | |||
* Note that fcntl(2) for F_GETFL and F_SETFL can't be | |||
* interrupted by a signal. */ | |||
if ((flags = fcntl(c->fd, F_GETFL)) == -1) { | |||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); | |||
redisNetClose(c); | |||
return REDIS_ERR; | |||
} | |||
if (blocking) | |||
flags &= ~O_NONBLOCK; | |||
else | |||
flags |= O_NONBLOCK; | |||
if (fcntl(c->fd, F_SETFL, flags) == -1) { | |||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); | |||
redisNetClose(c); | |||
return REDIS_ERR; | |||
} | |||
#else | |||
u_long mode = blocking ? 0 : 1; | |||
if (ioctl(c->fd, FIONBIO, &mode) == -1) { | |||
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "ioctl(FIONBIO)"); | |||
redisNetClose(c); | |||
return REDIS_ERR; | |||
} | |||
#endif /* _WIN32 */ | |||
return REDIS_OK; | |||
} | |||
int redisKeepAlive(redisContext *c, int interval) { | |||
int val = 1; | |||
redisFD fd = c->fd; | |||
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){ | |||
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); | |||
return REDIS_ERR; | |||
} | |||
val = interval; | |||
#if defined(__APPLE__) && defined(__MACH__) | |||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) { | |||
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); | |||
return REDIS_ERR; | |||
} | |||
#else | |||
#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__) | |||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) { | |||
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); | |||
return REDIS_ERR; | |||
} | |||
val = interval/3; | |||
if (val == 0) val = 1; | |||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) { | |||
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); | |||
return REDIS_ERR; | |||
} | |||
val = 3; | |||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) { | |||
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); | |||
return REDIS_ERR; | |||
} | |||
#endif | |||
#endif | |||
return REDIS_OK; | |||
} | |||
static int redisSetTcpNoDelay(redisContext *c) { | |||
int yes = 1; | |||
if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { | |||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); | |||
redisNetClose(c); | |||
return REDIS_ERR; | |||
} | |||
return REDIS_OK; | |||
} | |||
#define __MAX_MSEC (((LONG_MAX) - 999) / 1000) | |||
static int redisContextTimeoutMsec(redisContext *c, long *result) | |||
{ | |||
const struct timeval *timeout = c->timeout; | |||
long msec = -1; | |||
/* Only use timeout when not NULL. */ | |||
if (timeout != NULL) { | |||
if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { | |||
*result = msec; | |||
return REDIS_ERR; | |||
} | |||
msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000); | |||
if (msec < 0 || msec > INT_MAX) { | |||
msec = INT_MAX; | |||
} | |||
} | |||
*result = msec; | |||
return REDIS_OK; | |||
} | |||
static int redisContextWaitReady(redisContext *c, long msec) { | |||
struct pollfd wfd[1]; | |||
wfd[0].fd = c->fd; | |||
wfd[0].events = POLLOUT; | |||
if (errno == EINPROGRESS) { | |||
int res; | |||
if ((res = poll(wfd, 1, msec)) == -1) { | |||
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); | |||
redisNetClose(c); | |||
return REDIS_ERR; | |||
} else if (res == 0) { | |||
errno = ETIMEDOUT; | |||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); | |||
redisNetClose(c); | |||
return REDIS_ERR; | |||
} | |||
if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) { | |||
redisCheckSocketError(c); | |||
return REDIS_ERR; | |||
} | |||
return REDIS_OK; | |||
} | |||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); | |||
redisNetClose(c); | |||
return REDIS_ERR; | |||
} | |||
int redisCheckConnectDone(redisContext *c, int *completed) { | |||
int rc = connect(c->fd, (const struct sockaddr *)c->saddr, c->addrlen); | |||
if (rc == 0) { | |||
*completed = 1; | |||
return REDIS_OK; | |||
} | |||
switch (errno) { | |||
case EISCONN: | |||
*completed = 1; | |||
return REDIS_OK; | |||
case EALREADY: | |||
case EINPROGRESS: | |||
case EWOULDBLOCK: | |||
*completed = 0; | |||
return REDIS_OK; | |||
default: | |||
return REDIS_ERR; | |||
} | |||
} | |||
int redisCheckSocketError(redisContext *c) { | |||
int err = 0, errno_saved = errno; | |||
socklen_t errlen = sizeof(err); | |||
if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { | |||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)"); | |||
return REDIS_ERR; | |||
} | |||
if (err == 0) { | |||
err = errno_saved; | |||
} | |||
if (err) { | |||
errno = err; | |||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); | |||
return REDIS_ERR; | |||
} | |||
return REDIS_OK; | |||
} | |||
int redisContextSetTimeout(redisContext *c, const struct timeval tv) { | |||
const void *to_ptr = &tv; | |||
size_t to_sz = sizeof(tv); | |||
#ifdef _WIN32 | |||
DWORD timeout_msec = tv.tv_sec * 1000 + tv.tv_usec / 1000; | |||
to_ptr = &timeout_msec; | |||
to_sz = sizeof(timeout_msec); | |||
#endif | |||
if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) { | |||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); | |||
return REDIS_ERR; | |||
} | |||
if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,to_ptr,to_sz) == -1) { | |||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)"); | |||
return REDIS_ERR; | |||
} | |||
return REDIS_OK; | |||
} | |||
static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, | |||
const struct timeval *timeout, | |||
const char *source_addr) { | |||
redisFD s; | |||
int rv, n; | |||
char _port[6]; /* strlen("65535"); */ | |||
struct addrinfo hints, *servinfo, *bservinfo, *p, *b; | |||
int blocking = (c->flags & REDIS_BLOCK); | |||
int reuseaddr = (c->flags & REDIS_REUSEADDR); | |||
int reuses = 0; | |||
long timeout_msec = -1; | |||
servinfo = NULL; | |||
c->connection_type = REDIS_CONN_TCP; | |||
c->tcp.port = port; | |||
/* We need to take possession of the passed parameters | |||
* to make them reusable for a reconnect. | |||
* We also carefully check we don't free data we already own, | |||
* as in the case of the reconnect method. | |||
* | |||
* This is a bit ugly, but atleast it works and doesn't leak memory. | |||
**/ | |||
if (c->tcp.host != addr) { | |||
free(c->tcp.host); | |||
c->tcp.host = hi_strdup(addr); | |||
} | |||
if (timeout) { | |||
if (c->timeout != timeout) { | |||
if (c->timeout == NULL) | |||
c->timeout = hi_malloc(sizeof(struct timeval)); | |||
memcpy(c->timeout, timeout, sizeof(struct timeval)); | |||
} | |||
} else { | |||
free(c->timeout); | |||
c->timeout = NULL; | |||
} | |||
if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) { | |||
__redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified"); | |||
goto error; | |||
} | |||
if (source_addr == NULL) { | |||
free(c->tcp.source_addr); | |||
c->tcp.source_addr = NULL; | |||
} else if (c->tcp.source_addr != source_addr) { | |||
free(c->tcp.source_addr); | |||
c->tcp.source_addr = hi_strdup(source_addr); | |||
} | |||
snprintf(_port, 6, "%d", port); | |||
memset(&hints,0,sizeof(hints)); | |||
hints.ai_family = AF_INET; | |||
hints.ai_socktype = SOCK_STREAM; | |||
/* Try with IPv6 if no IPv4 address was found. We do it in this order since | |||
* in a Redis client you can't afford to test if you have IPv6 connectivity | |||
* as this would add latency to every connect. Otherwise a more sensible | |||
* route could be: Use IPv6 if both addresses are available and there is IPv6 | |||
* connectivity. */ | |||
if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) { | |||
hints.ai_family = AF_INET6; | |||
if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) { | |||
__redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv)); | |||
return REDIS_ERR; | |||
} | |||
} | |||
for (p = servinfo; p != NULL; p = p->ai_next) { | |||
addrretry: | |||
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == REDIS_INVALID_FD) | |||
continue; | |||
c->fd = s; | |||
if (redisSetBlocking(c,0) != REDIS_OK) | |||
goto error; | |||
if (c->tcp.source_addr) { | |||
int bound = 0; | |||
/* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */ | |||
if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) { | |||
char buf[128]; | |||
snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv)); | |||
__redisSetError(c,REDIS_ERR_OTHER,buf); | |||
goto error; | |||
} | |||
if (reuseaddr) { | |||
n = 1; | |||
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n, | |||
sizeof(n)) < 0) { | |||
freeaddrinfo(bservinfo); | |||
goto error; | |||
} | |||
} | |||
for (b = bservinfo; b != NULL; b = b->ai_next) { | |||
if (bind(s,b->ai_addr,b->ai_addrlen) != -1) { | |||
bound = 1; | |||
break; | |||
} | |||
} | |||
freeaddrinfo(bservinfo); | |||
if (!bound) { | |||
char buf[128]; | |||
snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno)); | |||
__redisSetError(c,REDIS_ERR_OTHER,buf); | |||
goto error; | |||
} | |||
} | |||
/* For repeat connection */ | |||
free(c->saddr); | |||
c->saddr = hi_malloc(p->ai_addrlen); | |||
memcpy(c->saddr, p->ai_addr, p->ai_addrlen); | |||
c->addrlen = p->ai_addrlen; | |||
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { | |||
if (errno == EHOSTUNREACH) { | |||
redisNetClose(c); | |||
continue; | |||
} else if (errno == EINPROGRESS) { | |||
if (blocking) { | |||
goto wait_for_ready; | |||
} | |||
/* This is ok. | |||
* Note that even when it's in blocking mode, we unset blocking | |||
* for `connect()` | |||
*/ | |||
} else if (errno == EADDRNOTAVAIL && reuseaddr) { | |||
if (++reuses >= REDIS_CONNECT_RETRIES) { | |||
goto error; | |||
} else { | |||
redisNetClose(c); | |||
goto addrretry; | |||
} | |||
} else { | |||
wait_for_ready: | |||
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) | |||
goto error; | |||
} | |||
} | |||
if (blocking && redisSetBlocking(c,1) != REDIS_OK) | |||
goto error; | |||
if (redisSetTcpNoDelay(c) != REDIS_OK) | |||
goto error; | |||
c->flags |= REDIS_CONNECTED; | |||
rv = REDIS_OK; | |||
goto end; | |||
} | |||
if (p == NULL) { | |||
char buf[128]; | |||
snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno)); | |||
__redisSetError(c,REDIS_ERR_OTHER,buf); | |||
goto error; | |||
} | |||
error: | |||
rv = REDIS_ERR; | |||
end: | |||
if(servinfo) { | |||
freeaddrinfo(servinfo); | |||
} | |||
return rv; // Need to return REDIS_OK if alright | |||
} | |||
int redisContextConnectTcp(redisContext *c, const char *addr, int port, | |||
const struct timeval *timeout) { | |||
return _redisContextConnectTcp(c, addr, port, timeout, NULL); | |||
} | |||
int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, | |||
const struct timeval *timeout, | |||
const char *source_addr) { | |||
return _redisContextConnectTcp(c, addr, port, timeout, source_addr); | |||
} | |||
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { | |||
#ifndef _WIN32 | |||
int blocking = (c->flags & REDIS_BLOCK); | |||
struct sockaddr_un *sa; | |||
long timeout_msec = -1; | |||
if (redisCreateSocket(c,AF_UNIX) < 0) | |||
return REDIS_ERR; | |||
if (redisSetBlocking(c,0) != REDIS_OK) | |||
return REDIS_ERR; | |||
c->connection_type = REDIS_CONN_UNIX; | |||
if (c->unix_sock.path != path) | |||
c->unix_sock.path = hi_strdup(path); | |||
if (timeout) { | |||
if (c->timeout != timeout) { | |||
if (c->timeout == NULL) | |||
c->timeout = hi_malloc(sizeof(struct timeval)); | |||
memcpy(c->timeout, timeout, sizeof(struct timeval)); | |||
} | |||
} else { | |||
free(c->timeout); | |||
c->timeout = NULL; | |||
} | |||
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK) | |||
return REDIS_ERR; | |||
sa = (struct sockaddr_un*)(c->saddr = hi_malloc(sizeof(struct sockaddr_un))); | |||
c->addrlen = sizeof(struct sockaddr_un); | |||
sa->sun_family = AF_UNIX; | |||
strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1); | |||
if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) { | |||
if (errno == EINPROGRESS && !blocking) { | |||
/* This is ok. */ | |||
} else { | |||
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) | |||
return REDIS_ERR; | |||
} | |||
} | |||
/* Reset socket to be blocking after connect(2). */ | |||
if (blocking && redisSetBlocking(c,1) != REDIS_OK) | |||
return REDIS_ERR; | |||
c->flags |= REDIS_CONNECTED; | |||
return REDIS_OK; | |||
#else | |||
/* We currently do not support Unix sockets for Windows. */ | |||
/* TODO(m): https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ */ | |||
errno = EPROTONOSUPPORT; | |||
return REDIS_ERR; | |||
#endif /* _WIN32 */ | |||
} |
@@ -1,54 +0,0 @@ | |||
/* Extracted from anet.c to work properly with Hiredis error reporting. | |||
* | |||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> | |||
* Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>, | |||
* Jan-Erik Rediger <janerik at fnordig dot com> | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions are met: | |||
* | |||
* * Redistributions of source code must retain the above copyright notice, | |||
* this list of conditions and the following disclaimer. | |||
* * Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* * Neither the name of Redis nor the names of its contributors may be used | |||
* to endorse or promote products derived from this software without | |||
* specific prior written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
* POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
#ifndef __NET_H | |||
#define __NET_H | |||
#include "hiredis.h" | |||
void redisNetClose(redisContext *c); | |||
int redisNetRead(redisContext *c, char *buf, size_t bufcap); | |||
int redisNetWrite(redisContext *c); | |||
int redisCheckSocketError(redisContext *c); | |||
int redisContextSetTimeout(redisContext *c, const struct timeval tv); | |||
int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); | |||
int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, | |||
const struct timeval *timeout, | |||
const char *source_addr); | |||
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout); | |||
int redisKeepAlive(redisContext *c, int interval); | |||
int redisCheckConnectDone(redisContext *c, int *completed); | |||
#endif |
@@ -1,669 +0,0 @@ | |||
/* | |||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> | |||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions are met: | |||
* | |||
* * Redistributions of source code must retain the above copyright notice, | |||
* this list of conditions and the following disclaimer. | |||
* * Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* * Neither the name of Redis nor the names of its contributors may be used | |||
* to endorse or promote products derived from this software without | |||
* specific prior written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
* POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
#include "fmacros.h" | |||
#include <string.h> | |||
#include <stdlib.h> | |||
#ifndef _MSC_VER | |||
#include <unistd.h> | |||
#include <strings.h> | |||
#endif | |||
#include <assert.h> | |||
#include <errno.h> | |||
#include <ctype.h> | |||
#include <limits.h> | |||
#include <math.h> | |||
#include "read.h" | |||
#include "sds.h" | |||
#include "win32.h" | |||
static void __redisReaderSetError(redisReader *r, int type, const char *str) { | |||
size_t len; | |||
if (r->reply != NULL && r->fn && r->fn->freeObject) { | |||
r->fn->freeObject(r->reply); | |||
r->reply = NULL; | |||
} | |||
/* Clear input buffer on errors. */ | |||
sdsfree(r->buf); | |||
r->buf = NULL; | |||
r->pos = r->len = 0; | |||
/* Reset task stack. */ | |||
r->ridx = -1; | |||
/* Set error. */ | |||
r->err = type; | |||
len = strlen(str); | |||
len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1); | |||
memcpy(r->errstr,str,len); | |||
r->errstr[len] = '\0'; | |||
} | |||
static size_t chrtos(char *buf, size_t size, char byte) { | |||
size_t len = 0; | |||
switch(byte) { | |||
case '\\': | |||
case '"': | |||
len = snprintf(buf,size,"\"\\%c\"",byte); | |||
break; | |||
case '\n': len = snprintf(buf,size,"\"\\n\""); break; | |||
case '\r': len = snprintf(buf,size,"\"\\r\""); break; | |||
case '\t': len = snprintf(buf,size,"\"\\t\""); break; | |||
case '\a': len = snprintf(buf,size,"\"\\a\""); break; | |||
case '\b': len = snprintf(buf,size,"\"\\b\""); break; | |||
default: | |||
if (isprint(byte)) | |||
len = snprintf(buf,size,"\"%c\"",byte); | |||
else | |||
len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte); | |||
break; | |||
} | |||
return len; | |||
} | |||
static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) { | |||
char cbuf[8], sbuf[128]; | |||
chrtos(cbuf,sizeof(cbuf),byte); | |||
snprintf(sbuf,sizeof(sbuf), | |||
"Protocol error, got %s as reply type byte", cbuf); | |||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf); | |||
} | |||
static void __redisReaderSetErrorOOM(redisReader *r) { | |||
__redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory"); | |||
} | |||
static char *readBytes(redisReader *r, unsigned int bytes) { | |||
char *p; | |||
if (r->len-r->pos >= bytes) { | |||
p = r->buf+r->pos; | |||
r->pos += bytes; | |||
return p; | |||
} | |||
return NULL; | |||
} | |||
/* Find pointer to \r\n. */ | |||
static char *seekNewline(char *s, size_t len) { | |||
int pos = 0; | |||
int _len = len-1; | |||
/* Position should be < len-1 because the character at "pos" should be | |||
* followed by a \n. Note that strchr cannot be used because it doesn't | |||
* allow to search a limited length and the buffer that is being searched | |||
* might not have a trailing NULL character. */ | |||
while (pos < _len) { | |||
while(pos < _len && s[pos] != '\r') pos++; | |||
if (pos==_len) { | |||
/* Not found. */ | |||
return NULL; | |||
} else { | |||
if (s[pos+1] == '\n') { | |||
/* Found. */ | |||
return s+pos; | |||
} else { | |||
/* Continue searching. */ | |||
pos++; | |||
} | |||
} | |||
} | |||
return NULL; | |||
} | |||
/* Convert a string into a long long. Returns REDIS_OK if the string could be | |||
* parsed into a (non-overflowing) long long, REDIS_ERR otherwise. The value | |||
* will be set to the parsed value when appropriate. | |||
* | |||
* Note that this function demands that the string strictly represents | |||
* a long long: no spaces or other characters before or after the string | |||
* representing the number are accepted, nor zeroes at the start if not | |||
* for the string "0" representing the zero number. | |||
* | |||
* Because of its strictness, it is safe to use this function to check if | |||
* you can convert a string into a long long, and obtain back the string | |||
* from the number without any loss in the string representation. */ | |||
static int string2ll(const char *s, size_t slen, long long *value) { | |||
const char *p = s; | |||
size_t plen = 0; | |||
int negative = 0; | |||
unsigned long long v; | |||
if (plen == slen) | |||
return REDIS_ERR; | |||
/* Special case: first and only digit is 0. */ | |||
if (slen == 1 && p[0] == '0') { | |||
if (value != NULL) *value = 0; | |||
return REDIS_OK; | |||
} | |||
if (p[0] == '-') { | |||
negative = 1; | |||
p++; plen++; | |||
/* Abort on only a negative sign. */ | |||
if (plen == slen) | |||
return REDIS_ERR; | |||
} | |||
/* First digit should be 1-9, otherwise the string should just be 0. */ | |||
if (p[0] >= '1' && p[0] <= '9') { | |||
v = p[0]-'0'; | |||
p++; plen++; | |||
} else if (p[0] == '0' && slen == 1) { | |||
*value = 0; | |||
return REDIS_OK; | |||
} else { | |||
return REDIS_ERR; | |||
} | |||
while (plen < slen && p[0] >= '0' && p[0] <= '9') { | |||
if (v > (ULLONG_MAX / 10)) /* Overflow. */ | |||
return REDIS_ERR; | |||
v *= 10; | |||
if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */ | |||
return REDIS_ERR; | |||
v += p[0]-'0'; | |||
p++; plen++; | |||
} | |||
/* Return if not all bytes were used. */ | |||
if (plen < slen) | |||
return REDIS_ERR; | |||
if (negative) { | |||
if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */ | |||
return REDIS_ERR; | |||
if (value != NULL) *value = -v; | |||
} else { | |||
if (v > LLONG_MAX) /* Overflow. */ | |||
return REDIS_ERR; | |||
if (value != NULL) *value = v; | |||
} | |||
return REDIS_OK; | |||
} | |||
static char *readLine(redisReader *r, int *_len) { | |||
char *p, *s; | |||
int len; | |||
p = r->buf+r->pos; | |||
s = seekNewline(p,(r->len-r->pos)); | |||
if (s != NULL) { | |||
len = s-(r->buf+r->pos); | |||
r->pos += len+2; /* skip \r\n */ | |||
if (_len) *_len = len; | |||
return p; | |||
} | |||
return NULL; | |||
} | |||
static void moveToNextTask(redisReader *r) { | |||
redisReadTask *cur, *prv; | |||
while (r->ridx >= 0) { | |||
/* Return a.s.a.p. when the stack is now empty. */ | |||
if (r->ridx == 0) { | |||
r->ridx--; | |||
return; | |||
} | |||
cur = &(r->rstack[r->ridx]); | |||
prv = &(r->rstack[r->ridx-1]); | |||
assert(prv->type == REDIS_REPLY_ARRAY || | |||
prv->type == REDIS_REPLY_MAP || | |||
prv->type == REDIS_REPLY_SET); | |||
if (cur->idx == prv->elements-1) { | |||
r->ridx--; | |||
} else { | |||
/* Reset the type because the next item can be anything */ | |||
assert(cur->idx < prv->elements); | |||
cur->type = -1; | |||
cur->elements = -1; | |||
cur->idx++; | |||
return; | |||
} | |||
} | |||
} | |||
static int processLineItem(redisReader *r) { | |||
redisReadTask *cur = &(r->rstack[r->ridx]); | |||
void *obj; | |||
char *p; | |||
int len; | |||
if ((p = readLine(r,&len)) != NULL) { | |||
if (cur->type == REDIS_REPLY_INTEGER) { | |||
if (r->fn && r->fn->createInteger) { | |||
long long v; | |||
if (string2ll(p, len, &v) == REDIS_ERR) { | |||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL, | |||
"Bad integer value"); | |||
return REDIS_ERR; | |||
} | |||
obj = r->fn->createInteger(cur,v); | |||
} else { | |||
obj = (void*)REDIS_REPLY_INTEGER; | |||
} | |||
} else if (cur->type == REDIS_REPLY_DOUBLE) { | |||
if (r->fn && r->fn->createDouble) { | |||
char buf[326], *eptr; | |||
double d; | |||
if ((size_t)len >= sizeof(buf)) { | |||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL, | |||
"Double value is too large"); | |||
return REDIS_ERR; | |||
} | |||
memcpy(buf,p,len); | |||
buf[len] = '\0'; | |||
if (strcasecmp(buf,",inf") == 0) { | |||
d = INFINITY; /* Positive infinite. */ | |||
} else if (strcasecmp(buf,",-inf") == 0) { | |||
d = -INFINITY; /* Negative infinite. */ | |||
} else { | |||
d = strtod((char*)buf,&eptr); | |||
if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) { | |||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL, | |||
"Bad double value"); | |||
return REDIS_ERR; | |||
} | |||
} | |||
obj = r->fn->createDouble(cur,d,buf,len); | |||
} else { | |||
obj = (void*)REDIS_REPLY_DOUBLE; | |||
} | |||
} else if (cur->type == REDIS_REPLY_NIL) { | |||
if (r->fn && r->fn->createNil) | |||
obj = r->fn->createNil(cur); | |||
else | |||
obj = (void*)REDIS_REPLY_NIL; | |||
} else if (cur->type == REDIS_REPLY_BOOL) { | |||
int bval = p[0] == 't' || p[0] == 'T'; | |||
if (r->fn && r->fn->createBool) | |||
obj = r->fn->createBool(cur,bval); | |||
else | |||
obj = (void*)REDIS_REPLY_BOOL; | |||
} else { | |||
/* Type will be error or status. */ | |||
if (r->fn && r->fn->createString) | |||
obj = r->fn->createString(cur,p,len); | |||
else | |||
obj = (void*)(size_t)(cur->type); | |||
} | |||
if (obj == NULL) { | |||
__redisReaderSetErrorOOM(r); | |||
return REDIS_ERR; | |||
} | |||
/* Set reply if this is the root object. */ | |||
if (r->ridx == 0) r->reply = obj; | |||
moveToNextTask(r); | |||
return REDIS_OK; | |||
} | |||
return REDIS_ERR; | |||
} | |||
static int processBulkItem(redisReader *r) { | |||
redisReadTask *cur = &(r->rstack[r->ridx]); | |||
void *obj = NULL; | |||
char *p, *s; | |||
long long len; | |||
unsigned long bytelen; | |||
int success = 0; | |||
p = r->buf+r->pos; | |||
s = seekNewline(p,r->len-r->pos); | |||
if (s != NULL) { | |||
p = r->buf+r->pos; | |||
bytelen = s-(r->buf+r->pos)+2; /* include \r\n */ | |||
if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) { | |||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL, | |||
"Bad bulk string length"); | |||
return REDIS_ERR; | |||
} | |||
if (len < -1 || (LLONG_MAX > SIZE_MAX && len > (long long)SIZE_MAX)) { | |||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL, | |||
"Bulk string length out of range"); | |||
return REDIS_ERR; | |||
} | |||
if (len == -1) { | |||
/* The nil object can always be created. */ | |||
if (r->fn && r->fn->createNil) | |||
obj = r->fn->createNil(cur); | |||
else | |||
obj = (void*)REDIS_REPLY_NIL; | |||
success = 1; | |||
} else { | |||
/* Only continue when the buffer contains the entire bulk item. */ | |||
bytelen += len+2; /* include \r\n */ | |||
if (r->pos+bytelen <= r->len) { | |||
if (r->fn && r->fn->createString) | |||
obj = r->fn->createString(cur,s+2,len); | |||
else | |||
obj = (void*)REDIS_REPLY_STRING; | |||
success = 1; | |||
} | |||
} | |||
/* Proceed when obj was created. */ | |||
if (success) { | |||
if (obj == NULL) { | |||
__redisReaderSetErrorOOM(r); | |||
return REDIS_ERR; | |||
} | |||
r->pos += bytelen; | |||
/* Set reply if this is the root object. */ | |||
if (r->ridx == 0) r->reply = obj; | |||
moveToNextTask(r); | |||
return REDIS_OK; | |||
} | |||
} | |||
return REDIS_ERR; | |||
} | |||
/* Process the array, map and set types. */ | |||
static int processAggregateItem(redisReader *r) { | |||
redisReadTask *cur = &(r->rstack[r->ridx]); | |||
void *obj; | |||
char *p; | |||
long long elements; | |||
int root = 0, len; | |||
/* Set error for nested multi bulks with depth > 7 */ | |||
if (r->ridx == 8) { | |||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL, | |||
"No support for nested multi bulk replies with depth > 7"); | |||
return REDIS_ERR; | |||
} | |||
if ((p = readLine(r,&len)) != NULL) { | |||
if (string2ll(p, len, &elements) == REDIS_ERR) { | |||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL, | |||
"Bad multi-bulk length"); | |||
return REDIS_ERR; | |||
} | |||
root = (r->ridx == 0); | |||
if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX)) { | |||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL, | |||
"Multi-bulk length out of range"); | |||
return REDIS_ERR; | |||
} | |||
if (elements == -1) { | |||
if (r->fn && r->fn->createNil) | |||
obj = r->fn->createNil(cur); | |||
else | |||
obj = (void*)REDIS_REPLY_NIL; | |||
if (obj == NULL) { | |||
__redisReaderSetErrorOOM(r); | |||
return REDIS_ERR; | |||
} | |||
moveToNextTask(r); | |||
} else { | |||
if (cur->type == REDIS_REPLY_MAP) elements *= 2; | |||
if (r->fn && r->fn->createArray) | |||
obj = r->fn->createArray(cur,elements); | |||
else | |||
obj = (void*)(long)cur->type; | |||
if (obj == NULL) { | |||
__redisReaderSetErrorOOM(r); | |||
return REDIS_ERR; | |||
} | |||
/* Modify task stack when there are more than 0 elements. */ | |||
if (elements > 0) { | |||
cur->elements = elements; | |||
cur->obj = obj; | |||
r->ridx++; | |||
r->rstack[r->ridx].type = -1; | |||
r->rstack[r->ridx].elements = -1; | |||
r->rstack[r->ridx].idx = 0; | |||
r->rstack[r->ridx].obj = NULL; | |||
r->rstack[r->ridx].parent = cur; | |||
r->rstack[r->ridx].privdata = r->privdata; | |||
} else { | |||
moveToNextTask(r); | |||
} | |||
} | |||
/* Set reply if this is the root object. */ | |||
if (root) r->reply = obj; | |||
return REDIS_OK; | |||
} | |||
return REDIS_ERR; | |||
} | |||
static int processItem(redisReader *r) { | |||
redisReadTask *cur = &(r->rstack[r->ridx]); | |||
char *p; | |||
/* check if we need to read type */ | |||
if (cur->type < 0) { | |||
if ((p = readBytes(r,1)) != NULL) { | |||
switch (p[0]) { | |||
case '-': | |||
cur->type = REDIS_REPLY_ERROR; | |||
break; | |||
case '+': | |||
cur->type = REDIS_REPLY_STATUS; | |||
break; | |||
case ':': | |||
cur->type = REDIS_REPLY_INTEGER; | |||
break; | |||
case ',': | |||
cur->type = REDIS_REPLY_DOUBLE; | |||
break; | |||
case '_': | |||
cur->type = REDIS_REPLY_NIL; | |||
break; | |||
case '$': | |||
cur->type = REDIS_REPLY_STRING; | |||
break; | |||
case '*': | |||
cur->type = REDIS_REPLY_ARRAY; | |||
break; | |||
case '%': | |||
cur->type = REDIS_REPLY_MAP; | |||
break; | |||
case '~': | |||
cur->type = REDIS_REPLY_SET; | |||
break; | |||
case '#': | |||
cur->type = REDIS_REPLY_BOOL; | |||
break; | |||
default: | |||
__redisReaderSetErrorProtocolByte(r,*p); | |||
return REDIS_ERR; | |||
} | |||
} else { | |||
/* could not consume 1 byte */ | |||
return REDIS_ERR; | |||
} | |||
} | |||
/* process typed item */ | |||
switch(cur->type) { | |||
case REDIS_REPLY_ERROR: | |||
case REDIS_REPLY_STATUS: | |||
case REDIS_REPLY_INTEGER: | |||
case REDIS_REPLY_DOUBLE: | |||
case REDIS_REPLY_NIL: | |||
case REDIS_REPLY_BOOL: | |||
return processLineItem(r); | |||
case REDIS_REPLY_STRING: | |||
return processBulkItem(r); | |||
case REDIS_REPLY_ARRAY: | |||
case REDIS_REPLY_MAP: | |||
case REDIS_REPLY_SET: | |||
return processAggregateItem(r); | |||
default: | |||
assert(NULL); | |||
return REDIS_ERR; /* Avoid warning. */ | |||
} | |||
} | |||
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { | |||
redisReader *r; | |||
r = calloc(1,sizeof(redisReader)); | |||
if (r == NULL) | |||
return NULL; | |||
r->fn = fn; | |||
r->buf = sdsempty(); | |||
r->maxbuf = REDIS_READER_MAX_BUF; | |||
if (r->buf == NULL) { | |||
free(r); | |||
return NULL; | |||
} | |||
r->ridx = -1; | |||
return r; | |||
} | |||
void redisReaderFree(redisReader *r) { | |||
if (r == NULL) | |||
return; | |||
if (r->reply != NULL && r->fn && r->fn->freeObject) | |||
r->fn->freeObject(r->reply); | |||
sdsfree(r->buf); | |||
free(r); | |||
} | |||
int redisReaderFeed(redisReader *r, const char *buf, size_t len) { | |||
sds newbuf; | |||
/* Return early when this reader is in an erroneous state. */ | |||
if (r->err) | |||
return REDIS_ERR; | |||
/* Copy the provided buffer. */ | |||
if (buf != NULL && len >= 1) { | |||
/* Destroy internal buffer when it is empty and is quite large. */ | |||
if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) { | |||
sdsfree(r->buf); | |||
r->buf = sdsempty(); | |||
r->pos = 0; | |||
/* r->buf should not be NULL since we just free'd a larger one. */ | |||
assert(r->buf != NULL); | |||
} | |||
newbuf = sdscatlen(r->buf,buf,len); | |||
if (newbuf == NULL) { | |||
__redisReaderSetErrorOOM(r); | |||
return REDIS_ERR; | |||
} | |||
r->buf = newbuf; | |||
r->len = sdslen(r->buf); | |||
} | |||
return REDIS_OK; | |||
} | |||
int redisReaderGetReply(redisReader *r, void **reply) { | |||
/* Default target pointer to NULL. */ | |||
if (reply != NULL) | |||
*reply = NULL; | |||
/* Return early when this reader is in an erroneous state. */ | |||
if (r->err) | |||
return REDIS_ERR; | |||
/* When the buffer is empty, there will never be a reply. */ | |||
if (r->len == 0) | |||
return REDIS_OK; | |||
/* Set first item to process when the stack is empty. */ | |||
if (r->ridx == -1) { | |||
r->rstack[0].type = -1; | |||
r->rstack[0].elements = -1; | |||
r->rstack[0].idx = -1; | |||
r->rstack[0].obj = NULL; | |||
r->rstack[0].parent = NULL; | |||
r->rstack[0].privdata = r->privdata; | |||
r->ridx = 0; | |||
} | |||
/* Process items in reply. */ | |||
while (r->ridx >= 0) | |||
if (processItem(r) != REDIS_OK) | |||
break; | |||
/* Return ASAP when an error occurred. */ | |||
if (r->err) | |||
return REDIS_ERR; | |||
/* Discard part of the buffer when we've consumed at least 1k, to avoid | |||
* doing unnecessary calls to memmove() in sds.c. */ | |||
if (r->pos >= 1024) { | |||
sdsrange(r->buf,r->pos,-1); | |||
r->pos = 0; | |||
r->len = sdslen(r->buf); | |||
} | |||
/* Emit a reply when there is one. */ | |||
if (r->ridx == -1) { | |||
if (reply != NULL) { | |||
*reply = r->reply; | |||
} else if (r->reply != NULL && r->fn && r->fn->freeObject) { | |||
r->fn->freeObject(r->reply); | |||
} | |||
r->reply = NULL; | |||
} | |||
return REDIS_OK; | |||
} |
@@ -1,122 +0,0 @@ | |||
/* | |||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> | |||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions are met: | |||
* | |||
* * Redistributions of source code must retain the above copyright notice, | |||
* this list of conditions and the following disclaimer. | |||
* * Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* * Neither the name of Redis nor the names of its contributors may be used | |||
* to endorse or promote products derived from this software without | |||
* specific prior written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
* POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
#ifndef __HIREDIS_READ_H | |||
#define __HIREDIS_READ_H | |||
#include <stdio.h> /* for size_t */ | |||
#define REDIS_ERR -1 | |||
#define REDIS_OK 0 | |||
/* When an error occurs, the err flag in a context is set to hold the type of | |||
* error that occurred. REDIS_ERR_IO means there was an I/O error and you | |||
* should use the "errno" variable to find out what is wrong. | |||
* For other values, the "errstr" field will hold a description. */ | |||
#define REDIS_ERR_IO 1 /* Error in read or write */ | |||
#define REDIS_ERR_EOF 3 /* End of file */ | |||
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */ | |||
#define REDIS_ERR_OOM 5 /* Out of memory */ | |||
#define REDIS_ERR_TIMEOUT 6 /* Timed out */ | |||
#define REDIS_ERR_OTHER 2 /* Everything else... */ | |||
#define REDIS_REPLY_STRING 1 | |||
#define REDIS_REPLY_ARRAY 2 | |||
#define REDIS_REPLY_INTEGER 3 | |||
#define REDIS_REPLY_NIL 4 | |||
#define REDIS_REPLY_STATUS 5 | |||
#define REDIS_REPLY_ERROR 6 | |||
#define REDIS_REPLY_DOUBLE 7 | |||
#define REDIS_REPLY_BOOL 8 | |||
#define REDIS_REPLY_VERB 9 | |||
#define REDIS_REPLY_MAP 9 | |||
#define REDIS_REPLY_SET 10 | |||
#define REDIS_REPLY_ATTR 11 | |||
#define REDIS_REPLY_PUSH 12 | |||
#define REDIS_REPLY_BIGNUM 13 | |||
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */ | |||
#ifdef __cplusplus | |||
extern "C" { | |||
#endif | |||
typedef struct redisReadTask { | |||
int type; | |||
int elements; /* number of elements in multibulk container */ | |||
int idx; /* index in parent (array) object */ | |||
void *obj; /* holds user-generated value for a read task */ | |||
struct redisReadTask *parent; /* parent task */ | |||
void *privdata; /* user-settable arbitrary field */ | |||
} redisReadTask; | |||
typedef struct redisReplyObjectFunctions { | |||
void *(*createString)(const redisReadTask*, char*, size_t); | |||
void *(*createArray)(const redisReadTask*, size_t); | |||
void *(*createInteger)(const redisReadTask*, long long); | |||
void *(*createDouble)(const redisReadTask*, double, char*, size_t); | |||
void *(*createNil)(const redisReadTask*); | |||
void *(*createBool)(const redisReadTask*, int); | |||
void (*freeObject)(void*); | |||
} redisReplyObjectFunctions; | |||
typedef struct redisReader { | |||
int err; /* Error flags, 0 when there is no error */ | |||
char errstr[128]; /* String representation of error when applicable */ | |||
char *buf; /* Read buffer */ | |||
size_t pos; /* Buffer cursor */ | |||
size_t len; /* Buffer length */ | |||
size_t maxbuf; /* Max length of unused buffer */ | |||
redisReadTask rstack[9]; | |||
int ridx; /* Index of current read task */ | |||
void *reply; /* Temporary reply pointer */ | |||
redisReplyObjectFunctions *fn; | |||
void *privdata; | |||
} redisReader; | |||
/* Public API for the protocol parser. */ | |||
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn); | |||
void redisReaderFree(redisReader *r); | |||
int redisReaderFeed(redisReader *r, const char *buf, size_t len); | |||
int redisReaderGetReply(redisReader *r, void **reply); | |||
#define redisReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p)) | |||
#define redisReaderGetObject(_r) (((redisReader*)(_r))->reply) | |||
#define redisReaderGetError(_r) (((redisReader*)(_r))->errstr) | |||
#ifdef __cplusplus | |||
} | |||
#endif | |||
#endif |
@@ -1,276 +0,0 @@ | |||
/* SDSLib 2.0 -- A C dynamic strings library | |||
* | |||
* Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com> | |||
* Copyright (c) 2015, Oran Agra | |||
* Copyright (c) 2015, Redis Labs, Inc | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions are met: | |||
* | |||
* * Redistributions of source code must retain the above copyright notice, | |||
* this list of conditions and the following disclaimer. | |||
* * Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* * Neither the name of Redis nor the names of its contributors may be used | |||
* to endorse or promote products derived from this software without | |||
* specific prior written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
* POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
#ifndef __SDS_H | |||
#define __SDS_H | |||
#define SDS_MAX_PREALLOC (1024*1024) | |||
#ifdef _MSC_VER | |||
#define __attribute__(x) | |||
#endif | |||
#include <sys/types.h> | |||
#include <stdarg.h> | |||
#include <stdint.h> | |||
typedef char *sds; | |||
/* Note: sdshdr5 is never used, we just access the flags byte directly. | |||
* However is here to document the layout of type 5 SDS strings. */ | |||
struct __attribute__ ((__packed__)) sdshdr5 { | |||
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ | |||
char buf[]; | |||
}; | |||
struct __attribute__ ((__packed__)) sdshdr8 { | |||
uint8_t len; /* used */ | |||
uint8_t alloc; /* excluding the header and null terminator */ | |||
unsigned char flags; /* 3 lsb of type, 5 unused bits */ | |||
char buf[]; | |||
}; | |||
struct __attribute__ ((__packed__)) sdshdr16 { | |||
uint16_t len; /* used */ | |||
uint16_t alloc; /* excluding the header and null terminator */ | |||
unsigned char flags; /* 3 lsb of type, 5 unused bits */ | |||
char buf[]; | |||
}; | |||
struct __attribute__ ((__packed__)) sdshdr32 { | |||
uint32_t len; /* used */ | |||
uint32_t alloc; /* excluding the header and null terminator */ | |||
unsigned char flags; /* 3 lsb of type, 5 unused bits */ | |||
char buf[]; | |||
}; | |||
struct __attribute__ ((__packed__)) sdshdr64 { | |||
uint64_t len; /* used */ | |||
uint64_t alloc; /* excluding the header and null terminator */ | |||
unsigned char flags; /* 3 lsb of type, 5 unused bits */ | |||
char buf[]; | |||
}; | |||
#define SDS_TYPE_5 0 | |||
#define SDS_TYPE_8 1 | |||
#define SDS_TYPE_16 2 | |||
#define SDS_TYPE_32 3 | |||
#define SDS_TYPE_64 4 | |||
#define SDS_TYPE_MASK 7 | |||
#define SDS_TYPE_BITS 3 | |||
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))); | |||
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) | |||
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) | |||
static inline size_t sdslen(const sds s) { | |||
unsigned char flags = s[-1]; | |||
switch(flags&SDS_TYPE_MASK) { | |||
case SDS_TYPE_5: | |||
return SDS_TYPE_5_LEN(flags); | |||
case SDS_TYPE_8: | |||
return SDS_HDR(8,s)->len; | |||
case SDS_TYPE_16: | |||
return SDS_HDR(16,s)->len; | |||
case SDS_TYPE_32: | |||
return SDS_HDR(32,s)->len; | |||
case SDS_TYPE_64: | |||
return SDS_HDR(64,s)->len; | |||
} | |||
return 0; | |||
} | |||
static inline size_t sdsavail(const sds s) { | |||
unsigned char flags = s[-1]; | |||
switch(flags&SDS_TYPE_MASK) { | |||
case SDS_TYPE_5: { | |||
return 0; | |||
} | |||
case SDS_TYPE_8: { | |||
SDS_HDR_VAR(8,s); | |||
return sh->alloc - sh->len; | |||
} | |||
case SDS_TYPE_16: { | |||
SDS_HDR_VAR(16,s); | |||
return sh->alloc - sh->len; | |||
} | |||
case SDS_TYPE_32: { | |||
SDS_HDR_VAR(32,s); | |||
return sh->alloc - sh->len; | |||
} | |||
case SDS_TYPE_64: { | |||
SDS_HDR_VAR(64,s); | |||
return sh->alloc - sh->len; | |||
} | |||
} | |||
return 0; | |||
} | |||
static inline void sdssetlen(sds s, size_t newlen) { | |||
unsigned char flags = s[-1]; | |||
switch(flags&SDS_TYPE_MASK) { | |||
case SDS_TYPE_5: | |||
{ | |||
unsigned char *fp = ((unsigned char*)s)-1; | |||
*fp = (unsigned char)(SDS_TYPE_5 | (newlen << SDS_TYPE_BITS)); | |||
} | |||
break; | |||
case SDS_TYPE_8: | |||
SDS_HDR(8,s)->len = (uint8_t)newlen; | |||
break; | |||
case SDS_TYPE_16: | |||
SDS_HDR(16,s)->len = (uint16_t)newlen; | |||
break; | |||
case SDS_TYPE_32: | |||
SDS_HDR(32,s)->len = (uint32_t)newlen; | |||
break; | |||
case SDS_TYPE_64: | |||
SDS_HDR(64,s)->len = (uint64_t)newlen; | |||
break; | |||
} | |||
} | |||
static inline void sdsinclen(sds s, size_t inc) { | |||
unsigned char flags = s[-1]; | |||
switch(flags&SDS_TYPE_MASK) { | |||
case SDS_TYPE_5: | |||
{ | |||
unsigned char *fp = ((unsigned char*)s)-1; | |||
unsigned char newlen = SDS_TYPE_5_LEN(flags)+(unsigned char)inc; | |||
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); | |||
} | |||
break; | |||
case SDS_TYPE_8: | |||
SDS_HDR(8,s)->len += (uint8_t)inc; | |||
break; | |||
case SDS_TYPE_16: | |||
SDS_HDR(16,s)->len += (uint16_t)inc; | |||
break; | |||
case SDS_TYPE_32: | |||
SDS_HDR(32,s)->len += (uint32_t)inc; | |||
break; | |||
case SDS_TYPE_64: | |||
SDS_HDR(64,s)->len += (uint64_t)inc; | |||
break; | |||
} | |||
} | |||
/* sdsalloc() = sdsavail() + sdslen() */ | |||
static inline size_t sdsalloc(const sds s) { | |||
unsigned char flags = s[-1]; | |||
switch(flags&SDS_TYPE_MASK) { | |||
case SDS_TYPE_5: | |||
return SDS_TYPE_5_LEN(flags); | |||
case SDS_TYPE_8: | |||
return SDS_HDR(8,s)->alloc; | |||
case SDS_TYPE_16: | |||
return SDS_HDR(16,s)->alloc; | |||
case SDS_TYPE_32: | |||
return SDS_HDR(32,s)->alloc; | |||
case SDS_TYPE_64: | |||
return SDS_HDR(64,s)->alloc; | |||
} | |||
return 0; | |||
} | |||
static inline void sdssetalloc(sds s, size_t newlen) { | |||
unsigned char flags = s[-1]; | |||
switch(flags&SDS_TYPE_MASK) { | |||
case SDS_TYPE_5: | |||
/* Nothing to do, this type has no total allocation info. */ | |||
break; | |||
case SDS_TYPE_8: | |||
SDS_HDR(8,s)->alloc = (uint8_t)newlen; | |||
break; | |||
case SDS_TYPE_16: | |||
SDS_HDR(16,s)->alloc = (uint16_t)newlen; | |||
break; | |||
case SDS_TYPE_32: | |||
SDS_HDR(32,s)->alloc = (uint32_t)newlen; | |||
break; | |||
case SDS_TYPE_64: | |||
SDS_HDR(64,s)->alloc = (uint64_t)newlen; | |||
break; | |||
} | |||
} | |||
sds sdsnewlen(const void *init, size_t initlen); | |||
sds sdsnew(const char *init); | |||
sds sdsempty(void); | |||
sds sdsdup(const sds s); | |||
void sdsfree(sds s); | |||
sds sdsgrowzero(sds s, size_t len); | |||
sds sdscatlen(sds s, const void *t, size_t len); | |||
sds sdscat(sds s, const char *t); | |||
sds sdscatsds(sds s, const sds t); | |||
sds sdscpylen(sds s, const char *t, size_t len); | |||
sds sdscpy(sds s, const char *t); | |||
sds sdscatvprintf(sds s, const char *fmt, va_list ap); | |||
#ifdef __GNUC__ | |||
sds sdscatprintf(sds s, const char *fmt, ...) | |||
__attribute__((format(printf, 2, 3))); | |||
#else | |||
sds sdscatprintf(sds s, const char *fmt, ...); | |||
#endif | |||
sds sdscatfmt(sds s, char const *fmt, ...); | |||
sds sdstrim(sds s, const char *cset); | |||
void sdsrange(sds s, int start, int end); | |||
void sdsupdatelen(sds s); | |||
void sdsclear(sds s); | |||
int sdscmp(const sds s1, const sds s2); | |||
sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); | |||
void sdsfreesplitres(sds *tokens, int count); | |||
void sdstolower(sds s); | |||
void sdstoupper(sds s); | |||
sds sdsfromlonglong(long long value); | |||
sds sdscatrepr(sds s, const char *p, size_t len); | |||
sds *sdssplitargs(const char *line, int *argc); | |||
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); | |||
sds sdsjoin(char **argv, int argc, char *sep); | |||
sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); | |||
/* Low level functions exposed to the user API */ | |||
sds sdsMakeRoomFor(sds s, size_t addlen); | |||
void sdsIncrLen(sds s, int incr); | |||
sds sdsRemoveFreeSpace(sds s); | |||
size_t sdsAllocSize(sds s); | |||
void *sdsAllocPtr(sds s); | |||
/* Export the allocator used by SDS to the program using SDS. | |||
* Sometimes the program SDS is linked to, may use a different set of | |||
* allocators, but may want to allocate or free things that SDS will | |||
* respectively free or allocate. */ | |||
void *sds_malloc(size_t size); | |||
void *sds_realloc(void *ptr, size_t size); | |||
void sds_free(void *ptr); | |||
#ifdef REDIS_TEST | |||
int sdsTest(int argc, char *argv[]); | |||
#endif | |||
#endif |
@@ -1,42 +0,0 @@ | |||
/* SDSLib 2.0 -- A C dynamic strings library | |||
* | |||
* Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com> | |||
* Copyright (c) 2015, Oran Agra | |||
* Copyright (c) 2015, Redis Labs, Inc | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions are met: | |||
* | |||
* * Redistributions of source code must retain the above copyright notice, | |||
* this list of conditions and the following disclaimer. | |||
* * Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* * Neither the name of Redis nor the names of its contributors may be used | |||
* to endorse or promote products derived from this software without | |||
* specific prior written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
* POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
/* SDS allocator selection. | |||
* | |||
* This file is used in order to change the SDS allocator at compile time. | |||
* Just define the following defines to what you want to use. Also add | |||
* the include of your alternate allocator if needed (not needed in order | |||
* to use the default libc allocator). */ | |||
#define s_malloc malloc | |||
#define s_realloc realloc | |||
#define s_free free |
@@ -1,248 +0,0 @@ | |||
/* | |||
* Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu> | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions are met: | |||
* | |||
* * Redistributions of source code must retain the above copyright notice, | |||
* this list of conditions and the following disclaimer. | |||
* * Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* * Neither the name of Redis nor the names of its contributors may be used | |||
* to endorse or promote products derived from this software without | |||
* specific prior written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
* POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
#define REDIS_SOCKCOMPAT_IMPLEMENTATION | |||
#include "sockcompat.h" | |||
#ifdef _WIN32 | |||
static int _wsaErrorToErrno(int err) { | |||
switch (err) { | |||
case WSAEWOULDBLOCK: | |||
return EWOULDBLOCK; | |||
case WSAEINPROGRESS: | |||
return EINPROGRESS; | |||
case WSAEALREADY: | |||
return EALREADY; | |||
case WSAENOTSOCK: | |||
return ENOTSOCK; | |||
case WSAEDESTADDRREQ: | |||
return EDESTADDRREQ; | |||
case WSAEMSGSIZE: | |||
return EMSGSIZE; | |||
case WSAEPROTOTYPE: | |||
return EPROTOTYPE; | |||
case WSAENOPROTOOPT: | |||
return ENOPROTOOPT; | |||
case WSAEPROTONOSUPPORT: | |||
return EPROTONOSUPPORT; | |||
case WSAEOPNOTSUPP: | |||
return EOPNOTSUPP; | |||
case WSAEAFNOSUPPORT: | |||
return EAFNOSUPPORT; | |||
case WSAEADDRINUSE: | |||
return EADDRINUSE; | |||
case WSAEADDRNOTAVAIL: | |||
return EADDRNOTAVAIL; | |||
case WSAENETDOWN: | |||
return ENETDOWN; | |||
case WSAENETUNREACH: | |||
return ENETUNREACH; | |||
case WSAENETRESET: | |||
return ENETRESET; | |||
case WSAECONNABORTED: | |||
return ECONNABORTED; | |||
case WSAECONNRESET: | |||
return ECONNRESET; | |||
case WSAENOBUFS: | |||
return ENOBUFS; | |||
case WSAEISCONN: | |||
return EISCONN; | |||
case WSAENOTCONN: | |||
return ENOTCONN; | |||
case WSAETIMEDOUT: | |||
return ETIMEDOUT; | |||
case WSAECONNREFUSED: | |||
return ECONNREFUSED; | |||
case WSAELOOP: | |||
return ELOOP; | |||
case WSAENAMETOOLONG: | |||
return ENAMETOOLONG; | |||
case WSAEHOSTUNREACH: | |||
return EHOSTUNREACH; | |||
case WSAENOTEMPTY: | |||
return ENOTEMPTY; | |||
default: | |||
/* We just return a generic I/O error if we could not find a relevant error. */ | |||
return EIO; | |||
} | |||
} | |||
static void _updateErrno(int success) { | |||
errno = success ? 0 : _wsaErrorToErrno(WSAGetLastError()); | |||
} | |||
static int _initWinsock() { | |||
static int s_initialized = 0; | |||
if (!s_initialized) { | |||
static WSADATA wsadata; | |||
int err = WSAStartup(MAKEWORD(2,2), &wsadata); | |||
if (err != 0) { | |||
errno = _wsaErrorToErrno(err); | |||
return 0; | |||
} | |||
s_initialized = 1; | |||
} | |||
return 1; | |||
} | |||
int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) { | |||
/* Note: This function is likely to be called before other functions, so run init here. */ | |||
if (!_initWinsock()) { | |||
return EAI_FAIL; | |||
} | |||
switch (getaddrinfo(node, service, hints, res)) { | |||
case 0: return 0; | |||
case WSATRY_AGAIN: return EAI_AGAIN; | |||
case WSAEINVAL: return EAI_BADFLAGS; | |||
case WSAEAFNOSUPPORT: return EAI_FAMILY; | |||
case WSA_NOT_ENOUGH_MEMORY: return EAI_MEMORY; | |||
case WSAHOST_NOT_FOUND: return EAI_NONAME; | |||
case WSATYPE_NOT_FOUND: return EAI_SERVICE; | |||
case WSAESOCKTNOSUPPORT: return EAI_SOCKTYPE; | |||
default: return EAI_FAIL; /* Including WSANO_RECOVERY */ | |||
} | |||
} | |||
const char *win32_gai_strerror(int errcode) { | |||
switch (errcode) { | |||
case 0: errcode = 0; break; | |||
case EAI_AGAIN: errcode = WSATRY_AGAIN; break; | |||
case EAI_BADFLAGS: errcode = WSAEINVAL; break; | |||
case EAI_FAMILY: errcode = WSAEAFNOSUPPORT; break; | |||
case EAI_MEMORY: errcode = WSA_NOT_ENOUGH_MEMORY; break; | |||
case EAI_NONAME: errcode = WSAHOST_NOT_FOUND; break; | |||
case EAI_SERVICE: errcode = WSATYPE_NOT_FOUND; break; | |||
case EAI_SOCKTYPE: errcode = WSAESOCKTNOSUPPORT; break; | |||
default: errcode = WSANO_RECOVERY; break; /* Including EAI_FAIL */ | |||
} | |||
return gai_strerror(errcode); | |||
} | |||
void win32_freeaddrinfo(struct addrinfo *res) { | |||
freeaddrinfo(res); | |||
} | |||
SOCKET win32_socket(int domain, int type, int protocol) { | |||
SOCKET s; | |||
/* Note: This function is likely to be called before other functions, so run init here. */ | |||
if (!_initWinsock()) { | |||
return INVALID_SOCKET; | |||
} | |||
_updateErrno((s = socket(domain, type, protocol)) != INVALID_SOCKET); | |||
return s; | |||
} | |||
int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp) { | |||
int ret = ioctlsocket(fd, (long)request, argp); | |||
_updateErrno(ret != SOCKET_ERROR); | |||
return ret != SOCKET_ERROR ? ret : -1; | |||
} | |||
int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) { | |||
int ret = bind(sockfd, addr, addrlen); | |||
_updateErrno(ret != SOCKET_ERROR); | |||
return ret != SOCKET_ERROR ? ret : -1; | |||
} | |||
int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) { | |||
int ret = connect(sockfd, addr, addrlen); | |||
_updateErrno(ret != SOCKET_ERROR); | |||
/* For Winsock connect(), the WSAEWOULDBLOCK error means the same thing as | |||
* EINPROGRESS for POSIX connect(), so we do that translation to keep POSIX | |||
* logic consistent. */ | |||
if (errno == EWOULDBLOCK) { | |||
errno = EINPROGRESS; | |||
} | |||
return ret != SOCKET_ERROR ? ret : -1; | |||
} | |||
int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen) { | |||
int ret = 0; | |||
if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) { | |||
if (*optlen >= sizeof (struct timeval)) { | |||
struct timeval *tv = optval; | |||
DWORD timeout = 0; | |||
socklen_t dwlen = 0; | |||
ret = getsockopt(sockfd, level, optname, (char *)&timeout, &dwlen); | |||
tv->tv_sec = timeout / 1000; | |||
tv->tv_usec = (timeout * 1000) % 1000000; | |||
} else { | |||
ret = WSAEFAULT; | |||
} | |||
*optlen = sizeof (struct timeval); | |||
} else { | |||
ret = getsockopt(sockfd, level, optname, (char*)optval, optlen); | |||
} | |||
_updateErrno(ret != SOCKET_ERROR); | |||
return ret != SOCKET_ERROR ? ret : -1; | |||
} | |||
int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) { | |||
int ret = 0; | |||
if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) { | |||
struct timeval *tv = optval; | |||
DWORD timeout = tv->tv_sec * 1000 + tv->tv_usec / 1000; | |||
ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD)); | |||
} else { | |||
ret = setsockopt(sockfd, level, optname, (const char*)optval, optlen); | |||
} | |||
_updateErrno(ret != SOCKET_ERROR); | |||
return ret != SOCKET_ERROR ? ret : -1; | |||
} | |||
int win32_close(SOCKET fd) { | |||
int ret = closesocket(fd); | |||
_updateErrno(ret != SOCKET_ERROR); | |||
return ret != SOCKET_ERROR ? ret : -1; | |||
} | |||
ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags) { | |||
int ret = recv(sockfd, (char*)buf, (int)len, flags); | |||
_updateErrno(ret != SOCKET_ERROR); | |||
return ret != SOCKET_ERROR ? ret : -1; | |||
} | |||
ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags) { | |||
int ret = send(sockfd, (const char*)buf, (int)len, flags); | |||
_updateErrno(ret != SOCKET_ERROR); | |||
return ret != SOCKET_ERROR ? ret : -1; | |||
} | |||
int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout) { | |||
int ret = WSAPoll(fds, nfds, timeout); | |||
_updateErrno(ret != SOCKET_ERROR); | |||
return ret != SOCKET_ERROR ? ret : -1; | |||
} | |||
#endif /* _WIN32 */ |
@@ -1,91 +0,0 @@ | |||
/* | |||
* Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu> | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions are met: | |||
* | |||
* * Redistributions of source code must retain the above copyright notice, | |||
* this list of conditions and the following disclaimer. | |||
* * Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* * Neither the name of Redis nor the names of its contributors may be used | |||
* to endorse or promote products derived from this software without | |||
* specific prior written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
* POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
#ifndef __SOCKCOMPAT_H | |||
#define __SOCKCOMPAT_H | |||
#ifndef _WIN32 | |||
/* For POSIX systems we use the standard BSD socket API. */ | |||
#include <unistd.h> | |||
#include <sys/socket.h> | |||
#include <sys/select.h> | |||
#include <sys/un.h> | |||
#include <netinet/in.h> | |||
#include <netinet/tcp.h> | |||
#include <arpa/inet.h> | |||
#include <netdb.h> | |||
#include <poll.h> | |||
#else | |||
/* For Windows we use winsock. */ | |||
#undef _WIN32_WINNT | |||
#define _WIN32_WINNT 0x0600 /* To get WSAPoll etc. */ | |||
#include <winsock2.h> | |||
#include <ws2tcpip.h> | |||
#include <stddef.h> | |||
#ifdef _MSC_VER | |||
typedef signed long ssize_t; | |||
#endif | |||
/* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */ | |||
int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); | |||
const char *win32_gai_strerror(int errcode); | |||
void win32_freeaddrinfo(struct addrinfo *res); | |||
SOCKET win32_socket(int domain, int type, int protocol); | |||
int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp); | |||
int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen); | |||
int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen); | |||
int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen); | |||
int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen); | |||
int win32_close(SOCKET fd); | |||
ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags); | |||
ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags); | |||
typedef ULONG nfds_t; | |||
int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout); | |||
#ifndef REDIS_SOCKCOMPAT_IMPLEMENTATION | |||
#define getaddrinfo(node, service, hints, res) win32_getaddrinfo(node, service, hints, res) | |||
#undef gai_strerror | |||
#define gai_strerror(errcode) win32_gai_strerror(errcode) | |||
#define freeaddrinfo(res) win32_freeaddrinfo(res) | |||
#define socket(domain, type, protocol) win32_socket(domain, type, protocol) | |||
#define ioctl(fd, request, argp) win32_ioctl(fd, request, argp) | |||
#define bind(sockfd, addr, addrlen) win32_bind(sockfd, addr, addrlen) | |||
#define connect(sockfd, addr, addrlen) win32_connect(sockfd, addr, addrlen) | |||
#define getsockopt(sockfd, level, optname, optval, optlen) win32_getsockopt(sockfd, level, optname, optval, optlen) | |||
#define setsockopt(sockfd, level, optname, optval, optlen) win32_setsockopt(sockfd, level, optname, optval, optlen) | |||
#define close(fd) win32_close(fd) | |||
#define recv(sockfd, buf, len, flags) win32_recv(sockfd, buf, len, flags) | |||
#define send(sockfd, buf, len, flags) win32_send(sockfd, buf, len, flags) | |||
#define poll(fds, nfds, timeout) win32_poll(fds, nfds, timeout) | |||
#endif /* REDIS_SOCKCOMPAT_IMPLEMENTATION */ | |||
#endif /* _WIN32 */ | |||
#endif /* __SOCKCOMPAT_H */ |
@@ -1,448 +0,0 @@ | |||
/* | |||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> | |||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||
* Copyright (c) 2019, Redis Labs | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions are met: | |||
* | |||
* * Redistributions of source code must retain the above copyright notice, | |||
* this list of conditions and the following disclaimer. | |||
* * Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in the | |||
* documentation and/or other materials provided with the distribution. | |||
* * Neither the name of Redis nor the names of its contributors may be used | |||
* to endorse or promote products derived from this software without | |||
* specific prior written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
* POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
#include "hiredis.h" | |||
#include "async.h" | |||
#include <assert.h> | |||
#include <pthread.h> | |||
#include <errno.h> | |||
#include <string.h> | |||
#include <openssl/ssl.h> | |||
#include <openssl/err.h> | |||
#include "async_private.h" | |||
void __redisSetError(redisContext *c, int type, const char *str); | |||
/* The SSL context is attached to SSL/TLS connections as a privdata. */ | |||
typedef struct redisSSLContext { | |||
/** | |||
* OpenSSL SSL_CTX; It is optional and will not be set when using | |||
* user-supplied SSL. | |||
*/ | |||
SSL_CTX *ssl_ctx; | |||
/** | |||
* OpenSSL SSL object. | |||
*/ | |||
SSL *ssl; | |||
/** | |||
* SSL_write() requires to be called again with the same arguments it was | |||
* previously called with in the event of an SSL_read/SSL_write situation | |||
*/ | |||
size_t lastLen; | |||
/** Whether the SSL layer requires read (possibly before a write) */ | |||
int wantRead; | |||
/** | |||
* Whether a write was requested prior to a read. If set, the write() | |||
* should resume whenever a read takes place, if possible | |||
*/ | |||
int pendingWrite; | |||
} redisSSLContext; | |||
/* Forward declaration */ | |||
redisContextFuncs redisContextSSLFuncs; | |||
#ifdef HIREDIS_SSL_TRACE | |||
/** | |||
* Callback used for debugging | |||
*/ | |||
static void sslLogCallback(const SSL *ssl, int where, int ret) { | |||
const char *retstr; | |||
int should_log = 0; | |||
/* Ignore low-level SSL stuff */ | |||
if (where & SSL_CB_ALERT) { | |||
should_log = 1; | |||
} | |||
if (where == SSL_CB_HANDSHAKE_START || where == SSL_CB_HANDSHAKE_DONE) { | |||
should_log = 1; | |||
} | |||
if ((where & SSL_CB_EXIT) && ret == 0) { | |||
should_log = 1; | |||
} | |||
if (!should_log) { | |||
return; | |||
} | |||
retstr = SSL_alert_type_string(ret); | |||
printf("ST(0x%x). %s. R(0x%x)%s\n", where, SSL_state_string_long(ssl), ret, retstr); | |||
if (where == SSL_CB_HANDSHAKE_DONE) { | |||
printf("Using SSL version %s. Cipher=%s\n", SSL_get_version(ssl), SSL_get_cipher_name(ssl)); | |||
} | |||
} | |||
#endif | |||
/** | |||
* OpenSSL global initialization and locking handling callbacks. | |||
* Note that this is only required for OpenSSL < 1.1.0. | |||
*/ | |||
#if OPENSSL_VERSION_NUMBER < 0x10100000L | |||
#define HIREDIS_USE_CRYPTO_LOCKS | |||
#endif | |||
#ifdef HIREDIS_USE_CRYPTO_LOCKS | |||
typedef pthread_mutex_t sslLockType; | |||
static void sslLockInit(sslLockType *l) { | |||
pthread_mutex_init(l, NULL); | |||
} | |||
static void sslLockAcquire(sslLockType *l) { | |||
pthread_mutex_lock(l); | |||
} | |||
static void sslLockRelease(sslLockType *l) { | |||
pthread_mutex_unlock(l); | |||
} | |||
static pthread_mutex_t *ossl_locks; | |||
static void opensslDoLock(int mode, int lkid, const char *f, int line) { | |||
sslLockType *l = ossl_locks + lkid; | |||
if (mode & CRYPTO_LOCK) { | |||
sslLockAcquire(l); | |||
} else { | |||
sslLockRelease(l); | |||
} | |||
(void)f; | |||
(void)line; | |||
} | |||
static void initOpensslLocks(void) { | |||
unsigned ii, nlocks; | |||
if (CRYPTO_get_locking_callback() != NULL) { | |||
/* Someone already set the callback before us. Don't destroy it! */ | |||
return; | |||
} | |||
nlocks = CRYPTO_num_locks(); | |||
ossl_locks = hi_malloc(sizeof(*ossl_locks) * nlocks); | |||
for (ii = 0; ii < nlocks; ii++) { | |||
sslLockInit(ossl_locks + ii); | |||
} | |||
CRYPTO_set_locking_callback(opensslDoLock); | |||
} | |||
#endif /* HIREDIS_USE_CRYPTO_LOCKS */ | |||
/** | |||
* SSL Connection initialization. | |||
*/ | |||
static int redisSSLConnect(redisContext *c, SSL_CTX *ssl_ctx, SSL *ssl) { | |||
if (c->privdata) { | |||
__redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated"); | |||
return REDIS_ERR; | |||
} | |||
c->privdata = calloc(1, sizeof(redisSSLContext)); | |||
c->funcs = &redisContextSSLFuncs; | |||
redisSSLContext *rssl = c->privdata; | |||
rssl->ssl_ctx = ssl_ctx; | |||
rssl->ssl = ssl; | |||
SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); | |||
SSL_set_fd(rssl->ssl, c->fd); | |||
SSL_set_connect_state(rssl->ssl); | |||
ERR_clear_error(); | |||
int rv = SSL_connect(rssl->ssl); | |||
if (rv == 1) { | |||
return REDIS_OK; | |||
} | |||
rv = SSL_get_error(rssl->ssl, rv); | |||
if (((c->flags & REDIS_BLOCK) == 0) && | |||
(rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) { | |||
return REDIS_OK; | |||
} | |||
if (c->err == 0) { | |||
char err[512]; | |||
if (rv == SSL_ERROR_SYSCALL) | |||
snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno)); | |||
else { | |||
unsigned long e = ERR_peek_last_error(); | |||
snprintf(err,sizeof(err)-1,"SSL_connect failed: %s", | |||
ERR_reason_error_string(e)); | |||
} | |||
__redisSetError(c, REDIS_ERR_IO, err); | |||
} | |||
return REDIS_ERR; | |||
} | |||
int redisInitiateSSL(redisContext *c, SSL *ssl) { | |||
return redisSSLConnect(c, NULL, ssl); | |||
} | |||
int redisSecureConnection(redisContext *c, const char *capath, | |||
const char *certpath, const char *keypath, const char *servername) { | |||
SSL_CTX *ssl_ctx = NULL; | |||
SSL *ssl = NULL; | |||
/* Initialize global OpenSSL stuff */ | |||
static int isInit = 0; | |||
if (!isInit) { | |||
isInit = 1; | |||
SSL_library_init(); | |||
#ifdef HIREDIS_USE_CRYPTO_LOCKS | |||
initOpensslLocks(); | |||
#endif | |||
} | |||
ssl_ctx = SSL_CTX_new(SSLv23_client_method()); | |||
if (!ssl_ctx) { | |||
__redisSetError(c, REDIS_ERR_OTHER, "Failed to create SSL_CTX"); | |||
goto error; | |||
} | |||
#ifdef HIREDIS_SSL_TRACE | |||
SSL_CTX_set_info_callback(ssl_ctx, sslLogCallback); | |||
#endif | |||
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); | |||
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); | |||
if ((certpath != NULL && keypath == NULL) || (keypath != NULL && certpath == NULL)) { | |||
__redisSetError(c, REDIS_ERR_OTHER, "certpath and keypath must be specified together"); | |||
goto error; | |||
} | |||
if (capath) { | |||
if (!SSL_CTX_load_verify_locations(ssl_ctx, capath, NULL)) { | |||
__redisSetError(c, REDIS_ERR_OTHER, "Invalid CA certificate"); | |||
goto error; | |||
} | |||
} | |||
if (certpath) { | |||
if (!SSL_CTX_use_certificate_chain_file(ssl_ctx, certpath)) { | |||
__redisSetError(c, REDIS_ERR_OTHER, "Invalid client certificate"); | |||
goto error; | |||
} | |||
if (!SSL_CTX_use_PrivateKey_file(ssl_ctx, keypath, SSL_FILETYPE_PEM)) { | |||
__redisSetError(c, REDIS_ERR_OTHER, "Invalid client key"); | |||
goto error; | |||
} | |||
} | |||
ssl = SSL_new(ssl_ctx); | |||
if (!ssl) { | |||
__redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance"); | |||
goto error; | |||
} | |||
if (servername) { | |||
if (!SSL_set_tlsext_host_name(ssl, servername)) { | |||
__redisSetError(c, REDIS_ERR_OTHER, "Couldn't set server name indication"); | |||
goto error; | |||
} | |||
} | |||
return redisSSLConnect(c, ssl_ctx, ssl); | |||
error: | |||
if (ssl) SSL_free(ssl); | |||
if (ssl_ctx) SSL_CTX_free(ssl_ctx); | |||
return REDIS_ERR; | |||
} | |||
static int maybeCheckWant(redisSSLContext *rssl, int rv) { | |||
/** | |||
* If the error is WANT_READ or WANT_WRITE, the appropriate flags are set | |||
* and true is returned. False is returned otherwise | |||
*/ | |||
if (rv == SSL_ERROR_WANT_READ) { | |||
rssl->wantRead = 1; | |||
return 1; | |||
} else if (rv == SSL_ERROR_WANT_WRITE) { | |||
rssl->pendingWrite = 1; | |||
return 1; | |||
} else { | |||
return 0; | |||
} | |||
} | |||
/** | |||
* Implementation of redisContextFuncs for SSL connections. | |||
*/ | |||
static void redisSSLFreeContext(void *privdata){ | |||
redisSSLContext *rsc = privdata; | |||
if (!rsc) return; | |||
if (rsc->ssl) { | |||
SSL_free(rsc->ssl); | |||
rsc->ssl = NULL; | |||
} | |||
if (rsc->ssl_ctx) { | |||
SSL_CTX_free(rsc->ssl_ctx); | |||
rsc->ssl_ctx = NULL; | |||
} | |||
free(rsc); | |||
} | |||
static int redisSSLRead(redisContext *c, char *buf, size_t bufcap) { | |||
redisSSLContext *rssl = c->privdata; | |||
int nread = SSL_read(rssl->ssl, buf, bufcap); | |||
if (nread > 0) { | |||
return nread; | |||
} else if (nread == 0) { | |||
__redisSetError(c, REDIS_ERR_EOF, "Server closed the connection"); | |||
return -1; | |||
} else { | |||
int err = SSL_get_error(rssl->ssl, nread); | |||
if (c->flags & REDIS_BLOCK) { | |||
/** | |||
* In blocking mode, we should never end up in a situation where | |||
* we get an error without it being an actual error, except | |||
* in the case of EINTR, which can be spuriously received from | |||
* debuggers or whatever. | |||
*/ | |||
if (errno == EINTR) { | |||
return 0; | |||
} else { | |||
const char *msg = NULL; | |||
if (errno == EAGAIN) { | |||
msg = "Resource temporarily unavailable"; | |||
} | |||
__redisSetError(c, REDIS_ERR_IO, msg); | |||
return -1; | |||
} | |||
} | |||
/** | |||
* We can very well get an EWOULDBLOCK/EAGAIN, however | |||
*/ | |||
if (maybeCheckWant(rssl, err)) { | |||
return 0; | |||
} else { | |||
__redisSetError(c, REDIS_ERR_IO, NULL); | |||
return -1; | |||
} | |||
} | |||
} | |||
static int redisSSLWrite(redisContext *c) { | |||
redisSSLContext *rssl = c->privdata; | |||
size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf); | |||
int rv = SSL_write(rssl->ssl, c->obuf, len); | |||
if (rv > 0) { | |||
rssl->lastLen = 0; | |||
} else if (rv < 0) { | |||
rssl->lastLen = len; | |||
int err = SSL_get_error(rssl->ssl, rv); | |||
if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(rssl, err)) { | |||
return 0; | |||
} else { | |||
__redisSetError(c, REDIS_ERR_IO, NULL); | |||
return -1; | |||
} | |||
} | |||
return rv; | |||
} | |||
static void redisSSLAsyncRead(redisAsyncContext *ac) { | |||
int rv; | |||
redisSSLContext *rssl = ac->c.privdata; | |||
redisContext *c = &ac->c; | |||
rssl->wantRead = 0; | |||
if (rssl->pendingWrite) { | |||
int done; | |||
/* This is probably just a write event */ | |||
rssl->pendingWrite = 0; | |||
rv = redisBufferWrite(c, &done); | |||
if (rv == REDIS_ERR) { | |||
__redisAsyncDisconnect(ac); | |||
return; | |||
} else if (!done) { | |||
_EL_ADD_WRITE(ac); | |||
} | |||
} | |||
rv = redisBufferRead(c); | |||
if (rv == REDIS_ERR) { | |||
__redisAsyncDisconnect(ac); | |||
} else { | |||
_EL_ADD_READ(ac); | |||
redisProcessCallbacks(ac); | |||
} | |||
} | |||
static void redisSSLAsyncWrite(redisAsyncContext *ac) { | |||
int rv, done = 0; | |||
redisSSLContext *rssl = ac->c.privdata; | |||
redisContext *c = &ac->c; | |||
rssl->pendingWrite = 0; | |||
rv = redisBufferWrite(c, &done); | |||
if (rv == REDIS_ERR) { | |||
__redisAsyncDisconnect(ac); | |||
return; | |||
} | |||
if (!done) { | |||
if (rssl->wantRead) { | |||
/* Need to read-before-write */ | |||
rssl->pendingWrite = 1; | |||
_EL_DEL_WRITE(ac); | |||
} else { | |||
/* No extra reads needed, just need to write more */ | |||
_EL_ADD_WRITE(ac); | |||
} | |||
} else { | |||
/* Already done! */ | |||
_EL_DEL_WRITE(ac); | |||
} | |||
/* Always reschedule a read */ | |||
_EL_ADD_READ(ac); | |||
} | |||
redisContextFuncs redisContextSSLFuncs = { | |||
.free_privdata = redisSSLFreeContext, | |||
.async_read = redisSSLAsyncRead, | |||
.async_write = redisSSLAsyncWrite, | |||
.read = redisSSLRead, | |||
.write = redisSSLWrite | |||
}; | |||
@@ -1,70 +0,0 @@ | |||
#!/bin/sh -ue | |||
REDIS_SERVER=${REDIS_SERVER:-redis-server} | |||
REDIS_PORT=${REDIS_PORT:-56379} | |||
REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443} | |||
TEST_SSL=${TEST_SSL:-0} | |||
SSL_TEST_ARGS= | |||
tmpdir=$(mktemp -d) | |||
PID_FILE=${tmpdir}/hiredis-test-redis.pid | |||
SOCK_FILE=${tmpdir}/hiredis-test-redis.sock | |||
if [ "$TEST_SSL" = "1" ]; then | |||
SSL_CA_CERT=${tmpdir}/ca.crt | |||
SSL_CA_KEY=${tmpdir}/ca.key | |||
SSL_CERT=${tmpdir}/redis.crt | |||
SSL_KEY=${tmpdir}/redis.key | |||
openssl genrsa -out ${tmpdir}/ca.key 4096 | |||
openssl req \ | |||
-x509 -new -nodes -sha256 \ | |||
-key ${SSL_CA_KEY} \ | |||
-days 3650 \ | |||
-subj '/CN=Hiredis Test CA' \ | |||
-out ${SSL_CA_CERT} | |||
openssl genrsa -out ${SSL_KEY} 2048 | |||
openssl req \ | |||
-new -sha256 \ | |||
-key ${SSL_KEY} \ | |||
-subj '/CN=Hiredis Test Cert' | \ | |||
openssl x509 \ | |||
-req -sha256 \ | |||
-CA ${SSL_CA_CERT} \ | |||
-CAkey ${SSL_CA_KEY} \ | |||
-CAserial ${tmpdir}/ca.txt \ | |||
-CAcreateserial \ | |||
-days 365 \ | |||
-out ${SSL_CERT} | |||
SSL_TEST_ARGS="--ssl-host 127.0.0.1 --ssl-port ${REDIS_SSL_PORT} --ssl-ca-cert ${SSL_CA_CERT} --ssl-cert ${SSL_CERT} --ssl-key ${SSL_KEY}" | |||
fi | |||
cleanup() { | |||
set +e | |||
kill $(cat ${PID_FILE}) | |||
rm -rf ${tmpdir} | |||
} | |||
trap cleanup INT TERM EXIT | |||
cat > ${tmpdir}/redis.conf <<EOF | |||
daemonize yes | |||
pidfile ${PID_FILE} | |||
port ${REDIS_PORT} | |||
bind 127.0.0.1 | |||
unixsocket ${SOCK_FILE} | |||
EOF | |||
if [ "$TEST_SSL" = "1" ]; then | |||
cat >> ${tmpdir}/redis.conf <<EOF | |||
tls-port ${REDIS_SSL_PORT} | |||
tls-ca-cert-file ${SSL_CA_CERT} | |||
tls-cert-file ${SSL_CERT} | |||
tls-key-file ${SSL_KEY} | |||
EOF | |||
fi | |||
cat ${tmpdir}/redis.conf | |||
${REDIS_SERVER} ${tmpdir}/redis.conf | |||
${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS} |
@@ -1,56 +0,0 @@ | |||
#ifndef _WIN32_HELPER_INCLUDE | |||
#define _WIN32_HELPER_INCLUDE | |||
#ifdef _MSC_VER | |||
#include <winsock2.h> /* for struct timeval */ | |||
#ifndef inline | |||
#define inline __inline | |||
#endif | |||
#ifndef strcasecmp | |||
#define strcasecmp stricmp | |||
#endif | |||
#ifndef strncasecmp | |||
#define strncasecmp strnicmp | |||
#endif | |||
#ifndef va_copy | |||
#define va_copy(d,s) ((d) = (s)) | |||
#endif | |||
#ifndef snprintf | |||
#define snprintf c99_snprintf | |||
__inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap) | |||
{ | |||
int count = -1; | |||
if (size != 0) | |||
count = _vsnprintf_s(str, size, _TRUNCATE, format, ap); | |||
if (count == -1) | |||
count = _vscprintf(format, ap); | |||
return count; | |||
} | |||
__inline int c99_snprintf(char* str, size_t size, const char* format, ...) | |||
{ | |||
int count; | |||
va_list ap; | |||
va_start(ap, format); | |||
count = c99_vsnprintf(str, size, format, ap); | |||
va_end(ap); | |||
return count; | |||
} | |||
#endif | |||
#endif /* _MSC_VER */ | |||
#ifdef _WIN32 | |||
#define strerror_r(errno,buf,len) strerror_s(buf,len,errno) | |||
#endif /* _WIN32 */ | |||
#endif /* _WIN32_HELPER_INCLUDE */ |