Skip to content
This repository
Browse code

c++0x version of realplexor

  • Loading branch information...
commit abb18babc5e1d471a3264c1f2415578526bf4a26 1 parent 05b339e
Dmitry Koterov authored November 25, 2011

Showing 31 changed files with 2,962 additions and 1 deletion. Show diff stats Hide diff stats

  1. 1  .gitignore
  2. 2  cpp/msdev/.gitignore
  3. 20  cpp/msdev/msdev.sln
  4. BIN  cpp/msdev/msdev.suo
  5. 108  cpp/msdev/msdev.vcxproj
  6. 105  cpp/msdev/msdev.vcxproj.filters
  7. 3  cpp/msdev/msdev.vcxproj.user
  8. 257  cpp/src/Connection/In.h
  9. 146  cpp/src/Connection/Wait.h
  10. 38  cpp/src/Make.sh
  11. 316  cpp/src/Realplexor/Common.h
  12. 215  cpp/src/Realplexor/Config.h
  13. 110  cpp/src/Realplexor/Event/Connection.h
  14. 217  cpp/src/Realplexor/Event/Server.h
  15. 23  cpp/src/Realplexor/Event/Signal.h
  16. 42  cpp/src/Realplexor/Event/Timer.h
  17. 73  cpp/src/Realplexor/Tools.h
  18. 60  cpp/src/Storage/CleanupTimers.h
  19. 74  cpp/src/Storage/ConnectedFhs.h
  20. 96  cpp/src/Storage/DataToSend.h
  21. 62  cpp/src/Storage/Events.h
  22. 83  cpp/src/Storage/OnlineTimers.h
  23. 67  cpp/src/Storage/PairsByFhs.h
  24. 240  cpp/src/dklab_realplexor.cpp
  25. 129  cpp/src/dklab_realplexor.h
  26. 132  cpp/src/utils/Socket.h
  27. 39  cpp/src/utils/checked_map.h
  28. 37  cpp/src/utils/ev++0x.h
  29. 166  cpp/src/utils/misc.h
  30. 44  cpp/src/utils/prefix_checker.h
  31. 58  cpp/src/utils/stdmiss.h
1  .gitignore
... ...
@@ -1,2 +1 @@
1 1
 dklab_realplexor
2  
-cpp
2  cpp/msdev/.gitignore
... ...
@@ -0,0 +1,2 @@
  1
+Debug
  2
+Release
20  cpp/msdev/msdev.sln
... ...
@@ -0,0 +1,20 @@
  1
+
  2
+Microsoft Visual Studio Solution File, Format Version 11.00
  3
+# Visual Studio 2010
  4
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "msdev", "msdev.vcxproj", "{9ED95E42-4629-4E6D-B5B0-F1A0A5B28A80}"
  5
+EndProject
  6
+Global
  7
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
  8
+		Debug|Win32 = Debug|Win32
  9
+		Release|Win32 = Release|Win32
  10
+	EndGlobalSection
  11
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
  12
+		{9ED95E42-4629-4E6D-B5B0-F1A0A5B28A80}.Debug|Win32.ActiveCfg = Debug|Win32
  13
+		{9ED95E42-4629-4E6D-B5B0-F1A0A5B28A80}.Debug|Win32.Build.0 = Debug|Win32
  14
+		{9ED95E42-4629-4E6D-B5B0-F1A0A5B28A80}.Release|Win32.ActiveCfg = Release|Win32
  15
+		{9ED95E42-4629-4E6D-B5B0-F1A0A5B28A80}.Release|Win32.Build.0 = Release|Win32
  16
+	EndGlobalSection
  17
+	GlobalSection(SolutionProperties) = preSolution
  18
+		HideSolutionNode = FALSE
  19
+	EndGlobalSection
  20
+EndGlobal
BIN  cpp/msdev/msdev.suo
Binary file not shown
108  cpp/msdev/msdev.vcxproj
... ...
@@ -0,0 +1,108 @@
  1
+<?xml version="1.0" encoding="utf-8"?>
  2
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  3
+  <ItemGroup Label="ProjectConfigurations">
  4
+    <ProjectConfiguration Include="Debug|Win32">
  5
+      <Configuration>Debug</Configuration>
  6
+      <Platform>Win32</Platform>
  7
+    </ProjectConfiguration>
  8
+    <ProjectConfiguration Include="Release|Win32">
  9
+      <Configuration>Release</Configuration>
  10
+      <Platform>Win32</Platform>
  11
+    </ProjectConfiguration>
  12
+  </ItemGroup>
  13
+  <PropertyGroup Label="Globals">
  14
+    <ProjectGuid>{9ED95E42-4629-4E6D-B5B0-F1A0A5B28A80}</ProjectGuid>
  15
+    <Keyword>Win32Proj</Keyword>
  16
+    <RootNamespace>msdev</RootNamespace>
  17
+  </PropertyGroup>
  18
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
  19
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
  20
+    <ConfigurationType>Application</ConfigurationType>
  21
+    <UseDebugLibraries>true</UseDebugLibraries>
  22
+    <CharacterSet>Unicode</CharacterSet>
  23
+  </PropertyGroup>
  24
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
  25
+    <ConfigurationType>Application</ConfigurationType>
  26
+    <UseDebugLibraries>false</UseDebugLibraries>
  27
+    <WholeProgramOptimization>true</WholeProgramOptimization>
  28
+    <CharacterSet>Unicode</CharacterSet>
  29
+  </PropertyGroup>
  30
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
  31
+  <ImportGroup Label="ExtensionSettings">
  32
+  </ImportGroup>
  33
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
  34
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  35
+  </ImportGroup>
  36
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
  37
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  38
+  </ImportGroup>
  39
+  <PropertyGroup Label="UserMacros" />
  40
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
  41
+    <LinkIncremental>true</LinkIncremental>
  42
+    <IncludePath>$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(WindowsSdkDir)include;$(FrameworkSDKDir)\include;C:\TMP\src\boost_1_46_1;c:\tmp\src\linux_include;C:\TMP\src\libev</IncludePath>
  43
+  </PropertyGroup>
  44
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
  45
+    <LinkIncremental>false</LinkIncremental>
  46
+    <IncludePath>$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(WindowsSdkDir)include;$(FrameworkSDKDir)\include;C:\TMP\src\boost_1_46_1;c:\tmp\src\linux_include</IncludePath>
  47
+  </PropertyGroup>
  48
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
  49
+    <ClCompile>
  50
+      <PrecompiledHeader>
  51
+      </PrecompiledHeader>
  52
+      <WarningLevel>Level3</WarningLevel>
  53
+      <Optimization>Disabled</Optimization>
  54
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
  55
+    </ClCompile>
  56
+    <Link>
  57
+      <SubSystem>Console</SubSystem>
  58
+      <GenerateDebugInformation>true</GenerateDebugInformation>
  59
+    </Link>
  60
+  </ItemDefinitionGroup>
  61
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
  62
+    <ClCompile>
  63
