2016-11-10 02:47:25 -06:00
|
|
|
/* -*- 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
|
|
|
|
|
2016-12-16 00:02:16 -06:00
|
|
|
#include "config.h"
|
|
|
|
|
2016-11-21 04:06:05 -06:00
|
|
|
#include <cstdlib>
|
2016-11-10 02:47:25 -06:00
|
|
|
#include <mutex>
|
2016-11-21 04:06:05 -06:00
|
|
|
#include <thread>
|
2016-11-10 02:47:25 -06:00
|
|
|
|
|
|
|
#include <Poco/Net/WebSocket.h>
|
|
|
|
|
|
|
|
#include <Common.hpp>
|
2016-11-24 08:56:06 -06:00
|
|
|
#include <Protocol.hpp>
|
2016-11-10 02:47:25 -06:00
|
|
|
#include <Log.hpp>
|
|
|
|
|
|
|
|
/// WebSocket that is thread safe, and handles large frames transparently.
|
2016-11-12 12:41:54 -06:00
|
|
|
/// Careful - sendFrame and receiveFrame are _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.
|
2016-11-10 02:47:25 -06:00
|
|
|
class LOOLWebSocket : public Poco::Net::WebSocket
|
|
|
|
{
|
2016-11-12 12:41:54 -06:00
|
|
|
private:
|
2016-11-10 02:47:25 -06:00
|
|
|
std::mutex _mutex;
|
|
|
|
|
2016-11-28 04:26:41 -06:00
|
|
|
#if ENABLE_DEBUG
|
2016-12-12 22:08:01 -06:00
|
|
|
static std::chrono::milliseconds getWebSocketDelay()
|
2016-11-21 04:06:05 -06:00
|
|
|
{
|
2016-11-28 04:26:41 -06:00
|
|
|
unsigned long baseDelay = 0;
|
|
|
|
unsigned long jitter = 0;
|
2016-11-21 04:06:05 -06:00
|
|
|
if (std::getenv("LOOL_WS_DELAY"))
|
|
|
|
{
|
|
|
|
baseDelay = std::stoul(std::getenv("LOOL_WS_DELAY"));
|
|
|
|
}
|
|
|
|
if (std::getenv("LOOL_WS_JITTER"))
|
|
|
|
{
|
|
|
|
jitter = std::stoul(std::getenv("LOOL_WS_JITTER"));
|
|
|
|
}
|
|
|
|
|
2016-11-24 03:38:00 -06:00
|
|
|
return std::chrono::milliseconds(baseDelay + (jitter > 0 ? (std::rand() % jitter) : 0));
|
2016-11-21 04:06:05 -06:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2016-11-10 02:47:25 -06:00
|
|
|
public:
|
2016-11-12 12:41:54 -06:00
|
|
|
LOOLWebSocket(const Socket& socket) :
|
|
|
|
Poco::Net::WebSocket(socket)
|
2016-11-10 02:47:25 -06:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2016-11-12 12:41:54 -06:00
|
|
|
LOOLWebSocket(Poco::Net::HTTPServerRequest& request,
|
|
|
|
Poco::Net::HTTPServerResponse& response) :
|
|
|
|
Poco::Net::WebSocket(request, response)
|
2016-11-10 02:47:25 -06:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2016-11-12 12:41:54 -06:00
|
|
|
LOOLWebSocket(Poco::Net::HTTPClientSession& cs,
|
|
|
|
Poco::Net::HTTPRequest& request,
|
|
|
|
Poco::Net::HTTPResponse& response) :
|
|
|
|
Poco::Net::WebSocket(cs, request, response)
|
2016-11-10 02:47:25 -06:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2016-11-12 12:41:54 -06:00
|
|
|
LOOLWebSocket(Poco::Net::HTTPClientSession& cs,
|
|
|
|
Poco::Net::HTTPRequest& request,
|
|
|
|
Poco::Net::HTTPResponse& response,
|
|
|
|
Poco::Net::HTTPCredentials& credentials) :
|
|
|
|
Poco::Net::WebSocket(cs, request, response, credentials)
|
2016-11-10 02:47:25 -06:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2016-11-25 03:46:01 -06:00
|
|
|
/// Wrapper for Poco::Net::WebSocket::receiveFrame() that handles PING frames
|
|
|
|
/// (by replying with a PONG frame) and PONG frames. PONG frames are ignored.
|
2016-12-02 08:43:39 -06:00
|
|
|
|
|
|
|
/// Returns number of bytes received, or 0 if the Poco receiveFrame() returned 0,
|
|
|
|
/// or -1 if no "interesting" (not PING or PONG) frame was actually received).
|
|
|
|
|
2016-11-25 03:46:01 -06:00
|
|
|
/// Should we also factor out the handling of non-final and continuation frames into this?
|
|
|
|
int receiveFrame(char* buffer, const int length, int& flags)
|
|
|
|
{
|
2016-11-28 04:26:41 -06:00
|
|
|
#if ENABLE_DEBUG
|
2016-11-25 03:46:01 -06:00
|
|
|
// Delay receiving the frame
|
|
|
|
std::this_thread::sleep_for(getWebSocketDelay());
|
|
|
|
#endif
|
2016-12-12 22:08:01 -06:00
|
|
|
// Timeout is in microseconds. We don't need this, except to yield the cpu.
|
|
|
|
static const Poco::Timespan waitTime(POLL_TIMEOUT_MS * 1000 / 10);
|
|
|
|
static const Poco::Timespan waitZero(0);
|
|
|
|
std::unique_lock<std::mutex> lock(_mutex);
|
2016-11-25 03:46:01 -06:00
|
|
|
|
|
|
|
while (poll(waitTime, Poco::Net::Socket::SELECT_READ))
|
|
|
|
{
|
|
|
|
const int n = Poco::Net::WebSocket::receiveFrame(buffer, length, flags);
|
2016-12-02 06:32:11 -06:00
|
|
|
LOG_TRC("Got frame: " << LOOLProtocol::getAbbreviatedFrameDump(buffer, n, flags));
|
2016-12-12 22:08:01 -06:00
|
|
|
|
2016-12-13 16:55:10 -06:00
|
|
|
if ((flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_CLOSE)
|
|
|
|
{
|
|
|
|
// Nothing to do.
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_PING)
|
2016-11-25 03:46:01 -06:00
|
|
|
{
|
2016-12-12 22:08:01 -06:00
|
|
|
// Echo back the ping message.
|
2016-12-15 07:51:07 -06:00
|
|
|
if (Poco::Net::WebSocket::sendFrame(buffer, n, WebSocket::FRAME_FLAG_FIN | WebSocket::FRAME_OP_PONG) != n)
|
2016-12-12 22:08:01 -06:00
|
|
|
{
|
|
|
|
LOG_WRN("Sending Pong failed.");
|
|
|
|
return -1;
|
|
|
|
}
|
2016-11-25 03:46:01 -06:00
|
|
|
}
|
2016-12-13 16:55:10 -06:00
|
|
|
else if ((flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_PONG)
|
2016-11-25 03:46:01 -06:00
|
|
|
{
|
2016-12-12 22:08:01 -06:00
|
|
|
// In case we do send pings in the future.
|
2016-11-25 03:46:01 -06:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-12 22:08:01 -06:00
|
|
|
// Not ready for read.
|
2016-11-25 03:46:01 -06:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2016-11-12 12:41:54 -06:00
|
|
|
/// Wrapper for Poco::Net::WebSocket::sendFrame() that handles large frames.
|
2016-11-12 12:41:16 -06:00
|
|
|
int sendFrame(const char* buffer, const int length, const int flags = FRAME_TEXT)
|
2016-11-10 02:47:25 -06:00
|
|
|
{
|
2016-11-28 04:26:41 -06:00
|
|
|
#if ENABLE_DEBUG
|
2016-11-21 04:06:05 -06:00
|
|
|
// Delay sending the frame
|
|
|
|
std::this_thread::sleep_for(getWebSocketDelay());
|
|
|
|
#endif
|
2016-12-12 22:08:01 -06:00
|
|
|
static const Poco::Timespan waitZero(0);
|
2016-11-12 12:41:16 -06:00
|
|
|
std::unique_lock<std::mutex> lock(_mutex);
|
2016-11-10 02:47:25 -06:00
|
|
|
|
2016-11-26 21:28:48 -06:00
|
|
|
if (length >= LARGE_MESSAGE_SIZE)
|
2016-11-25 03:48:59 -06:00
|
|
|
{
|
|
|
|
const std::string nextmessage = "nextmessage: size=" + std::to_string(length);
|
2016-12-12 22:08:01 -06:00
|
|
|
const int size = nextmessage.size();
|
|
|
|
|
2016-12-15 07:51:07 -06:00
|
|
|
if (Poco::Net::WebSocket::sendFrame(nextmessage.data(), size) == size)
|
2016-12-12 22:08:01 -06:00
|
|
|
{
|
|
|
|
LOG_TRC("Sent long message preample: " + nextmessage);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LOG_WRN("Failed to send long message preample.");
|
|
|
|
return -1;
|
|
|
|
}
|
2016-11-25 03:48:59 -06:00
|
|
|
}
|
|
|
|
|
2016-12-15 07:51:07 -06:00
|
|
|
int result = Poco::Net::WebSocket::sendFrame(buffer, length, flags);
|
2016-11-12 12:41:16 -06:00
|
|
|
|
|
|
|
lock.unlock();
|
|
|
|
|
|
|
|
if (result != length)
|
|
|
|
{
|
|
|
|
LOG_ERR("Sent incomplete message, expected " << length << " bytes but sent " << result <<
|
2016-12-12 22:08:01 -06:00
|
|
|
" for: " << LOOLProtocol::getAbbreviatedFrameDump(buffer, length, flags));
|
2016-11-12 12:41:16 -06:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-12-02 06:30:53 -06:00
|
|
|
LOG_TRC("Sent frame: " << LOOLProtocol::getAbbreviatedFrameDump(buffer, length, flags));
|
2016-11-12 12:41:16 -06:00
|
|
|
}
|
2016-11-10 02:47:25 -06:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|