[Libreoffice-commits] online.git: Branch 'private/Ashod/nonblocking' - Makefile.am net/loolnb.cpp net/Socket.hpp net/WebSocketHandler.cpp net/WebSocketHandler.hpp

Jan Holesovsky kendy at collabora.com
Thu Feb 23 17:02:48 UTC 2017


 Makefile.am              |    1 
 net/Socket.hpp           |    4 
 net/WebSocketHandler.cpp |   95 +++++++++++++++++
 net/WebSocketHandler.hpp |  185 ++++++++++++++++++++++++++++++++++
 net/loolnb.cpp           |  250 -----------------------------------------------
 5 files changed, 289 insertions(+), 246 deletions(-)

New commits:
commit 70a473b081063a04bb22e79e569cb0b19fdf6b88
Author: Jan Holesovsky <kendy at collabora.com>
Date:   Thu Feb 23 17:57:59 2017 +0100

    nb: Moved the WebSocketHandler to an own header & cpp.
    
    The cpp is there to keep the header free from the POCO dependency.
    
    Change-Id: I2202c4ed403084ec1ed40001e10b1ec4fc226f7b

diff --git a/Makefile.am b/Makefile.am
index e4d4f75..cc5f9f7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -92,6 +92,7 @@ loolwsd_fuzzer_SOURCES = $(loolwsd_sources) \
 
 loolnb_SOURCES = net/loolnb.cpp \
                  net/Ssl.cpp \
+                 net/WebSocketHandler.cpp \
                  common/Log.cpp \
                  common/Util.cpp
 
diff --git a/net/Socket.hpp b/net/Socket.hpp
index 6020ca8..08112c5 100644
--- a/net/Socket.hpp
+++ b/net/Socket.hpp
@@ -16,9 +16,13 @@
 #include <unistd.h>
 
 #include <atomic>
+#include <cassert>
 #include <cerrno>
 #include <cstdlib>
 #include <cstring>
+#include <iostream>
+#include <memory>
+#include <mutex>
 #include <sstream>
 
 #include <Poco/Net/SocketAddress.h>