+      <WarningLevel>Level3</WarningLevel>
  64
+      <PrecompiledHeader>
  65
+      </PrecompiledHeader>
  66
+      <Optimization>MaxSpeed</Optimization>
  67
+      <FunctionLevelLinking>true</FunctionLevelLinking>
  68
+      <IntrinsicFunctions>true</IntrinsicFunctions>
  69
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
  70
+    </ClCompile>
  71
+    <Link>
  72
+      <SubSystem>Console</SubSystem>
  73
+      <GenerateDebugInformation>true</GenerateDebugInformation>
  74
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
  75
+      <OptimizeReferences>true</OptimizeReferences>
  76
+    </Link>
  77
+  </ItemDefinitionGroup>
  78
+  <ItemGroup>
  79
+    <ClInclude Include="..\src\Connection\In.h" />
  80
+    <ClInclude Include="..\src\Connection\Wait.h" />
  81
+    <ClInclude Include="..\src\dklab_realplexor.h" />
  82
+    <ClInclude Include="..\src\Realplexor\Common.h" />
  83
+    <ClInclude Include="..\src\Realplexor\Config.h" />
  84
+    <ClInclude Include="..\src\Realplexor\Event\Connection.h" />
  85
+    <ClInclude Include="..\src\Realplexor\Event\Server.h" />
  86
+    <ClInclude Include="..\src\Realplexor\Event\Signal.h" />
  87
+    <ClInclude Include="..\src\Realplexor\Event\Timer.h" />
  88
+    <ClInclude Include="..\src\Realplexor\Tools.h" />
  89
+    <ClInclude Include="..\src\Storage\CleanupTimers.h" />
  90
+    <ClInclude Include="..\src\Storage\ConnectedFhs.h" />
  91
+    <ClInclude Include="..\src\Storage\DataToSend.h" />
  92
+    <ClInclude Include="..\src\Storage\Events.h" />
  93
+    <ClInclude Include="..\src\Storage\OnlineTimers.h" />
  94
+    <ClInclude Include="..\src\Storage\PairsByFhs.h" />
  95
+    <ClInclude Include="..\src\utils\checked_map.h" />
  96
+    <ClInclude Include="..\src\utils\ev++0x.h" />
  97
+    <ClInclude Include="..\src\utils\misc.h" />
  98
+    <ClInclude Include="..\src\utils\prefix_checker.h" />
  99
+    <ClInclude Include="..\src\utils\Socket.h" />
  100
+    <ClInclude Include="..\src\utils\stdmiss.h" />
  101
+  </ItemGroup>
  102
+  <ItemGroup>
  103
+    <ClCompile Include="..\src\dklab_realplexor.cpp" />
  104
+  </ItemGroup>
  105
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
  106
+  <ImportGroup Label="ExtensionTargets">
  107
+  </ImportGroup>
  108
+</Project>
105  cpp/msdev/msdev.vcxproj.filters
... ...
@@ -0,0 +1,105 @@
  1
+<?xml version="1.0" encoding="utf-8"?>
  2
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  3
+  <ItemGroup>
  4
+    <Filter Include="Файлы исходного кода">
  5
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
  6
+      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
  7
+    </Filter>
  8
+    <Filter Include="Заголовочные файлы">
  9
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
  10
+      <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
  11
+    </Filter>
  12
+    <Filter Include="Файлы ресурсов">
  13
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
  14
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
  15
+    </Filter>
  16
+    <Filter Include="Файлы исходного кода\Realplexor">
  17
+      <UniqueIdentifier>{5262f083-86b3-4b4c-9466-49633d079574}</UniqueIdentifier>
  18
+    </Filter>
  19
+    <Filter Include="Файлы исходного кода\Realplexor\Event">
  20
+      <UniqueIdentifier>{5d4dc7b5-037a-4384-98cb-85c51f301d0a}</UniqueIdentifier>
  21
+    </Filter>
  22
+    <Filter Include="Файлы исходного кода\Connection">
  23
+      <UniqueIdentifier>{49c71d4c-67b2-4a90-a2d6-234c0ed3c1f5}</UniqueIdentifier>
  24
+    </Filter>
  25
+    <Filter Include="Файлы исходного кода\Storage">
  26
+      <UniqueIdentifier>{c40bb46f-7882-4103-8886-7a4190fcde53}</UniqueIdentifier>
  27
+    </Filter>
  28
+    <Filter Include="Файлы исходного кода\utils">
  29
+      <UniqueIdentifier>{8160d2a8-2ca8-410f-b6e3-8bab9531b2fd}</UniqueIdentifier>
  30
+    </Filter>
  31
+  </ItemGroup>
  32
+  <ItemGroup>
  33
+    <ClInclude Include="..\src\Realplexor\Common.h">
  34
+      <Filter>Файлы исходного кода\Realplexor</Filter>
  35
+    </ClInclude>
  36
+    <ClInclude Include="..\src\Realplexor\Config.h">
  37
+      <Filter>Файлы исходного кода\Realplexor</Filter>
  38
+    </ClInclude>
  39
+    <ClInclude Include="..\src\Realplexor\Tools.h">
  40
+      <Filter>Файлы исходного кода\Realplexor</Filter>
  41
+    </ClInclude>
  42
+    <ClInclude Include="..\src\Realplexor\Event\Connection.h">
  43
+      <Filter>Файлы исходного кода\Realplexor\Event</Filter>
  44
+    </ClInclude>
  45
+    <ClInclude Include="..\src\Realplexor\Event\Server.h">
  46
+      <Filter>Файлы исходного кода\Realplexor\Event</Filter>
  47
+    </ClInclude>
  48
+    <ClInclude Include="..\src\Realplexor\Event\Signal.h">
  49
+      <Filter>Файлы исходного кода\Realplexor\Event</Filter>
  50
+    </ClInclude>
  51
+    <ClInclude Include="..\src\Realplexor\Event\Timer.h">
  52
+      <Filter>Файлы исходного кода\Realplexor\Event</Filter>
  53
+    </ClInclude>
  54
+    <ClInclude Include="..\src\Connection\In.h">
  55
+      <Filter>Файлы исходного кода\Connection</Filter>
  56
+    </ClInclude>
  57
+    <ClInclude Include="..\src\Connection\Wait.h">
  58
+      <Filter>Файлы исходного кода\Connection</Filter>
  59
+    </ClInclude>
  60
+    <ClInclude Include="..\src\Storage\CleanupTimers.h">
  61
+      <Filter>Файлы исходного кода\Storage</Filter>
  62
+    </ClInclude>
  63
+    <ClInclude Include="..\src\Storage\OnlineTimers.h">
  64
+      <Filter>Файлы исходного кода\Storage</Filter>
  65
+    </ClInclude>
  66
+    <ClInclude Include="..\src\Storage\Events.h">
  67
+      <Filter>Файлы исходного кода\Storage</Filter>
  68
+    </ClInclude>
  69
+    <ClInclude Include="..\src\dklab_realplexor.h">
  70
