Skip to content
This repository was archived by the owner on May 6, 2021. It is now read-only.

Commit e9045e1

Browse files
committed
Merge pull request #192 from Gamadril/master
Added very basic WebSocket support to json server
2 parents 25438c1 + 157f424 commit e9045e1

File tree

2 files changed

+197
-15
lines changed

2 files changed

+197
-15
lines changed

libsrc/jsonserver/JsonClientConnection.cpp

Lines changed: 184 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
// Qt includes
1111
#include <QResource>
1212
#include <QDateTime>
13+
#include <QCryptographicHash>
14+
#include <QHostInfo>
1315

1416
// hyperion util includes
1517
#include <hyperion/ImageProcessorFactory.h>
@@ -25,7 +27,8 @@ JsonClientConnection::JsonClientConnection(QTcpSocket *socket, Hyperion * hyperi
2527
_socket(socket),
2628
_imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()),
2729
_hyperion(hyperion),
28-
_receiveBuffer()
30+
_receiveBuffer(),
31+
_webSocketHandshakeDone(false)
2932
{
3033
// connect internal signals and slots
3134
connect(_socket, SIGNAL(disconnected()), this, SLOT(socketClosed()));
@@ -41,26 +44,164 @@ JsonClientConnection::~JsonClientConnection()
4144
void JsonClientConnection::readData()
4245
{
4346
_receiveBuffer += _socket->readAll();
44-
45-
int bytes = _receiveBuffer.indexOf('\n') + 1;
46-
while(bytes > 0)
47+
48+
if (_webSocketHandshakeDone)
4749
{
48-
// create message string
49-
std::string message(_receiveBuffer.data(), bytes);
50-
51-
// remove message data from buffer
52-
_receiveBuffer = _receiveBuffer.mid(bytes);
53-
54-
// handle message
55-
handleMessage(message);
50+
// websocket mode, data frame
51+
handleWebSocketFrame();
52+
} else
53+
{
54+
// might be a handshake request or raw socket data
55+
if(_receiveBuffer.contains("Upgrade: websocket"))
56+
{
57+
doWebSocketHandshake();
58+
} else
59+
{
60+
// raw socket data, handling as usual
61+
int bytes = _receiveBuffer.indexOf('\n') + 1;
62+
while(bytes > 0)
63+
{
64+
// create message string
65+
std::string message(_receiveBuffer.data(), bytes);
66+
67+
// remove message data from buffer
68+
_receiveBuffer = _receiveBuffer.mid(bytes);
69+
70+
// handle message
71+
handleMessage(message);
72+
73+
// try too look up '\n' again
74+
bytes = _receiveBuffer.indexOf('\n') + 1;
75+
}
76+
}
77+
}
78+
}
5679

57-
// try too look up '\n' again
58-
bytes = _receiveBuffer.indexOf('\n') + 1;
80+
void JsonClientConnection::handleWebSocketFrame()
81+
{
82+
if ((_receiveBuffer.at(0) & 0x80) == 0x80)
83+
{
84+
// final bit found, frame complete
85+
quint8 * maskKey = NULL;
86+
quint8 opCode = _receiveBuffer.at(0) & 0x0F;
87+
bool isMasked = (_receiveBuffer.at(1) & 0x80) == 0x80;
88+
quint64 payloadLength = _receiveBuffer.at(1) & 0x7F;
89+
quint32 index = 2;
90+
91+
switch (payloadLength)
92+
{
93+
case 126:
94+
payloadLength = ((_receiveBuffer.at(2) << 8) & 0xFF00) | (_receiveBuffer.at(3) & 0xFF);
95+
index += 2;
96+
break;
97+
case 127:
98+
payloadLength = 0;
99+
for (uint i=0; i < 8; i++) {
100+
payloadLength |= ((quint64)(_receiveBuffer.at(index+i) & 0xFF)) << (8*(7-i));
101+
}
102+
index += 8;
103+
break;
104+
default:
105+
break;
106+
}
107+
108+
if (isMasked)
109+
{
110+
// if the data is masked we need to get the key for unmasking
111+
maskKey = new quint8[4];
112+
for (uint i=0; i < 4; i++)
113+
{
114+
maskKey[i] = _receiveBuffer.at(index + i);
115+
}
116+
index += 4;
117+
}
118+
119+
// check the type of data frame
120+
switch (opCode)
121+
{
122+
case 0x01:
123+
{
124+
// frame contains text, extract it
125+
QByteArray result = _receiveBuffer.mid(index, payloadLength);
126+
_receiveBuffer.clear();
127+
128+
// unmask data if necessary
129+
if (isMasked)
130+
{
131+
for (uint i=0; i < payloadLength; i++)
132+
{
133+
result[i] = (result[i] ^ maskKey[i % 4]);
134+
}
135+
if (maskKey != NULL)
136+
{
137+
delete[] maskKey;
138+
maskKey = NULL;
139+
}
140+
}
141+
142+
handleMessage(QString(result).toStdString());
143+
}
144+
break;
145+
case 0x08:
146+
{
147+
// close request, confirm
148+
quint8 close[] = {0x88, 0};
149+
_socket->write((const char*)close, 2);
150+
_socket->flush();
151+
_socket->close();
152+
}
153+
break;
154+
case 0x09:
155+
{
156+
// ping received, send pong
157+
quint8 pong[] = {0x0A, 0};
158+
_socket->write((const char*)pong, 2);
159+
_socket->flush();
160+
}
161+
break;
162+
}
163+
} else
164+
{
165+
std::cout << "Someone is sending very big messages over several frames... it's not supported yet" << std::endl;
166+
quint8 close[] = {0x88, 0};
167+
_socket->write((const char*)close, 2);
168+
_socket->flush();
169+
_socket->close();
59170
}
60171
}
61172

173+
void JsonClientConnection::doWebSocketHandshake()
174+
{
175+
// http header, might not be a very reliable check...
176+
std::cout << "Websocket handshake" << std::endl;
177+
178+
// get the key to prepare an answer
179+
int start = _receiveBuffer.indexOf("Sec-WebSocket-Key") + 19;
180+
std::string value(_receiveBuffer.mid(start, _receiveBuffer.indexOf("\r\n", start) - start).data());
181+
_receiveBuffer.clear();
182+
183+
// must be always appended
184+
value += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
185+
186+
// generate sha1 hash
187+
QByteArray hash = QCryptographicHash::hash(value.c_str(), QCryptographicHash::Sha1);
188+
189+
// prepare an answer
190+
std::ostringstream h;
191+
h << "HTTP/1.1 101 Switching Protocols\r\n" <<
192+
"Upgrade: websocket\r\n" <<
193+
"Connection: Upgrade\r\n" <<
194+
"Sec-WebSocket-Accept: " << QString(hash.toBase64()).toStdString() << "\r\n\r\n";
195+
196+
_socket->write(h.str().c_str());
197+
_socket->flush();
198+
// we are in WebSocket mode, data frames should follow next
199+
_webSocketHandshakeDone = true;
200+
}
201+
62202
void JsonClientConnection::socketClosed()
63203
{
204+
_webSocketHandshakeDone = false;
64205
emit connectionClosed(this);
65206
}
66207

@@ -205,6 +346,9 @@ void JsonClientConnection::handleServerInfoCommand(const Json::Value &)
205346
Json::Value result;
206347
result["success"] = true;
207348
Json::Value & info = result["info"];
349+
350+
// add host name for remote clients
351+
info["hostname"] = QHostInfo::localHostName().toStdString();
208352

209353
// collect priority information
210354
Json::Value & priorities = info["priorities"] = Json::Value(Json::arrayValue);
@@ -362,7 +506,32 @@ void JsonClientConnection::sendMessage(const Json::Value &message)
362506
{
363507
Json::FastWriter writer;
364508
std::string serializedReply = writer.write(message);
365-
_socket->write(serializedReply.data(), serializedReply.length());
509+
510+
if (!_webSocketHandshakeDone)
511+
{
512+
// raw tcp socket mode
513+
_socket->write(serializedReply.data(), serializedReply.length());
514+
} else
515+
{
516+
// websocket mode
517+
quint32 size = serializedReply.length();
518+
519+
// prepare data frame
520+
QByteArray response;
521+
response.append(0x81);
522+
if (size > 125)
523+
{
524+
response.append(0x7E);
525+
response.append((size >> 8) & 0xFF);
526+
response.append(size & 0xFF);
527+
} else {
528+
response.append(size);
529+
}
530+
531+
response.append(serializedReply.c_str(), serializedReply.length());
532+
533+
_socket->write(response.data(), response.length());
534+
}
366535
}
367536

368537
void JsonClientConnection::sendSuccessReply()

libsrc/jsonserver/JsonClientConnection.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,16 @@ private slots:
136136
/// @param error String describing the error
137137
///
138138
void sendErrorReply(const std::string & error);
139+
140+
///
141+
/// Do handshake for a websocket connection
142+
///
143+
void doWebSocketHandshake();
144+
145+
///
146+
/// Handle incoming websocket data frame
147+
///
148+
void handleWebSocketFrame();
139149

140150
private:
141151
///
@@ -161,4 +171,7 @@ private slots:
161171

162172
/// The buffer used for reading data from the socket
163173
QByteArray _receiveBuffer;
174+
175+
/// used for WebSocket detection and connection handling
176+
bool _webSocketHandshakeDone;
164177
};

0 commit comments

Comments
 (0)