diff --git a/net/WebSocketHandler.cpp b/net/WebSocketHandler.cpp
new file mode 100644
index 0000000..8ad433e
--- /dev/null
+++ b/net/WebSocketHandler.cpp
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "WebSocketHandler.hpp"
+
+#include <Poco/MemoryStream.h>
+#include <Poco/Net/HTTPRequest.h>
+#include <Poco/Net/WebSocket.h>
+#include <Poco/StringTokenizer.h>
+
+void WebSocketHandler::handleWebsocketUpgrade()
+{
+    int number = 0;
+    Poco::MemoryInputStream message(&_socket->_inBuffer[0], _socket->_inBuffer.size());
+    Poco::Net::HTTPRequest req;
+    req.read(message);
+
+    // if we succeeded - remove that from our input buffer
+    // FIXME: We should check if this is GET or POST. For GET, we only
+    // can have a single request (headers only). For POST, we can/should
+    // use Poco HTMLForm to parse the post message properly.
+    // Otherwise, we should catch exceptions from the previous read/parse
+    // and assume we don't have sufficient data, so we wait some more.
+    _socket->_inBuffer.clear();
+
+    Poco::StringTokenizer tokens(req.getURI(), "/?");
+    if (tokens.count() == 4)
+    {
+        std::string subpool = tokens[2];
+        number = std::stoi(tokens[3]);
+
+        // complex algorithmic core:
+        number = number + 1;
+
+        std::string numberString = std::to_string(number);
+        std::ostringstream oss;
+        oss << "HTTP/1.1 200 OK\r\n"
+            << "Date: Once, Upon a time GMT\r\n" // Mon, 27 Jul 2009 12:28:53 GMT
+            << "Server: madeup string (Linux)\r\n"
+            << "Content-Length: " << numberString.size() << "\r\n"
+            << "Content-Type: text/plain\r\n"
+            << "Connection: Closed\r\n"
+            << "\r\n"
+            << numberString;
+        ;
+        std::string str = oss.str();
+        _socket->_outBuffer.insert(_socket->_outBuffer.end(), str.begin(), str.end());
+    }
+    else if (tokens.count() == 2 && tokens[1] == "ws")
+    { // create our websocket goodness ...
+        _wsVersion = std::stoi(req.get("Sec-WebSocket-Version", "13"));
+        _wsKey = req.get("Sec-WebSocket-Key", "");
+        _wsProtocol = req.get("Sec-WebSocket-Protocol", "chat");
+        std::cerr << "version " << _wsVersion << " key '" << _wsKey << "\n";
+        // FIXME: other sanity checks ...
+
+        std::ostringstream oss;
+        oss << "HTTP/1.1 101 Switching Protocols\r\n"
+            << "Upgrade: websocket\r\n"
+            << "Connection: Upgrade\r\n"
+            << "Sec-Websocket-Accept: " << computeAccept(_wsKey) << "\r\n"
+            << "\r\n";
+        std::string str = oss.str();
+        _socket->_outBuffer.insert(_socket->_outBuffer.end(), str.begin(), str.end());
+        _wsState = WEBSOCKET;
+    }
+    else
+        std::cerr << " unknown tokens " << tokens.count() << std::endl;
+}
+
+namespace {
+
+/// To make the protected 'computeAccept' accessible.
+class PublicComputeAccept : public Poco::Net::WebSocket {
+public:
+    static std::string doComputeAccept(const std::string &key)
+    {
+        return computeAccept(key);
+    }
+};
+
+}
+
+std::string WebSocketHandler::computeAccept(const std::string &key)
+{
+    return PublicComputeAccept::doComputeAccept(key);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/net/WebSocketHandler.hpp b/net/WebSocketHandler.hpp
new file mode 100644
index 0000000..286ac53
--- /dev/null
+++ b/net/WebSocketHandler.hpp
@@ -0,0 +1,185 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef INCLUDED_WEBSOCKETHANDLER_HPP
+#define INCLUDED_WEBSOCKETHANDLER_HPP
+
+#include "Socket.hpp"
+
+class WebSocketHandler : public SocketHandlerInterface
+{
+    std::unique_ptr<StreamSocket> _socket;
+    int _wsVersion;
+    std::string _wsKey;
+    std::string _wsProtocol;
+    std::vector<char> _wsPayload;
+    enum { HTTP, WEBSOCKET } _wsState;
+
+public:
+    WebSocketHandler() :
+        _wsVersion(0),
+        _wsState(HTTP)
+    {
+    }
+
+    /// Implementation of the SocketHandlerInterface.
+    virtual void setSocket(StreamSocket* socket) override
+    {
+        _socket.reset(socket);
+    }
+
+    /// Upgrade the http(s) connection to a websocket.
+    void handleWebsocketUpgrade();
+
+    enum WSOpCode {
+        Continuation, // 0x0
+        Text,         // 0x1
+        Binary,       // 0x2
+        Reserved1,    // 0x3
+        Reserved2,    // 0x4
+        Reserved3,    // 0x5
+        Reserved4,    // 0x6
+        Reserved5,    // 0x7
+        Close,        // 0x8
+        Ping,         // 0x9
+        Pong          // 0xa
+        // ... reserved
+    };
+
+    /// Implementation of the SocketHandlerInterface.
+    virtual void handleIncomingMessage() override
+    {
+        std::cerr << "incoming message with buffer size " << _socket->_inBuffer.size() << "\n";
+        if (_wsState == HTTP)
+        {
+            handleWebsocketUpgrade();
+            return;
+        }
+
+        // websocket fun !
+        size_t len = _socket->_inBuffer.size();
+        if (len < 2) // partial read
+            return;
+
+        unsigned char *p = reinterpret_cast<unsigned char*>(&_socket->_inBuffer[0]);
+        bool fin = p[0] & 0x80;
+        WSOpCode code = static_cast<WSOpCode>(p[0] & 0x0f);
+        bool hasMask = p[1] & 0x80;
+        size_t payloadLen = p[1] & 0x7f;
+        size_t headerLen = 2;
+
+        // normally - 7 bit length.
+        if (payloadLen == 126) // 2 byte length
+        {
+            if (len < 2 + 2)
+                return;
+
+            payloadLen = (((unsigned)p[2]) << 8) | ((unsigned)p[3]);
+            headerLen += 2;
+        }
+        else if (payloadLen == 127) // 8 byte length
+        {
+            if (len < 2 + 8)
+                return;
+
+            payloadLen = ((((uint64_t)(p[9])) <<  0) + (((uint64_t)(p[8])) <<  8) +
+                          (((uint64_t)(p[7])) << 16) + (((uint64_t)(p[6])) << 24) +
+                          (((uint64_t)(p[5])) << 32) + (((uint64_t)(p[4])) << 40) +
+                          (((uint64_t)(p[3])) << 48) + (((uint64_t)(p[2])) << 56));
+            // FIXME: crop read length to remove top / sign bits.
+            headerLen += 8;
+        }
+
+        unsigned char *data, *mask;
+
+        if (hasMask)
+        {
+            mask = p + headerLen;
+            headerLen += 4;
+        }
+
+        if (payloadLen + headerLen > len)
+        { // partial read wait for more data.
+            return;
+        }
+
+        data = p + headerLen;
+
+        if (hasMask)
+        {
+            for (size_t i = 0; i < payloadLen; ++i)
+                data[i] = data[i] ^ mask[i % 4];
+
+            // FIXME: copy and un-mask at the same time ...
+            _wsPayload.insert(_wsPayload.end(), data, data + payloadLen);
+        } else
+            _wsPayload.insert(_wsPayload.end(), data, data + payloadLen);
+
+        _socket->_inBuffer.erase(_socket->_inBuffer.begin(), _socket->_inBuffer.begin() + headerLen + payloadLen);
+
+        // FIXME: fin, aggregating payloads into _wsPayload etc.
+        handleMessage(fin, code, _wsPayload);
+        _wsPayload.clear();
+    }
+
+    void sendMessage(const std::vector<char> &data,
+                        WSOpCode code = WSOpCode::Binary)
+    {
+        size_t len = data.size();
+        bool fin = false;
+        bool mask = false;
+
+        unsigned char header[2];
+        header[0] = (fin ? 0x80 : 0) | static_cast<unsigned char>(code);
+        header[1] = mask ? 0x80 : 0;
+        _socket->_outBuffer.push_back((char)header[0]);
+
+        // no out-bound masking ...
+        if (len < 126)
+        {
+            header[1] |= len;
+            _socket->_outBuffer.push_back((char)header[1]);
+        }
+        else if (len <= 0xffff)
+        {
+            header[1] |= 126;
+            _socket->_outBuffer.push_back((char)header[1]);
+            _socket->_outBuffer.push_back(static_cast<char>((len >> 8) & 0xff));
+            _socket->_outBuffer.push_back(static_cast<char>((len >> 0) & 0xff));
+        }
+        else
+        {
+            header[1] |= 127;
+            _socket->_outBuffer.push_back((char)header[1]);
+            _socket->_outBuffer.push_back(static_cast<char>((len >> 56) & 0xff));
+            _socket->_outBuffer.push_back(static_cast<char>((len >> 48) & 0xff));
+            _socket->_outBuffer.push_back(static_cast<char>((len >> 40) & 0xff));
+            _socket->_outBuffer.push_back(static_cast<char>((len >> 32) & 0xff));
+            _socket->_outBuffer.push_back(static_cast<char>((len >> 24) & 0xff));
+            _socket->_outBuffer.push_back(static_cast<char>((len >> 16) & 0xff));
+            _socket->_outBuffer.push_back(static_cast<char>((len >> 8) & 0xff));
+            _socket->_outBuffer.push_back(static_cast<char>((len >> 0) & 0xff));
+        }
+
+        // FIXME: pick random number and mask in the outbuffer etc.
+        assert (!mask);
+
+        _socket->_outBuffer.insert(_socket->_outBuffer.end(), data.begin(), data.end());
+    }
+
+    /// To me overriden to handle the websocket messages the way you need.
+    virtual void handleMessage(bool fin, WSOpCode code, std::vector<char> &data) = 0;
+
+private:
+    static std::string computeAccept(const std::string &key);
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/net/loolnb.cpp b/net/loolnb.cpp
index 0428b4b..201f8a4 100644
--- a/net/loolnb.cpp
+++ b/net/loolnb.cpp
@@ -25,239 +25,17 @@
 #include <Poco/Runnable.h>
 #include <Poco/Thread.h>
 
-using Poco::MemoryInputStream;
-using Poco::StringTokenizer;
-
 #include "Socket.hpp"
 #include "ServerSocket.hpp"
 #include "SslSocket.hpp"
+#include "WebSocketHandler.hpp"
+
+using Poco::MemoryInputStream;
+using Poco::StringTokenizer;
 
 constexpr int HttpPortNumber = 9191;
 constexpr int SslPortNumber = 9193;
 
-static std::string computeAccept(const std::string &key);
-
-class WebSocketHandler : public SocketHandlerInterface
-{
-    std::unique_ptr<StreamSocket> _socket;
-    int _wsVersion;
-    std::string _wsKey;
-    std::string _wsProtocol;
-    std::vector<char> _wsPayload;
-    enum { HTTP, WEBSOCKET } _wsState;
-
-public:
-    WebSocketHandler() :
-        _wsVersion(0),
-        _wsState(HTTP)
-    {
-    }
-
-    /// Implementation of the SocketHandlerInterface.
-    virtual void setSocket(StreamSocket* socket) override
-    {
-        _socket.reset(socket);
-    }
-
-    void handleWebsocketUpgrade()
-    {
-        int number = 0;
-        MemoryInputStream message(&_socket->_inBuffer[0], _socket->_inBuffer.size());
-        Poco::Net::HTTPRequest req;
-        req.read(message);
-
-        // if we succeeded - remove that from our input buffer
-        // FIXME: We should check if this is GET or POST. For GET, we only
-        // can have a single request (headers only). For POST, we can/should
-        // use Poco HTMLForm to parse the post message properly.
-        // Otherwise, we should catch exceptions from the previous read/parse
-        // and assume we don't have sufficient data, so we wait some more.
-        _socket->_inBuffer.clear();
-
-        StringTokenizer tokens(req.getURI(), "/?");
-        if (tokens.count() == 4)
-        {
-            std::string subpool = tokens[2];
-            number = std::stoi(tokens[3]);
-
-            // complex algorithmic core:
-            number = number + 1;
-
-            std::string numberString = std::to_string(number);
-            std::ostringstream oss;
-            oss << "HTTP/1.1 200 OK\r\n"
-                << "Date: Once, Upon a time GMT\r\n" // Mon, 27 Jul 2009 12:28:53 GMT
-                << "Server: madeup string (Linux)\r\n"
-                << "Content-Length: " << numberString.size() << "\r\n"
-                << "Content-Type: text/plain\r\n"
-                << "Connection: Closed\r\n"
-                << "\r\n"
-                << numberString;
-            ;
-            std::string str = oss.str();
-            _socket->_outBuffer.insert(_socket->_outBuffer.end(), str.begin(), str.end());
-        }
-        else if (tokens.count() == 2 && tokens[1] == "ws")
-        { // create our websocket goodness ...
-            _wsVersion = std::stoi(req.get("Sec-WebSocket-Version", "13"));
-            _wsKey = req.get("Sec-WebSocket-Key", "");
-            _wsProtocol = req.get("Sec-WebSocket-Protocol", "chat");
-            std::cerr << "version " << _wsVersion << " key '" << _wsKey << "\n";
-            // FIXME: other sanity checks ...
-
-            std::ostringstream oss;
-            oss << "HTTP/1.1 101 Switching Protocols\r\n"
-                << "Upgrade: websocket\r\n"
-                << "Connection: Upgrade\r\n"
-                << "Sec-Websocket-Accept: " << computeAccept(_wsKey) << "\r\n"
-                << "\r\n";
-            std::string str = oss.str();
-            _socket->_outBuffer.insert(_socket->_outBuffer.end(), str.begin(), str.end());
-            _wsState = WEBSOCKET;
-        }
-        else
-            std::cerr << " unknown tokens " << tokens.count() << std::endl;
-    }
-
-    enum WSOpCode {
-        Continuation, // 0x0
-        Text,         // 0x1
-        Binary,       // 0x2
-        Reserved1,    // 0x3
-        Reserved2,    // 0x4
-        Reserved3,    // 0x5
-        Reserved4,    // 0x6
-        Reserved5,    // 0x7
-        Close,        // 0x8
-        Ping,         // 0x9
-        Pong          // 0xa
-        // ... reserved
-    };
-
-    /// Implementation of the SocketHandlerInterface.
-    virtual void handleIncomingMessage() override
-    {
-        std::cerr << "incoming message with buffer size " << _socket->_inBuffer.size() << "\n";
-        if (_wsState == HTTP)
-        {
-            handleWebsocketUpgrade();
-            return;
-        }
-
-        // websocket fun !
-        size_t len = _socket->_inBuffer.size();
-        if (len < 2) // partial read
-            return;
-
-        unsigned char *p = reinterpret_cast<unsigned char*>(&_socket->_inBuffer[0]);
-        bool fin = p[0] & 0x80;
-        WSOpCode code = static_cast<WSOpCode>(p[0] & 0x0f);
-        bool hasMask = p[1] & 0x80;
-        size_t payloadLen = p[1] & 0x7f;
-        size_t headerLen = 2;
-
-        // normally - 7 bit length.
-        if (payloadLen == 126) // 2 byte length
-        {
-            if (len < 2 + 2)
-                return;
-
-            payloadLen = (((unsigned)p[2]) << 8) | ((unsigned)p[3]);
-            headerLen += 2;
-        }
-        else if (payloadLen == 127) // 8 byte length
-        {
-            if (len < 2 + 8)
-                return;
-
-            payloadLen = ((((uint64_t)(p[9])) <<  0) + (((uint64_t)(p[8])) <<  8) +
-                          (((uint64_t)(p[7])) << 16) + (((uint64_t)(p[6])) << 24) +
-                          (((uint64_t)(p[5])) << 32) + (((uint64_t)(p[4])) << 40) +
-                          (((uint64_t)(p[3])) << 48) + (((uint64_t)(p[2])) << 56));
-            // FIXME: crop read length to remove top / sign bits.
-            headerLen += 8;
-        }
-
-        unsigned char *data, *mask;
-
-        if (hasMask)
-        {
-            mask = p + headerLen;
-            headerLen += 4;
-        }
-
-        if (payloadLen + headerLen > len)
-        { // partial read wait for more data.
-            return;
-        }
-
-        data = p + headerLen;
-
-        if (hasMask)
-        {
-            for (size_t i = 0; i < payloadLen; ++i)
-                data[i] = data[i] ^ mask[i % 4];
-
-            // FIXME: copy and un-mask at the same time ...
-            _wsPayload.insert(_wsPayload.end(), data, data + payloadLen);
-        } else
-            _wsPayload.insert(_wsPayload.end(), data, data + payloadLen);
-
-        _socket->_inBuffer.erase(_socket->_inBuffer.begin(), _socket->_inBuffer.begin() + headerLen + payloadLen);
-
-        // FIXME: fin, aggregating payloads into _wsPayload etc.
-        handleMessage(fin, code, _wsPayload);
-        _wsPayload.clear();
-    }
-
-    void sendMessage(const std::vector<char> &data,
-                        WSOpCode code = WSOpCode::Binary)
-    {
-        size_t len = data.size();
-        bool fin = false;
-        bool mask = false;
-
-        unsigned char header[2];
-        header[0] = (fin ? 0x80 : 0) | static_cast<unsigned char>(code);
-        header[1] = mask ? 0x80 : 0;
-        _socket->_outBuffer.push_back((char)header[0]);
-
-        // no out-bound masking ...
-        if (len < 126)
-        {
-            header[1] |= len;
-            _socket->_outBuffer.push_back((char)header[1]);
-        }
-        else if (len <= 0xffff)
-        {
-            header[1] |= 126;
-            _socket->_outBuffer.push_back((char)header[1]);
-            _socket->_outBuffer.push_back(static_cast<char>((len >> 8) & 0xff));
-            _socket->_outBuffer.push_back(static_cast<char>((len >> 0) & 0xff));
-        }
-        else
-        {
-            header[1] |= 127;
-            _socket->_outBuffer.push_back((char)header[1]);
-            _socket->_outBuffer.push_back(static_cast<char>((len >> 56) & 0xff));
-            _socket->_outBuffer.push_back(static_cast<char>((len >> 48) & 0xff));
-            _socket->_outBuffer.push_back(static_cast<char>((len >> 40) & 0xff));
-            _socket->_outBuffer.push_back(static_cast<char>((len >> 32) & 0xff));
-            _socket->_outBuffer.push_back(static_cast<char>((len >> 24) & 0xff));
-            _socket->_outBuffer.push_back(static_cast<char>((len >> 16) & 0xff));
-            _socket->_outBuffer.push_back(static_cast<char>((len >> 8) & 0xff));
-            _socket->_outBuffer.push_back(static_cast<char>((len >> 0) & 0xff));
-        }
-
-        // FIXME: pick random number and mask in the outbuffer etc.
-        assert (!mask);
-
-        _socket->_outBuffer.insert(_socket->_outBuffer.end(), data.begin(), data.end());
-    }
-
-    virtual void handleMessage(bool fin, WSOpCode code, std::vector<char> &data) = 0;
-};
-
 class SimpleResponseClient : public WebSocketHandler
 {
 public:
@@ -417,24 +195,4 @@ int main(int argc, const char**argv)
     return 0;
 }
 
-// Saves writing this ourselves:
-
-#include <Poco/Net/WebSocket.h>
-
-namespace {
-#include <Poco/Net/WebSocket.h>
-    struct Puncture : private Poco::Net::WebSocket {
-        static std::string doComputeAccept(const std::string &key)
-        {
-            return computeAccept(key);
-        }
-    };
-}
-
-static std::string computeAccept(const std::string &key)
-{
-    return Puncture::doComputeAccept(key);
-}
-
-
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */


More information about the Libreoffice-commits mailing list