[Libreoffice-commits] online.git: Branch 'distro/collabora/collabora-online-1-9' - 13 commits - loleaflet/build loleaflet/dist loleaflet/main.js loleaflet/reference.html loleaflet/src loolwsd/Admin.cpp loolwsd/Admin.hpp loolwsd/AdminModel.cpp loolwsd/AdminModel.hpp loolwsd/ClientSession.cpp loolwsd/ClientSession.hpp loolwsd/Connect.cpp loolwsd/DocumentBroker.cpp loolwsd/DocumentBroker.hpp loolwsd/Exceptions.hpp loolwsd/IoUtil.cpp loolwsd/IoUtil.hpp loolwsd/LOOLKit.cpp loolwsd/LOOLProtocol.hpp loolwsd/LOOLSession.cpp loolwsd/LOOLSession.hpp loolwsd/LOOLStress.cpp loolwsd/LOOLWebSocket.hpp loolwsd/LOOLWSD.cpp loolwsd/Makefile.am loolwsd/protocol.txt loolwsd/Storage.cpp loolwsd/Storage.hpp loolwsd/test loolwsd/Unit.hpp loolwsd/UnitHTTP.cpp loolwsd/UnitHTTP.hpp loolwsd/Util.cpp loolwsd/Util.hpp

Jan Holesovsky kendy at collabora.com
Thu Nov 10 09:54:56 UTC 2016


 loleaflet/build/deps.js                  |    5 +
 loleaflet/dist/toolbar/toolbar.js        |    2 
 loleaflet/main.js                        |    7 --
 loleaflet/reference.html                 |   91 +++++++++++++++++++++++++++++
 loleaflet/src/control/Control.Menubar.js |    2 
 loleaflet/src/core/Socket.js             |   24 ++++++-
 loleaflet/src/layer/tile/TileLayer.js    |    2 
 loleaflet/src/map/Map.js                 |   69 ++++------------------
 loleaflet/src/map/handler/Map.WOPI.js    |   66 +++++++++++++++++++++
 loolwsd/Admin.cpp                        |    5 -
 loolwsd/Admin.hpp                        |    4 -
 loolwsd/AdminModel.cpp                   |   34 ++++------
 loolwsd/AdminModel.hpp                   |   13 ++--
 loolwsd/ClientSession.cpp                |   15 ++++
 loolwsd/ClientSession.hpp                |    9 ++
 loolwsd/Connect.cpp                      |   10 +--
 loolwsd/DocumentBroker.cpp               |   26 +++++++-
 loolwsd/DocumentBroker.hpp               |   18 ++---
 loolwsd/Exceptions.hpp                   |    2 
 loolwsd/IoUtil.cpp                       |   79 +++++++++----------------
 loolwsd/IoUtil.hpp                       |   29 ++-------
 loolwsd/LOOLKit.cpp                      |   18 ++---
 loolwsd/LOOLProtocol.hpp                 |    5 -
 loolwsd/LOOLSession.cpp                  |   16 ++---
 loolwsd/LOOLSession.hpp                  |   10 +--
 loolwsd/LOOLStress.cpp                   |    6 -
 loolwsd/LOOLWSD.cpp                      |   97 +++++++++++++++++--------------
 loolwsd/LOOLWebSocket.hpp                |   86 +++++++++++++++++++++++++++
 loolwsd/Makefile.am                      |    3 
 loolwsd/Storage.cpp                      |   12 ++-
 loolwsd/Storage.hpp                      |    9 ++
 loolwsd/Unit.hpp                         |    6 -
 loolwsd/UnitHTTP.cpp                     |    2 
 loolwsd/UnitHTTP.hpp                     |    4 -
 loolwsd/Util.cpp                         |   35 +++++------
 loolwsd/Util.hpp                         |    1 
 loolwsd/protocol.txt                     |   21 ++++++
 loolwsd/test/TileCacheTests.cpp          |   56 ++++++++---------
 loolwsd/test/UnitAdmin.cpp               |   20 +++---
 loolwsd/test/UnitFonts.cpp               |   10 +--
 loolwsd/test/UnitFuzz.cpp                |    4 -
 loolwsd/test/UnitPrefork.cpp             |    8 +-
 loolwsd/test/helpers.hpp                 |   40 ++++++------
 loolwsd/test/httpcrashtest.cpp           |   12 +--
 loolwsd/test/httpwserror.cpp             |   12 +--
 loolwsd/test/httpwstest.cpp              |   78 ++++++++++++------------
 46 files changed, 677 insertions(+), 406 deletions(-)

New commits:
commit 0c242a4160be7aaf06486cc007c2eb92f7a83454
Author: Jan Holesovsky <kendy at collabora.com>
Date:   Thu Nov 10 09:47:25 2016 +0100

    Implement a thread-safe websocket inherited from Poco::Net::WebSocket.
    
    sendFrame() implemented in LOOLWebSocket is thread safe, and also deals with
    large messages - sends the "nextmessage: size=..." frame before the actual
    large frame.
    
    The problem this is attempting to solve was that when sending a large frame,
    it was split to multiple packets.  During that, another frame was sent from a
    different thread; which lead to confusion, and the resulting frame was
    corrupted (because it ended up composed from unrelated packets).
    
    Change-Id: Ie85952e431b1cad2fdc6e3c64df8a444ea0ae971
    (cherry picked from commit 0674e87831ecf035f6880b9e68d3b9fdd51c1f31)

diff --git a/loolwsd/Admin.cpp b/loolwsd/Admin.cpp
index 04d479b..12b4c50 100644
--- a/loolwsd/Admin.cpp
+++ b/loolwsd/Admin.cpp
@@ -21,7 +21,6 @@
 #include <Poco/Net/HTTPServerResponse.h>
 #include <Poco/Net/NetException.h>
 #include <Poco/Net/SecureServerSocket.h>
-#include <Poco/Net/WebSocket.h>
 #include <Poco/StringTokenizer.h>
 #include <Poco/Util/ServerApplication.h>
 #include <Poco/Util/Timer.h>
@@ -33,6 +32,7 @@
 #include "FileServer.hpp"
 #include "IoUtil.hpp"
 #include "LOOLProtocol.hpp"
+#include <LOOLWebSocket.hpp>
 #include "LOOLWSD.hpp"
 #include "Log.hpp"
 #include "Storage.hpp"
@@ -47,7 +47,6 @@ using Poco::Net::HTTPRequestHandler;
 using Poco::Net::HTTPResponse;
 using Poco::Net::HTTPServerRequest;
 using Poco::Net::HTTPServerResponse;
-using Poco::Net::WebSocket;
 using Poco::Util::Application;
 
 bool AdminRequestHandler::adminCommandHandler(const std::vector<char>& payload)
@@ -207,7 +206,7 @@ bool AdminRequestHandler::adminCommandHandler(const std::vector<char>& payload)
 /// Handle admin requests.
 void AdminRequestHandler::handleWSRequests(HTTPServerRequest& request, HTTPServerResponse& response, int sessionId)
 {
-    _adminWs = std::make_shared<WebSocket>(request, response);
+    _adminWs = std::make_shared<LOOLWebSocket>(request, response);
 
     {
         std::unique_lock<std::mutex> modelLock(_admin->getLock());
diff --git a/loolwsd/Admin.hpp b/loolwsd/Admin.hpp
index e82775d..cb5d83f 100644
--- a/loolwsd/Admin.hpp
+++ b/loolwsd/Admin.hpp
@@ -15,7 +15,6 @@
 #include <Poco/Net/HTTPRequest.h>
 #include <Poco/Net/HTTPRequestHandler.h>
 #include <Poco/Net/HTTPServer.h>
-#include <Poco/Net/WebSocket.h>
 #include <Poco/Runnable.h>
 #include <Poco/Types.h>
 #include <Poco/Util/Timer.h>
@@ -23,6 +22,7 @@
 
 #include "AdminModel.hpp"
 #include "Log.hpp"
+#include <LOOLWebSocket.hpp>
 
 class Admin;
 
@@ -43,7 +43,7 @@ private:
 
 private:
     Admin* _admin;
-    std::shared_ptr<Poco::Net::WebSocket> _adminWs;
+    std::shared_ptr<LOOLWebSocket> _adminWs;
     int _sessionId;
     bool _isAuthenticated;
 };
diff --git a/loolwsd/AdminModel.cpp b/loolwsd/AdminModel.cpp
index d460e53..62d4ab7 100644
--- a/loolwsd/AdminModel.cpp
+++ b/loolwsd/AdminModel.cpp
@@ -15,12 +15,12 @@
 #include <sstream>
 #include <string>
 
-#include <Poco/Net/WebSocket.h>
 #include <Poco/Process.h>
 #include <Poco/StringTokenizer.h>
 #include <Poco/URI.h>
 
 #include "LOOLProtocol.hpp"
+#include <LOOLWebSocket.hpp>
 #include "Log.hpp"
 #include "Unit.hpp"
 #include "Util.hpp"
@@ -147,7 +147,7 @@ unsigned AdminModel::getTotalMemoryUsage()
     return totalMem;
 }
 