+      <Filter>Файлы исходного кода</Filter>
  71
+    </ClInclude>
  72
+    <ClInclude Include="..\src\utils\checked_map.h">
  73
+      <Filter>Файлы исходного кода\utils</Filter>
  74
+    </ClInclude>
  75
+    <ClInclude Include="..\src\utils\ev++0x.h">
  76
+      <Filter>Файлы исходного кода\utils</Filter>
  77
+    </ClInclude>
  78
+    <ClInclude Include="..\src\utils\misc.h">
  79
+      <Filter>Файлы исходного кода\utils</Filter>
  80
+    </ClInclude>
  81
+    <ClInclude Include="..\src\utils\Socket.h">
  82
+      <Filter>Файлы исходного кода\utils</Filter>
  83
+    </ClInclude>
  84
+    <ClInclude Include="..\src\utils\stdmiss.h">
  85
+      <Filter>Файлы исходного кода\utils</Filter>
  86
+    </ClInclude>
  87
+    <ClInclude Include="..\src\Storage\ConnectedFhs.h">
  88
+      <Filter>Файлы исходного кода\Storage</Filter>
  89
+    </ClInclude>
  90
+    <ClInclude Include="..\src\Storage\DataToSend.h">
  91
+      <Filter>Файлы исходного кода\Storage</Filter>
  92
+    </ClInclude>
  93
+    <ClInclude Include="..\src\Storage\PairsByFhs.h">
  94
+      <Filter>Файлы исходного кода\Storage</Filter>
  95
+    </ClInclude>
  96
+    <ClInclude Include="..\src\utils\prefix_checker.h">
  97
+      <Filter>Файлы исходного кода\utils</Filter>
  98
+    </ClInclude>
  99
+  </ItemGroup>
  100
+  <ItemGroup>
  101
+    <ClCompile Include="..\src\dklab_realplexor.cpp">
  102
+      <Filter>Файлы исходного кода</Filter>
  103
+    </ClCompile>
  104
+  </ItemGroup>
  105
+</Project>
3  cpp/msdev/msdev.vcxproj.user
... ...
@@ -0,0 +1,3 @@
  1
+<?xml version="1.0" encoding="utf-8"?>
  2
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  3
+</Project>
257  cpp/src/Connection/In.h
... ...
@@ -0,0 +1,257 @@
  1
+#ifndef REALPLEXOR_CONNECTION_IN_H
  2
+#define REALPLEXOR_CONNECTION_IN_H
  3
+
  4
