From bbc6fe389d07309057827125c9068903183f3a52 Mon Sep 17 00:00:00 2001 From: Gamadril Date: Sat, 25 Oct 2014 22:35:53 +0200 Subject: [PATCH] Added very basic WebSocket support to json server Former-commit-id: 5d62331eecdbd10287ba0520bbb06814bca0bca7 --- libsrc/jsonserver/JsonClientConnection.cpp | 150 ++++++++++++++++++++- libsrc/jsonserver/JsonClientConnection.h | 3 + 2 files changed, 152 insertions(+), 1 deletion(-) diff --git a/libsrc/jsonserver/JsonClientConnection.cpp b/libsrc/jsonserver/JsonClientConnection.cpp index d478a4d2..b82b51d7 100644 --- a/libsrc/jsonserver/JsonClientConnection.cpp +++ b/libsrc/jsonserver/JsonClientConnection.cpp @@ -10,6 +10,8 @@ // Qt includes #include #include +#include +#include // hyperion util includes #include @@ -27,6 +29,7 @@ JsonClientConnection::JsonClientConnection(QTcpSocket *socket, Hyperion * hyperi _hyperion(hyperion), _receiveBuffer() { + _webSocketHandshakeDone = false; // connect internal signals and slots connect(_socket, SIGNAL(disconnected()), this, SLOT(socketClosed())); connect(_socket, SIGNAL(readyRead()), this, SLOT(readData())); @@ -41,7 +44,126 @@ JsonClientConnection::~JsonClientConnection() void JsonClientConnection::readData() { _receiveBuffer += _socket->readAll(); + + if (_webSocketHandshakeDone) { // websocket mode, data frame + quint8 opCode = 0; + quint64 payloadLength = 0; + bool isMasked = false; + quint32 index = 0; + quint8 maskKey[4]; + + if ((_receiveBuffer.at(0) & 0x80) == 0x80) { // final bit + opCode = _receiveBuffer.at(0) & 0x0F; + isMasked = (_receiveBuffer.at(1) & 0x80) == 0x80; + payloadLength = _receiveBuffer.at(1) & 0x7F; + index = 2; + + switch (payloadLength) { + case 126: + payloadLength = ((_receiveBuffer.at(2) << 8) & 0xFF00) | (_receiveBuffer.at(3) & 0xFF); + index += 2; + break; + case 127: { + payloadLength = 0; + for (int i=0; i < 8; i++) { + payloadLength |= ((quint64)(_receiveBuffer.at(index+i) & 0xFF)) << (8*(7-i)); + } + index += 8; + } + break; + default: + break; + } + + if (isMasked) { // if the data is masked we need to get the key for unmasking + for (int i=0; i < 4; i++) { + maskKey[i] = _receiveBuffer.at(index + i); + } + index += 4; + } + + // check the type of data frame + switch (opCode) { + case 0x01: { // text + QByteArray result = _receiveBuffer.mid(index, payloadLength); + _receiveBuffer.clear(); + + // unmask data if necessary + if (isMasked) { + for (uint i=0 ; i < payloadLength; i++) { + result[i] = (result[i] ^ maskKey[i % 4]); + } + } + + handleMessage(QString(result).toStdString()); + } + break; + case 0x08: { // close + quint8 close[]={0x88, 0}; + _socket->write((const char*)close, 2); + _socket->flush(); + _socket->close(); + } + break; + case 0x09: { // ping, send pong + quint8 close[]={0x0A, 0}; + _socket->write((const char*)close, 2); + _socket->flush(); + } + break; + } + } else { + std::cout << "Someone is sending very big messages over several frames... it's not supported yet" << std::endl; + quint8 close[]={0x88, 0}; + _socket->write((const char*)close, 2); + _socket->flush(); + _socket->close(); + } + } else { // might be a handshake request or raw socket data + if(_receiveBuffer.contains("Upgrade: websocket")){ // http header, might not be a very reliable check... + std::cout << "Websocket handshake" << std::endl; + + // get the key to tprepare an answer + int start = _receiveBuffer.indexOf("Sec-WebSocket-Key") + 19; + std::string value(_receiveBuffer.mid(start, _receiveBuffer.indexOf("\r\n", start) - start).data()); + _receiveBuffer.clear(); + // must be always appended + value += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + // generate sha1 hash + QByteArray hash = QCryptographicHash::hash(value.c_str(), QCryptographicHash::Sha1); + + // prepare an answer + std::ostringstream h; + h << "HTTP/1.1 101 Switching Protocols\r\n" << + "Upgrade: websocket\r\n" << + "Connection: Upgrade\r\n" << + "Sec-WebSocket-Accept: " << QString(hash.toBase64()).toStdString() << "\r\n\r\n"; + + _socket->write(h.str().c_str()); + _socket->flush(); + _webSocketHandshakeDone = true; // we are in WebSocket mode, data frames should follow next + } else { // raw socket data, handling as usual + int bytes = _receiveBuffer.indexOf('\n') + 1; + while(bytes > 0) + { + // create message string + std::string message(_receiveBuffer.data(), bytes); + + // remove message data from buffer + _receiveBuffer = _receiveBuffer.mid(bytes); + + // handle message + handleMessage(message); + + // try too look up '\n' again + bytes = _receiveBuffer.indexOf('\n') + 1; + } + } + } + + /* int bytes = _receiveBuffer.indexOf('\n') + 1; while(bytes > 0) { @@ -57,10 +179,12 @@ void JsonClientConnection::readData() // try too look up '\n' again bytes = _receiveBuffer.indexOf('\n') + 1; } + */ } void JsonClientConnection::socketClosed() { + _webSocketHandshakeDone = false; emit connectionClosed(this); } @@ -205,6 +329,9 @@ void JsonClientConnection::handleServerInfoCommand(const Json::Value &) Json::Value result; result["success"] = true; Json::Value & info = result["info"]; + + // add host name for remote clients + info["hostname"] = QHostInfo::localHostName().toStdString(); // collect priority information Json::Value & priorities = info["priorities"] = Json::Value(Json::arrayValue); @@ -362,7 +489,28 @@ void JsonClientConnection::sendMessage(const Json::Value &message) { Json::FastWriter writer; std::string serializedReply = writer.write(message); - _socket->write(serializedReply.data(), serializedReply.length()); + + if (!_webSocketHandshakeDone) { // raw tcp socket mode + _socket->write(serializedReply.data(), serializedReply.length()); + } else { // websocket mode + quint32 size = serializedReply.length(); + + // prepare data frame + QByteArray response; + response.append(0x81); + if (size > 125) { + response.append(0x7E); + response.append((size >> 8) & 0xFF); + response.append(size & 0xFF); + } else { + response.append(size); + } + + QByteArray data(serializedReply.c_str(), serializedReply.length()); + response.append(data); + + _socket->write(response.data(), response.length()); + } } void JsonClientConnection::sendSuccessReply() diff --git a/libsrc/jsonserver/JsonClientConnection.h b/libsrc/jsonserver/JsonClientConnection.h index dc80deba..b036e638 100644 --- a/libsrc/jsonserver/JsonClientConnection.h +++ b/libsrc/jsonserver/JsonClientConnection.h @@ -161,4 +161,7 @@ private: /// The buffer used for reading data from the socket QByteArray _receiveBuffer; + + /// used for WebSocket detection and connection handling + bool _webSocketHandshakeDone; };