-void AdminModel::subscribe(int nSessionId, std::shared_ptr<Poco::Net::WebSocket>& ws)
+void AdminModel::subscribe(int nSessionId, std::shared_ptr<LOOLWebSocket>& ws)
 {
     const auto ret = _subscribers.emplace(nSessionId, Subscriber(nSessionId, ws));
     if (!ret.second)
diff --git a/loolwsd/AdminModel.hpp b/loolwsd/AdminModel.hpp
index a1c4812..b24776e 100644
--- a/loolwsd/AdminModel.hpp
+++ b/loolwsd/AdminModel.hpp
@@ -14,10 +14,10 @@
 #include <set>
 #include <string>
 
-#include <Poco/Net/WebSocket.h>
 #include <Poco/Process.h>
 
 #include "Log.hpp"
+#include <LOOLWebSocket.hpp>
 #include "Util.hpp"
 
 /// A client view in Admin controller.
@@ -91,7 +91,7 @@ private:
 class Subscriber
 {
 public:
-    Subscriber(int sessionId, std::shared_ptr<Poco::Net::WebSocket>& ws)
+    Subscriber(int sessionId, std::shared_ptr<LOOLWebSocket>& ws)
         : _sessionId(sessionId),
           _ws(ws),
           _start(std::time(nullptr))
@@ -117,8 +117,9 @@ public:
 private:
     /// Admin session Id
     int _sessionId;
-    /// WebSocket to use to send messages to session
-    std::weak_ptr<Poco::Net::WebSocket> _ws;
+
+    /// LOOLWebSocket to use to send messages to session
+    std::weak_ptr<LOOLWebSocket> _ws;
 
     std::set<std::string> _subscriptions;
 
@@ -145,7 +146,7 @@ public:
     /// Returns memory consumed by all active loolkit processes
     unsigned getTotalMemoryUsage();
 
-    void subscribe(int sessionId, std::shared_ptr<Poco::Net::WebSocket>& ws);
+    void subscribe(int sessionId, std::shared_ptr<LOOLWebSocket>& ws);
     void subscribe(int sessionId, const std::string& command);
 
     void unsubscribe(int sessionId, const std::string& command);
diff --git a/loolwsd/ClientSession.cpp b/loolwsd/ClientSession.cpp
index c186619..24b53c5 100644
--- a/loolwsd/ClientSession.cpp
+++ b/loolwsd/ClientSession.cpp
@@ -35,7 +35,7 @@ using namespace LOOLProtocol;
 using Poco::StringTokenizer;
 
 ClientSession::ClientSession(const std::string& id,
-                             const std::shared_ptr<Poco::Net::WebSocket>& ws,
+                             const std::shared_ptr<LOOLWebSocket>& ws,
                              const std::shared_ptr<DocumentBroker>& docBroker,
                              const Poco::URI& uriPublic,
                              const bool readOnly) :
diff --git a/loolwsd/ClientSession.hpp b/loolwsd/ClientSession.hpp
index 77ddcfa..4340f46 100644
--- a/loolwsd/ClientSession.hpp
+++ b/loolwsd/ClientSession.hpp
@@ -23,7 +23,7 @@ class ClientSession final : public LOOLSession, public std::enable_shared_from_t
 {
 public:
     ClientSession(const std::string& id,
-                  const std::shared_ptr<Poco::Net::WebSocket>& ws,
+                  const std::shared_ptr<LOOLWebSocket>& ws,
                   const std::shared_ptr<DocumentBroker>& docBroker,
                   const Poco::URI& uriPublic,
                   const bool isReadOnly = false);
diff --git a/loolwsd/Connect.cpp b/loolwsd/Connect.cpp
index 3c14b22..5b550dd 100644
--- a/loolwsd/Connect.cpp
+++ b/loolwsd/Connect.cpp
@@ -31,7 +31,6 @@
 #include <Poco/Net/TCPServer.h>
 #include <Poco/Net/TCPServerConnection.h>
 #include <Poco/Net/TCPServerConnectionFactory.h>
-#include <Poco/Net/WebSocket.h>
 #include <Poco/Process.h>
 #include <Poco/SharedPtr.h>
 #include <Poco/StringTokenizer.h>
@@ -43,6 +42,7 @@
 
 #include "Common.hpp"
 #include "LOOLProtocol.hpp"
+#include <LOOLWebSocket.hpp>
 #include "Log.hpp"
 #include "Util.hpp"
 
@@ -72,11 +72,11 @@ using Poco::Util::Application;
 static bool closeExpected = false;
 static std::mutex coutMutex;
 
-/// Prints incoming data from a WebSocket.
+/// Prints incoming data from a LOOLWebSocket.
 class Output : public Runnable
 {
 public:
-    Output(WebSocket& ws) :
+    Output(LOOLWebSocket& ws) :
         _ws(ws)
     {
     }
@@ -131,7 +131,7 @@ public:
         }
     }
 
-    WebSocket& _ws;
+    LOOLWebSocket& _ws;
 };
 
 /// Program for interactive or scripted testing of a lool server.
@@ -179,7 +179,7 @@ protected:
         URI::encode(args[0], ":/?", encodedUri);
         HTTPRequest request(HTTPRequest::HTTP_GET, "/lool/" + encodedUri + "/ws");
         HTTPResponse response;
-        WebSocket ws(cs, request, response);
+        LOOLWebSocket ws(cs, request, response);
 
         ws.setReceiveTimeout(0);
 
diff --git a/loolwsd/DocumentBroker.hpp b/loolwsd/DocumentBroker.hpp
index 3212715..fc035f7 100644
--- a/loolwsd/DocumentBroker.hpp
+++ b/loolwsd/DocumentBroker.hpp
@@ -21,11 +21,11 @@
 #include <string>
 #include <thread>
 
-#include <Poco/Net/WebSocket.h>
 #include <Poco/URI.h>
 
 #include "IoUtil.hpp"
 #include "Log.hpp"
+#include <LOOLWebSocket.hpp>
 #include "Storage.hpp"
 #include "TileCache.hpp"
 #include "Util.hpp"
@@ -39,8 +39,8 @@ class ChildProcess
 {
 public:
     /// @param pid is the process ID of the child.
-    /// @param ws is the control WebSocket to the child.
-    ChildProcess(const Poco::Process::PID pid, const std::shared_ptr<Poco::Net::WebSocket>& ws) :
+    /// @param ws is the control LOOLWebSocket to the child.
+    ChildProcess(const Poco::Process::PID pid, const std::shared_ptr<LOOLWebSocket>& ws) :
         _pid(pid),
         _ws(ws),
         _stop(false)
@@ -163,7 +163,7 @@ private:
 
 private:
     Poco::Process::PID _pid;
-    std::shared_ptr<Poco::Net::WebSocket> _ws;
+    std::shared_ptr<LOOLWebSocket> _ws;
     std::weak_ptr<DocumentBroker> _docBroker;
     std::thread _thread;
     std::atomic<bool> _stop;
diff --git a/loolwsd/Exceptions.hpp b/loolwsd/Exceptions.hpp
index ae2d1a6..b05d98d 100644
--- a/loolwsd/Exceptions.hpp
+++ b/loolwsd/Exceptions.hpp
@@ -59,7 +59,7 @@ public:
 };
 
 /// An generic error-message exception meant to
-/// propagate via a valid WebSocket to the client.
+/// propagate via a valid LOOLWebSocket to the client.
 /// The contents of what() will be displayed on screen.
 class WebSocketErrorMessageException : public LoolException
 {
diff --git a/loolwsd/IoUtil.cpp b/loolwsd/IoUtil.cpp
index 04a6ed5..75b7026 100644
--- a/loolwsd/IoUtil.cpp
+++ b/loolwsd/IoUtil.cpp
@@ -22,13 +22,13 @@
 
 #include <Poco/Net/NetException.h>
 #include <Poco/Net/Socket.h>
-#include <Poco/Net/WebSocket.h>
 #include <Poco/StringTokenizer.h>
 #include <Poco/Thread.h>
 #include <Poco/URI.h>
 
 #include "Common.hpp"
 #include "LOOLProtocol.hpp"
+#include <LOOLWebSocket.hpp>
 #include "Log.hpp"
 #include "Util.hpp"
 
@@ -38,7 +38,7 @@ using Poco::Net::WebSocket;
 namespace IoUtil
 {
 
-int receiveFrame(WebSocket& socket, void* buffer, int length, int& flags)
+int receiveFrame(LOOLWebSocket& socket, void* buffer, int length, int& flags)
 {
     while (!TerminationFlag)
     {
@@ -60,9 +60,9 @@ int receiveFrame(WebSocket& socket, void* buffer, int length, int& flags)
     return -1;
 }
 
-// Synchronously process WebSocket requests and dispatch to handler.
+// Synchronously process LOOLWebSocket requests and dispatch to handler.
 // Handler returns false to end.
-void SocketProcessor(const std::shared_ptr<WebSocket>& ws,
+void SocketProcessor(const std::shared_ptr<LOOLWebSocket>& ws,
                      const std::function<bool(const std::vector<char>&)>& handler,
                      const std::function<void()>& closeFrame,
                      const std::function<bool()>& stopPredicate)
@@ -197,11 +197,11 @@ void SocketProcessor(const std::shared_ptr<WebSocket>& ws,
     LOG_INF("SocketProcessor finished.");
 }
 
-void shutdownWebSocket(const std::shared_ptr<Poco::Net::WebSocket>& ws)
+void shutdownWebSocket(const std::shared_ptr<LOOLWebSocket>& ws)
 {
     try
     {
-        // Calling WebSocket::shutdown, in case of error, would try to send a 'close' frame
+        // Calling LOOLWebSocket::shutdown, in case of error, would try to send a 'close' frame
         // which won't work in case of broken pipe or timeout from peer. Just close the
         // socket in that case preventing 'close' frame from being sent.
         if (ws && ws->poll(Poco::Timespan(0), Socket::SelectMode::SELECT_ERROR))
@@ -258,25 +258,6 @@ ssize_t readFromPipe(int pipe, char* buffer, ssize_t size)
     return bytes;
 }
 
-void sendLargeFrame(const std::shared_ptr<Poco::Net::WebSocket>& ws, const char *message, int length, int flags)
-{
-    // Size after which messages will be sent preceded with
-    // 'nextmessage' frame to let the receiver know in advance
-    // the size of larger coming message. All messages up to this
-    // size are considered small messages.
-    constexpr int SMALL_MESSAGE_SIZE = READ_BUFFER_SIZE / 2;
-
-    if (length > SMALL_MESSAGE_SIZE)
-    {
-        const std::string nextmessage = "nextmessage: size=" + std::to_string(length);
-        ws->sendFrame(nextmessage.data(), nextmessage.size());
-        LOG_DBG("Message is long, sent " << nextmessage);
-    }
-
-    ws->sendFrame(message, length, flags);
-    LOG_DBG("Sent frame: " << LOOLProtocol::getAbbreviatedMessage(std::string(message, length)));
-}
-
 /// Reads a single line from a pipe.
 /// Returns 0 for timeout, <0 for error, and >0 on success.
 /// On success, line will contain the read message.
diff --git a/loolwsd/IoUtil.hpp b/loolwsd/IoUtil.hpp
index e0068b0..dba0aa7 100644
--- a/loolwsd/IoUtil.hpp
+++ b/loolwsd/IoUtil.hpp
@@ -14,24 +14,26 @@
 #include <memory>
 #include <string>
 
-#include <Poco/Net/WebSocket.h>
+#include <LOOLWebSocket.hpp>
 
 namespace IoUtil
 {
-    // Wrapper for WebSocket::receiveFrame() that handles PING frames (by replying with a
+    // Wrapper for LOOLWebSocket::receiveFrame() that handles PING frames (by replying with a
     // PONG frame) and PONG frames. PONG frames are ignored.
     // Should we also factor out the handling of non-final and continuation frames into this?
-    int receiveFrame(Poco::Net::WebSocket& socket, void* buffer, int length, int& flags);
+    // TODO: move this to LOOLWebSocket directly
+    int receiveFrame(LOOLWebSocket& socket, void* buffer, int length, int& flags);
 
-    /// Synchronously process WebSocket requests and dispatch to handler.
+    /// Synchronously process LOOLWebSocket requests and dispatch to handler.
     /// Handler returns false to end.
-    void SocketProcessor(const std::shared_ptr<Poco::Net::WebSocket>& ws,
+    void SocketProcessor(const std::shared_ptr<LOOLWebSocket>& ws,
                          const std::function<bool(const std::vector<char>&)>& handler,
                          const std::function<void()>& closeFrame,
                          const std::function<bool()>& stopPredicate);
 
-    /// Call WebSocket::shutdown() ignoring Poco::IOException.
-    void shutdownWebSocket(const std::shared_ptr<Poco::Net::WebSocket>& ws);
+    /// Call LOOLWebSocket::shutdown() ignoring Poco::IOException.
+    /// TODO: consider moving this directly to LOOLWebSocket
+    void shutdownWebSocket(const std::shared_ptr<LOOLWebSocket>& ws);
 
     ssize_t writeToPipe(int pipe, const char* buffer, ssize_t size);
     inline ssize_t writeToPipe(int pipe, const std::string& message)
@@ -41,16 +43,6 @@ namespace IoUtil
 
     ssize_t readFromPipe(int pipe, char* buffer, ssize_t size);
 
-    /// Send frame.  If it is too long, send a 'nextmessage:' before the real
-    /// frame.
-    void sendLargeFrame(const std::shared_ptr<Poco::Net::WebSocket>& ws, const char *message, int length, int flags = Poco::Net::WebSocket::FRAME_TEXT);
-
-    /// Send frame as above, the std::string variant.
-    inline void sendLargeFrame(const std::shared_ptr<Poco::Net::WebSocket>& ws, const std::vector<char> &message, int flags = Poco::Net::WebSocket::FRAME_TEXT)
-    {
-        sendLargeFrame(ws, message.data(), message.size(), flags);
-    }
-
     /// Helper class to handle reading from a pipe.
     class PipeReader
     {
diff --git a/loolwsd/LOOLKit.cpp b/loolwsd/LOOLKit.cpp
index f228be9..397dcc5 100644
--- a/loolwsd/LOOLKit.cpp
+++ b/loolwsd/LOOLKit.cpp
@@ -40,7 +40,6 @@
 #include <Poco/Net/HTTPResponse.h>
 #include <Poco/Net/NetException.h>
 #include <Poco/Net/Socket.h>
-#include <Poco/Net/WebSocket.h>
 #include <Poco/NotificationQueue.h>
 #include <Poco/Process.h>
 #include <Poco/Runnable.h>
@@ -55,6 +54,7 @@
 #include "LOKitHelper.hpp"
 #include "LOOLKit.hpp"
 #include "LOOLProtocol.hpp"
+#include <LOOLWebSocket.hpp>
 #include "LibreOfficeKit.hpp"
 #include "Log.hpp"
 #include "Png.hpp"
@@ -280,7 +280,7 @@ public:
              const std::string& docKey,
              const std::string& url,
              std::shared_ptr<TileQueue> tileQueue,
-             const std::shared_ptr<WebSocket>& ws)
+             const std::shared_ptr<LOOLWebSocket>& ws)
       : _loKit(loKit),
         _jailId(jailId),
         _docKey(docKey),
@@ -450,7 +450,7 @@ public:
         LOG_INF("setDocumentPassword returned");
     }
 
-    void renderTile(StringTokenizer& tokens, const std::shared_ptr<Poco::Net::WebSocket>& ws)
+    void renderTile(StringTokenizer& tokens, const std::shared_ptr<LOOLWebSocket>& ws)
     {
         assert(ws && "Expected a non-null websocket.");
         auto tile = TileDesc::parse(tokens);
@@ -505,10 +505,10 @@ public:
         }
 
         LOG_TRC("Sending render-tile response (" + std::to_string(output.size()) + " bytes) for: " + response);
-        IoUtil::sendLargeFrame(ws, output, WebSocket::FRAME_BINARY);
+        ws->sendFrame(output.data(), output.size(), WebSocket::FRAME_BINARY);
     }
 
-    void renderCombinedTiles(StringTokenizer& tokens, const std::shared_ptr<Poco::Net::WebSocket>& ws)
+    void renderCombinedTiles(StringTokenizer& tokens, const std::shared_ptr<LOOLWebSocket>& ws)
     {
         assert(ws && "Expected a non-null websocket.");
         auto tileCombined = TileCombined::parse(tokens);
@@ -605,7 +605,7 @@ public:
         std::copy(tileMsg.begin(), tileMsg.end(), response.begin());
         std::copy(output.begin(), output.end(), response.begin() + tileMsg.size());
 
-        IoUtil::sendLargeFrame(ws, response, WebSocket::FRAME_BINARY);
+        ws->sendFrame(response.data(), response.size(), WebSocket::FRAME_BINARY);
     }
 
     bool sendTextFrame(const std::string& message) override
@@ -618,7 +618,7 @@ public:
                 return false;
             }
 
-            IoUtil::sendLargeFrame(_ws, message.data(), message.size());
+            _ws->sendFrame(message.data(), message.size());
             return true;
         }
         catch (const Exception& exc)
@@ -1244,7 +1244,7 @@ private:
 
     std::shared_ptr<lok::Document> _loKitDocument;
     std::shared_ptr<TileQueue> _tileQueue;
-    std::shared_ptr<WebSocket> _ws;
+    std::shared_ptr<LOOLWebSocket> _ws;
 
     // Document password provided
     std::string _docPassword;
@@ -1462,7 +1462,7 @@ void lokit_main(const std::string& childRoot,
 
         HTTPRequest request(HTTPRequest::HTTP_GET, requestUrl);
         HTTPResponse response;
-        auto ws = std::make_shared<WebSocket>(cs, request, response);
+        auto ws = std::make_shared<LOOLWebSocket>(cs, request, response);
         ws->setReceiveTimeout(0);
 
         auto queue = std::make_shared<TileQueue>();
diff --git a/loolwsd/LOOLProtocol.hpp b/loolwsd/LOOLProtocol.hpp
index fe11f2e..2c158ff 100644
--- a/loolwsd/LOOLProtocol.hpp
+++ b/loolwsd/LOOLProtocol.hpp
@@ -16,9 +16,10 @@
 #include <string>
 
 #include <Poco/Format.h>
-#include <Poco/Net/WebSocket.h>
 #include <Poco/StringTokenizer.h>
 
+#include <LOOLWebSocket.hpp>
+
 #define LOK_USE_UNSTABLE_API
 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
 
@@ -189,7 +190,7 @@ namespace LOOLProtocol
         return getAbbreviatedMessage(message.data(), message.size());
     }
 
-    // Return a string dump of a WebSocket frame: Its opcode, length, first line (if present),
+    // Return a string dump of a LOOLWebSocket frame: Its opcode, length, first line (if present),
     // flags.  For human-readable logging purposes. Format not guaranteed to be stable. Not to be
     // inspected programmatically.
     inline
diff --git a/loolwsd/LOOLSession.cpp b/loolwsd/LOOLSession.cpp
index db091f5..97ae19e 100644
--- a/loolwsd/LOOLSession.cpp
+++ b/loolwsd/LOOLSession.cpp
@@ -27,7 +27,6 @@
 
 #include <Poco/Exception.h>
 #include <Poco/Net/Socket.h>
-#include <Poco/Net/WebSocket.h>
 #include <Poco/Path.h>
 #include <Poco/String.h>
 #include <Poco/StringTokenizer.h>
@@ -36,6 +35,7 @@
 #include "Common.hpp"
 #include "IoUtil.hpp"
 #include "LOOLProtocol.hpp"
+#include <LOOLWebSocket.hpp>
 #include "Log.hpp"
 #include "TileCache.hpp"
 #include "Util.hpp"
@@ -49,7 +49,7 @@ using Poco::Net::WebSocket;
 using Poco::StringTokenizer;
 
 LOOLSession::LOOLSession(const std::string& id, const Kind kind,
-                         std::shared_ptr<WebSocket> ws) :
+                         std::shared_ptr<LOOLWebSocket> ws) :
     _id(id),
     _kind(kind),
     _kindString(kind == Kind::ToClient ? "ToClient" :
@@ -84,7 +84,7 @@ bool LOOLSession::sendTextFrame(const char* buffer, const int length)
             return false;
         }
 
-        IoUtil::sendLargeFrame(_ws, buffer, length);
+        _ws->sendFrame(buffer, length);
         return true;
     }
     catch (const Exception& exc)
@@ -109,7 +109,7 @@ bool LOOLSession::sendBinaryFrame(const char *buffer, int length)
             return false;
         }
 