+namespace Connection {
  5
+using namespace Realplexor;
  6
+using std::shared_ptr;
  7
+using std::exception;
  8
+
  9
+class In: public Realplexor::Event::Connection
  10
+{
  11
+    shared_ptr<DataPairChain> pairs;
  12
+    shared_ptr<LimitIdsSet> limit_ids;
  13
+    CredPair cred;
  14
+
  15
+public:
  16
+    
  17
+    // Called on a new connection.
  18
+    In(filehandle_t fh, Realplexor::Event::ServerBase* server): Connection(fh, server) 
  19
+    {
  20
+        pairs.reset(new DataPairChain());
  21
+        limit_ids.reset(new LimitIdsSet());
  22
+    }
  23
+
  24
+    // Hack: unfortunately C++ cannot call overriden virtual functions from base class destructors.
  25
+    virtual ~In()
  26
+    {
  27
+        ondestruct();
  28
+    }
  29
+
  30
+    // Called on timeout.
  31
+    void ontimeout()
  32
+    {
  33
+        Realplexor::Event::Connection::ontimeout();
  34
+        pairs->clear();
  35
+        data = "";
  36
+    }
  37
+
  38
+    // Called on error. 
  39
+    void onerror(const string& msg)
  40
+    {
  41
+        Realplexor::Event::Connection::onerror(msg);
  42
+        pairs->clear();
  43
+        data = "";
  44
+    }   
  45
+
  46
+    // Called when a data is available to read.
  47
+    void onread(size_t nread)
  48
+    {
  49
+        Realplexor::Event::Connection::onread(nread);
  50
+
  51
+        // Try to extract ID from the new data chunk.
  52
+        if (!pairs->size()) {
  53
+            if (Realplexor::Common::extract_pairs(data, *pairs, *limit_ids, cred)) {
  54
+                DEBUG(
  55
+                    "parsed IDs" 
  56
+                    + (limit_ids->size()? "; limiters are (" + join(sort_keys(*limit_ids), ", ") + ")" : "")
  57
+                    + (cred.login.length()? "; login is \"" + cred.login + "\"" : "")
  58
+                );
  59
+                _assert_auth();
  60
+            }
  61
+        }
  62
+
  63
+        // Try to process cmd.
  64
+        if (_try_process_cmd(false)) return;
  65
+
  66
+        // Check for the data overflow.
  67
+        if (data.length() > CONFIG.in_maxlen) {
  68
+            die("overflow (received " + lexical_cast<string>(data.length()) + " bytes total)");
  69
+        }
  70
+    }
  71
+
  72
+    // Called on client side disconnect.
  73
+    virtual void onclose() 
  74
+    {
  75
+        // First, try to process cmd.
  76
+        if (_try_process_cmd(true)) return;
  77
+        // Then, try to send messages.
  78
+        if (_try_process_pairs()) return;
  79
+    }
  80
+
  81
+private:
  82
+
  83
+    // Assert that authentication is OK.
  84
+    void _assert_auth()
  85
+    {
  86
+        try {
  87
+            if (cred.login.length()) {
  88
+                // Login + password are passed. Check credentials.
  89
+                if (!CONFIG.users.count(cred.login)) {
  90
+                    die("unknown login: " + cred.login);
  91
+                }
  92
+                string pwd_hash = CONFIG.users.get(cred.login);
  93
+                if (crypt(cred.password.c_str(), pwd_hash.c_str()) != pwd_hash) {
  94
+                    die("invalid password for login: " + cred.login);
  95
+                }
  96
+            } else if (!CONFIG.users.count("")) {
  97
+                // Guest access, but no guest account is found.
  98
+                die("access denied for guest user");
  99
+            }
  100
+        } catch (exception& e) {
  101
+            pairs->clear();
  102
+            data = "";
  103
+            _send_response(string(e.what()) + "\n", "403 Access Deined");
  104
+            throw;
  105
+        }
  106
+    }
  107
+
  108
+    // Process aux commands (may be started from the beginning
  109
+    // of the data of from the first \r\n\r\n part and finished
  110
+    // always by \n).
  111
+    bool _try_process_cmd(bool finished_reading)
  112
+    {
  113
+        if (!data.length()) return false;
  114
+        // Try to extract cmd.
  115
+        string tail_re = finished_reading? "\r?\n\r?\n|$" : "\r?\n\r?\n";
  116
+        regex re_in_cmd("(?:^|\r?\n\r?\n)(ONLINE|STATS|WATCH)(?:\\s+([^\r\n]*))?(?:" + tail_re + ")", regex::icase);
  117
+        boost::smatch m;
  118
+        if (!regex_search(data, m, re_in_cmd)) return false;
  119
+        string cmd = to_upper_copy(string(m[1]));
  120
+        string arg = m[2];
  121
+        // Cmd extracted, process it.
  122
+        pairs->clear();
  123
+        data = "";
  124
+        // Assert authorization.
  125
+        _assert_auth();
  126
+        DEBUG("received aux command: " + cmd + (arg.length()? " " + arg : ""));
  127
+        fh()->shutdown(0); // stop reading
  128
+        if (cmd == "ONLINE") {
  129
+            _cmd_online(arg);
  130
+        } else if (cmd == "STATS") {
  131
+            _cmd_stats(arg);
  132
+        } else if (cmd == "WATCH") {
  133
+            _cmd_watch(arg);
  134
+        }
  135
+        return true;
  136
+    }
  137
+
  138
+    // Try to process pairs.
  139
+    bool _try_process_pairs()
  140
+    {
  141
+        if (!data.length()) return false;
  142
+        if (pairs->size()) {
  143
+            // Clear headers from the data.
  144
+            size_t pos_body;
  145
+            if (!get_http_body(data, pos_body)) {
  146
+                DEBUG("passed empty HTTP body, ignored");
  147
+                data = "";
  148
+                return false;
  149
+            }
  150
+            vector<ident_t> ids_to_process;
  151
+            auto checker = _id_prefixes_to_checker("");
  152
+            auto rdata = shared_ptr<string>(new string(data, pos_body));
  153
+            for (auto& pair: *pairs) {
  154
+                cursor_t cursor = pair.cursor;
  155
+                ident_t id = pair.id;
  156
+                // Check if it is not own pair.
  157
+                if (!checker->matched(id)) {
  158
+                    DEBUG("skipping not owned [" + id + "] for login " + cred.login);//
  159
+                    continue;
  160
+                }
  161
+                // Add data to queue and set lifetime.
  162
+                ids_to_process.push_back(id);
  163
+                data_to_send.add_dataref_to_id(id, cursor, rdata, limit_ids);
  164
+                int timeout = CONFIG.clean_id_after;
  165
+                auto callback = [id, timeout]() {
  166
+                    data_to_send.clear_id(id); 
  167
+                    LOGGER("[" + id + "] cleaned, because no data is pushed within last " + lexical_cast<string>(timeout) + " seconds");
  168
+                };
  169
+                cleanup_timers.start_timer_for_id<decltype(callback)>(id, timeout, callback);
  170
+            }
  171
+            // One debug message per connection.
  172
+            if (ids_to_process.size()) {
  173
+                DEBUG("added data for [" + join(ids_to_process, ",") + "]");
  174
+            }
  175
+            // Send pending data.
  176
+            Realplexor::Common::send_pendings(ids_to_process);
  177
+        }
  178
+        return false;
  179
+    }
  180
+
  181
+    // Convert space-delimited ID prefixes list to prefix checker.
  182
+    shared_ptr<prefix_checker> _id_prefixes_to_checker(const string& id_prefixes)
  183
+    {
  184
+        vector<string> list;
  185
+        if (id_prefixes.length()) list = split(regex("\\s+"), id_prefixes);
  186
+        return shared_ptr<prefix_checker>(new prefix_checker(list, cred.login.length()? cred.login + "_" : ""));
  187
+    }
  188
+
  189
+    // Command: fetch all online IDs.
  190
+    void _cmd_online(const string& id_prefixes)
  191
+    {
  192
+        vector<ident_t> ids;
  193
+        online_timers.get_ids_ref(_id_prefixes_to_checker(id_prefixes), ids);
  194
+        DEBUG("sending " + lexical_cast<string>(ids.size()) + " online identifiers");
  195
+        _send_response(join(apply(ids, [](string& id) { return id + " " + lexical_cast<string>(connected_fhs.get_num_fhs_by_id(id)) + "\n"; }), ""));
  196
+    }
  197
+
  198
+    // Command: watch for clients online/offline status changes.
  199
+    void _cmd_watch(const string& arg) 
  200
+    {
  201
+        smatch m;
  202
+        cursor_t cursor = 0;
  203
+        string id_prefixes = "";
  204
+        try {
  205
+            if (regex_search(arg, m, regex("^(\\S+)\\s+(.*)$"))) {
  206
+                cursor = lexical_cast<cursor_t>(m[1]);
  207
+                id_prefixes = m[2];
  208
+            } else {
  209
+                cursor = lexical_cast<cursor_t>(arg);
  210
+            }
  211
+        } catch (bad_lexical_cast& e) {
  212
+            cursor = 0;
  213
+        }
  214
+        DataEventChain list;
  215
+        events.get_recent_events(cursor, _id_prefixes_to_checker(id_prefixes), list);
  216
+        DEBUG("sending " + lexical_cast<string>(list.size()) + " events");
  217
+        _send_response(join(apply(list, [](DataEvent& e) { return e.getType() + " " + lexical_cast<string>(e.cursor) + ":" + e.id + "\n"; }), ""));
  218
+    }
  219
+
  220
+    // Command: dump debug statistics.
  221
+    // This command is for internal debugging only.
  222
+    void _cmd_stats(const string& arg)
  223
+    {
  224
+        if (cred.login.length()) return;
  225
+        DEBUG("sending stats");
  226
+        _send_response(
  227
+            "[data_to_send]\n" +
  228
+            data_to_send.get_stats() +
  229
+            "\n[connected_fhs]\n" +
  230
+            connected_fhs.get_stats() +
  231
+            "\n[online_timers]\n" +
  232
+            online_timers.get_stats() +
  233
+            "\n[cleanup_timers]\n" +
  234
+            cleanup_timers.get_stats() +
  235
+            "\n[pairs_by_fhs]\n" +
  236
+            pairs_by_fhs.get_stats()
  237
+        );
  238
+    }
  239
+
  240
+    // Send response anc close the connection.
  241
+    void _send_response(const string& d, const string& code = "")
  242
+    {
  243
+        fh()->write(
  244
+            "HTTP/1.0 " + (code.length()? code : "200 OK") + "\r\n" +
  245
+            "Content-Type: text/plain\r\n" +
  246
+            "Content-Length: " + lexical_cast<string>(d.length()) + "\r\n\r\n" +
  247
+            d
  248
+        );
  249
+        fh()->flush(); // MUST be executed! else SIGPIPE may be issued
  250
+        fh()->shutdown(2);
  251
+        pairs->clear();
  252
+        data = "";
  253
+    }
  254
+};
  255
+
  256
+}
  257
+#endif
146  cpp/src/Connection/Wait.h
... ...
@@ -0,0 +1,146 @@
  1
+//
  2
+// WAIT connection.
  3
+//
  4
+#ifndef REALPLEXOR_CONNECTION_WAIT_H
  5
+#define REALPLEXOR_CONNECTION_WAIT_H
  6
+
  7
