add websocket server

This commit is contained in:
Simon Stürz 2015-08-04 02:10:58 +02:00 committed by Michael Zanetti
parent 7f87cdd777
commit 557baff39d
19 changed files with 393 additions and 108 deletions

View File

@ -30,4 +30,4 @@ Q_LOGGING_CATEGORY(dcWebServer, "WebServer")
Q_LOGGING_CATEGORY(dcJsonRpc, "JsonRpc")
Q_LOGGING_CATEGORY(dcRest, "Rest")
Q_LOGGING_CATEGORY(dcLogEngine, "LogEngine")
Q_LOGGING_CATEGORY(dcWebSocketServer, "WebSocketServer")

View File

@ -34,5 +34,6 @@ Q_DECLARE_LOGGING_CATEGORY(dcRest)
Q_DECLARE_LOGGING_CATEGORY(dcLogEngine)
Q_DECLARE_LOGGING_CATEGORY(dcTcpServer)
Q_DECLARE_LOGGING_CATEGORY(dcWebServer)
Q_DECLARE_LOGGING_CATEGORY(dcWebSocketServer)
#endif // LOGGINGCATEGORYS_H

View File

@ -45,6 +45,10 @@
#include "logginghandler.h"
#include "statehandler.h"
#ifdef WEBSOCKET
#include "websocketserver.h"
#endif
#include <QJsonDocument>
#include <QStringList>
@ -57,6 +61,9 @@ JsonRPCServer::JsonRPCServer(QObject *parent):
#else
m_tcpServer(new TcpServer(this)),
#endif
#ifdef WEBSOCKET
m_websocketServer(new WebSocketServer(this)),
#endif
m_notificationId(0)
{
// First, define our own JSONRPC methods
@ -90,6 +97,17 @@ JsonRPCServer::JsonRPCServer(QObject *parent):
connect(m_tcpServer, SIGNAL(dataAvailable(QUuid, QString, QString, QVariantMap)), this, SLOT(processData(QUuid, QString, QString, QVariantMap)));
m_tcpServer->startServer();
m_interfaces.append(m_tcpServer);
#ifdef WEBSOCKET
connect(m_websocketServer, SIGNAL(clientConnected(const QUuid &)), this, SLOT(clientConnected(const QUuid &)));
connect(m_websocketServer, SIGNAL(clientDisconnected(const QUuid &)), this, SLOT(clientDisconnected(const QUuid &)));
connect(m_websocketServer, SIGNAL(dataAvailable(QUuid, QString, QString, QVariantMap)), this, SLOT(processData(QUuid, QString, QString, QVariantMap)));
m_websocketServer->startServer();
m_interfaces.append(m_websocketServer);
#endif
QMetaObject::invokeMethod(this, "setup", Qt::QueuedConnection);
}
@ -156,6 +174,8 @@ void JsonRPCServer::setup()
void JsonRPCServer::processData(const QUuid &clientId, const QString &targetNamespace, const QString &method, const QVariantMap &message)
{
TransportInterface *interface = qobject_cast<TransportInterface *>(sender());
// Note: id, targetNamespace and method already checked in TcpServer
int commandId = message.value("id").toInt();
QVariantMap params = message.value("params").toMap();
@ -165,7 +185,7 @@ void JsonRPCServer::processData(const QUuid &clientId, const QString &targetName
JsonHandler *handler = m_handlers.value(targetNamespace);
QPair<bool, QString> validationResult = handler->validateParams(method, params);
if (!validationResult.first) {
m_tcpServer->sendErrorResponse(clientId, commandId, "Invalid params: " + validationResult.second);
interface->sendErrorResponse(clientId, commandId, "Invalid params: " + validationResult.second);
return;
}
@ -175,6 +195,7 @@ void JsonRPCServer::processData(const QUuid &clientId, const QString &targetName
JsonReply *reply;
QMetaObject::invokeMethod(handler, method.toLatin1().data(), Q_RETURN_ARG(JsonReply*, reply), Q_ARG(QVariantMap, params));
if (reply->type() == JsonReply::TypeAsync) {
m_asyncReplies.insert(reply, interface);
reply->setClientId(clientId);
reply->setCommandId(commandId);
connect(reply, &JsonReply::finished, this, &JsonRPCServer::asyncReplyFinished);
@ -182,7 +203,7 @@ void JsonRPCServer::processData(const QUuid &clientId, const QString &targetName
} else {
Q_ASSERT_X((targetNamespace == "JSONRPC" && method == "Introspect") || handler->validateReturns(method, reply->data()).first
,"validating return value", formatAssertion(targetNamespace, method, handler, reply->data()).toLatin1().data());
m_tcpServer->sendResponse(clientId, commandId, reply->data());
interface->sendResponse(clientId, commandId, reply->data());
reply->deleteLater();
}
}
@ -199,7 +220,7 @@ QString JsonRPCServer::formatAssertion(const QString &targetNamespace, const QSt
void JsonRPCServer::sendNotification(const QVariantMap &params)
{
JsonHandler *handler = qobject_cast<JsonHandler*>(sender());
JsonHandler *handler = qobject_cast<JsonHandler *>(sender());
QMetaMethod method = handler->metaObject()->method(senderSignalIndex());
QVariantMap notification;
@ -207,20 +228,21 @@ void JsonRPCServer::sendNotification(const QVariantMap &params)
notification.insert("notification", handler->name() + "." + method.name());
notification.insert("params", params);
emit notificationDataReady(notification);
m_tcpServer->sendData(m_clients.keys(true), notification);
foreach (TransportInterface *interface, m_interfaces) {
interface->sendData(m_clients.keys(true), notification);
}
}
void JsonRPCServer::asyncReplyFinished()
{
JsonReply *reply = qobject_cast<JsonReply*>(sender());
JsonReply *reply = qobject_cast<JsonReply *>(sender());
TransportInterface *interface = m_asyncReplies.take(reply);
if (!reply->timedOut()) {
Q_ASSERT_X(reply->handler()->validateReturns(reply->method(), reply->data()).first
,"validating return value", formatAssertion(reply->handler()->name(), reply->method(), reply->handler(), reply->data()).toLatin1().data());
m_tcpServer->sendResponse(reply->clientId(), reply->commandId(), reply->data());
interface->sendResponse(reply->clientId(), reply->commandId(), reply->data());
} else {
m_tcpServer->sendErrorResponse(reply->clientId(), reply->commandId(), "Command timed out");
interface->sendErrorResponse(reply->clientId(), reply->commandId(), "Command timed out");
}
reply->deleteLater();
@ -239,15 +261,17 @@ void JsonRPCServer::registerHandler(JsonHandler *handler)
void JsonRPCServer::clientConnected(const QUuid &clientId)
{
// Notifications disabled by default
m_clients.insert(clientId, false);
// Notifications enabled by default
m_clients.insert(clientId, true);
TransportInterface *interface = qobject_cast<TransportInterface *>(sender());
QVariantMap handshake;
handshake.insert("id", 0);
handshake.insert("server", "guh JSONRPC interface");
handshake.insert("version", GUH_VERSION_STRING);
handshake.insert("protocol version", JSON_PROTOCOL_VERSION);
m_tcpServer->sendData(clientId, handshake);
interface->sendData(clientId, handshake);
}
void JsonRPCServer::clientDisconnected(const QUuid &clientId)

View File

@ -24,6 +24,7 @@
#include "plugin/deviceclass.h"
#include "jsonhandler.h"
#include "transportinterface.h"
#include "types/action.h"
#include "types/event.h"
@ -35,6 +36,9 @@
class Device;
namespace guhserver {
#ifdef WEBSOCKET
class WebSocketServer;
#endif
#ifdef TESTING_ENABLED
class MockTcpServer;
@ -58,7 +62,6 @@ public:
signals:
void commandReceived(const QString &targetNamespace, const QString &command, const QVariantMap &params);
void notificationDataReady(const QVariantMap &notification);
private slots:
void setup();
@ -83,7 +86,14 @@ private:
#else
TcpServer *m_tcpServer;
#endif
QHash<QString, JsonHandler*> m_handlers;
#ifdef WEBSOCKET
WebSocketServer *m_websocketServer;
#endif
QList<TransportInterface *> m_interfaces;
QHash<QString, JsonHandler *> m_handlers;
QHash<JsonReply *, TransportInterface *> m_asyncReplies;
// clientId, notificationsEnabled
QHash<QUuid, bool> m_clients;

View File

@ -64,6 +64,7 @@ int main(int argc, char *argv[])
s_loggingFilters.insert("Connection", true);
s_loggingFilters.insert("TcpServer", true);
s_loggingFilters.insert("WebServer", true);
s_loggingFilters.insert("WebSocketServer", true);
s_loggingFilters.insert("JsonRpc", false);
s_loggingFilters.insert("Rest", true);
s_loggingFilters.insert("Hardware", false);

View File

@ -1,6 +1,8 @@
contains(DEFINES, WEBSOCKET){
QT += websockets
SOURCES += $$top_srcdir/server/websocketserver.cpp
HEADERS += $$top_srcdir/server/websocketserver.h
}
@ -24,7 +26,6 @@ SOURCES += $$top_srcdir/server/guhcore.cpp \
$$top_srcdir/server/webserver.cpp \
$$top_srcdir/server/transportinterface.cpp \
$$top_srcdir/server/servermanager.cpp \
$$top_srcdir/server/websocketserver.cpp \
$$top_srcdir/server/httprequest.cpp \
$$top_srcdir/server/httpreply.cpp \
$$top_srcdir/server/rest/restserver.cpp \
@ -36,7 +37,6 @@ SOURCES += $$top_srcdir/server/guhcore.cpp \
$$top_srcdir/server/rest/pluginsresource.cpp \
$$top_srcdir/server/rest/rulesresource.cpp \
HEADERS += $$top_srcdir/server/guhcore.h \
$$top_srcdir/server/tcpserver.h \
$$top_srcdir/server/ruleengine.h \
@ -58,7 +58,6 @@ HEADERS += $$top_srcdir/server/guhcore.h \
$$top_srcdir/server/webserver.h \
$$top_srcdir/server/transportinterface.h \
$$top_srcdir/server/servermanager.h \
$$top_srcdir/server/websocketserver.h \
$$top_srcdir/server/httprequest.h \
$$top_srcdir/server/httpreply.h \
$$top_srcdir/server/rest/restserver.h \

View File

@ -19,6 +19,7 @@
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "servermanager.h"
#include "guhsettings.h"
namespace guhserver {

View File

@ -26,8 +26,7 @@
#include "loggingcategories.h"
#include "jsonrpc/jsonrpcserver.h"
#include "rest/restserver.h"
#include "websocketserver.h"
namespace guhserver {

View File

@ -65,6 +65,10 @@ TcpServer::TcpServer(QObject *parent) :
settings.endGroup();
}
TcpServer::~TcpServer()
{
}
void TcpServer::sendData(const QList<QUuid> &clients, const QVariantMap &data)
{
foreach (const QUuid &client, clients) {
@ -93,75 +97,10 @@ void TcpServer::reloadNetworkInterfaces()
settings.endGroup();
}
void TcpServer::validateMessage(const QUuid &clientId, const QByteArray &data)
{
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if(error.error != QJsonParseError::NoError) {
qCWarning(dcJsonRpc) << "Failed to parse JSON data" << data << ":" << error.errorString();
sendErrorResponse(clientId, -1, QString("Failed to parse JSON data: %1").arg(error.errorString()));
return;
}
QVariantMap message = jsonDoc.toVariant().toMap();
bool success;
int commandId = message.value("id").toInt(&success);
if (!success) {
qCWarning(dcJsonRpc) << "Error parsing command. Missing \"id\":" << message;
sendErrorResponse(clientId, commandId, "Error parsing command. Missing 'id'");
return;
}
QStringList commandList = message.value("method").toString().split('.');
if (commandList.count() != 2) {
qCWarning(dcJsonRpc) << "Error parsing method.\nGot:" << message.value("method").toString() << "\nExpected: \"Namespace.method\"";
sendErrorResponse(clientId, commandId, QString("Error parsing method. Got: '%1'', Expected: 'Namespace.method'").arg(message.value("method").toString()));
return;
}
QString targetNamespace = commandList.first();
QString method = commandList.last();
JsonHandler *handler = GuhCore::instance()->jsonRPCServer()->handlers().value(targetNamespace);
if (!handler) {
sendErrorResponse(clientId, commandId, "No such namespace");
return;
}
if (!handler->hasMethod(method)) {
sendErrorResponse(clientId, commandId, "No such method");
return;
}
emit dataAvailable(clientId, targetNamespace, method, message);
}
void TcpServer::sendResponse(const QUuid &clientId, int commandId, const QVariantMap &params)
{
QVariantMap response;
response.insert("id", commandId);
response.insert("status", "success");
response.insert("params", params);
sendData(clientId, response);
}
void TcpServer::sendErrorResponse(const QUuid &clientId, int commandId, const QString &error)
{
QVariantMap errorResponse;
errorResponse.insert("id", commandId);
errorResponse.insert("status", "error");
errorResponse.insert("error", error);
sendData(clientId, errorResponse);
}
void TcpServer::sendData(const QUuid &clientId, const QVariantMap &data)
{
QTcpSocket *client = m_clientList.value(clientId);
QTcpSocket *client = 0;
client = m_clientList.value(clientId);
if (client) {
client->write(QJsonDocument::fromVariant(data).toJson());
}

View File

@ -38,7 +38,8 @@ class TcpServer : public TransportInterface
Q_OBJECT
public:
explicit TcpServer(QObject *parent = 0);
~TcpServer();
void sendData(const QUuid &clientId, const QVariantMap &data) override;
void sendData(const QList<QUuid> &clients, const QVariantMap &data) override;
@ -53,11 +54,7 @@ private:
QStringList m_ipVersions;
void reloadNetworkInterfaces();
void validateMessage(const QUuid &clientId, const QByteArray &data);
public:
void sendResponse(const QUuid &clientId, int commandId, const QVariantMap &params = QVariantMap());
void sendErrorResponse(const QUuid &clientId, int commandId, const QString &error);
private slots:
void onClientConnected();

View File

@ -19,6 +19,11 @@
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "transportinterface.h"
#include "loggingcategories.h"
#include "jsonhandler.h"
#include "guhcore.h"
#include <QJsonDocument>
namespace guhserver {
@ -31,4 +36,70 @@ TransportInterface::~TransportInterface()
{
}
void TransportInterface::sendResponse(const QUuid &clientId, int commandId, const QVariantMap &params)
{
QVariantMap response;
response.insert("id", commandId);
response.insert("status", "success");
response.insert("params", params);
sendData(clientId, response);
}
void TransportInterface::sendErrorResponse(const QUuid &clientId, int commandId, const QString &error)
{
QVariantMap errorResponse;
errorResponse.insert("id", commandId);
errorResponse.insert("status", "error");
errorResponse.insert("error", error);
sendData(clientId, errorResponse);
}
void TransportInterface::validateMessage(const QUuid &clientId, const QByteArray &data)
{
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if(error.error != QJsonParseError::NoError) {
qCWarning(dcJsonRpc) << "Failed to parse JSON data" << data << ":" << error.errorString();
sendErrorResponse(clientId, -1, QString("Failed to parse JSON data: %1").arg(error.errorString()));
return;
}
QVariantMap message = jsonDoc.toVariant().toMap();
bool success;
int commandId = message.value("id").toInt(&success);
if (!success) {
qCWarning(dcJsonRpc) << "Error parsing command. Missing \"id\":" << message;
sendErrorResponse(clientId, commandId, "Error parsing command. Missing 'id'");
return;
}
QStringList commandList = message.value("method").toString().split('.');
if (commandList.count() != 2) {
qCWarning(dcJsonRpc) << "Error parsing method.\nGot:" << message.value("method").toString() << "\nExpected: \"Namespace.method\"";
sendErrorResponse(clientId, commandId, QString("Error parsing method. Got: '%1'', Expected: 'Namespace.method'").arg(message.value("method").toString()));
return;
}
QString targetNamespace = commandList.first();
QString method = commandList.last();
JsonHandler *handler = GuhCore::instance()->jsonRPCServer()->handlers().value(targetNamespace);
if (!handler) {
sendErrorResponse(clientId, commandId, "No such namespace");
return;
}
if (!handler->hasMethod(method)) {
sendErrorResponse(clientId, commandId, "No such method");
return;
}
emit dataAvailable(clientId, targetNamespace, method, message);
}
}

View File

@ -33,11 +33,17 @@ class TransportInterface : public QObject
Q_OBJECT
public:
explicit TransportInterface(QObject *parent = 0);
virtual ~TransportInterface();
virtual ~TransportInterface() = 0;
virtual void sendData(const QUuid &clientId, const QVariantMap &data) = 0;
virtual void sendData(const QList<QUuid> &clients, const QVariantMap &data) = 0;
void sendResponse(const QUuid &clientId, int commandId, const QVariantMap &params = QVariantMap());
void sendErrorResponse(const QUuid &clientId, int commandId, const QString &error);
protected:
void validateMessage(const QUuid &clientId, const QByteArray &data);
signals:
void clientConnected(const QUuid &clientId);
void clientDisconnected(const QUuid &clientId);

View File

@ -42,21 +42,15 @@ WebServer::WebServer(QObject *parent) :
{
// load webserver settings
GuhSettings settings(GuhSettings::SettingsRoleGlobal);
qCDebug(dcWebServer) << "Loading webserver settings from:" << settings.fileName();
qCDebug(dcWebSocketServer) << "Loading web socket server settings from:" << settings.fileName();
settings.beginGroup("Webserver");
settings.beginGroup("WebSocketServer");
m_port = settings.value("port", 3000).toInt();
m_useSsl = settings.value("https", false).toBool();
m_webinterfaceDir = QDir(settings.value("publicFolder", "/usr/share/guh-webinterface/public/").toString());
QString certificateFileName = settings.value("certificate", QVariant("/etc/ssl/certs/guhd-certificate.crt")).toString();
QString keyFileName = settings.value("certificate-key", QVariant("/etc/ssl/private/guhd-certificate.key")).toString();
settings.endGroup();
// check public directory
qCDebug(dcWebServer) << "Publish webinterface folder" << m_webinterfaceDir.path();
if (!m_webinterfaceDir.exists())
qCWarning(dcWebServer) << "Web interface public folder" << m_webinterfaceDir.path() << "does not exist.";
// check SSL
if (m_useSsl && !QSslSocket::supportsSsl()) {
qCWarning(dcWebServer) << "SSL is not supported/installed on this platform.";

View File

@ -19,12 +19,31 @@
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "websocketserver.h"
#include "guhsettings.h"
#include "loggingcategories.h"
#include <QJsonDocument>
namespace guhserver {
WebSocketServer::WebSocketServer(QObject *parent) :
TransportInterface(parent)
TransportInterface(parent),
m_server(0)
{
// load webserver settings
GuhSettings settings(GuhSettings::SettingsRoleGlobal);
qCDebug(dcWebServer) << "Loading webserver settings from:" << settings.fileName();
settings.beginGroup("WebSocketServer");
m_port = settings.value("port", 3001).toInt();
m_useSsl = settings.value("https", false).toBool();
settings.endGroup();
// check SSL
if (m_useSsl && !QSslSocket::supportsSsl()) {
qCWarning(dcWebServer) << "SSL is not supported/installed on this platform.";
m_useSsl = false;
}
}
WebSocketServer::~WebSocketServer()
@ -33,23 +52,122 @@ WebSocketServer::~WebSocketServer()
void WebSocketServer::sendData(const QUuid &clientId, const QVariantMap &data)
{
Q_UNUSED(clientId)
Q_UNUSED(data)
QWebSocket *client = 0;
client = m_clientList.value(clientId);
if (client) {
client->sendTextMessage(QJsonDocument::fromVariant(data).toJson());
}
}
void WebSocketServer::sendData(const QList<QUuid> &clients, const QVariantMap &data)
{
Q_UNUSED(clients)
Q_UNUSED(data)
foreach (const QUuid &client, clients) {
sendData(client, data);
}
}
void WebSocketServer::onClientConnected()
{
// got a new client connected
QWebSocket *client = m_server->nextPendingConnection();
qCDebug(dcConnection) << "Websocket server: new client connected:" << client->peerAddress().toString();
// check websocket version
if (client->version() != QWebSocketProtocol::Version13) {
qCWarning(dcWebSocketServer) << "Client with invalid protocol version" << client->version() << ". Rejecting.";
client->close(QWebSocketProtocol::CloseCodeProtocolError, QString("invalid protocol version: %1 != Supported Version 13").arg(client->version()));
delete client;
return;
}
QUuid clientId = QUuid::createUuid();
// append the new client to the client list
m_clientList.insert(clientId, client);
connect(client, SIGNAL(pong(quint64,QByteArray)), this, SLOT(onPing(quint64,QByteArray)));
connect(client, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(onBinaryMessageReceived(QByteArray)));
connect(client, SIGNAL(textMessageReceived(QString)), this, SLOT(onTextMessageReceived(QString)));
connect(client, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onClientError(QAbstractSocket::SocketError)));
connect(client, SIGNAL(disconnected()), this, SLOT(onClientDisconnected()));
emit clientConnected(clientId);
}
void WebSocketServer::onClientDisconnected()
{
QWebSocket *client = qobject_cast<QWebSocket *>(sender());
qCDebug(dcConnection) << "Websocket server: client disconnected:" << client->peerAddress().toString();
QUuid clientId = m_clientList.key(client);
m_clientList.take(clientId)->deleteLater();
}
void WebSocketServer::onBinaryMessageReceived(const QByteArray &data)
{
QWebSocket *client = qobject_cast<QWebSocket *>(sender());
qCDebug(dcWebSocketServer) << "Binary message from" << client->peerAddress().toString() << ":" << data;
}
void WebSocketServer::onTextMessageReceived(const QString &message)
{
QWebSocket *client = qobject_cast<QWebSocket *>(sender());
qCDebug(dcWebSocketServer) << "Text message from" << client->peerAddress().toString() << ":" << message;
validateMessage(m_clientList.key(client), message.toUtf8());
}
void WebSocketServer::onClientError(QAbstractSocket::SocketError error)
{
QWebSocket *client = qobject_cast<QWebSocket *>(sender());
qCWarning(dcConnection) << "Websocket client error:" << error << client->errorString();
}
void WebSocketServer::onServerError(QAbstractSocket::SocketError error)
{
qCWarning(dcConnection) << "Websocket server error:" << error << m_server->errorString();
}
void WebSocketServer::onPing(quint64 elapsedTime, const QByteArray &payload)
{
QWebSocket *client = qobject_cast<QWebSocket *>(sender());
qCDebug(dcWebSocketServer) << "ping response" << client->peerAddress() << elapsedTime << payload;
}
bool WebSocketServer::startServer()
{
return false;
if (m_server) {
qCWarning(dcConnection) << "There is allready a websocket server instance. This should never happen!!! Please report this bug!";
return false;
}
if (m_useSsl) {
m_server = new QWebSocketServer("guh", QWebSocketServer::SecureMode, this);
} else {
m_server = new QWebSocketServer("guh", QWebSocketServer::NonSecureMode, this);
}
connect (m_server, &QWebSocketServer::newConnection, this, &WebSocketServer::onClientConnected);
connect (m_server, &QWebSocketServer::acceptError, this, &WebSocketServer::onServerError);
if (!m_server->listen(QHostAddress::Any, m_port)) {
qCWarning(dcConnection) << "Websocket server" << m_server->serverName() << QString("could not listen on %1:%2").arg(m_server->serverAddress().toString()).arg(m_port);
return false;
}
if (m_server->secureMode() == QWebSocketServer::NonSecureMode) {
qCDebug(dcConnection) << "Started websocket server" << m_server->serverName() << QString("on ws://%1:%2").arg(m_server->serverAddress().toString()).arg(m_port);
} else {
qCDebug(dcConnection) << "Started websocket server" << m_server->serverName() << QString("on wss://%1:%2").arg(m_server->serverAddress().toString()).arg(m_port);
}
qCDebug(dcWebSocketServer) << "Supported protocol versions" << m_server->supportedVersions();
return true;
}
bool WebSocketServer::stopServer()
{
qCDebug(dcConnection) << "Stopping websocket server";
m_server->close();
delete m_server;
m_server = 0;
return false;
}

View File

@ -25,6 +25,8 @@
#include <QUuid>
#include <QVariant>
#include <QList>
#include <QWebSocket>
#include <QWebSocketServer>
#include "transportinterface.h"
@ -43,6 +45,23 @@ public:
void sendData(const QUuid &clientId, const QVariantMap &data) override;
void sendData(const QList<QUuid> &clients, const QVariantMap &data) override;
private:
QWebSocketServer *m_server;
QHash<QUuid, QWebSocket *> m_clientList;
bool m_enabled;
bool m_useSsl;
qint16 m_port;
private slots:
void onClientConnected();
void onClientDisconnected();
void onBinaryMessageReceived(const QByteArray &data);
void onTextMessageReceived(const QString &message);
void onClientError(QAbstractSocket::SocketError error);
void onServerError(QAbstractSocket::SocketError error);
void onPing(quint64 elapsedTime, const QByteArray & payload);
public slots:
bool startServer() override;
bool stopServer() override;

View File

@ -14,3 +14,6 @@ SUBDIRS = versioning \
restplugins \
restvendors \
restrules \
websocketserver \

View File

@ -98,7 +98,8 @@ void TestJSONRPC::testHandshake()
QJsonDocument jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray());
QVariantMap handShake = jsonDoc.toVariant().toMap();
QVERIFY2(handShake.value("version").toString() == GUH_VERSION_STRING, "Handshake version doesn't match Guh version.");
QString guhVersionString(GUH_VERSION_STRING);
QVERIFY2(handShake.value("version").toString() == guhVersionString, "Handshake version doesn't match Guh version.");
m_mockTcpServer->clientDisconnected(newClientId);
}

View File

@ -0,0 +1,93 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of guh. *
* *
* Guh is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, version 2 of the License. *
* *
* Guh is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with guh. If not, see <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "guhtestbase.h"
#include "guhcore.h"
#include "devicemanager.h"
#include "mocktcpserver.h"
#include "webserver.h"
#include <QtTest/QtTest>
#include <QCoreApplication>
#include <QMetaType>
#include <QByteArray>
#include <QSignalSpy>
#include <QWebSocket>
#include <QJsonDocument>
using namespace guhserver;
class TestWebSocketServer: public GuhTestBase
{
Q_OBJECT
private slots:
void testHandshake();
void pingTest();
void introspect();
private:
};
void TestWebSocketServer::testHandshake()
{
QWebSocket *socket = new QWebSocket("guh tests", QWebSocketProtocol::Version13);
QSignalSpy spy(socket, SIGNAL(textMessageReceived(QString)));
socket->open(QUrl(QStringLiteral("ws://localhost:3001")));
spy.wait();
QVERIFY2(spy.count() > 0, "Did not get the handshake message upon connect.");
QJsonDocument jsonDoc = QJsonDocument::fromJson(spy.first().first().toByteArray());
QVariantMap handShake = jsonDoc.toVariant().toMap();
QString guhVersionString(GUH_VERSION_STRING);
QString jsonProtocolVersionString(JSON_PROTOCOL_VERSION);
QCOMPARE(handShake.value("version").toString(), guhVersionString);
QCOMPARE(handShake.value("protocol version").toString(), jsonProtocolVersionString);
socket->close();
socket->deleteLater();
}
void TestWebSocketServer::pingTest()
{
QWebSocket *socket = new QWebSocket("guh tests", QWebSocketProtocol::Version13);
QSignalSpy spyConnection(socket, SIGNAL(connected()));
socket->open(QUrl(QStringLiteral("ws://localhost:3001")));
spyConnection.wait();
QVERIFY2(spyConnection.count() > 0, "not connected");
QSignalSpy spyPong(socket, SIGNAL(pong(quint64,QByteArray)));
socket->ping("hallo");
spyPong.wait();
QVERIFY2(spyPong.count() > 0, "no pong");
qDebug() << "ping response" << spyPong.first().at(0) << spyPong.first().at(1).toString();
}
void TestWebSocketServer::introspect()
{
}
#include "testwebsocketserver.moc"
QTEST_MAIN(TestWebSocketServer)

View File

@ -0,0 +1,9 @@
include(../../../guh.pri)
include(../autotests.pri)
TARGET = websocketserver
contains(DEFINES, WEBSOCKET){
QT += websockets
SOURCES += testwebsocketserver.cpp
}