-        IoUtil::sendLargeFrame(_ws, buffer, length, WebSocket::FRAME_BINARY);
+        _ws->sendFrame(buffer, length, WebSocket::FRAME_BINARY);
         return true;
     }
     catch (const Exception& exc)
@@ -213,7 +213,7 @@ void LOOLSession::shutdown(Poco::UInt16 statusCode, const std::string& statusMes
         }
         catch (const Poco::Exception &exc)
         {
-            LOG_WRN("LOOLSession::shutdown WebSocket: Exception: " <<
+            LOG_WRN("LOOLSession::shutdown LOOLWebSocket: Exception: " <<
                     exc.displayText() << (exc.nested() ? " (" + exc.nested()->displayText() + ")" : ""));
         }
     }
diff --git a/loolwsd/LOOLSession.hpp b/loolwsd/LOOLSession.hpp
index cd6b9fe..736e63d 100644
--- a/loolwsd/LOOLSession.hpp
+++ b/loolwsd/LOOLSession.hpp
@@ -17,18 +17,18 @@
 #include <ostream>
 
 #include <Poco/Buffer.h>
-#include <Poco/Net/WebSocket.h>
 #include <Poco/Path.h>
 #include <Poco/Process.h>
 #include <Poco/StringTokenizer.h>
 #include <Poco/Types.h>
 
 #include "LOOLProtocol.hpp"
+#include <LOOLWebSocket.hpp>
 #include "Log.hpp"
 #include "MessageQueue.hpp"
 #include "TileCache.hpp"
 
-/// Base class of a WebSocket session.
+/// Base class of a LOOLWebSocket session.
 class LOOLSession
 {
 public:
@@ -83,7 +83,7 @@ public:
 
 protected:
     LOOLSession(const std::string& id, const Kind kind,
-                std::shared_ptr<Poco::Net::WebSocket> ws);
+                std::shared_ptr<LOOLWebSocket> ws);
     virtual ~LOOLSession();
 
     /// Parses the options of the "load" command, shared between MasterProcessSession::loadDocument() and ChildProcessSession::loadDocument().
@@ -146,7 +146,7 @@ private:
 
     // In the master process, the websocket to the LOOL client or the jailed child process. In a
     // jailed process, the websocket to the parent.
-    std::shared_ptr<Poco::Net::WebSocket> _ws;
+    std::shared_ptr<LOOLWebSocket> _ws;
 
     /// True if we have been disconnected.
     std::atomic<bool> _disconnected;
diff --git a/loolwsd/LOOLStress.cpp b/loolwsd/LOOLStress.cpp
index 484d3fb..7dbe7a6 100644
--- a/loolwsd/LOOLStress.cpp
+++ b/loolwsd/LOOLStress.cpp
@@ -121,7 +121,7 @@ public:
     }
 
     const std::string& getName() const { return _name; }
-    std::shared_ptr<Poco::Net::WebSocket> getWS() const { return _ws; };
+    std::shared_ptr<LOOLWebSocket> getWS() const { return _ws; };
 
     /// Send a command to the server.
     void send(const std::string& data) const
@@ -143,7 +143,7 @@ public:
     }
 
 private:
-    Connection(const std::string& documentURL, const std::string& sessionId, std::shared_ptr<Poco::Net::WebSocket>& ws) :
+    Connection(const std::string& documentURL, const std::string& sessionId, std::shared_ptr<LOOLWebSocket>& ws) :
         _documentURL(documentURL),
         _sessionId(sessionId),
         _name(sessionId + ' '),
@@ -155,7 +155,7 @@ private:
     const std::string _documentURL;
     const std::string _sessionId;
     const std::string _name;
-    std::shared_ptr<Poco::Net::WebSocket> _ws;
+    std::shared_ptr<LOOLWebSocket> _ws;
     static std::mutex Mutex;
 };
 
diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index 40876de..87cf4a6 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -77,7 +77,6 @@
 #include <Poco/Net/SecureServerSocket.h>
 #include <Poco/Net/ServerSocket.h>
 #include <Poco/Net/SocketAddress.h>
-#include <Poco/Net/WebSocket.h>
 #include <Poco/Path.h>
 #include <Poco/Pipe.h>
 #include <Poco/Process.h>
@@ -103,6 +102,7 @@
 #include "IoUtil.hpp"
 #include "LOOLProtocol.hpp"
 #include "LOOLSession.hpp"
+#include <LOOLWebSocket.hpp>
 #include "Log.hpp"
 #include "PrisonerSession.hpp"
 #include "QueueHandler.hpp"