+namespace Connection {
  8
+using namespace Realplexor;
  9
+using std::shared_ptr;
  10
+
  11
+class Wait: public Realplexor::Event::Connection
  12
+{
  13
+    shared_ptr<DataPairChain> pairs;
  14
+    string _name;
  15
+
  16
+public:
  17
+    Wait(filehandle_t fh, Realplexor::Event::ServerBase* server): Connection(fh, server) 
  18
+    {
  19
+        pairs.reset(new DataPairChain());
  20
+    }
  21
+    
  22
+    // Hack: unfortunately C++ cannot call overriden virtual functions from base class destructors.
  23
+    virtual ~Wait()
  24
+    {
  25
+        ondestruct();
  26
+    }
  27
+
  28
+    // Called when a data is available to read.
  29
+    virtual void onread(size_t nread) 
  30
+    {
  31
+        Realplexor::Event::Connection::onread(nread);
  32
+
  33
+        // Data must be ignored, identifier is already extracted.
  34
+        if (pairs->size()) {
  35
+            return;
  36
+        }
  37
+
  38
+        // Try to extract IDs from the new data chunk.
  39
+        Realplexor::LimitIdsSet limit_ids;
  40
+        Realplexor::CredPair cred;
  41
+        if (Realplexor::Common::extract_pairs(data, *pairs, limit_ids, cred)) {
  42
+            if (!pairs->size()) throw runtime_error("Empty identifier passed");
  43
+            
  44
+            // Check if we have special marker: IFRAME.
  45
+            if (pairs->begin()->id == CONFIG.iframe_id) {
  46
+                pairs->clear();
  47
+                DEBUG("IFRAME marker received, sending content");
  48
+                Realplexor::Common::send_static(fh(), CONFIG.static_iframe.content, CONFIG.static_iframe.time, "text/html; charset=" + CONFIG.charset);
  49
+                return;
  50
+            }
  51
+            // Check if we have special marker: SCRIPT.
  52
+            if (pairs->begin()->id == CONFIG.script_id) {
  53
+                pairs->clear();
  54
+                DEBUG("SCRIPT marker received, sending content");
  55
+                Realplexor::Common::send_static(fh(), CONFIG.static_script.content, CONFIG.static_script.time, "text/javascript; charset=" + CONFIG.charset);
  56
+                return;
  57
+            }
  58
+
  59
+            // IDs are extracted. Send response headers immediately.
  60
+            // We send response AFTER reading IDs, because before 
  61
+            // this reading we don't know if a static page or 
  62
+            // a data was requested.
  63
+            fh()->write(
  64
+                "HTTP/1.1 200 OK\r\n"
  65
+                "Connection: close\r\n"
  66
+                "Cache-Control: no-store, no-cache, must-revalidate\r\n"
  67
+                "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
  68
+                "Content-Type: text/javascript; charset=" + CONFIG.charset + "\r\n\r\n" +
  69
+                " \r\n" // this immediate space plus text/javascript hides XMLHttpRequest in FireBug console
  70
+            );
  71
+            fh()->flush();
  72
+
  73
+            // Ignore all other input from IN and register identifiers.
  74
+            data = "";
  75
+            pairs_by_fhs.set_pairs_for_fh(fh(), pairs);
  76
+            IdsToSendSet ids_to_process;
  77
+            for (auto& pair: *pairs) {
  78
+                connected_fhs.add_to_id(pair.id, pair.cursor, fh());
  79
+                // Create new online timer, but do not start it - it is 
  80
+                // started at LAST connection close, later.
  81
+                string id = pair.id;
  82
+                auto callback = [id]() { 
  83
+                    LOGGER("[" + id + "] is now offline");
  84
+                    events.notify(DataEventType::OFFLINE, id);
  85
+                    // It is better to change the order of upper two lines for more clear logging,
  86
+                    // but it is already covered by auto-tests, so...
  87
+                };
  88
+                bool firstTime = online_timers.assign_stopped_timer_for_id<decltype(callback)>(id, callback);
  89
+                if (firstTime) {
  90
+                    // If above returned true, this ID was offline, but become online.
  91
+                    events.notify(DataEventType::ONLINE, id);
  92
+                }
  93
+                ids_to_process.insert(pair.id);
  94
+            }
  95
+            DEBUG("registered"); // ids are already in the debug line prefix
  96
+            // Try to send pendings.
  97
+            Realplexor::Common::send_pendings(ids_to_process);
  98
+            return;
  99
+        }
  100
+
  101
+        // Check for the data overflow.
  102
+        if (data.length() > CONFIG.wait_maxlen) {
  103
+            throw runtime_error("overflow (received " + lexical_cast<string>(data.length()) + " bytes total)");
  104
+        }
  105
+    }
  106
+
  107
+    // Called on timeout (send error message).
  108
+    virtual void ontimeout()
  109
+    {
  110
+        if (fh()) {
  111
+            fh()->flush();
  112
+            fh()->shutdown(2);
  113
+        }
  114
+        Realplexor::Event::Connection::ontimeout();
  115
+    }
  116
+
  117
+    // Called on client disconnect.
  118
+    virtual void onclose() 
  119
+    {
  120
+        if (pairs->size()) {
  121
+            for (auto& pair: *pairs) {
  122
+                // Remove the client from all lists.
  123
+                connected_fhs.del_from_id_by_fh(pair.id, fh());
  124
+                // Turn on offline timer if it was THE LAST connection.
  125
+                if (!connected_fhs.get_num_fhs_by_id(pair.id)) {
  126
+                    online_timers.start_timer_by_id(pair.id, CONFIG.offline_timeout);
  127
+                }
  128
+            }
  129
+        }
  130
+        pairs_by_fhs.remove_by_fh(fh());
  131
+    }
  132
+
  133
+    // Connection name is its ID.
  134
+    virtual string name()
  135
+    {
  136
+        if (!_name.length() && pairs->size()) {
  137
+            _name = lexical_cast<string>(pairs->begin()->cursor) + ":" + pairs->begin()->id +
  138
+                (pairs->size() > 1? "(and " + lexical_cast<string>(pairs->size() - 1) + " more)" : "");
  139
+        }
  140
+        return _name;
  141
+    }
  142
+
  143
+};
  144
+
  145
+}
  146
+#endif
38  cpp/src/Make.sh
... ...
@@ -0,0 +1,38 @@
  1
+#!/bin/bash
  2
+
  3
+# 
  4
+# Build & install GCC pre-requisities:
  5
+#   gmp, mpc, mprf
  6
+#
  7
+# Build & install GCC 4.6:
  8
+#   echo "/usr/local/lib" >> /etc/ld.so.conf.d/local.conf && ldconfig
  9
+#   ./configure --with-mpc=/usr/local --with-gmp=/usr/local --with-mpfr=/usr/local --enable-languages=c,c++ --without-ppl --without-cloog
  10
+#
  11
+# Now you have the new GCC installed.
  12
+#
  13
+# Build & install Boost with C++0x support:
  14
+#   ./bootstrap.sh
  15
