Move WebSocket to Webserver & HttpJsonRpc async (#486)

* Move WebSocket to Webserver and HttpJsonRpc is now async

* revert...
This commit is contained in:
brindosch
2017-11-20 00:06:45 +01:00
committed by GitHub
parent 9799fae7f9
commit 0f9f3a17e7
19 changed files with 733 additions and 722 deletions

View File

@@ -4,6 +4,8 @@
#include "QtHttpReply.h"
#include "QtHttpServer.h"
#include "QtHttpHeader.h"
#include "WebSocketClient.h"
#include "WebJsonRpc.h"
#include <QCryptographicHash>
#include <QTcpSocket>
@@ -109,7 +111,18 @@ void QtHttpClientWrapper::onClientDataReceived (void) {
}
switch (m_parsingStatus) { // handle parsing status end/error
case RequestParsed: { // a valid request has ben fully parsed
// add post data to request
// Catch websocket header "Upgrade"
if(m_currentRequest->getHeader(QtHttpHeader::Upgrade) == "websocket")
{
if(m_websocketClient == Q_NULLPTR)
{
// disconnect this slot from socket for further requests
disconnect(m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
m_websocketClient = new WebSocketClient(m_currentRequest, m_sockClient, this);
}
break;
}
// add post data to request and catch /jsonrpc subroute url
if ( m_currentRequest->getCommand() == "POST")
{
QtHttpPostData postData;
@@ -126,12 +139,27 @@ void QtHttpClientWrapper::onClientDataReceived (void) {
postData.insert(QString::fromUtf8(keyValue.at(0)),value);
}
m_currentRequest->setPostData(postData);
// catch /jsonrpc in url, we need async callback, StaticFileServing is sync
QString path = m_currentRequest->getUrl ().path ();
QStringList uri_parts = path.split('/', QString::SkipEmptyParts);
if ( ! uri_parts.empty() && uri_parts.at(0) == "json-rpc" )
{
if(m_webJsonRpc == Q_NULLPTR)
{
m_webJsonRpc = new WebJsonRpc(m_currentRequest, m_serverHandle, this);
}
m_webJsonRpc->handleMessage(m_currentRequest);
break;
}
}
QtHttpReply reply (m_serverHandle);
connect (&reply, &QtHttpReply::requestSendHeaders,
this, &QtHttpClientWrapper::onReplySendHeadersRequested);
connect (&reply, &QtHttpReply::requestSendData,
this, &QtHttpClientWrapper::onReplySendDataRequested);
emit m_serverHandle->requestNeedsReply (m_currentRequest, &reply); // allow app to handle request
m_parsingStatus = sendReplyToClient (&reply);
break;
@@ -201,8 +229,16 @@ void QtHttpClientWrapper::onReplySendDataRequested (void) {
}
}
void QtHttpClientWrapper::sendToClientWithReply(QtHttpReply * reply) {
connect (reply, &QtHttpReply::requestSendHeaders,
this, &QtHttpClientWrapper::onReplySendHeadersRequested);
connect (reply, &QtHttpReply::requestSendData,
this, &QtHttpClientWrapper::onReplySendDataRequested);
m_parsingStatus = sendReplyToClient (reply);
}
QtHttpClientWrapper::ParsingStatus QtHttpClientWrapper::sendReplyToClient (QtHttpReply * reply) {
if (reply != Q_NULLPTR) {
if (reply != Q_NULLPTR) {
if (!reply->useChunked ()) {
//reply->appendRawData (CRLF);
// send all headers and all data in one shot

View File

@@ -9,6 +9,8 @@ class QTcpSocket;
class QtHttpRequest;
class QtHttpReply;
class QtHttpServer;
class WebSocketClient;
class WebJsonRpc;
class QtHttpClientWrapper : public QObject {
Q_OBJECT
@@ -29,6 +31,8 @@ public:
};
QString getGuid (void);
/// @brief Wrapper for sendReplyToClient(), handles m_parsingStatus and signal connect
void sendToClientWithReply (QtHttpReply * reply);
private slots:
void onClientDataReceived (void);
@@ -41,11 +45,13 @@ protected slots:
void onReplySendDataRequested (void);
private:
QString m_guid;
ParsingStatus m_parsingStatus;
QTcpSocket * m_sockClient;
QtHttpRequest * m_currentRequest;
QtHttpServer * m_serverHandle;
QString m_guid;
ParsingStatus m_parsingStatus;
QTcpSocket * m_sockClient;
QtHttpRequest * m_currentRequest;
QtHttpServer * m_serverHandle;
WebSocketClient * m_websocketClient = nullptr;
WebJsonRpc * m_webJsonRpc = nullptr;
};
#endif // QTHTTPCLIENTWRAPPER_H

View File

@@ -3,30 +3,34 @@
#include <QByteArray>
const QByteArray & QtHttpHeader::Server = QByteArrayLiteral ("Server");
const QByteArray & QtHttpHeader::Date = QByteArrayLiteral ("Date");
const QByteArray & QtHttpHeader::Host = QByteArrayLiteral ("Host");
const QByteArray & QtHttpHeader::Accept = QByteArrayLiteral ("Accept");
const QByteArray & QtHttpHeader::Cookie = QByteArrayLiteral ("Cookie");
const QByteArray & QtHttpHeader::ContentType = QByteArrayLiteral ("Content-Type");
const QByteArray & QtHttpHeader::ContentLength = QByteArrayLiteral ("Content-Length");
const QByteArray & QtHttpHeader::Connection = QByteArrayLiteral ("Connection");
const QByteArray & QtHttpHeader::UserAgent = QByteArrayLiteral ("User-Agent");
const QByteArray & QtHttpHeader::AcceptCharset = QByteArrayLiteral ("Accept-Charset");
const QByteArray & QtHttpHeader::AcceptEncoding = QByteArrayLiteral ("Accept-Encoding");
const QByteArray & QtHttpHeader::AcceptLanguage = QByteArrayLiteral ("Accept-Language");
const QByteArray & QtHttpHeader::Authorization = QByteArrayLiteral ("Authorization");
const QByteArray & QtHttpHeader::CacheControl = QByteArrayLiteral ("Cache-Control");
const QByteArray & QtHttpHeader::ContentMD5 = QByteArrayLiteral ("Content-MD5");
const QByteArray & QtHttpHeader::ProxyAuthorization = QByteArrayLiteral ("Proxy-Authorization");
const QByteArray & QtHttpHeader::Range = QByteArrayLiteral ("Range");
const QByteArray & QtHttpHeader::ContentEncoding = QByteArrayLiteral ("Content-Encoding");
const QByteArray & QtHttpHeader::ContentLanguage = QByteArrayLiteral ("Content-Language");
const QByteArray & QtHttpHeader::ContentLocation = QByteArrayLiteral ("Content-Location");
const QByteArray & QtHttpHeader::ContentRange = QByteArrayLiteral ("Content-Range");
const QByteArray & QtHttpHeader::Expires = QByteArrayLiteral ("Expires");
const QByteArray & QtHttpHeader::LastModified = QByteArrayLiteral ("Last-Modified");
const QByteArray & QtHttpHeader::Location = QByteArrayLiteral ("Location");
const QByteArray & QtHttpHeader::SetCookie = QByteArrayLiteral ("Set-Cookie");
const QByteArray & QtHttpHeader::TransferEncoding = QByteArrayLiteral ("Transfer-Encoding");
const QByteArray & QtHttpHeader::ContentDisposition = QByteArrayLiteral ("Content-Disposition");
const QByteArray & QtHttpHeader::Server = QByteArrayLiteral ("Server");
const QByteArray & QtHttpHeader::Date = QByteArrayLiteral ("Date");
const QByteArray & QtHttpHeader::Host = QByteArrayLiteral ("Host");
const QByteArray & QtHttpHeader::Accept = QByteArrayLiteral ("Accept");
const QByteArray & QtHttpHeader::Cookie = QByteArrayLiteral ("Cookie");
const QByteArray & QtHttpHeader::ContentType = QByteArrayLiteral ("Content-Type");
const QByteArray & QtHttpHeader::ContentLength = QByteArrayLiteral ("Content-Length");
const QByteArray & QtHttpHeader::Connection = QByteArrayLiteral ("Connection");
const QByteArray & QtHttpHeader::UserAgent = QByteArrayLiteral ("User-Agent");
const QByteArray & QtHttpHeader::AcceptCharset = QByteArrayLiteral ("Accept-Charset");
const QByteArray & QtHttpHeader::AcceptEncoding = QByteArrayLiteral ("Accept-Encoding");
const QByteArray & QtHttpHeader::AcceptLanguage = QByteArrayLiteral ("Accept-Language");
const QByteArray & QtHttpHeader::Authorization = QByteArrayLiteral ("Authorization");
const QByteArray & QtHttpHeader::CacheControl = QByteArrayLiteral ("Cache-Control");
const QByteArray & QtHttpHeader::ContentMD5 = QByteArrayLiteral ("Content-MD5");
const QByteArray & QtHttpHeader::ProxyAuthorization = QByteArrayLiteral ("Proxy-Authorization");
const QByteArray & QtHttpHeader::Range = QByteArrayLiteral ("Range");
const QByteArray & QtHttpHeader::ContentEncoding = QByteArrayLiteral ("Content-Encoding");
const QByteArray & QtHttpHeader::ContentLanguage = QByteArrayLiteral ("Content-Language");
const QByteArray & QtHttpHeader::ContentLocation = QByteArrayLiteral ("Content-Location");
const QByteArray & QtHttpHeader::ContentRange = QByteArrayLiteral ("Content-Range");
const QByteArray & QtHttpHeader::Expires = QByteArrayLiteral ("Expires");
const QByteArray & QtHttpHeader::LastModified = QByteArrayLiteral ("Last-Modified");
const QByteArray & QtHttpHeader::Location = QByteArrayLiteral ("Location");
const QByteArray & QtHttpHeader::SetCookie = QByteArrayLiteral ("Set-Cookie");
const QByteArray & QtHttpHeader::TransferEncoding = QByteArrayLiteral ("Transfer-Encoding");
const QByteArray & QtHttpHeader::ContentDisposition = QByteArrayLiteral ("Content-Disposition");
const QByteArray & QtHttpHeader::Upgrade = QByteArrayLiteral ("Upgrade");
const QByteArray & QtHttpHeader::SecWebSocketKey = QByteArrayLiteral ("Sec-WebSocket-Key");
const QByteArray & QtHttpHeader::SecWebSocketProtocol = QByteArrayLiteral ("Sec-WebSocket-Protocol");
const QByteArray & QtHttpHeader::SecWebSocketVersion = QByteArrayLiteral ("Sec-WebSocket-Version");

View File

@@ -32,6 +32,11 @@ public:
static const QByteArray & SetCookie;
static const QByteArray & TransferEncoding;
static const QByteArray & ContentDisposition;
// Websocket specific headers
static const QByteArray & Upgrade;
static const QByteArray & SecWebSocketKey;
static const QByteArray & SecWebSocketProtocol;
static const QByteArray & SecWebSocketVersion;
};
#endif // QTHTTPHEADER_H

View File

@@ -4,7 +4,6 @@
#include "QtHttpReply.h"
#include "QtHttpClientWrapper.h"
#include <QDebug>
#include <QUrlQuery>
const QString & QtHttpServer::HTTP_VERSION = QStringLiteral ("HTTP/1.1");

View File

@@ -34,7 +34,6 @@ StaticFileServing::StaticFileServing (Hyperion *hyperion, QString baseUrl, quint
connect (_server, &QtHttpServer::requestNeedsReply, this, &StaticFileServing::onRequestNeedsReply);
_server->start (port);
}
StaticFileServing::~StaticFileServing ()
@@ -60,14 +59,10 @@ void StaticFileServing::onServerStarted (quint16 port)
txtRecord
);
Debug(_log, "Web Config mDNS responder started");
// json-rpc for http
_jsonProcessor = new JsonProcessor(QString("HTTP-API"), _log, true);
}
void StaticFileServing::onServerStopped () {
Info(_log, "stopped %s", _server->getServerName().toStdString().c_str());
delete _jsonProcessor;
}
void StaticFileServing::onServerError (QString msg)
@@ -113,50 +108,30 @@ void StaticFileServing::printErrorToReply (QtHttpReply * reply, QtHttpReply::Sta
void StaticFileServing::onRequestNeedsReply (QtHttpRequest * request, QtHttpReply * reply)
{
QString command = request->getCommand ();
if (command == QStringLiteral ("GET") || command == QStringLiteral ("POST"))
if (command == QStringLiteral ("GET"))
{
QString path = request->getUrl ().path ();
QStringList uri_parts = path.split('/', QString::SkipEmptyParts);
// special uri handling for server commands
if ( ! uri_parts.empty() )
if ( ! uri_parts.empty() && uri_parts.at(0) == "cgi" )
{
if(uri_parts.at(0) == "cgi")
uri_parts.removeAt(0);
try
{
uri_parts.removeAt(0);
try
{
_cgi.exec(uri_parts, request, reply);
}
catch(int err)
{
Error(_log,"Exception while executing cgi %s : %d", path.toStdString().c_str(), err);
printErrorToReply (reply, QtHttpReply::InternalError, "script failed (" % path % ")");
}
catch(std::exception &e)
{
Error(_log,"Exception while executing cgi %s : %s", path.toStdString().c_str(), e.what());
printErrorToReply (reply, QtHttpReply::InternalError, "script failed (" % path % ")");
}
return;
_cgi.exec(uri_parts, request, reply);
}
else if ( uri_parts.at(0) == "json-rpc" )
catch(int err)
{
QMetaObject::Connection m_connection;
QByteArray data = request->getRawData();
QtHttpRequest::ClientInfo info = request->getClientInfo();
m_connection = QObject::connect(_jsonProcessor, &JsonProcessor::callbackMessage,
[reply](QJsonObject result) {
QJsonDocument doc(result);
reply->addHeader ("Content-Type", "application/json");
reply->appendRawData (doc.toJson());
});
_jsonProcessor->handleMessage(data,info.clientAddress.toString());
QObject::disconnect( m_connection );
return;
Error(_log,"Exception while executing cgi %s : %d", path.toStdString().c_str(), err);
printErrorToReply (reply, QtHttpReply::InternalError, "script failed (" % path % ")");
}
catch(std::exception &e)
{
Error(_log,"Exception while executing cgi %s : %s", path.toStdString().c_str(), e.what());
printErrorToReply (reply, QtHttpReply::InternalError, "script failed (" % path % ")");
}
return;
}
Q_INIT_RESOURCE(WebConfig);

View File

@@ -12,8 +12,6 @@
#include <hyperion/Hyperion.h>
#include <utils/Logger.h>
#include <utils/JsonProcessor.h>
class StaticFileServing : public QObject {
Q_OBJECT
@@ -35,7 +33,6 @@ private:
QMimeDatabase * _mimeDb;
CgiHandler _cgi;
Logger * _log;
JsonProcessor * _jsonProcessor;
void printErrorToReply (QtHttpReply * reply, QtHttpReply::StatusCode code, QString errorMessage);

View File

@@ -0,0 +1,38 @@
#include "WebJsonRpc.h"
#include "QtHttpReply.h"
#include "QtHttpRequest.h"
#include "QtHttpServer.h"
#include "QtHttpClientWrapper.h"
#include <utils/JsonProcessor.h>
WebJsonRpc::WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, QtHttpClientWrapper* parent)
: QObject(parent)
, _server(server)
, _wrapper(parent)
, _log(Logger::getInstance("HTTPJSONRPC"))
{
const QString client = request->getClientInfo().clientAddress.toString();
_jsonProcessor = new JsonProcessor(client, _log, this, true);
connect(_jsonProcessor, &JsonProcessor::callbackMessage, this, &WebJsonRpc::handleCallback);
}
void WebJsonRpc::handleMessage(QtHttpRequest* request)
{
QByteArray data = request->getRawData();
_unlocked = true;
_jsonProcessor->handleMessage(data);
}
void WebJsonRpc::handleCallback(QJsonObject obj)
{
// guard against wrong callbacks; TODO: Remove when JsonProcessor is more solid
if(!_unlocked) return;
_unlocked = false;
// construct reply with headers timestamp and server name
QtHttpReply reply(_server);
QJsonDocument doc(obj);
reply.addHeader ("Content-Type", "application/json");
reply.appendRawData (doc.toJson());
_wrapper->sendToClientWithReply(&reply);
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include <utils/Logger.h>
class QtHttpServer;
class QtHttpRequest;
class QtHttpClientWrapper;
class JsonProcessor;
class WebJsonRpc : public QObject {
Q_OBJECT
public:
WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, QtHttpClientWrapper* parent);
void handleMessage(QtHttpRequest* request);
private:
QtHttpServer* _server;
QtHttpClientWrapper* _wrapper;
Logger* _log;
JsonProcessor* _jsonProcessor;
bool _unlocked = false;
private slots:
void handleCallback(QJsonObject obj);
};

View File

@@ -0,0 +1,336 @@
#include "WebSocketClient.h"
#include "QtHttpRequest.h"
#include "QtHttpHeader.h"
#include <hyperion/Hyperion.h>
#include <utils/JsonProcessor.h>
#include <QTcpSocket>
#include <QtEndian>
#include <QCryptographicHash>
WebSocketClient::WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, QObject* parent)
: QObject(parent)
, _socket(sock)
, _log(Logger::getInstance("WEBSOCKET"))
, _hyperion(Hyperion::getInstance())
{
// connect socket; disconnect handled from QtHttpServer
connect(_socket, &QTcpSocket::readyRead , this, &WebSocketClient::handleWebSocketFrame);
// QtHttpRequest contains all headers for handshake
QByteArray secWebSocketKey = request->getHeader(QtHttpHeader::SecWebSocketKey);
const QString client = request->getClientInfo().clientAddress.toString();
// Json processor
_jsonProcessor = new JsonProcessor(client, _log, this);
connect(_jsonProcessor, &JsonProcessor::callbackMessage, this, &WebSocketClient::sendMessage);
Debug(_log, "New connection from %s", QSTRING_CSTR(client));
// do handshake
secWebSocketKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
QByteArray hash = QCryptographicHash::hash(secWebSocketKey, QCryptographicHash::Sha1).toBase64();
QString data
= QString("HTTP/1.1 101 Switching Protocols\r\n")
+ QString("Upgrade: websocket\r\n")
+ QString("Connection: Upgrade\r\n")
+ QString("Sec-WebSocket-Accept: ")+QString(hash.data()) + "\r\n\r\n";
_socket->write(QSTRING_CSTR(data), data.size());
_socket->flush();
}
void WebSocketClient::handleWebSocketFrame(void)
{
// we are on no continious reading from socket from call before
if (!_notEnoughData)
{
getWsFrameHeader(&_wsh);
}
if(_socket->bytesAvailable() < (qint64)_wsh.payloadLength)
{
//printf("not enough data %llu %llu\n", _socket->bytesAvailable(), _wsh.payloadLength);
_notEnoughData=true;
return;
}
_notEnoughData = false;
QByteArray buf = _socket->read(_wsh.payloadLength);
//printf("opcode %x payload bytes %llu avail: %llu\n", _wsh.opCode, _wsh.payloadLength, _socket->bytesAvailable());
if (OPCODE::invalid((OPCODE::value)_wsh.opCode))
{
sendClose(CLOSECODE::INV_TYPE, "invalid opcode");
return;
}
// check the type of data frame
bool isContinuation=false;
switch (_wsh.opCode)
{
case OPCODE::CONTINUATION:
isContinuation = true;
// no break here, just jump over to opcode text
case OPCODE::BINARY:
case OPCODE::TEXT:
{
// check for protocal violations
if (_onContinuation && !isContinuation)
{
sendClose(CLOSECODE::VIOLATION, "protocol violation, somebody sends frames in between continued frames");
return;
}
if (!_wsh.masked && _wsh.opCode == OPCODE::TEXT)
{
sendClose(CLOSECODE::VIOLATION, "protocol violation, unmasked text frames not allowed");
return;
}
// unmask data
for (int i=0; i < buf.size(); i++)
{
buf[i] = buf[i] ^ _wsh.key[i % 4];
}
_onContinuation = !_wsh.fin || isContinuation;
// frame contains text, extract it, append data if this is a continuation
if (_wsh.fin && ! isContinuation) // one frame
{
_wsReceiveBuffer.clear();
}
_wsReceiveBuffer.append(buf);
// this is the final frame, decode and handle data
if (_wsh.fin)
{
_onContinuation = false;
if (_wsh.opCode == OPCODE::TEXT)
{
_jsonProcessor->handleMessage(QString(_wsReceiveBuffer));
}
else
{
handleBinaryMessage(_wsReceiveBuffer);
}
_wsReceiveBuffer.clear();
}
}
break;
case OPCODE::CLOSE:
{
sendClose(CLOSECODE::NORMAL);
}
break;
case OPCODE::PING:
{
// ping received, send pong
quint8 pong[] = {OPCODE::PONG, 0};
_socket->write((const char*)pong, 2);
_socket->flush();
}
break;
case OPCODE::PONG:
{
Error(_log, "pong received, protocol violation!");
}
default:
Warning(_log, "strange %d\n%s\n", _wsh.opCode, QSTRING_CSTR(QString(buf)));
}
}
void WebSocketClient::getWsFrameHeader(WebSocketHeader* header)
{
char fin_rsv_opcode, mask_length;
_socket->getChar(&fin_rsv_opcode);
_socket->getChar(&mask_length);
header->fin = (fin_rsv_opcode & BHB0_FIN) == BHB0_FIN;
header->opCode = fin_rsv_opcode & BHB0_OPCODE;
header->masked = (mask_length & BHB1_MASK) == BHB1_MASK;
header->payloadLength = mask_length & BHB1_PAYLOAD;
// get size of payload
switch (header->payloadLength)
{
case payload_size_code_16bit:
{
QByteArray buf = _socket->read(2);
header->payloadLength = ((buf.at(0) << 8) & 0xFF00) | (buf.at(1) & 0xFF);
}
break;
case payload_size_code_64bit:
{
QByteArray buf = _socket->read(8);
header->payloadLength = 0;
for (uint i=0; i < 8; i++)
{
header->payloadLength |= ((quint64)(buf.at(i) & 0xFF)) << (8*(7-i));
}
}
break;
}
// if the data is masked we need to get the key for unmasking
if (header->masked)
{
_socket->read(header->key, 4);
}
}
/// See http://tools.ietf.org/html/rfc6455#section-5.2 for more information
void WebSocketClient::sendClose(int status, QString reason)
{
Debug(_log, "send close: %d %s", status, QSTRING_CSTR(reason));
ErrorIf(!reason.isEmpty(), _log, QSTRING_CSTR(reason));
_receiveBuffer.clear();
QByteArray sendBuffer;
sendBuffer.append(136+(status-1000));
int length = reason.size();
if(length >= 126)
{
sendBuffer.append( (length > 0xffff) ? 127 : 126);
int num_bytes = (length > 0xffff) ? 8 : 2;
for(int c = num_bytes - 1; c != -1; c--)
{
sendBuffer.append( quint8((static_cast<unsigned long long>(length) >> (8 * c)) % 256));
}
}
else
{
sendBuffer.append(quint8(length));
}
sendBuffer.append(reason);
_socket->write(sendBuffer);
_socket->flush();
_socket->close();
}
void WebSocketClient::handleBinaryMessage(QByteArray &data)
{
uint8_t priority = data.at(0);
unsigned duration_s = data.at(1);
unsigned imgSize = data.size() - 4;
unsigned width = ((data.at(2) << 8) & 0xFF00) | (data.at(3) & 0xFF);
unsigned height = imgSize / width;
if ( ! (imgSize) % width)
{
Error(_log, "data size is not multiple of width");
return;
}
Image<ColorRgb> image;
image.resize(width, height);
memcpy(image.memptr(), data.data()+4, imgSize);
_hyperion->setImage(priority, image, duration_s*1000);
}
qint64 WebSocketClient::sendMessage(QJsonObject obj)
{
QJsonDocument writer(obj);
QByteArray data = writer.toJson(QJsonDocument::Compact) + "\n";
if (!_socket || (_socket->state() != QAbstractSocket::ConnectedState)) return 0;
qint64 payloadWritten = 0;
quint32 payloadSize = data.size();
const char * payload = data.data();
qint32 numFrames = payloadSize / FRAME_SIZE_IN_BYTES + ((quint64(payloadSize) % FRAME_SIZE_IN_BYTES) > 0 ? 1 : 0);
for (int i = 0; i < numFrames; ++i)
{
const bool isLastFrame = (i == (numFrames - 1));
quint64 position = i * FRAME_SIZE_IN_BYTES;
quint32 frameSize = (payloadSize-position >= FRAME_SIZE_IN_BYTES) ? FRAME_SIZE_IN_BYTES : (payloadSize-position);
QByteArray buf = makeFrameHeader(OPCODE::TEXT, frameSize, isLastFrame);
sendMessage_Raw(buf);
qint64 written = sendMessage_Raw(payload+position,frameSize);
if (written > 0)
{
payloadWritten += written;
}
else
{
_socket->flush();
Error(_log, "Error writing bytes to socket: %s", QSTRING_CSTR(_socket->errorString()));
break;
}
}
if (payloadSize != payloadWritten)
{
Error(_log, "Error writing bytes to socket %d bytes from %d written", payloadWritten, payloadSize);
return -1;
}
return payloadWritten;
}
qint64 WebSocketClient::sendMessage_Raw(const char* data, quint64 size)
{
return _socket->write(data, size);
}
qint64 WebSocketClient::sendMessage_Raw(QByteArray &data)
{
return _socket->write(data.data(), data.size());
}
QByteArray WebSocketClient::makeFrameHeader(quint8 opCode, quint64 payloadLength, bool lastFrame)
{
QByteArray header;
if (payloadLength <= 0x7FFFFFFFFFFFFFFFULL)
{
//FIN, RSV1-3, opcode (RSV-1, RSV-2 and RSV-3 are zero)
quint8 byte = static_cast<quint8>((opCode & 0x0F) | (lastFrame ? 0x80 : 0x00));
header.append(static_cast<char>(byte));
byte = 0x00;
if (payloadLength <= 125)
{
byte |= static_cast<quint8>(payloadLength);
header.append(static_cast<char>(byte));
}
else if (payloadLength <= 0xFFFFU)
{
byte |= 126;
header.append(static_cast<char>(byte));
quint16 swapped = qToBigEndian<quint16>(static_cast<quint16>(payloadLength));
header.append(static_cast<const char *>(static_cast<const void *>(&swapped)), 2);
}
else
{
byte |= 127;
header.append(static_cast<char>(byte));
quint64 swapped = qToBigEndian<quint64>(payloadLength);
header.append(static_cast<const char *>(static_cast<const void *>(&swapped)), 8);
}
}
else
{
Error(_log, "Payload too big!");
}
return header;
}

View File

@@ -0,0 +1,72 @@
#pragma once
#include <utils/Logger.h>
#include "WebSocketUtils.h"
class QTcpSocket;
class QtHttpRequest;
class Hyperion;
class JsonProcessor;
class WebSocketClient : public QObject {
Q_OBJECT
public:
WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, QObject* parent);
struct WebSocketHeader
{
bool fin;
quint8 opCode;
bool masked;
quint64 payloadLength;
char key[4];
};
private:
QTcpSocket* _socket;
Logger* _log;
Hyperion* _hyperion;
JsonProcessor* _jsonProcessor;
void getWsFrameHeader(WebSocketHeader* header);
void sendClose(int status, QString reason = "");
void handleBinaryMessage(QByteArray &data);
qint64 sendMessage_Raw(const char* data, quint64 size);
qint64 sendMessage_Raw(QByteArray &data);
QByteArray makeFrameHeader(quint8 opCode, quint64 payloadLength, bool lastFrame);
/// The buffer used for reading data from the socket
QByteArray _receiveBuffer;
/// buffer for websockets multi frame receive
QByteArray _wsReceiveBuffer;
quint8 _maskKey[4];
bool _onContinuation = false;
// true when data is missing for parsing
bool _notEnoughData = false;
// websocket header store
WebSocketHeader _wsh;
// masks for fields in the basic header
static uint8_t const BHB0_OPCODE = 0x0F;
static uint8_t const BHB0_RSV3 = 0x10;
static uint8_t const BHB0_RSV2 = 0x20;
static uint8_t const BHB0_RSV1 = 0x40;
static uint8_t const BHB0_FIN = 0x80;
static uint8_t const BHB1_PAYLOAD = 0x7F;
static uint8_t const BHB1_MASK = 0x80;
static uint8_t const payload_size_code_16bit = 0x7E; // 126
static uint8_t const payload_size_code_64bit = 0x7F; // 127
static const quint64 FRAME_SIZE_IN_BYTES = 512 * 512 * 2; //maximum size of a frame when sending a message
private slots:
void handleWebSocketFrame(void);
qint64 sendMessage(QJsonObject obj);
};

View File

@@ -0,0 +1,68 @@
#pragma once
/// Constants and utility functions related to WebSocket opcodes
/**
* WebSocket Opcodes are 4 bits. See RFC6455 section 5.2.
*/
namespace OPCODE {
enum value {
CONTINUATION = 0x0,
TEXT = 0x1,
BINARY = 0x2,
RSV3 = 0x3,
RSV4 = 0x4,
RSV5 = 0x5,
RSV6 = 0x6,
RSV7 = 0x7,
CLOSE = 0x8,
PING = 0x9,
PONG = 0xA,
CONTROL_RSVB = 0xB,
CONTROL_RSVC = 0xC,
CONTROL_RSVD = 0xD,
CONTROL_RSVE = 0xE,
CONTROL_RSVF = 0xF
};
/// Check if an opcode is reserved
/**
* @param v The opcode to test.
* @return Whether or not the opcode is reserved.
*/
inline bool reserved(value v) {
return (v >= RSV3 && v <= RSV7) || (v >= CONTROL_RSVB && v <= CONTROL_RSVF);
}
/// Check if an opcode is invalid
/**
* Invalid opcodes are negative or require greater than 4 bits to store.
*
* @param v The opcode to test.
* @return Whether or not the opcode is invalid.
*/
inline bool invalid(value v) {
return (v > 0xF || v < 0);
}
/// Check if an opcode is for a control frame
/**
* @param v The opcode to test.
* @return Whether or not the opcode is a control opcode.
*/
inline bool is_control(value v) {
return v >= 0x8;
}
}
namespace CLOSECODE {
enum value {
NORMAL = 1000,
AWAY = 1001,
TERM = 1002,
INV_TYPE = 1003,
INV_DATA = 1007,
VIOLATION = 1008,
BIG_MSG = 1009,
UNEXPECTED= 1011
};
}