@@ -180,7 +180,7 @@ namespace
 {
 
 static inline
-void shutdownLimitReached(WebSocket& ws)
+void shutdownLimitReached(LOOLWebSocket& ws)
 {
     const std::string error = Poco::format(PAYLOAD_UNAVAILABLE_LIMIT_REACHED, MAX_DOCUMENTS, MAX_CONNECTIONS);
 
@@ -519,7 +519,7 @@ private:
                     DocBrokers.emplace(docKey, docBroker);
 
                     // Load the document.
-                    std::shared_ptr<WebSocket> ws;
+                    std::shared_ptr<LOOLWebSocket> ws;
                     auto session = std::make_shared<ClientSession>(id, ws, docBroker, uriPublic);
 
                     auto sessionsCount = docBroker->addSession(session);
@@ -704,7 +704,7 @@ private:
     }
 
     /// Handle GET requests.
-    static void handleGetRequest(const std::string& uri, std::shared_ptr<WebSocket>& ws, const std::string& id)
+    static void handleGetRequest(const std::string& uri, std::shared_ptr<LOOLWebSocket>& ws, const std::string& id)
     {
         LOG_INF("Starting GET request handler for session [" << id << "].");
 
@@ -1049,7 +1049,7 @@ public:
             --LOOLWSD::NumConnections;
             LOG_ERR("Limit on maximum number of connections of " << MAX_CONNECTIONS << " reached.");
             // accept hand shake
-            WebSocket ws(request, response);
+            LOOLWebSocket ws(request, response);
             shutdownLimitReached(ws);
             return;
         }
@@ -1118,7 +1118,7 @@ public:
             }
             else if (reqPathTokens.count() > 2 && reqPathTokens[0] == "lool" && reqPathTokens[2] == "ws")
             {
-                auto ws = std::make_shared<WebSocket>(request, response);
+                auto ws = std::make_shared<LOOLWebSocket>(request, response);
                 responded = true; // After upgrading to WS we should not set HTTP response.
                 try
                 {
@@ -1233,7 +1233,7 @@ public:
         }
 
         LOG_INF("New child [" << pid << "].");
-        auto ws = std::make_shared<WebSocket>(request, response);
+        auto ws = std::make_shared<LOOLWebSocket>(request, response);
         UnitWSD::get().newChild(ws);
 
         addNewChild(std::make_shared<ChildProcess>(pid, ws));
@@ -1276,7 +1276,7 @@ public:
         {
             requestHandler = FileServer::createRequestHandler();
         }
-        // Admin WebSocket Connections
+        // Admin LOOLWebSocket Connections
         else if (reqPathSegs.size() >= 2 && reqPathSegs[0] == "lool" && reqPathSegs[1] == "adminws")
         {
             requestHandler = Admin::createRequestHandler();
diff --git a/loolwsd/LOOLWebSocket.hpp b/loolwsd/LOOLWebSocket.hpp
new file mode 100644
index 0000000..ffd4887
--- /dev/null
+++ b/loolwsd/LOOLWebSocket.hpp
@@ -0,0 +1,86 @@
+/* -*- 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_LOOLWEBSOCKET_HPP
+#define INCLUDED_LOOLWEBSOCKET_HPP
+
+#include <mutex>
+
+#include <Poco/Net/WebSocket.h>
+
+#include <Common.hpp>
+#include <LOOLProtocol.hpp>
+#include <Log.hpp>
+
+/// WebSocket that is thread safe, and handles large frames transparently.
+class LOOLWebSocket : public Poco::Net::WebSocket
+{
+    std::mutex _mutex;
+
+public:
+    LOOLWebSocket(const Socket & socket) :
+        Poco::Net::WebSocket(socket),
+        _mutex()
+    {
+    }
+
+    LOOLWebSocket(Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response) :
+        Poco::Net::WebSocket(request, response),
+        _mutex()
+    {
+    }
+
+    LOOLWebSocket(Poco::Net::HTTPClientSession & cs, Poco::Net::HTTPRequest & request, Poco::Net::HTTPResponse & response) :
+        Poco::Net::WebSocket(cs, request, response),
+        _mutex()
+    {
+    }
+
+    LOOLWebSocket(Poco::Net::HTTPClientSession & cs, Poco::Net::HTTPRequest & request, Poco::Net::HTTPResponse & response, Poco::Net::HTTPCredentials & credentials) :
+        Poco::Net::WebSocket(cs, request, response, credentials),
+        _mutex()
+    {
+    }
+
+    /// Careful - sendFrame is _not_ virtual, we need to make sure that we use
+    /// LOOLWebSocket all over the place
+    /// It would be a kind of more natural to encapsulate Poco::Net::WebSocket
+    /// instead of inheriting (from that reason), but that would requite much
+    /// larger code changes.
+    int sendFrame(const void * buffer, int length, int flags = FRAME_TEXT)
+    {
+        std::lock_guard<std::mutex> lock(_mutex);
+
+        // Size after which messages will be sent preceded with
+        // 'nextmessage' frame to let the receiver know in advance
+        // the size of larger coming message. All messages up to this
+        // size are considered small messages.
+        constexpr int SMALL_MESSAGE_SIZE = READ_BUFFER_SIZE / 2;
+
+        if (length > SMALL_MESSAGE_SIZE)
+        {
+            const std::string nextmessage = "nextmessage: size=" + std::to_string(length);
+            Poco::Net::WebSocket::sendFrame(nextmessage.data(), nextmessage.size());
+            Log::debug("Message is long, sent " + nextmessage);
+        }
+
+        int result = Poco::Net::WebSocket::sendFrame(buffer, length, flags);
+        // FIXME we want an abbreviated message here, but we'd have a circular
+        // dependency with LOOLProtocol, so use the full message here before
+        // we move getAbbreviatedMessage() to Log (where it belongs anyway).
+        //Log::debug("Sent frame: " + LOOLProtocol::getAbbreviatedMessage(static_cast<const char*>(buffer), length));
+        Log::debug("Sent frame: " + std::string(static_cast<const char*>(buffer), length));
+
+        return result;
+    }
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/loolwsd/Makefile.am b/loolwsd/Makefile.am
index 1f47750..cb4a7a9 100644
--- a/loolwsd/Makefile.am
+++ b/loolwsd/Makefile.am
@@ -82,7 +82,8 @@ looltool_SOURCES = LOOLTool.cpp
 
 loolstress_CPPFLAGS = -DTDOC=\"$(abs_top_srcdir)/test/data\"
 loolstress_SOURCES = LOOLStress.cpp \
-                     LOOLProtocol.cpp
+                     LOOLProtocol.cpp \
+		     Log.cpp
 
 noinst_HEADERS = Admin.hpp \
                  AdminModel.hpp \
diff --git a/loolwsd/Unit.hpp b/loolwsd/Unit.hpp
index 97b7103..7b6c6f8 100644
--- a/loolwsd/Unit.hpp
+++ b/loolwsd/Unit.hpp
@@ -14,7 +14,7 @@
 #include <memory>
 #include <string>
 
-#include <Poco/Net/WebSocket.h>
+#include <LOOLWebSocket.hpp>
 
 class UnitBase;
 class UnitWSD;
@@ -139,7 +139,7 @@ public:
     /// Tweak the count of pre-spawned kits.
     virtual void preSpawnCount(int& /* numPrefork */) {}
     /// When a new child kit process reports
-    virtual void newChild(const std::shared_ptr<Poco::Net::WebSocket>& /* socket */) {}
+    virtual void newChild(const std::shared_ptr<LOOLWebSocket>& /* socket */) {}
     /// Intercept createStorage
     virtual bool createStorage(const Poco::URI& /* uri */,
                                const std::string& /* jailRoot */,
@@ -221,7 +221,7 @@ public:
     virtual void postFork() {}
 
     /// Kit got a message
-    virtual bool filterKitMessage(const std::shared_ptr<Poco::Net::WebSocket>& /* ws */,
+    virtual bool filterKitMessage(const std::shared_ptr<LOOLWebSocket>& /* ws */,
                                   std::string& /* message */)
     {
         return false;
diff --git a/loolwsd/UnitHTTP.cpp b/loolwsd/UnitHTTP.cpp
index d42aea0..6775695 100644
--- a/loolwsd/UnitHTTP.cpp
+++ b/loolwsd/UnitHTTP.cpp
@@ -26,7 +26,7 @@ UnitWebSocket::UnitWebSocket(const std::string &docURL)
         _session = UnitHTTP::createSession();
 
         // FIXME: leaking the session - hey ho ... do we need a UnitSocket ?
-        _socket = new Poco::Net::WebSocket(*_session, request, response);
+        _socket = new LOOLWebSocket(*_session, request, response);
     } catch (const Poco::Exception &ex) {
         std::cerr << "Exception creating websocket " << ex.displayText() << std::endl;
         throw;
diff --git a/loolwsd/UnitHTTP.hpp b/loolwsd/UnitHTTP.hpp
index 3c28613..7765e47 100644
--- a/loolwsd/UnitHTTP.hpp
+++ b/loolwsd/UnitHTTP.hpp
@@ -18,10 +18,10 @@
 #include <Poco/Net/HTTPServerRequest.h>
 #include <Poco/Net/HTTPServerResponse.h>
 #include <Poco/Net/SocketAddress.h>
-#include <Poco/Net/WebSocket.h>
 #include <Poco/Version.h>
 
 #include "Common.hpp"
+#include <LOOLWebSocket.hpp>
 
 using Poco::Net::SocketAddress;
 using Poco::Net::HTTPServerParams;
@@ -121,7 +121,7 @@ namespace UnitHTTP
 class UnitWebSocket
 {
     Poco::Net::HTTPClientSession* _session;
-    Poco::Net::WebSocket* _socket;
+    LOOLWebSocket* _socket;
 
 public:
     /// Get a websocket connected for a given URL
diff --git a/loolwsd/Util.hpp b/loolwsd/Util.hpp
index cde7828..ae4c3cd 100644
--- a/loolwsd/Util.hpp
+++ b/loolwsd/Util.hpp
@@ -20,7 +20,6 @@
 #include <string>
 
 #include <Poco/File.h>
-#include <Poco/Net/WebSocket.h>
 #include <Poco/Path.h>
 #include <Poco/Process.h>
 #include <Poco/RegularExpression.h>
diff --git a/loolwsd/test/TileCacheTests.cpp b/loolwsd/test/TileCacheTests.cpp
index ade2ae0..5343092 100644
--- a/loolwsd/test/TileCacheTests.cpp
+++ b/loolwsd/test/TileCacheTests.cpp
@@ -9,11 +9,11 @@
 
 #include "config.h"
 
-#include <Poco/Net/WebSocket.h>
 #include <cppunit/extensions/HelperMacros.h>
 
 #include "Common.hpp"
 #include "LOOLProtocol.hpp"
+#include <LOOLWebSocket.hpp>
 #include "MessageQueue.hpp"
 #include "Png.hpp"
 #include "TileCache.hpp"
@@ -94,17 +94,17 @@ class TileCacheTests : public CPPUNIT_NS::TestFixture
 
     void tileInvalidatePart(const std::string& filename, const std::string& testname);
 
-    void checkTiles(Poco::Net::WebSocket& socket,
+    void checkTiles(std::shared_ptr<LOOLWebSocket>& socket,
                     const std::string& type,
                     const std::string& name = "checkTiles ");
 
-    void requestTiles(Poco::Net::WebSocket& socket,
+    void requestTiles(std::shared_ptr<LOOLWebSocket>& socket,
                       const int part,
                       const int docWidth,
                       const int docHeight,
                       const std::string& name = "requestTiles ");
 
-    void checkBlackTiles(Poco::Net::WebSocket& socket,
+    void checkBlackTiles(std::shared_ptr<LOOLWebSocket>& socket,
                          const int part,
                          const int docWidth,
                          const int docHeight,
@@ -212,7 +212,7 @@ void TileCacheTests::testSimpleCombine()
 
 void TileCacheTests::testPerformance()
 {
-    auto socket = *loadDocAndGetSocket("hello.odt", _uri, "performance ");
+    auto socket = loadDocAndGetSocket("hello.odt", _uri, "performance ");
 
     Poco::Timestamp timestamp;
     for (auto x = 0; x < 5; ++x)
@@ -230,13 +230,13 @@ void TileCacheTests::testPerformance()
               << " ms. Per-tile: " << timestamp.elapsed() / (1000. * 5 * 8) << "ms."
               << std::endl;
 
-    socket.shutdown();
+    socket->shutdown();
 }
 
 void TileCacheTests::testCancelTiles()
 {
     const auto testName = "cancelTiles ";
-    auto socket = *loadDocAndGetSocket("setclientpart.ods", _uri, testName);
+    auto socket = loadDocAndGetSocket("setclientpart.ods", _uri, testName);
 
     // Request a huge tile, and cancel immediately.
     sendTextFrame(socket, "tilecombine part=0 width=2560 height=2560 tileposx=0 tileposy=0 tilewidth=38400 tileheight=38400");
@@ -322,10 +322,10 @@ void TileCacheTests::testImpressTiles()
     try
     {
         const std::string testName = "impressTiles ";
-        auto socket = *loadDocAndGetSocket("setclientpart.odp", _uri, testName);
+        auto socket = loadDocAndGetSocket("setclientpart.odp", _uri, testName);
 
         sendTextFrame(socket, "tile part=0 width=180 height=135 tileposx=0 tileposy=0 tilewidth=15875 tileheight=11906 id=0", testName);
-        getTileMessage(socket, testName);
+        getTileMessage(*socket, testName);
     }
     catch (const Poco::Exception& exc)
     {
@@ -338,11 +338,11 @@ void TileCacheTests::testClientPartImpress()
     try
     {
         const std::string testName = "clientPartImpress ";
-        auto socket = *loadDocAndGetSocket("setclientpart.odp", _uri, testName);
+        auto socket = loadDocAndGetSocket("setclientpart.odp", _uri, testName);
 
         checkTiles(socket, "presentation", testName);
 
-        socket.shutdown();
+        socket->shutdown();
     }
     catch (const Poco::Exception& exc)
     {
@@ -355,11 +355,11 @@ void TileCacheTests::testClientPartCalc()
     try
     {
         const std::string testName = "clientPartCalc ";
-        auto socket = *loadDocAndGetSocket("setclientpart.ods", _uri, testName);
+        auto socket = loadDocAndGetSocket("setclientpart.ods", _uri, testName);
 
         checkTiles(socket, "spreadsheet", testName);
 
-        socket.shutdown();
+        socket->shutdown();
     }
     catch (const Poco::Exception& exc)
     {
@@ -371,7 +371,7 @@ void TileCacheTests::testTilesRenderedJustOnce()
 {
     const auto testname = "tilesRenderdJustOnce ";
 
-    auto socket = *loadDocAndGetSocket("with_comment.odt", _uri, testname);
+    auto socket = loadDocAndGetSocket("with_comment.odt", _uri, testname);
 
     assertResponseString(socket, "statechanged: .uno:AcceptTrackedChange=", testname);
 
@@ -439,13 +439,13 @@ void TileCacheTests::testTilesRenderedJustOnceMultiClient()
     getDocumentPathAndURL("with_comment.odt", documentPath, documentURL);
 
     std::cerr << "Connecting first client." << std::endl;
-    auto socket = *loadDocAndGetSocket(_uri, documentURL, testname1);
+    auto socket = loadDocAndGetSocket(_uri, documentURL, testname1);
     std::cerr << "Connecting second client." << std::endl;
-    auto socket2 = *loadDocAndGetSocket(_uri, documentURL, testname2);
+    auto socket2 = loadDocAndGetSocket(_uri, documentURL, testname2);
     std::cerr << "Connecting third client." << std::endl;
-    auto socket3 = *loadDocAndGetSocket(_uri, documentURL, testname3);
+    auto socket3 = loadDocAndGetSocket(_uri, documentURL, testname3);
     std::cerr << "Connecting fourth client." << std::endl;
-    auto socket4 = *loadDocAndGetSocket(_uri, documentURL, "tilesRenderdJustOnce-4 ");
+    auto socket4 = loadDocAndGetSocket(_uri, documentURL, "tilesRenderdJustOnce-4 ");
 
     for (int i = 0; i < 10; ++i)
     {
@@ -562,7 +562,7 @@ void TileCacheTests::testLoad12ods()
     try
     {
         const auto testName = "load12ods ";
-        auto socket = *loadDocAndGetSocket("load12.ods", _uri, testName);
+        auto socket = loadDocAndGetSocket("load12.ods", _uri, testName);
 
         int docSheet = -1;
         int docSheets = 0;
@@ -616,7 +616,7 @@ void TileCacheTests::checkBlackTile(std::stringstream& tile)
     CPPUNIT_ASSERT_MESSAGE("The tile is 90% black", (black * 100) / (height * width) < 90);
 }
 
-void TileCacheTests::checkBlackTiles(Poco::Net::WebSocket& socket, const int /*part*/, const int /*docWidth*/, const int /*docHeight*/, const std::string& name)
+void TileCacheTests::checkBlackTiles(std::shared_ptr<LOOLWebSocket>& socket, const int /*part*/, const int /*docWidth*/, const int /*docHeight*/, const std::string& name)
 {
     // Check the last row of tiles to verify that the tiles
     // render correctly and there are no black tiles.
@@ -645,7 +645,7 @@ void TileCacheTests::testTileInvalidateWriter()
     getDocumentPathAndURL("empty.odt", documentPath, documentURL);
     Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
 
-    auto socket = *loadDocAndGetSocket(_uri, documentURL);
+    auto socket = loadDocAndGetSocket(_uri, documentURL);
 
     std::string text = "Test. Now go 3 \"Enters\":\n\n\nNow after the enters, goes this text";
     for (char ch : text)
@@ -682,7 +682,7 @@ void TileCacheTests::testTileInvalidateWriterPage()
     getDocumentPathAndURL("empty.odt", documentPath, documentURL);
     Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
 
-    auto socket = *loadDocAndGetSocket(_uri, documentURL, testname);
+    auto socket = loadDocAndGetSocket(_uri, documentURL, testname);
 
     sendChar(socket, '\n', skCtrl, testname); // Send Ctrl+Enter (page break).
     assertResponseString(socket, "invalidatetiles:", testname);
@@ -703,7 +703,7 @@ void TileCacheTests::testWriterAnyKey()
     getDocumentPathAndURL("empty.odt", documentPath, documentURL);
     Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
 
-    auto socket = *loadDocAndGetSocket(_uri, documentURL);
+    auto socket = loadDocAndGetSocket(_uri, documentURL);
 
     // Now test "usual" keycodes (TODO: whole 32-bit range)
     for (int i=0; i<0x1000; ++i)
@@ -783,7 +783,7 @@ void TileCacheTests::testWriterAnyKey()
 void TileCacheTests::testTileInvalidateCalc()
 {
     const std::string testname = "tileInvalidateCalc ";
-    auto socket = *loadDocAndGetSocket("empty.ods", _uri, testname);
+    auto socket = loadDocAndGetSocket("empty.ods", _uri, testname);
 
     std::string text = "Test. Now go 3 \"Enters\": Now after the enters, goes this text";
     for (char ch : text)
@@ -815,13 +815,13 @@ void TileCacheTests::tileInvalidatePart(const std::string& filename, const std::
 
     std::string documentPath, documentURL;
     getDocumentPathAndURL(filename, documentPath, documentURL);
-    auto socket1 = *loadDocAndGetSocket(_uri, documentURL);
+    auto socket1 = loadDocAndGetSocket(_uri, documentURL);
 
     sendTextFrame(socket1, "setclientpart part=2", testname1);
     assertResponseString(socket1, "setpart:", testname1);
     sendTextFrame(socket1, "mouse type=buttondown x=7886 y=8929 count=1 buttons=1 modifier=0", testname1);
 
-    auto socket2 = *loadDocAndGetSocket(_uri, documentURL);
+    auto socket2 = loadDocAndGetSocket(_uri, documentURL);
     sendTextFrame(socket2, "setclientpart part=5", testname2);
     assertResponseString(socket2, "setpart:", testname2);
     sendTextFrame(socket2, "mouse type=buttondown x=7886 y=8929 count=1 buttons=1 modifier=0", testname2);
@@ -855,7 +855,7 @@ void TileCacheTests::testTileInvalidatePartImpress()
     tileInvalidatePart("setclientpart.odp", "tileInvalidatePartImpress");
 }
 
-void TileCacheTests::checkTiles(Poco::Net::WebSocket& socket, const std::string& docType, const std::string& name)
+void TileCacheTests::checkTiles(std::shared_ptr<LOOLWebSocket>& socket, const std::string& docType, const std::string& name)
 {
     const std::string current = "current=";
     const std::string height = "height=";
@@ -922,7 +922,7 @@ void TileCacheTests::checkTiles(Poco::Net::WebSocket& socket, const std::string&
     }
 }
 
-void TileCacheTests::requestTiles(Poco::Net::WebSocket& socket, const int part, const int docWidth, const int docHeight, const std::string& name)
+void TileCacheTests::requestTiles(std::shared_ptr<LOOLWebSocket>& socket, const int part, const int docWidth, const int docHeight, const std::string& name)
 {
     // twips
     const int tileSize = 3840;
diff --git a/loolwsd/test/UnitAdmin.cpp b/loolwsd/test/UnitAdmin.cpp
index 9f704c1..96ccee8 100644
--- a/loolwsd/test/UnitAdmin.cpp
+++ b/loolwsd/test/UnitAdmin.cpp
@@ -45,14 +45,14 @@ private:
     std::string _jwtCookie;
     bool _isTestRunning = false;
     const Poco::URI _uri;
-    std::shared_ptr<Poco::Net::WebSocket> _adminWs;
+    std::shared_ptr<LOOLWebSocket> _adminWs;
 
     typedef TestResult (UnitAdmin::*AdminTest)(void);
     std::vector<AdminTest> _tests;
 
-    std::shared_ptr<Poco::Net::WebSocket> _docWs1;
-    std::shared_ptr<Poco::Net::WebSocket> _docWs2;
-    std::shared_ptr<Poco::Net::WebSocket> _docWs3;
+    std::shared_ptr<LOOLWebSocket> _docWs1;
+    std::shared_ptr<LOOLWebSocket> _docWs2;
+    std::shared_ptr<LOOLWebSocket> _docWs3;
     int _docPid1;
     int _docPid2;
     int _docPid3;
@@ -131,7 +131,7 @@ private:
         HTTPRequest request(HTTPRequest::HTTP_GET, "/lool/adminws/");
         std::unique_ptr<HTTPClientSession> session(UnitHTTP::createSession());
 
-        _adminWs = std::make_shared<Poco::Net::WebSocket>(*session, request, response);
+        _adminWs = std::make_shared<LOOLWebSocket>(*session, request, response);
         const std::string testMessage = "documents";
         std::unique_lock<std::mutex> lock(_messageReceivedMutex);
         _messageReceived.clear();
@@ -162,7 +162,7 @@ private:
         HTTPRequest request(HTTPRequest::HTTP_GET, "/lool/adminws/");
         std::unique_ptr<HTTPClientSession> session(UnitHTTP::createSession());
 
-        _adminWs = std::make_shared<Poco::Net::WebSocket>(*session, request, response);
+        _adminWs = std::make_shared<LOOLWebSocket>(*session, request, response);
         const std::string testMessage = "auth jwt=incorrectJWT";
         std::unique_lock<std::mutex> lock(_messageReceivedMutex);
         _messageReceived.clear();
@@ -193,7 +193,7 @@ private:
         HTTPRequest request(HTTPRequest::HTTP_GET, "/lool/adminws/");
         std::unique_ptr<HTTPClientSession> session(UnitHTTP::createSession());
 
-        _adminWs = std::make_shared<Poco::Net::WebSocket>(*session, request, response);
+        _adminWs = std::make_shared<LOOLWebSocket>(*session, request, response);
         const std::string authMessage = "auth jwt=" + _jwtCookie;
         _adminWs->sendFrame(authMessage.data(), authMessage.size());
 
@@ -216,7 +216,7 @@ private:
 
         std::unique_lock<std::mutex> lock(_messageReceivedMutex);
         _messageReceived.clear();
-        _docWs1 = std::make_shared<Poco::Net::WebSocket>(*session1, request1, response1);
+        _docWs1 = std::make_shared<LOOLWebSocket>(*session1, request1, response1);
         _docWs1->sendFrame(loadMessage1.data(), loadMessage1.size());
         if (_messageReceivedCV.wait_for(lock, std::chrono::milliseconds(_messageTimeoutMilliSeconds)) == std::cv_status::timeout)
         {
@@ -244,7 +244,7 @@ private:
         // Open another view of same document
         lock.lock(); // lock _messageReceivedMutex
         _messageReceived.clear();
-        _docWs2 = std::make_shared<Poco::Net::WebSocket>(*session2, request1, response1);
+        _docWs2 = std::make_shared<LOOLWebSocket>(*session2, request1, response1);
         _docWs2->sendFrame(loadMessage1.data(), loadMessage1.size());
         if (_messageReceivedCV.wait_for(lock, std::chrono::milliseconds(_messageTimeoutMilliSeconds)) == std::cv_status::timeout)
         {
@@ -279,7 +279,7 @@ private:
 
         lock.lock(); // lock _messageReceivedMutex
         _messageReceived.clear();
-        _docWs3 = std::make_shared<Poco::Net::WebSocket>(*session3, request2, response2);
+        _docWs3 = std::make_shared<LOOLWebSocket>(*session3, request2, response2);
         _docWs3->sendFrame(loadMessage2.data(), loadMessage2.size());
         if (_messageReceivedCV.wait_for(lock, std::chrono::milliseconds(_messageTimeoutMilliSeconds)) == std::cv_status::timeout)
         {
diff --git a/loolwsd/test/UnitFonts.cpp b/loolwsd/test/UnitFonts.cpp
index c82dcbb..60312d4 100644
--- a/loolwsd/test/UnitFonts.cpp
+++ b/loolwsd/test/UnitFonts.cpp
@@ -41,7 +41,7 @@ namespace {
             return std::string("can't find unit_online_get_fonts hook");
     }
 
-    std::string readFontList(const std::shared_ptr< Poco::Net::WebSocket > &socket)
+    std::string readFontList(const std::shared_ptr<LOOLWebSocket> &socket)
     {
         int flags;
         char buffer[100 * 1000];
@@ -89,7 +89,7 @@ public:
         }
     }
 
-    virtual void newChild(const std::shared_ptr<Poco::Net::WebSocket> &socket) override
+    virtual void newChild(const std::shared_ptr<LOOLWebSocket> &socket) override
     {
         Log::info("Fetching font list from kit");
         socket->sendFrame("unit-getfontlist: \n",
@@ -106,7 +106,7 @@ public:
         if (type == UnitWSD::TestRequest::TEST_REQ_PRISONER &&
             request.getURI().find(UNIT_URI) == 0)
         {
-            auto ws = std::make_shared<Poco::Net::WebSocket>(request, response);
+            auto ws = std::make_shared<LOOLWebSocket>(request, response);
             _fontsBroker = readFontList(ws);
             check();
             return true;
@@ -133,7 +133,7 @@ public:
         Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET,
                                        std::string(UNIT_URI));
         Poco::Net::HTTPResponse response;
-        auto ws = std::make_shared<Poco::Net::WebSocket>(cs, request, response);
+        auto ws = std::make_shared<LOOLWebSocket>(cs, request, response);
         ws->setReceiveTimeout(0);
         Log::info("Fetching font list from forkit");
         std::string fontListMsg = getFontList() + "\n";
@@ -141,7 +141,7 @@ public:
     }
 
     // Called from WSD and handled inside the kit.
-    virtual bool filterKitMessage(const std::shared_ptr<Poco::Net::WebSocket> &ws,
+    virtual bool filterKitMessage(const std::shared_ptr<LOOLWebSocket> &ws,
                                   std::string &message) override
     {
         const std::string token = LOOLProtocol::getFirstToken(message);
diff --git a/loolwsd/test/UnitFuzz.cpp b/loolwsd/test/UnitFuzz.cpp
index 57622b3..7454aa4 100644
--- a/loolwsd/test/UnitFuzz.cpp
+++ b/loolwsd/test/UnitFuzz.cpp
@@ -17,12 +17,12 @@
 #include "Common.hpp"
 #include "IoUtil.hpp"
 #include "LOOLProtocol.hpp"
+#include <LOOLWebSocket.hpp>
 #include "Unit.hpp"
 #include "Util.hpp"
 
 #include <Poco/Timestamp.h>
 #include <Poco/StringTokenizer.h>
-#include <Poco/Net/WebSocket.h>
 
 // Inside the WSD process
 class UnitFuzz : public UnitWSD
@@ -55,7 +55,7 @@ public:
     {
     }
 
-    virtual bool filterKitMessage(const std::shared_ptr<Poco::Net::WebSocket> & /* ws */,
+    virtual bool filterKitMessage(const std::shared_ptr<LOOLWebSocket> & /* ws */,
                                   std::string & /* message */) override
     {
         return false;
diff --git a/loolwsd/test/UnitPrefork.cpp b/loolwsd/test/UnitPrefork.cpp
index e6837f5..9f5463b 100644
--- a/loolwsd/test/UnitPrefork.cpp
+++ b/loolwsd/test/UnitPrefork.cpp
@@ -21,11 +21,11 @@
 
 #include <Poco/Timestamp.h>
 #include <Poco/StringTokenizer.h>
-#include <Poco/Net/WebSocket.h>
 
 #include "Common.hpp"
 #include "IoUtil.hpp"
 #include "LOOLProtocol.hpp"
+#include <LOOLWebSocket.hpp>
 #include "Log.hpp"
 #include "Unit.hpp"
 #include "Util.hpp"
@@ -41,7 +41,7 @@ class UnitPrefork : public UnitWSD
     size_t _childDirty;
     std::mutex _mutex;
     std::condition_variable _cv;
-    std::vector< std::shared_ptr<Poco::Net::WebSocket> > _childSockets;
+    std::vector< std::shared_ptr<LOOLWebSocket> > _childSockets;
 
 public:
     UnitPrefork()
@@ -81,7 +81,7 @@ public:
         return true;
     }
 
-    virtual void newChild(const std::shared_ptr<Poco::Net::WebSocket> &socket) override
+    virtual void newChild(const std::shared_ptr<LOOLWebSocket> &socket) override
     {
         std::unique_lock<std::mutex> lock(_mutex);
 
@@ -286,7 +286,7 @@ public:
         }
     }
 
-    virtual bool filterKitMessage(const std::shared_ptr<Poco::Net::WebSocket> &ws,
+    virtual bool filterKitMessage(const std::shared_ptr<LOOLWebSocket> &ws,
                                   std::string &message) override
     {
         const auto token = LOOLProtocol::getFirstToken(message);
diff --git a/loolwsd/test/helpers.hpp b/loolwsd/test/helpers.hpp
index b6d6d78..4f12c2e 100644
--- a/loolwsd/test/helpers.hpp
+++ b/loolwsd/test/helpers.hpp
@@ -32,7 +32,6 @@
 #include <Poco/Net/PrivateKeyPassphraseHandler.h>
 #include <Poco/Net/SSLManager.h>
 #include <Poco/Net/Socket.h>
-#include <Poco/Net/WebSocket.h>
 #include <Poco/Path.h>
 #include <Poco/StreamCopier.h>
 #include <Poco/StringTokenizer.h>
@@ -42,6 +41,7 @@
 
 #include <Common.hpp>
 #include <LOOLProtocol.hpp>
+#include <LOOLWebSocket.hpp>
 #include <UserMessages.hpp>
 #include <Util.hpp>
 
@@ -121,14 +121,14 @@ void getDocumentPathAndURL(const std::string& docFilename, std::string& document
 }
 
 inline
-void sendTextFrame(Poco::Net::WebSocket& socket, const std::string& string, const std::string& name = "")
+void sendTextFrame(LOOLWebSocket& socket, const std::string& string, const std::string& name = "")
 {
     std::cerr << name << "Sending " << string.size() << " bytes: " << LOOLProtocol::getAbbreviatedMessage(string) << std::endl;
     socket.sendFrame(string.data(), string.size());
 }
 
 inline
-void sendTextFrame(const std::shared_ptr<Poco::Net::WebSocket>& socket, const std::string& string, const std::string& name = "")
+void sendTextFrame(const std::shared_ptr<LOOLWebSocket>& socket, const std::string& string, const std::string& name = "")
 {
     sendTextFrame(*socket, string, name);
 }
@@ -160,7 +160,7 @@ std::string getTestServerURI()
 }
 
 inline
-int getErrorCode(Poco::Net::WebSocket& ws, std::string& message)
+int getErrorCode(LOOLWebSocket& ws, std::string& message)
 {
     int flags = 0;
     int bytes = 0;
@@ -187,7 +187,7 @@ int getErrorCode(Poco::Net::WebSocket& ws, std::string& message)
 }
 
 inline
-std::vector<char> getResponseMessage(Poco::Net::WebSocket& ws, const std::string& prefix, std::string name = "", const size_t timeoutMs = 10000)
+std::vector<char> getResponseMessage(LOOLWebSocket& ws, const std::string& prefix, std::string name = "", const size_t timeoutMs = 10000)
 {
     name = name + '[' + prefix + "] ";
     try
@@ -289,7 +289,7 @@ std::vector<char> getResponseMessage(Poco::Net::WebSocket& ws, const std::string
 }
 
 inline
-std::vector<char> getResponseMessage(const std::shared_ptr<Poco::Net::WebSocket>& ws, const std::string& prefix, const std::string& name = "", const size_t timeoutMs = 10000)
+std::vector<char> getResponseMessage(const std::shared_ptr<LOOLWebSocket>& ws, const std::string& prefix, const std::string& name = "", const size_t timeoutMs = 10000)
 {
     return getResponseMessage(*ws, prefix, name, timeoutMs);
 }
@@ -319,7 +319,7 @@ std::string assertNotInResponse(T& ws, const std::string& prefix, const std::str
 }
 
 inline
-bool isDocumentLoaded(Poco::Net::WebSocket& ws, const std::string& name = "", bool isView = true)
+bool isDocumentLoaded(LOOLWebSocket& ws, const std::string& name = "", bool isView = true)
 {
     const std::string prefix = isView ? "status:" : "statusindicatorfinish:";
     const auto message = getResponseString(ws, prefix, name);
@@ -327,7 +327,7 @@ bool isDocumentLoaded(Poco::Net::WebSocket& ws, const std::string& name = "", bo
 }
 
 inline
-bool isDocumentLoaded(std::shared_ptr<Poco::Net::WebSocket>& ws, const std::string& name = "", bool isView = true)
+bool isDocumentLoaded(std::shared_ptr<LOOLWebSocket>& ws, const std::string& name = "", bool isView = true)
 {
     return isDocumentLoaded(*ws, name, isView);
 }
@@ -337,7 +337,7 @@ bool isDocumentLoaded(std::shared_ptr<Poco::Net::WebSocket>& ws, const std::stri
 // The result, it is mostly time outs to get messages in the unit test and it could fail.
 // connectLOKit ensures the websocket is connected to a kit process.
 inline
-std::shared_ptr<Poco::Net::WebSocket>
+std::shared_ptr<LOOLWebSocket>
 connectLOKit(const Poco::URI& uri,
              Poco::Net::HTTPRequest& request,
              Poco::Net::HTTPResponse& response,
@@ -349,7 +349,7 @@ connectLOKit(const Poco::URI& uri,
         std::unique_ptr<Poco::Net::HTTPClientSession> session(createSession(uri));
 
         std::cerr << name << "Connecting... " << std::endl;
-        auto ws = std::make_shared<Poco::Net::WebSocket>(*session, request, response);
+        auto ws = std::make_shared<LOOLWebSocket>(*session, request, response);
         getResponseMessage(ws, "statusindicator: ready", name);
 
         return ws;
@@ -360,7 +360,7 @@ connectLOKit(const Poco::URI& uri,
 }
 
 inline
-std::shared_ptr<Poco::Net::WebSocket> loadDocAndGetSocket(const Poco::URI& uri, const std::string& documentURL, const std::string& name = "", bool isView = true)
+std::shared_ptr<LOOLWebSocket> loadDocAndGetSocket(const Poco::URI& uri, const std::string& documentURL, const std::string& name = "", bool isView = true)
 {
     try
     {
@@ -385,7 +385,7 @@ std::shared_ptr<Poco::Net::WebSocket> loadDocAndGetSocket(const Poco::URI& uri,
 }
 
 inline
-std::shared_ptr<Poco::Net::WebSocket> loadDocAndGetSocket(const std::string& docFilename, const Poco::URI& uri, const std::string& name = "", bool isView = true)
+std::shared_ptr<LOOLWebSocket> loadDocAndGetSocket(const std::string& docFilename, const Poco::URI& uri, const std::string& name = "", bool isView = true)
 {
     try
     {
@@ -404,7 +404,7 @@ std::shared_ptr<Poco::Net::WebSocket> loadDocAndGetSocket(const std::string& doc
 
 inline
 void SocketProcessor(const std::string& name,
-                     const std::shared_ptr<Poco::Net::WebSocket>& socket,
+                     const std::shared_ptr<LOOLWebSocket>& socket,
                      const std::function<bool(const std::string& msg)>& handler,
                      const size_t timeoutMs = 10000)
 {
@@ -457,13 +457,13 @@ void parseDocSize(const std::string& message, const std::string& type,
 }
 
 inline
-std::vector<char> getTileMessage(Poco::Net::WebSocket& ws, const std::string& name = "")
+std::vector<char> getTileMessage(LOOLWebSocket& ws, const std::string& name = "")
 {
     return getResponseMessage(ws, "tile", name);
 }
 
 inline
-std::vector<char> assertTileMessage(Poco::Net::WebSocket& ws, const std::string& name = "")
+std::vector<char> assertTileMessage(LOOLWebSocket& ws, const std::string& name = "")
 {
     const auto response = getTileMessage(ws, name);
 
@@ -482,7 +482,7 @@ std::vector<char> assertTileMessage(Poco::Net::WebSocket& ws, const std::string&
 }
 
 inline
-std::vector<char> assertTileMessage(const std::shared_ptr<Poco::Net::WebSocket>& ws, const std::string& name = "")
+std::vector<char> assertTileMessage(const std::shared_ptr<LOOLWebSocket>& ws, const std::string& name = "")
 {
     return assertTileMessage(*ws, name);
 }
@@ -521,25 +521,25 @@ inline int getCharKey(char ch, SpecialKey specialKeys)
     return result | specialKeys;
 }
 
-inline void sendKeyEvent(Poco::Net::WebSocket& socket, const char* type, int chr, int key, const std::string& testname = "")
+inline void sendKeyEvent(std::shared_ptr<LOOLWebSocket>& socket, const char* type, int chr, int key, const std::string& testname = "")
 {
     std::ostringstream ssIn;
     ssIn << "key type=" << type << " char=" << chr << " key=" << key;
     sendTextFrame(socket, ssIn.str(), testname);
 }
 
-inline void sendKeyPress(Poco::Net::WebSocket& socket, int chr, int key, const std::string& testname)
+inline void sendKeyPress(std::shared_ptr<LOOLWebSocket>& socket, int chr, int key, const std::string& testname)
 {
     sendKeyEvent(socket, "input", chr, key, testname);
     sendKeyEvent(socket, "up", chr, key, testname);
 }
 
-inline void sendChar(Poco::Net::WebSocket& socket, char ch, SpecialKey specialKeys=skNone, const std::string& testname = "")
+inline void sendChar(std::shared_ptr<LOOLWebSocket>& socket, char ch, SpecialKey specialKeys=skNone, const std::string& testname = "")
 {
     sendKeyPress(socket, getCharChar(ch, specialKeys), getCharKey(ch, specialKeys), testname);
 }
 
-inline void sendText(Poco::Net::WebSocket& socket, const std::string& text, const std::string& testname = "")
+inline void sendText(std::shared_ptr<LOOLWebSocket>& socket, const std::string& text, const std::string& testname = "")
 {
     for (char ch : text)
     {
diff --git a/loolwsd/test/httpcrashtest.cpp b/loolwsd/test/httpcrashtest.cpp
index ecaf4af..3bd4bcd 100644
--- a/loolwsd/test/httpcrashtest.cpp
+++ b/loolwsd/test/httpcrashtest.cpp
@@ -30,7 +30,6 @@
 #include <Poco/Net/PrivateKeyPassphraseHandler.h>
 #include <Poco/Net/SSLManager.h>
 #include <Poco/Net/Socket.h>
-#include <Poco/Net/WebSocket.h>
 #include <Poco/Path.h>
 #include <Poco/StreamCopier.h>
 #include <Poco/StringTokenizer.h>
@@ -42,6 +41,7 @@
 #include <UserMessages.hpp>
 #include <Util.hpp>
 #include <LOOLProtocol.hpp>
+#include <LOOLWebSocket.hpp>
 #include "helpers.hpp"
 #include "countloolkits.hpp"
 
@@ -144,7 +144,7 @@ void HTTPCrashTest::testCrashKit()
     const auto testname = "crashKit ";
     try
     {
-        auto socket = *loadDocAndGetSocket("empty.odt", _uri, testname);
+        auto socket = loadDocAndGetSocket("empty.odt", _uri, testname);
 
         killLoKitProcesses();
 
@@ -156,7 +156,7 @@ void HTTPCrashTest::testCrashKit()
         getResponseMessage(socket, "", testname, 1000);
 
         // 5 seconds timeout
-        socket.setReceiveTimeout(5000000);
+        socket->setReceiveTimeout(5000000);
 
         // receive close frame handshake
         int bytes;
@@ -164,16 +164,16 @@ void HTTPCrashTest::testCrashKit()
         char buffer[READ_BUFFER_SIZE];
         do
         {
-            bytes = socket.receiveFrame(buffer, sizeof(buffer), flags);
+            bytes = socket->receiveFrame(buffer, sizeof(buffer), flags);
             std::cerr << testname << "Got " << LOOLProtocol::getAbbreviatedFrameDump(buffer, bytes, flags) << std::endl;
         }
         while ((flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) != Poco::Net::WebSocket::FRAME_OP_CLOSE);
 
         // respond close frame
-        socket.shutdown();
+        socket->shutdown();
 
         // no more messages is received.
-        bytes = socket.receiveFrame(buffer, sizeof(buffer), flags);
+        bytes = socket->receiveFrame(buffer, sizeof(buffer), flags);
         CPPUNIT_ASSERT_MESSAGE("Expected no more data", bytes <= 2); // The 2-byte marker is ok.
         CPPUNIT_ASSERT_EQUAL(0x88, flags);
     }
diff --git a/loolwsd/test/httpwserror.cpp b/loolwsd/test/httpwserror.cpp
index 1d686e4..0bd8b65 100644
--- a/loolwsd/test/httpwserror.cpp
+++ b/loolwsd/test/httpwserror.cpp
@@ -17,13 +17,13 @@
 #include <Poco/Net/HTTPResponse.h>
 #include <Poco/Net/HTTPSClientSession.h>
 #include <Poco/Net/NetException.h>
-#include <Poco/Net/WebSocket.h>
 #include <Poco/URI.h>
 
 #include <cppunit/extensions/HelperMacros.h>
 
 #include "Common.hpp"
 #include "LOOLProtocol.hpp"
+#include <LOOLWebSocket.hpp>
 #include "helpers.hpp"
 #include "countloolkits.hpp"
 
@@ -101,7 +101,7 @@ void HTTPWSError::testMaxDocuments()
     try
     {
         // Load a document.
-        std::vector<std::shared_ptr<Poco::Net::WebSocket>> docs;
+        std::vector<std::shared_ptr<LOOLWebSocket>> docs;
 
         std::cerr << "Loading max number of documents: " << MAX_DOCUMENTS << std::endl;
         for (int it = 1; it <= MAX_DOCUMENTS; ++it)
@@ -118,7 +118,7 @@ void HTTPWSError::testMaxDocuments()
         getDocumentPathAndURL("empty.odt", docPath, docURL);
         Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, docURL);
         std::unique_ptr<Poco::Net::HTTPClientSession> session(helpers::createSession(_uri));
-        Poco::Net::WebSocket socket(*session, request, _response);
+        LOOLWebSocket socket(*session, request, _response);
 
         // send loolclient, load and partpagerectangles
         sendTextFrame(socket, "loolclient ", testname);
@@ -157,11 +157,11 @@ void HTTPWSError::testMaxConnections()
         auto socket = loadDocAndGetSocket(_uri, docURL, testname);
         std::cerr << "Opened connect #1 of " << MAX_CONNECTIONS << std::endl;
 
-        std::vector<std::shared_ptr<Poco::Net::WebSocket>> views;
+        std::vector<std::shared_ptr<LOOLWebSocket>> views;
         for(int it = 1; it < MAX_CONNECTIONS; it++)
         {
             std::unique_ptr<Poco::Net::HTTPClientSession> session(createSession(_uri));
-            auto ws = std::make_shared<Poco::Net::WebSocket>(*session, request, _response);
+            auto ws = std::make_shared<LOOLWebSocket>(*session, request, _response);
             views.emplace_back(ws);
             std::cerr << "Opened connect #" << (it+1) << " of " << MAX_CONNECTIONS << std::endl;
         }
@@ -170,7 +170,7 @@ void HTTPWSError::testMaxConnections()
 
         // try to connect MAX_CONNECTIONS + 1
         std::unique_ptr<Poco::Net::HTTPClientSession> session(createSession(_uri));
-        Poco::Net::WebSocket socketN(*session, request, _response);
+        LOOLWebSocket socketN(*session, request, _response);
 
         // send loolclient, load and partpagerectangles
         sendTextFrame(socketN, "loolclient ", testname);
diff --git a/loolwsd/test/httpwstest.cpp b/loolwsd/test/httpwstest.cpp
index bbb4d61..93a024b 100644
--- a/loolwsd/test/httpwstest.cpp
+++ b/loolwsd/test/httpwstest.cpp
@@ -30,7 +30,6 @@
 #include <Poco/Net/PrivateKeyPassphraseHandler.h>
 #include <Poco/Net/SSLManager.h>
 #include <Poco/Net/Socket.h>
-#include <Poco/Net/WebSocket.h>
 #include <Poco/Path.h>
 #include <Poco/RegularExpression.h>
 #include <Poco/StreamCopier.h>
@@ -42,6 +41,7 @@
 
 #include "Common.hpp"
 #include "LOOLProtocol.hpp"
+#include <LOOLWebSocket.hpp>
 #include "Png.hpp"
 #include "UserMessages.hpp"
 #include "Util.hpp"
@@ -154,7 +154,7 @@ class HTTPWSTest : public CPPUNIT_NS::TestFixture
                    int& cursorWidth,
                    int& cursorHeight);
 
-    void limitCursor(std::function<void(const std::shared_ptr<Poco::Net::WebSocket>& socket,
+    void limitCursor(std::function<void(const std::shared_ptr<LOOLWebSocket>& socket,
                                         int cursorX, int cursorY,
                                         int cursorWidth, int cursorHeight,
                                         int docWidth, int docHeight)> keyhandler,
@@ -165,7 +165,7 @@ class HTTPWSTest : public CPPUNIT_NS::TestFixture
     std::string getFontList(const std::string& message);
     void testStateChanged(const std::string& filename, std::vector<std::string>& vecComands);
     double getColRowSize(const std::string& property, const std::string& message, int index);
-    double getColRowSize(const std::shared_ptr<Poco::Net::WebSocket>& socket, const std::string& item, int index);
+    double getColRowSize(const std::shared_ptr<LOOLWebSocket>& socket, const std::string& item, int index);
     void testEachView(const std::string& doc, const std::string& type, const std::string& protocol, const std::string& view, const std::string& testname);
 
 public:
@@ -249,7 +249,7 @@ void HTTPWSTest::testHandShake()
         Poco::Net::HTTPResponse response;
         Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
         std::unique_ptr<Poco::Net::HTTPClientSession> session(helpers::createSession(_uri));
-        Poco::Net::WebSocket socket(*session, request, response);
+        LOOLWebSocket socket(*session, request, response);
         socket.setReceiveTimeout(0);
 
         int flags = 0;
@@ -298,13 +298,13 @@ void HTTPWSTest::testCloseAfterClose()
     const auto testname = "closeAfterClose ";
     try
     {
-        auto socket = *loadDocAndGetSocket("hello.odt", _uri, testname);
+        auto socket = loadDocAndGetSocket("hello.odt", _uri, testname);
 
         // send normal socket shutdown
-        socket.shutdown();
+        socket->shutdown();
 
         // 5 seconds timeout
-        socket.setReceiveTimeout(5000000);
+        socket->setReceiveTimeout(5000000);
 
         // receive close frame handshake
         int bytes;
@@ -312,12 +312,12 @@ void HTTPWSTest::testCloseAfterClose()
         char buffer[READ_BUFFER_SIZE];
         do
         {
-            bytes = socket.receiveFrame(buffer, sizeof(buffer), flags);
+            bytes = socket->receiveFrame(buffer, sizeof(buffer), flags);
         }
         while (bytes && (flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) != Poco::Net::WebSocket::FRAME_OP_CLOSE);
 
         // no more messages is received.
-        bytes = socket.receiveFrame(buffer, sizeof(buffer), flags);
+        bytes = socket->receiveFrame(buffer, sizeof(buffer), flags);
         std::cout << "Received " << bytes << " bytes, flags: "<< std::hex << flags << std::dec << std::endl;
         CPPUNIT_ASSERT_EQUAL(0, bytes);
         CPPUNIT_ASSERT_EQUAL(0, flags);
@@ -387,7 +387,7 @@ void HTTPWSTest::testBadLoad()
         getDocumentPathAndURL("hello.odt", documentPath, documentURL);
 
         Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
-        Poco::Net::WebSocket socket = *connectLOKit(_uri, request, _response);
+        auto socket = connectLOKit(_uri, request, _response);
 
         // Before loading request status.
         sendTextFrame(socket, "status");
@@ -419,8 +419,8 @@ void HTTPWSTest::testGetTextSelection()
         std::string documentPath, documentURL;
         getDocumentPathAndURL("hello.odt", documentPath, documentURL);
 
-        auto socket = *loadDocAndGetSocket(_uri, documentURL, testname);
-        auto socket2 = *loadDocAndGetSocket(_uri, documentURL, testname);
+        auto socket = loadDocAndGetSocket(_uri, documentURL, testname);
+        auto socket2 = loadDocAndGetSocket(_uri, documentURL, testname);
 
         sendTextFrame(socket, "uno .uno:SelectAll", testname);
         sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8", testname);
@@ -446,9 +446,9 @@ void HTTPWSTest::testSaveOnDisconnect()
     int kitcount = -1;
     try
     {
-        auto socket = *loadDocAndGetSocket(_uri, documentURL, testname);
+        auto socket = loadDocAndGetSocket(_uri, documentURL, testname);
 
-        auto socket2 = *loadDocAndGetSocket(_uri, documentURL, testname);
+        auto socket2 = loadDocAndGetSocket(_uri, documentURL, testname);
         sendTextFrame(socket2, "userinactive");
 
         sendTextFrame(socket, "uno .uno:SelectAll", testname);
@@ -470,8 +470,8 @@ void HTTPWSTest::testSaveOnDisconnect()
 
         // Shutdown abruptly.
         std::cerr << "Closing connection after pasting." << std::endl;
-        socket.shutdown();
-        socket2.shutdown();
+        socket->shutdown();
+        socket2->shutdown();
     }
     catch (const Poco::Exception& exc)
     {
@@ -484,7 +484,7 @@ void HTTPWSTest::testSaveOnDisconnect()
     try
     {
         // Load the same document and check that the last changes (pasted text) is saved.
-        auto socket = *loadDocAndGetSocket(_uri, documentURL, testname);
+        auto socket = loadDocAndGetSocket(_uri, documentURL, testname);
 
         // Should have no new instances.
         CPPUNIT_ASSERT_EQUAL(kitcount, countLoolKitProcesses(kitcount));
@@ -509,7 +509,7 @@ void HTTPWSTest::testReloadWhileDisconnecting()
         std::string documentPath, documentURL;
         getDocumentPathAndURL("hello.odt", documentPath, documentURL);
 
-        auto socket = *loadDocAndGetSocket(_uri, documentURL, testname);
+        auto socket = loadDocAndGetSocket(_uri, documentURL, testname);
 
         sendTextFrame(socket, "uno .uno:SelectAll", testname);
         sendTextFrame(socket, "uno .uno:Delete", testname);
@@ -524,11 +524,11 @@ void HTTPWSTest::testReloadWhileDisconnecting()
 
         // Shutdown abruptly.
         std::cerr << "Closing connection after pasting." << std::endl;
-        socket.shutdown();
+        socket->shutdown();
 
         // Load the same document and check that the last changes (pasted text) is saved.
         std::cout << "Loading again." << std::endl;
-        socket = *loadDocAndGetSocket(_uri, documentURL, testname);
+        socket = loadDocAndGetSocket(_uri, documentURL, testname);
 
         // Should have no new instances.
         CPPUNIT_ASSERT_EQUAL(kitcount, countLoolKitProcesses(kitcount));
@@ -639,7 +639,7 @@ void HTTPWSTest::testRenderingOptions()
         const std::string options = "{\"rendering\":{\".uno:HideWhitespace\":{\"type\":\"boolean\",\"value\":\"true\"}}}";
 
         Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
-        Poco::Net::WebSocket socket = *connectLOKit(_uri, request, _response);
+        auto socket = connectLOKit(_uri, request, _response);
 
         sendTextFrame(socket, "load url=" + documentURL + " options=" + options);
         sendTextFrame(socket, "status");
@@ -671,7 +671,7 @@ void HTTPWSTest::testPasswordProtectedDocumentWithoutPassword()
         getDocumentPathAndURL("password-protected.ods", documentPath, documentURL);
 
         Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
-        Poco::Net::WebSocket socket = *connectLOKit(_uri, request, _response);
+        auto socket = connectLOKit(_uri, request, _response);
 
         // Send a load request without password first
         sendTextFrame(socket, "load url=" + documentURL);
@@ -702,7 +702,7 @@ void HTTPWSTest::testPasswordProtectedDocumentWithWrongPassword()
         getDocumentPathAndURL("password-protected.ods", documentPath, documentURL);
 
         Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
-        Poco::Net::WebSocket socket = *connectLOKit(_uri, request, _response);
+        auto socket = connectLOKit(_uri, request, _response);
 
         // Send a load request with incorrect password
         sendTextFrame(socket, "load url=" + documentURL + " password=2");
@@ -732,7 +732,7 @@ void HTTPWSTest::testPasswordProtectedDocumentWithCorrectPassword()
         getDocumentPathAndURL("password-protected.ods", documentPath, documentURL);
 
         Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
-        Poco::Net::WebSocket socket = *connectLOKit(_uri, request, _response);
+        auto socket = connectLOKit(_uri, request, _response);
 
         // Send a load request with correct password
         sendTextFrame(socket, "load url=" + documentURL + " password=1");
@@ -762,7 +762,7 @@ void HTTPWSTest::testInsertDelete()
         getDocumentPathAndURL("insert-delete.odp", documentPath, documentURL);
 
         Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
-        Poco::Net::WebSocket socket = *connectLOKit(_uri, request, _response);
+        auto socket = connectLOKit(_uri, request, _response);
 
         sendTextFrame(socket, "load url=" + documentURL);
         CPPUNIT_ASSERT_MESSAGE("cannot load the document " + documentURL, isDocumentLoaded(socket));
@@ -857,7 +857,7 @@ void HTTPWSTest::testSlideShow()
         getDocumentPathAndURL("setclientpart.odp", documentPath, documentURL);
 
         Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
-        Poco::Net::WebSocket socket = *connectLOKit(_uri, request, _response);
+        auto socket = connectLOKit(_uri, request, _response);
 
         sendTextFrame(socket, "load url=" + documentURL, testname);
         CPPUNIT_ASSERT_MESSAGE("cannot load the document " + documentURL, isDocumentLoaded(socket));
@@ -957,7 +957,7 @@ void HTTPWSTest::testMaxColumn()
     {
         limitCursor(
             // move cursor to last column
-            [](const std::shared_ptr<Poco::Net::WebSocket>& socket,
+            [](const std::shared_ptr<LOOLWebSocket>& socket,
                int cursorX, int cursorY, int cursorWidth, int cursorHeight,
                int docWidth, int docHeight)
             {
@@ -996,7 +996,7 @@ void HTTPWSTest::testMaxRow()
     {
         limitCursor(
             // move cursor to last row
-            [](const std::shared_ptr<Poco::Net::WebSocket>& socket,
+            [](const std::shared_ptr<LOOLWebSocket>& socket,
                int cursorX, int cursorY, int cursorWidth, int cursorHeight,
                int docWidth, int docHeight)
             {
@@ -1100,7 +1100,7 @@ void HTTPWSTest::getCursor(const std::string& message,
     CPPUNIT_ASSERT(cursorHeight >= 0);
 }
 
-void HTTPWSTest::limitCursor(std::function<void(const std::shared_ptr<Poco::Net::WebSocket>& socket,
+void HTTPWSTest::limitCursor(std::function<void(const std::shared_ptr<LOOLWebSocket>& socket,
                                                 int cursorX, int cursorY,
                                                 int cursorWidth, int cursorHeight,
                                                 int docWidth, int docHeight)> keyhandler,
@@ -1421,7 +1421,7 @@ void HTTPWSTest::testFontList()
     try
     {
         // Load a document
-        auto socket = *loadDocAndGetSocket("setclientpart.odp", _uri, testname);
+        auto socket = loadDocAndGetSocket("setclientpart.odp", _uri, testname);
 
         sendTextFrame(socket, "commandvalues command=.uno:CharFontName", testname);
         const auto response = getResponseMessage(socket, "commandvalues:", testname);
@@ -1664,7 +1664,7 @@ double HTTPWSTest::getColRowSize(const std::string& property, const std::string&
     return item->getValue<double>("size");
 }
 
-double HTTPWSTest::getColRowSize(const std::shared_ptr<Poco::Net::WebSocket>& socket, const std::string& item, int index)
+double HTTPWSTest::getColRowSize(const std::shared_ptr<LOOLWebSocket>& socket, const std::string& item, int index)
 {
     std::vector<char> response;
     response = getResponseMessage(socket, "commandvalues:", "testColumnRowResize ");
@@ -1879,7 +1879,7 @@ void HTTPWSTest::testEachView(const std::string& doc, const std::string& type,
         getDocumentPathAndURL(doc, documentPath, documentURL);
 
         int itView = 0;
-        auto socket = *loadDocAndGetSocket(_uri, documentURL, Poco::format(view, itView));
+        auto socket = loadDocAndGetSocket(_uri, documentURL, Poco::format(view, itView));
 
         // Check document size
         sendTextFrame(socket, "status", Poco::format(view, itView));
@@ -1904,7 +1904,7 @@ void HTTPWSTest::testEachView(const std::string& doc, const std::string& type,
         CPPUNIT_ASSERT_MESSAGE(Poco::format(error, itView, protocol), !response.empty());
 
         // Connect and load 0..N Views, where N<=limit
-        std::vector<std::shared_ptr<Poco::Net::WebSocket>> views;
+        std::vector<std::shared_ptr<LOOLWebSocket>> views;
 #if MAX_DOCUMENTS > 0
         const auto limit = std::min(5, MAX_DOCUMENTS - 1); // +1 connection above
 #else
@@ -1960,7 +1960,7 @@ void HTTPWSTest::testGraphicInvalidate()
     try
     {
         // Load a document.
-        auto socket = *loadDocAndGetSocket("shape.ods", _uri, testname);
+        auto socket = loadDocAndGetSocket("shape.ods", _uri, testname);
 
         // Send click message
         sendTextFrame(socket, "mouse type=buttondown x=1035 y=400 count=1 buttons=1 modifier=0", testname);
@@ -1993,7 +1993,7 @@ void HTTPWSTest::testCursorPosition()
         std::string response;
 
         getDocumentPathAndURL("Example.odt", docPath, docURL);
-        auto socket0 = *loadDocAndGetSocket(_uri, docURL, testname);
+        auto socket0 = loadDocAndGetSocket(_uri, docURL, testname);
 
         // receive cursor position
         response = getResponseString(socket0, "invalidatecursor:", testname);
@@ -2001,7 +2001,7 @@ void HTTPWSTest::testCursorPosition()
         CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), cursorTokens.count());
 
         // Create second view
-        auto socket1 = *loadDocAndGetSocket(_uri, docURL, testname);
+        auto socket1 = loadDocAndGetSocket(_uri, docURL, testname);
 
         //receive view cursor position
         response = getResponseString(socket1, "invalidateviewcursor:", testname);
@@ -2044,7 +2044,7 @@ void HTTPWSTest::testAlertAllUsers()
         for (int i = 0; i < 2; i++)
             request[i] = new Poco::Net::HTTPRequest(Poco::Net::HTTPRequest::HTTP_GET, docURL[i]);
 
-        std::shared_ptr<Poco::Net::WebSocket> socket[4];
+        std::shared_ptr<LOOLWebSocket> socket[4];
         for (int i = 0; i < 4; i++)
         {
             socket[i] = connectLOKit(_uri, *(request[i/2]), _response);
@@ -2082,8 +2082,8 @@ void HTTPWSTest::testViewInfoMsg()
     getDocumentPathAndURL("hello.odt", docPath, docURL);
 
     Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, docURL);
-    Poco::Net::WebSocket socket0 = *connectLOKit(_uri, request, _response);
-    Poco::Net::WebSocket socket1 = *connectLOKit(_uri, request, _response);
+    auto socket0 = connectLOKit(_uri, request, _response);
+    auto socket1 = connectLOKit(_uri, request, _response);
 
     std::string response;
     int part, parts, width, height;
commit 55a32f4ed18279d367d0622c4038823e111826b7
Author: Pranav Kant <pranavk at collabora.co.uk>
Date:   Tue Nov 8 19:07:28 2016 +0530

    tdf#103640: Implement OwnerTermination; send application-level close frame
    
    This implements a new feature 'OwnerTermination' for WOPI based
    hosts. WOPI hosts now have to enable this feature by mentioning
    'EnableOwnerTermination' as 'true' in their CheckFileInfo
    response. If the OwnerId of the file matches that of the UserId
    of the session, this session would be able to terminate all other
    sessions currently editing the same document.
    
    The reason for this kind of document termination is sent to all
    sessions in a new application-level 'close:' message. This new message is
    similar to the CLOSE frame of WebSocket protocol which doesn't
    seem to work across all browsers as of now. Eg: Chrome -
    https://bugs.chromium.org/p/chromium/issues/detail?id=426798
    After receiving this 'close: ' message, loleaflet acts
    accordingly and tells the WOPI host why the websocket was closed
    via post message API.
    
    Change-Id: I997aa2e7805157ed599a3946a877fd32477cee1b
    (cherry picked from commit b0933b063e8b0e8e0de7eeeb9706fb291af72b5c)

diff --git a/loleaflet/src/core/Socket.js b/loleaflet/src/core/Socket.js
index 21cc00e..87ffe91 100644
--- a/loleaflet/src/core/Socket.js
+++ b/loleaflet/src/core/Socket.js
@@ -190,6 +190,21 @@ L.Socket = L.Class.extend({
 
 			return;
 		}
+		else if (textMsg.startsWith('close: ')) {
+			textMsg = textMsg.substring('close: '.length);
+
+			// This is due to document owner terminating the session
+			if (textMsg === 'ownertermination') {
+				// Disconnect the websocket manually
+				this.close();
+				// Tell WOPI host about it which should handle this situation
+				this._map.fire('postMessage', {msgId: 'Session_Closed'});
+			}
+
+			this._map.remove();
+
+			return;
+		}
 		else if (textMsg.startsWith('error:') && command.errorCmd === 'internal') {
 			this._map._fatal = true;
 			if (command.errorKind === 'diskfull') {
diff --git a/loleaflet/src/map/handler/Map.WOPI.js b/loleaflet/src/map/handler/Map.WOPI.js
index 4e12dd3..d966306 100644
--- a/loleaflet/src/map/handler/Map.WOPI.js
+++ b/loleaflet/src/map/handler/Map.WOPI.js
@@ -40,6 +40,9 @@ L.Map.WOPI = L.Handler.extend({
 
 			this._postMessage('Get_Views_Resp', getMembersRespVal);
 		}
+		else if (msg.MessageId === 'Close_Session') {
+			this._map._socket.sendMessage('closedocument');
+		}
 	},
 
 	_postMessage: function(e) {
diff --git a/loolwsd/ClientSession.cpp b/loolwsd/ClientSession.cpp
index 69654da..c186619 100644
--- a/loolwsd/ClientSession.cpp
+++ b/loolwsd/ClientSession.cpp
@@ -43,6 +43,7 @@ ClientSession::ClientSession(const std::string& id,
     _docBroker(docBroker),
     _uriPublic(uriPublic),
     _isReadOnly(readOnly),
+    _isDocumentOwner(false),
     _loadPart(-1)
 {
     Log::info("ClientSession ctor [" + getName() + "].");
@@ -115,6 +116,7 @@ bool ClientSession::_handleInput(const char *buffer, int length)
              tokens[0] != "clientzoom" &&
              tokens[0] != "clientvisiblearea" &&
              tokens[0] != "commandvalues" &&
+             tokens[0] != "closedocument" &&
              tokens[0] != "downloadas" &&
              tokens[0] != "getchildid" &&
              tokens[0] != "gettextselection" &&
@@ -156,6 +158,17 @@ bool ClientSession::_handleInput(const char *buffer, int length)
     {
         return getCommandValues(buffer, length, tokens, docBroker);
     }
+    else if (tokens[0] == "closedocument")
+    {
+        // If this session is the owner of the file, let it close all sessions
+        if (_isDocumentOwner)
+        {
+            LOG_DBG("Session [" + getId() + "] requested owner termination");
+            docBroker->closeDocument("ownertermination");
+        }
+
+        return true;
+    }
     else if (tokens[0] == "partpagerectangles")
     {
         return getPartPageRectangles(buffer, length, docBroker);
diff --git a/loolwsd/ClientSession.hpp b/loolwsd/ClientSession.hpp
index 8713026..77ddcfa 100644
--- a/loolwsd/ClientSession.hpp
+++ b/loolwsd/ClientSession.hpp
@@ -37,8 +37,11 @@ public:
     std::shared_ptr<PrisonerSession> getPeer() const { return _peer; }
     bool shutdownPeer(Poco::UInt16 statusCode);
 
+    const std::string getUserId() const { return _userId; }
+
     void setUserId(const std::string& userId) { _userId = userId; }
     void setUserName(const std::string& userName) { _userName = userName; }
+    void setDocumentOwner(const bool isDocumentOwner) { _isDocumentOwner = isDocumentOwner; }
 
     /**
      * Return the URL of the saved-as document when it's ready. If called
@@ -66,7 +69,6 @@ private:
 
     bool loadDocument(const char* buffer, int length, Poco::StringTokenizer& tokens,
                       const std::shared_ptr<DocumentBroker>& docBroker);
-
     bool getStatus(const char* buffer, int length,
                    const std::shared_ptr<DocumentBroker>& docBroker);
     bool getCommandValues(const char* buffer, int length, Poco::StringTokenizer& tokens,
@@ -94,6 +96,9 @@ private:
     /// Whether the session is opened as readonly
     bool _isReadOnly;
 
+    /// Whether this session is the owner of currently opened document
+    bool _isDocumentOwner;
+
     /// Our peer that connects us to the child.
     std::shared_ptr<PrisonerSession> _peer;
 
diff --git a/loolwsd/DocumentBroker.cpp b/loolwsd/DocumentBroker.cpp
index 723b73b..295e07c 100644
--- a/loolwsd/DocumentBroker.cpp
+++ b/loolwsd/DocumentBroker.cpp
@@ -241,6 +241,13 @@ bool DocumentBroker::load(std::shared_ptr<ClientSession>& session, const std::st
             session->sendTextFrame("wopi: postmessageorigin " + wopifileinfo._postMessageOrigin);
         }
 
+        // Mark the session as 'Document owner' if WOPI hosts supports it
+        if (wopifileinfo._enableOwnerTermination && userid == _storage->getFileInfo()._ownerId)
+        {
+            LOG_DBG("Session [" + sessionId + "] is the document owner");
+            session->setDocumentOwner(true);
+        }
+
         getInfoCallDuration = wopifileinfo._callDuration;
     }
     else if (dynamic_cast<LocalStorage*>(_storage.get()) != nullptr)
@@ -895,14 +902,22 @@ void DocumentBroker::childSocketTerminated()
     }
 }
 
-void DocumentBroker::terminateChild(std::unique_lock<std::mutex>& lock)
+void DocumentBroker::terminateChild(std::unique_lock<std::mutex>& lock, const std::string& closeReason)
 {
     Util::assertIsLocked(_mutex);
     Util::assertIsLocked(lock);
 
     LOG_INF("Terminating child [" << getPid() << "] of doc [" << _docKey << "].");
 
-    assert(_sessions.empty() && "DocumentBroker still has unremoved sessions!");
+    // Close all running sessions
+    for (auto& pair : _sessions)
+    {
+        // See protocol.txt for this application-level close frame
+        pair.second->sendTextFrame("close: " + closeReason);
+        pair.second->shutdown(Poco::Net::WebSocket::WS_ENDPOINT_GOING_AWAY, closeReason);
+    }
+
+    std::this_thread::sleep_for (std::chrono::seconds(5));
 
     // First flag to stop as it might be waiting on our lock
     // to process some incoming message.
@@ -914,4 +929,11 @@ void DocumentBroker::terminateChild(std::unique_lock<std::mutex>& lock)
     _childProcess->close(false);
 }
 
+void DocumentBroker::closeDocument(const std::string& reason)
+{
+    auto lock = getLock();
+
+    terminateChild(lock, reason);
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/loolwsd/DocumentBroker.hpp b/loolwsd/DocumentBroker.hpp
index 7280fb6..3212715 100644
--- a/loolwsd/DocumentBroker.hpp
+++ b/loolwsd/DocumentBroker.hpp
@@ -275,6 +275,8 @@ public:
 
     int getRenderedTileCount() { return _debugRenderedTileCount; }
 
+    void closeDocument(const std::string& reason);
+
     /// Called by the ChildProcess object to notify
     /// that it has terminated on its own.
     /// This happens either when the child exists
@@ -286,7 +288,7 @@ public:
     /// We must be called under lock and it must be
     /// passed to us so we unlock before waiting on
     /// the ChildProcess thread, which can take our lock.
-    void terminateChild(std::unique_lock<std::mutex>& lock);
+    void terminateChild(std::unique_lock<std::mutex>& lock, const std::string& closeReason = "");
 
     /// Get the PID of the associated child process
     Poco::Process::PID getPid() const { return _childProcess->getPid(); }
diff --git a/loolwsd/LOOLSession.cpp b/loolwsd/LOOLSession.cpp
index 25bc408..db091f5 100644
--- a/loolwsd/LOOLSession.cpp
+++ b/loolwsd/LOOLSession.cpp
@@ -202,14 +202,14 @@ bool LOOLSession::handleDisconnect()
     return false;
 }
 
-void LOOLSession::shutdown(Poco::UInt16 statusCode)
+void LOOLSession::shutdown(Poco::UInt16 statusCode, const std::string& statusMessage)
 {
     if (_ws)
     {
         try
         {
-            LOG_TRC("Shutting down WS [" << getName() << "].");
-            _ws->shutdown(statusCode);
+            LOG_TRC("Shutting down WS [" << getName() << "] with statusCode [" << statusCode << "] and reason [" << statusMessage << "].");
+            _ws->shutdown(statusCode, statusMessage);
         }
         catch (const Poco::Exception &exc)
         {
diff --git a/loolwsd/LOOLSession.hpp b/loolwsd/LOOLSession.hpp
index 6ef8d22..cd6b9fe 100644
--- a/loolwsd/LOOLSession.hpp
+++ b/loolwsd/LOOLSession.hpp
@@ -62,7 +62,7 @@ public:
     /// Called to handle disconnection command from socket.
     virtual bool handleDisconnect();
 
-    void shutdown(Poco::UInt16 statusCode);
+    void shutdown(Poco::UInt16 statusCode, const std::string& statusMessage = "");
 
     bool isActive() const { return _isActive; }
     void setIsActive(bool active) { _isActive = active; }
diff --git a/loolwsd/Storage.cpp b/loolwsd/Storage.cpp
index 86ccdba..0a451ae 100644
--- a/loolwsd/Storage.cpp
+++ b/loolwsd/Storage.cpp
@@ -195,7 +195,7 @@ LocalStorage::LocalFileInfo LocalStorage::getLocalFileInfo(const Poco::URI& uriP
         const auto lastModified = file.getLastModified();
         const auto size = file.getSize();
 
-        _fileInfo = FileInfo({filename, "lool", lastModified, size});
+        _fileInfo = FileInfo({filename, "localhost", lastModified, size});
     }
 
     // Set automatic userid and username
@@ -318,6 +318,7 @@ WopiStorage::WOPIFileInfo WopiStorage::getWOPIFileInfo(const Poco::URI& uriPubli
     std::string userId;
     std::string userName;
     bool canWrite = false;
+    bool enableOwnerTermination = false;
     std::string postMessageOrigin;
     std::string resMsg;
     Poco::StreamCopier::copyToString(rs, resMsg);
@@ -345,6 +346,8 @@ WopiStorage::WOPIFileInfo WopiStorage::getWOPIFileInfo(const Poco::URI& uriPubli
         canWrite = canWriteVar.isString() ? (canWriteVar.toString() == "true") : false;
         const auto postMessageOriginVar = getOrWarn(object, "PostMessageOrigin");
         postMessageOrigin = postMessageOriginVar.isString() ? postMessageOriginVar.toString() : "";
+        const auto enableOwnerTerminationVar = getOrWarn(object, "EnableOwnerTermination");
+        enableOwnerTermination = enableOwnerTerminationVar.isString() ? (enableOwnerTerminationVar.toString() == "true") : false;
     }
     else
         Log::error("WOPI::CheckFileInfo is missing JSON payload");
@@ -355,7 +358,7 @@ WopiStorage::WOPIFileInfo WopiStorage::getWOPIFileInfo(const Poco::URI& uriPubli
         _fileInfo = FileInfo({filename, ownerId, Poco::Timestamp(), size});
     }
 
-    return WOPIFileInfo({userId, userName, canWrite, postMessageOrigin, callDuration});
+    return WOPIFileInfo({userId, userName, canWrite, postMessageOrigin, enableOwnerTermination, callDuration});
 }
 
 /// uri format: http://server/<...>/wopi*/files/<id>/content
diff --git a/loolwsd/Storage.hpp b/loolwsd/Storage.hpp
index 7912230..a1eb2cf 100644
--- a/loolwsd/Storage.hpp
+++ b/loolwsd/Storage.hpp
@@ -60,7 +60,7 @@ public:
         _uri(uri),
         _localStorePath(localStorePath),
         _jailPath(jailPath),
-        _fileInfo("", Poco::Timestamp(), 0),
+        _fileInfo("", "lool", Poco::Timestamp(), 0),
         _isLoaded(false)
     {
         Log::debug("Storage ctor: " + uri.toString());
@@ -169,11 +169,13 @@ public:
                      const std::string& username,
                      const bool userCanWrite,
                      const std::string& postMessageOrigin,
+                     const bool enableOwnerTermination,
                      const std::chrono::duration<double> callDuration)
             : _userid(userid),
               _username(username),
               _userCanWrite(userCanWrite),
               _postMessageOrigin(postMessageOrigin),
+              _enableOwnerTermination(enableOwnerTermination),
               _callDuration(callDuration)
             {
             }
@@ -186,6 +188,8 @@ public:
         bool _userCanWrite;
         /// WOPI Post message property
         std::string _postMessageOrigin;
+        /// If WOPI host has enabled owner termination feature on
+        bool _enableOwnerTermination;
         /// Time it took to call WOPI's CheckFileInfo
         std::chrono::duration<double> _callDuration;
     };
diff --git a/loolwsd/protocol.txt b/loolwsd/protocol.txt
index f6035b9..7fea786 100644
--- a/loolwsd/protocol.txt
+++ b/loolwsd/protocol.txt
@@ -164,6 +164,12 @@ userinactive
 
     See 'useractive'.
 
+closedocument
+
+    This gives document owners the ability to terminate all sessions currently
+    having that document opened. This functionality is enabled only in case WOPI
+    host mentions 'EnableOwnerTermination' flag in its CheckFileInfo response
+
 server -> client
 ================
 
@@ -220,6 +226,21 @@ error: cmd=<command> kind=<kind> [code=<error_code>] [params=1,2,3,...,N]
     <code> (when provided) further specifies the error as forwarded from
     LibreOffice
 
+close: <reason>
+
+    Ask a client to close the websocket connection with <reason>.
+    Exactly similar fields are also available in WebSocket protocol's
+    CLOSE frame, but some browser implementation (google-chrome) doesn't seem to
+    handle that well. This is a temporary application-level close websocket
+    to circumvent the same.
+
+    <reason> can have following values:
+    * ownertermination - If the session close is due to 'Document owner'
+    terminating the session.
+    (Document owner is the one who has the file ownership and hence have the
+    ability to kill all other sessions if EnableOwnerTermination flag in WOPI
+    CheckFileInfo is 'true' (assumed to be 'false' by default).
+
 getchildid: id=<id>
 
     Returns the child id
commit c7d9af10fb8135208cac2d98d8dbe65cdf6107a5
Author: Pranav Kant <pranavk at collabora.co.uk>
Date:   Tue Nov 8 13:54:02 2016 +0530

    loolwsd: This is not used anymore
    
    Change-Id: I7a0405faad5a2c1e15788c084d51bb6a4a39f82f
    (cherry picked from commit 410936b60012c5cbbd1032c2590582821ba22e62)

diff --git a/loolwsd/DocumentBroker.hpp b/loolwsd/DocumentBroker.hpp
index 94a13f2..7280fb6 100644
--- a/loolwsd/DocumentBroker.hpp
+++ b/loolwsd/DocumentBroker.hpp
@@ -275,10 +275,6 @@ public:
 
     int getRenderedTileCount() { return _debugRenderedTileCount; }
 
-    /// Returns time taken in making calls to storage during load
-    /// Currently, only makes sense in case storage is WOPI
-    const std::chrono::duration<double> getStorageLoadDuration() const;
-
     /// Called by the ChildProcess object to notify
     /// that it has terminated on its own.
     /// This happens either when the child exists
commit 3988b4d85151749d0fc618886fe149ad1f0e7538
Author: Pranav Kant <pranavk at collabora.co.uk>
Date:   Tue Nov 8 13:44:14 2016 +0530

    loolwsd: Read and store session owner ids
    
    Change-Id: If905344475eb6d9585eaa5a1df6882dc3a7bdb2b
    (cherry picked from commit a6d2fe80a1e7c2eee80aa5f592e5445f7c7e2b91)

diff --git a/loolwsd/Storage.cpp b/loolwsd/Storage.cpp
index ef21a8d..86ccdba 100644
--- a/loolwsd/Storage.cpp
+++ b/loolwsd/Storage.cpp
@@ -195,7 +195,7 @@ LocalStorage::LocalFileInfo LocalStorage::getLocalFileInfo(const Poco::URI& uriP
         const auto lastModified = file.getLastModified();
         const auto size = file.getSize();
 
-        _fileInfo = FileInfo({filename, lastModified, size});
+        _fileInfo = FileInfo({filename, "lool", lastModified, size});
     }
 
     // Set automatic userid and username
@@ -314,6 +314,7 @@ WopiStorage::WOPIFileInfo WopiStorage::getWOPIFileInfo(const Poco::URI& uriPubli
     // Parse the response.
     std::string filename;
     size_t size = 0;
+    std::string ownerId;
     std::string userId;
     std::string userName;
     bool canWrite = false;
@@ -334,6 +335,8 @@ WopiStorage::WOPIFileInfo WopiStorage::getWOPIFileInfo(const Poco::URI& uriPubli
         filename = getOrWarn(object, "BaseFileName").toString();
         const auto sizeVar = getOrWarn(object, "Size");
         size = std::stoul(sizeVar.toString(), nullptr, 0);
+        const auto ownerIdVar = getOrWarn(object, "OwnerId");
+        ownerId = (ownerIdVar.isString() ? ownerIdVar.toString() : "");
         const auto userIdVar = getOrWarn(object, "UserId");
         userId = (userIdVar.isString() ? userIdVar.toString() : "");
         const auto userNameVar = getOrWarn(object,"UserFriendlyName");
@@ -349,7 +352,7 @@ WopiStorage::WOPIFileInfo WopiStorage::getWOPIFileInfo(const Poco::URI& uriPubli
     if (!_fileInfo.isValid())
     {
         // WOPI doesn't support file last modified time.
-        _fileInfo = FileInfo({filename, Poco::Timestamp(), size});
+        _fileInfo = FileInfo({filename, ownerId, Poco::Timestamp(), size});
     }
 
     return WOPIFileInfo({userId, userName, canWrite, postMessageOrigin, callDuration});
diff --git a/loolwsd/Storage.hpp b/loolwsd/Storage.hpp
index a60d42b..7912230 100644
--- a/loolwsd/Storage.hpp
+++ b/loolwsd/Storage.hpp
@@ -31,9 +31,11 @@ public:
     {
     public:
         FileInfo(const std::string& filename,
+                 const std::string& ownerId,
                  const Poco::Timestamp& modifiedTime,
                  size_t size)
             : _filename(filename),
+              _ownerId(ownerId),
               _modifiedTime(modifiedTime),
               _size(size)
         {
@@ -45,6 +47,7 @@ public:
         }
 
         std::string _filename;
+        std::string _ownerId;
         Poco::Timestamp _modifiedTime;
         size_t _size;
     };
commit 3d11e8ccac8f830678cb76c7cbd351ab13bada6b
Author: Pranav Kant <pranavk at collabora.co.uk>
Date:   Mon Nov 7 22:56:54 2016 +0530

    Document PostMessage API in reference.html
    
    Change-Id: Ibdd3fc060a99f3b6185362fa3143014710873079
    (cherry picked from commit 7f77618f38291e9933825e60e771a20b1b658d7a)

diff --git a/loleaflet/reference.html b/loleaflet/reference.html
index ae31f6b..a07378f 100644
--- a/loleaflet/reference.html
+++ b/loleaflet/reference.html
@@ -67,6 +67,11 @@
 			<li><a href="#loleaflet-object-values">Object values</a></li>
 			<li><a href="#loleaflet-uno-commands">Uno commands</a></li>
 		</ul>
+		<h4 style="color:red;">PostMessage API</h4>
+		<ul>
+		        <li><a href="#loleaflet-postmessage-initialization">Initialization</a></li>

... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list