+#   ./bjam --toolset=gcc --cxxflags=-std=gnu++0x stage
  16
+#   ./bjam --toolset=gcc --cxxflags=-std=c++0x --build-type=complete --layout=tagged stage
  17
+#   ./bjam -a architecture=x86 instruction-set=i686 toolset=gcc cxxflags=-std=gnu++0x build-type=complete
  18
+#
  19
+# Build libev:
  20
+#   (cd libev && make)
  21
+#
  22
+# Install libmemcached:
  23
+#   yum install libmemcached-devel
  24
+#
  25
+
  26
+GCC="g++ -std=gnu++0x -static"
  27
+#GCC="g++ -std=gnu++0x"
  28
+#DEBUG="-g3 -O0"
  29
+DEBUG="-O3"
  30
+export INCLUDE=$INCLUDE:libev
  31
+export LIB=$INCLUDE:libev/.libs
  32
+
  33
+rm -f ../dklab_realplexor 2>/dev/null
  34
+$GCC dklab_realplexor.cpp \
  35
+    $DEBUG -Wfatal-errors -Wall -Werror \
  36
+    -pthread -lcrypt -lboost_filesystem -lboost_system -lboost_regex -lev \
  37
+    -o ../../dklab_realplexor
  38
+exit $?
316  cpp/src/Realplexor/Common.h
... ...
@@ -0,0 +1,316 @@
  1
+#ifndef REALPLEXOR_COMMON_H
  2
+#define REALPLEXOR_COMMON_H
  3
+
  4
+// Speedup macro.
  5
+#define LOGGER(s) if (CONFIG.verbosity > 0) Realplexor::Common::logger((s))
  6
+
  7
+
  8
+namespace Realplexor {
  9
+
  10
+class Common 
  11
+{
  12
+    // This is to execute a piece of code automatically.
  13
+    static Common instance;
  14
+    Common()
  15
+    {
  16
+        CONFIG.set_logger(&logger);
  17
+    }
  18
+
  19
+public:
  20
+
  21
+    // Logger routine.
  22
+    // This function MUST be declared in Realplexor::Common, after all of 
  23
+    // Storages are already defined.
  24
+    static void logger(const string& s)
  25
+    {
  26
+        int verb = CONFIG.verbosity;
  27
+        if (verb == 0) return;
  28
+        string msg = s;
  29
+        if (verb > 2) {
  30
+            msg = msg + "\n  " + 
  31
+                "[pairs_by_fhs=" + lexical_cast<string>(pairs_by_fhs.get_num_items()) +
  32
+                " data_to_send=" + lexical_cast<string>(data_to_send.get_num_items()) +
  33
+                " connected_fhs=" + lexical_cast<string>(connected_fhs.get_num_items()) +
  34
+                " online_timers=" + lexical_cast<string>(online_timers.get_num_items()) +
  35
+                " cleanup_timers=" + lexical_cast<string>(cleanup_timers.get_num_items()) +
  36
+                " events=" + lexical_cast<string>(events.get_num_items()) +
  37
+                "]";
  38
+        }
  39
+        if (verb >= 2) {
  40
+            cout << "[" << strftime_std(from_time_t(ev::now())) << "] " << msg << endl;
  41
+        } else {
  42
+            cout << msg << endl;
  43
+        }
  44
+    }
  45
+
  46
+    // Extract pairs [cursor, ID] from the client data. 
  47
+    // Return [ [cursor1, id1], [cursor2, id2], ... ] or undef if 
  48
+    // no "identifier" marker is found yet.
  49
+    //
  50
+    // If you call this sub in a list context, the second return
  51
+    // value is the list of IDs marked by "*" at identifier=...
  52
+    // (this means that these IDs must be listened by a client
  53
+    // too to receive the data).
  54
+    //
  55
+    // If login and password are specified, third return value
  56
+    // is [login, password] pair.
  57
+    // 
  58
+    // Format: 
  59
+    // - identifier=abc                         [single identifier]
  60
+    // - identifier=abc,def,ghi                 [multiple identifiers]
  61
+    // - identifier=12345.23:abc,12345:def      [where 12345... is the cursor]
  62
+    // - identifier=abc,def,*ghi,*jkl           [multiple ids, and (ghi, jkl) is returned as second list element]
  63
+    // - identifier=LOGIN:PASS@abc,10:def,*ghi  [same as above, but login and password are specified]
  64
+    //
  65
+    // Returns true if the extraction is succeeded.
  66
+    static bool extract_pairs(string& data, DataPairChain& pairs, LimitIdsSet& limit_ids, Realplexor::CredPair& cred)
  67
+    {
  68
+        //
  69
+        // TODO: this and derived functions must be strongly optimized.
  70
+        //
  71
+        // Return fast if no identifier marker is presented yet.
  72
+        if (data.find(CONFIG.IDENTIFIER_PLUS_EQ) == data.npos) return false;
  73
+        
  74
+        // Identifier marker seems to be presented. Remove referrer headers.
  75
+        const char* start = data.c_str();
  76
+        const char* p = strcasestr(start, "\nReferer:");
  77
+        if (p) {
  78
+            size_t pos = p - start + 1;
  79
+            while (pos < data.length() && data[pos] != '\n') data[pos++] = ' ';
  80
+        }
  81
+
  82
+        // Now check for identifier freely.
  83
+        string ids;
  84
+        if (!_extract_login_password_ids(data, cred, ids)) return false;
  85
+        if (!_split_ids(ids, pairs, limit_ids)) return false;
  86
+        return true;
  87
+    }
  88
+
  89
+    // Send IFRAME content.
  90
+    static void send_static(filehandle_t fh, const string& content, const string& last_modified, const string& type)
  91
+    {
  92
+        fh->write("HTTP/1.1 200 OK\r\n");
  93
+        fh->write("Connection: close\r\n");
  94
+        fh->write("Content-Type: " + type + "\r\n");
  95
+        fh->write("Last-Modified: " + last_modified + "\r\n");
  96
+        fh->write("Expires: Wed, 08 Jul 2037 22:53:52 GMT\r\n");
  97
+        fh->write("Cache-Control: public\r\n");
  98
+        fh->write("\r\n");
  99
+        fh->write(content);
  100
+        fh->flush(); // MUST be executed! shutdown() does not issue flush()!
  101
+        fh->shutdown(2); // don't use close, it breaks event machine!
  102
+    }
  103
+
  104
+    // Send first pending data to clients with specified IDs.
  105
+    // Remove sent data from the queue and close connections to clients.
  106
+    template <class Cont>
  107
+    static void send_pendings(const Cont& ids)
  108
+    {
  109
+        // Remove old data; do it BEFORE data processing/sending. Why?
  110
+        // Because if we receive 1000 new data rows for the same ID,
  111
+        // they will all be sent to all connected clients and slow down
  112
+        // the performance. When we clean the data before sending, we
  113
+        // guarantee that the inner loop will have less than MAX_DATA_FOR_ID
  114
+        // iterations for each ID.
  115
+        for (auto& id: ids) {
  116
+            data_to_send.clean_old_data_for_id(id, CONFIG.max_data_for_id);
  117
+        }
  118
+
  119
+        // Collect data to be sent to each connection at %data_by_fh.
  120
+        // For each connection also collect matched IDs, so each client
  121
+        // receives only the list of IDs which is matched by his request
  122
+        // (client does not see IDs of other clients).
  123
+        DataToSendByFh data_by_fh;
  124
+        set<ident_t> seen_ids; // ordered - for logging
  125
+
  126
+        // Iterate over all IDs to be checked.
  127
+        for (auto& id: ids) {
  128
+            // All data items for this ID.
  129
+            const DataChunkChain& data = data_to_send.get_data_by_id(id);
  130
+            if (!data.size()) continue;
  131
+            
  132
+            // Who listens this ID.
  133
+            const DataCursorFhByFh& fhs_hash = connected_fhs.get_hash_by_id(id);
  134
+            if (!fhs_hash.size()) continue;
  135
+
  136
+            // Iterate over all connections which listen this ID.
  137
+            for (const DataCursorFhByFh::value_type& cursor_and_fh: fhs_hash) {
  138
+                // Process a single FH which listens this ID at listen_cursor.
  139
+                cursor_t listen_cursor = cursor_and_fh.second.cursor;
  140
+                filehandle_t fh = cursor_and_fh.second.fh;
  141
+
  142
+                // What other IDs are listened by this FH.
  143
+                const DataPairChain& what_listens_this_fh = pairs_by_fhs.get_pairs_by_fh(fh);
  144
+                
  145
+                // Iterate over data items.
  146
+                for (const DataChunk& item: data) {
  147
+                    // If we found an element with smaller cursor, abort iteration,
  148
+                    // because all elements are sorted by cursor (bigger cursor first).
  149
+                    if (item.cursor <= listen_cursor) break;
  150
+                    
  151
+                    // Process a single data item in context of this FH.
  152
+                    const cursor_t&                cursor    = item.cursor;
  153
+                    const shared_ptr<string>&      rdata     = item.rdata;
  154
+                    const unordered_set<ident_t>&  limit_ids = *item.rlimit_ids;
  155
+                    
  156
+                    // Filter data invisible to this client.
  157
+                    if (limit_ids.size()) {
  158
+                        bool matched = false;
  159
+                        for (auto& id_which_is_listened: what_listens_this_fh) {
  160
+                            if (limit_ids.count(id_which_is_listened.id)) {
  161
+                                matched = true;
  162
+                                break;
  163
+                            }
  164
+                        }
  165
+                        if (!matched) continue;
  166
+                    }
  167
+
  168
+                    // Hash by dataref to avoid to send the same data 
  169
+                    // twice if it is appeared in multiple IDs.
  170
+                    if (!data_by_fh.count(fh.get()) || !data_by_fh[fh.get()].count(rdata.get())) {
  171
+                        DataToSendChunk& dts = data_by_fh[fh.get()][rdata.get()]; // it also creates this element
  172
+                        dts.fh      = fh;
  173
+                        dts.cursor  = cursor;
  174
+                        dts.rdata   = rdata;
  175
+                        dts.ids[id] = cursor;
  176
+                    } else {
  177
+                        // Add new ID to the list of IDs for this data.
  178
+                        data_by_fh[fh.get()][rdata.get()].ids[id] = cursor;
  179
+                    }
  180
+
  181
+                    // This is mostly for logging purposes.
  182
+                    seen_ids.insert(id);
  183
+                }
  184
+            }
  185
+        }
  186
+
  187
+        // Perform sending operation.
  188
+        _do_send(data_by_fh, seen_ids);
  189
+    }
  190
+
  191
+private:
  192
+
  193
+    // Shutdown a connection and remove all references to it.
  194
+    static int _shutdown_fh(filehandle_t fh)
  195
+    {
  196
+        // Remove all references to $fh from everywhere.
  197
+        for (auto& pair: pairs_by_fhs.get_pairs_by_fh(fh)) {
  198
+            connected_fhs.del_from_id_by_fh(pair.id, fh);
  199
+        }
  200
+        pairs_by_fhs.remove_by_fh(fh);
  201
+        fh->flush(); // MUST be executed! shutdown() does not issue flush()!
  202
+        return fh->shutdown(2);
  203
+    }
  204
+
  205
+    // Send data to each connection (json array format).
  206
+    // Response format is:
  207
+    // [
  208
+    //   {
  209
+    //     "ids": { "id1": cursor1, "id2": cursor2, ... },
  210
+    //     "data": <data from server without headers>
  211
+    //   },
  212
+    //   {
  213
+    //     "ids": { "id3": cursor3, "id4": cursor4, ... },
  214
+    //     "data": <data from server without headers>
  215
+    //   },
  216
+    //   ...
  217
+    // }
  218
+    static void _do_send(DataToSendByFh& data_by_fh, set<ident_t>& seen_ids)
  219
+    {
  220
+        for (DataToSendByFh::value_type &pair: data_by_fh) {
  221
+            vector<string> out_vec;
  222
+
  223
+            // Additional ordering by raw data is for better determinism in tests.
  224
+            vector<DataToSendChunk*> triple_ptrs;
  225
+            transform(
  226
+                pair.second.begin(), pair.second.end(), 
  227
+                std::back_inserter(triple_ptrs), 
  228
+                [](DataToSendByDataRef::value_type& p) { return &p.second; }
  229
+            );
  230
+            sort(
  231
+                triple_ptrs.begin(), triple_ptrs.end(), 
  232
+                [](DataToSendChunk* a, DataToSendChunk* b) { 
  233
+                    return a->cursor < b->cursor? true : (a->cursor > b->cursor? false : (*a->rdata < *b->rdata)); 
  234
+                }
  235
+            );
  236
+
  237
+            // Build JSON result.
  238
+            for (DataToSendChunk* triple: triple_ptrs) {
  239
+                // Build one response block.
  240
+                // It's very to send cursors as string to avoid rounding.
  241
+                vector<string> ids = apply(triple->ids, [](const std::pair<ident_t, cursor_t>& pair) { return "\"" + pair.first + "\": \"" + lexical_cast<string>(pair.second) + "\""; });
  242
+                out_vec.push_back(
  243
+                    "  {\n"
  244
+                    "    \"ids\": { " + join(ids, ", ") + " },\n"
  245
+                    "    \"data\":" + (triple->rdata->find("\n") != string::npos? "\n" : " ") + *triple->rdata + "\n"
  246
+                    "  }"
  247
+                );
  248
+            }
  249
+
  250
+            // Join response blocks into one "multipart".
  251
+            string out = "[\n" + join(out_vec, ",\n") + "\n]";
  252
+            filehandle_t fh = pair.second.begin()->second.fh;
  253
+            // Attention! We MUST use print, not syswrite, because print correctly
  254
+            // continues broken transmits for large data packets.
  255
+            int r1 = fh->write(out);
  256
+            int r2 = _shutdown_fh(fh);
  257
+            logger(
  258
+                "<- sending " + lexical_cast<string>(out_vec.size()) + " responses " +
  259
+                "(" + lexical_cast<string>(out.length()) + " bytes) from " +
  260
+                "[" + join(seen_ids, ", ") + "] (print=" + lexical_cast<string>(r1) + ", shutdown=" + lexical_cast<string>(r2) + ")"
  261
+            );
  262
+        }
  263
+    }
  264
+
  265
+
  266
+    // Parses the string:
  267
+    //   identifier=login:pass@aaaa:4,bbb:5,...
  268
+    //              ^^^^^ ^^^^ ^^^^^^^^^^^^^^^^
  269
+    // and also
  270
+    //   identifier=aaaa:4,bbb:5,...
  271
+    static bool _extract_login_password_ids(const string& data, Realplexor::CredPair& cred, string& ids)
  272
+    {
  273
+        boost::smatch m;
  274
+        if (!regex_search(data, m, CONFIG.RE_LOGIN_PASSWORD_ID)) {
  275
+            return false;
  276
+        }
  277
+        cred.login = m[1];
  278
+        cred.password = m[2];
  279
+        ids = m[3];
  280
+        return true;
  281
+    }
  282
+
  283
+    // Splits a comma-separated list of IDs.
  284
+    static bool _split_ids(const string& ids, DataPairChain& pairs, LimitIdsSet& limit_ids)
  285
+    {
  286
+        cursor_t time = 0;
  287
+        size_t pos = 0;
  288
+        while (pos < ids.length()) {
  289
+            size_t comma = ids.find(',', pos);
  290
+            if (comma == ids.npos) comma = ids.length();
  291
+            boost::smatch m;
  292
+            if (regex_search((ids.begin() + pos), (ids.begin() + comma), m, CONFIG.RE_CURSOR_ID)) {
  293
+                if (m[1].length()) {
  294
+                    // ID with limiter.
  295
+                    limit_ids.insert(m[3]);
  296
+                } else {
  297
+                    // Not limiter or limiter, but in WAIT line.
  298
+                    if (m[2].length()) {
  299
+                        // with cursor
  300
+                        pairs.push_back(Realplexor::DataPair(lexical_cast<cursor_t>(m[2]), m[3]));
  301
+                    } else {
  302
+                        if (!time) time = Realplexor::Tools::time_hi_res();
  303
+                        pairs.push_back(Realplexor::DataPair(time, m[3]));
  304
+                    }
  305
+                }
  306
+            }
  307
+            pos = comma + 1;
  308
+        }
  309
+        return true;
  310
+    }
  311
+};
  312
+
  313
+Common Common::instance;
  314
+
  315
+}
  316
+#endif
215  cpp/src/Realplexor/Config.h
... ...
@@ -0,0 +1,215 @@
  1
+#ifndef REALPLEXOR_CONFIG_H
  2
+#define REALPLEXOR_CONFIG_H
  3
+
  4
+
  5
+namespace Realplexor {
  6
+
  7
+class Config 
  8
+{
  9
+    checked_map<string, string> config;
  10
+    logger_t logger;
  11
+
  12
+    struct StaticFile
  13
+    {
  14
+        string content;
  15
+        string time;
  16
+    };
  17
+
  18
+public:
  19
+    int                          verbosity;
  20
+    checked_map<string, string>  users;
  21
+    size_t                       max_data_for_id;
  22
+    string                       wait_addr;
  23
+    int                          wait_timeout;
  24
+    string                       in_addr;
  25
+    int                          in_timeout;
  26
+    string                       su_user;
  27
+    double                       max_mem_mb;
  28
+    size_t                       event_chain_len;
  29
+    size_t                       in_maxlen;
  30
+    int                          clean_id_after;
  31
+    string                       charset;
  32
+    size_t                       wait_maxlen;
  33
+    int                          offline_timeout;
  34
+    string                       iframe_id;
  35
+    string                       script_id;
  36
+    StaticFile                   static_iframe;
  37
+    StaticFile                   static_script;
  38
+
  39
+    string  IDENTIFIER_PLUS_EQ;
  40
+    regex   RE_LOGIN_PASSWORD_ID;
  41
+    regex   RE_CURSOR_ID;
  42
+
  43
+    Config(): config("config"), users("users list") 
  44
+    {
  45
+        logger = [](const string&)->void{}; // default
  46
+    }
  47
+
  48
+    // Sets another logger routine for this config.
  49
+    void set_logger(logger_t l)
  50
+    {
  51
+        logger = l;
  52
+    }
  53
+
  54
+    // Loads the config from the file.
  55
+    void load(string add, bool silent = false)
  56
+    {
  57
+        // Reset config.
  58
+        config.clear();
  59
+        // Read default config.
  60
+        _load_config_file(get_root_dir() + "/dklab_realplexor.conf");
  61
+        // Read custom config.
  62
+        if (add.length()) {
  63
+            if (is_file(add)) {
  64
+                if (!silent) logger("CONFIG: appending configuration from " + add);
  65
+                _load_config_file(add);
  66
+            } else {
  67
+                logger("CONFIG: file " + add + " does not exist, skipping");
  68
+            }
  69
+        }
  70
+        _load_users(config.get("USERS_FILE"));
  71
+        _parse();
  72
+    }
  73
+
  74
+    // Returns "" if reloading is succeeded, else returns the name of
  75
+    // option which could not be reloaded.
  76
+    string reload(string add)
  77
+    {
  78
+        regex lowlevel("^(WAIT_ADDR|WAIT_TIMEOUT|IN_ADDIN_TIMEOUT|SU_.*)$");
  79
+        regex ignore("^(HOOK_|.*_CONTENT)$");
  80
+        // Load new config.
  81
+        auto old = config;
  82
+        try {
  83
+            load(add);
  84
+        } catch (const std::exception &e) {
  85
+            logger(string("Error reloading config, continue with old settings: ") + e.what());
  86
+            config = old;
  87
+            _parse();
  88
+            return "";
  89
+        }
  90
+        for (auto& i: config) {
  91
+            string opt = i.first;
  92
+            string v_old = old.count(opt)? old[opt] : "";
  93
+            string v_new = config.count(opt)? config[opt] : "";
  94
+            if (v_old != v_new) {
  95
+                if (regex_match(opt, lowlevel)) return opt;
  96
+                if (regex_match(opt, ignore)) continue;
  97
+                logger("Option " + opt + " is changed: " + v_old + " -> " + v_new);
  98
+            }
  99
+        }
  100
+        return "";
  101
+    }
  102
+
  103
+
  104
+private:
  105
+
  106
+    string _perl_to_text(string fname)
  107
+    {
  108
+        string data = read_file(fname);
  109
+        if (regex_search(data, regex("^[^#]*[%$]CONFIG", regex::icase))) {
  110
+            // Perl config: run it & print the result for later parsing.
  111
+            data = backtick(