From f1dd14527e13d62683d78afe705d71f5fd7a1f3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 15 Jul 2015 13:14:13 +0200 Subject: [PATCH] prepared tcp server and mock server for webserver add TransportInterface add basic structure of webserver --- guh.pro | 3 + libguh/loggingcategories.cpp | 2 + libguh/loggingcategories.h | 2 + server/guhcore.cpp | 5 ++ server/guhcore.h | 4 +- server/jsonrpc/jsonrpcserver.cpp | 83 +++++------------------- server/jsonrpc/jsonrpcserver.h | 7 +- server/main.cpp | 2 + server/server.pri | 6 ++ server/server.pro | 2 +- server/tcpserver.cpp | 94 +++++++++++++++++++++++---- server/tcpserver.h | 18 +++--- server/transportinterface.cpp | 30 +++++++++ server/transportinterface.h | 48 ++++++++++++++ server/webserver.cpp | 106 +++++++++++++++++++++++++++++++ server/webserver.h | 68 ++++++++++++++++++++ tests/auto/mocktcpserver.cpp | 76 ++++++++++++++++++++-- tests/auto/mocktcpserver.h | 17 +++-- 18 files changed, 467 insertions(+), 106 deletions(-) create mode 100644 server/transportinterface.cpp create mode 100644 server/transportinterface.h create mode 100644 server/webserver.cpp create mode 100644 server/webserver.h diff --git a/guh.pro b/guh.pro index ac478e48..78e983d2 100644 --- a/guh.pro +++ b/guh.pro @@ -5,7 +5,10 @@ TEMPLATE=subdirs SUBDIRS += libguh server plugins !disabletesting { + message("Building guh tests enabled") SUBDIRS += tests +} else { + message("Building guh tests disabled") } server.depends = libguh plugins diff --git a/libguh/loggingcategories.cpp b/libguh/loggingcategories.cpp index 6cf2066c..be01d891 100644 --- a/libguh/loggingcategories.cpp +++ b/libguh/loggingcategories.cpp @@ -25,6 +25,8 @@ Q_LOGGING_CATEGORY(dcDeviceManager, "DeviceManager") Q_LOGGING_CATEGORY(dcRuleEngine, "RuleEngine") Q_LOGGING_CATEGORY(dcHardware, "Hardware") Q_LOGGING_CATEGORY(dcConnection, "Connection") +Q_LOGGING_CATEGORY(dcTcpServer, "TcpServer") +Q_LOGGING_CATEGORY(dcWebServer, "WebServer") Q_LOGGING_CATEGORY(dcJsonRpc, "JsonRpc") Q_LOGGING_CATEGORY(dcLogEngine, "LogEngine") diff --git a/libguh/loggingcategories.h b/libguh/loggingcategories.h index 298a5ad4..e4ea2d91 100644 --- a/libguh/loggingcategories.h +++ b/libguh/loggingcategories.h @@ -31,5 +31,7 @@ Q_DECLARE_LOGGING_CATEGORY(dcHardware) Q_DECLARE_LOGGING_CATEGORY(dcConnection) Q_DECLARE_LOGGING_CATEGORY(dcJsonRpc) Q_DECLARE_LOGGING_CATEGORY(dcLogEngine) +Q_DECLARE_LOGGING_CATEGORY(dcTcpServer) +Q_DECLARE_LOGGING_CATEGORY(dcWebServer) #endif // LOGGINGCATEGORYS_H diff --git a/server/guhcore.cpp b/server/guhcore.cpp index 52908413..628b71cd 100644 --- a/server/guhcore.cpp +++ b/server/guhcore.cpp @@ -524,6 +524,11 @@ LogEngine* GuhCore::logEngine() const return m_logger; } +JsonRPCServer *GuhCore::jsonRPCServer() const +{ + return m_jsonServer; +} + void GuhCore::actionExecutionFinished(const ActionId &id, DeviceManager::DeviceError status) { emit actionExecuted(id, status); diff --git a/server/guhcore.h b/server/guhcore.h index 303b5744..1251e0d1 100644 --- a/server/guhcore.h +++ b/server/guhcore.h @@ -93,6 +93,7 @@ public: RuleEngine::RuleError disableRule(const RuleId &ruleId); LogEngine* logEngine() const; + JsonRPCServer *jsonRPCServer() const; signals: void eventTriggered(const Event &event); @@ -115,7 +116,8 @@ signals: private: RuleEngine *ruleEngine() const; - DeviceManager* deviceManager() const; + DeviceManager *deviceManager() const; + explicit GuhCore(QObject *parent = 0); static GuhCore *s_instance; RunningMode m_runningMode; diff --git a/server/jsonrpc/jsonrpcserver.cpp b/server/jsonrpc/jsonrpcserver.cpp index 1316ebdb..39f390fd 100644 --- a/server/jsonrpc/jsonrpcserver.cpp +++ b/server/jsonrpc/jsonrpcserver.cpp @@ -87,7 +87,7 @@ JsonRPCServer::JsonRPCServer(QObject *parent): // Now set up the logic connect(m_tcpServer, SIGNAL(clientConnected(const QUuid &)), this, SLOT(clientConnected(const QUuid &))); connect(m_tcpServer, SIGNAL(clientDisconnected(const QUuid &)), this, SLOT(clientDisconnected(const QUuid &))); - connect(m_tcpServer, SIGNAL(dataAvailable(const QUuid &, QByteArray)), this, SLOT(processData(const QUuid &, QByteArray))); + connect(m_tcpServer, SIGNAL(dataAvailable(QUuid, QString, QString, QVariantMap)), this, SLOT(processData(QUuid, QString, QString, QVariantMap))); m_tcpServer->startServer(); QMetaObject::invokeMethod(this, "setup", Qt::QueuedConnection); @@ -138,6 +138,11 @@ JsonReply* JsonRPCServer::SetNotificationStatus(const QVariantMap ¶ms) return createReply(returns); } +QHash JsonRPCServer::handlers() const +{ + return m_handlers; +} + void JsonRPCServer::setup() { registerHandler(this); @@ -149,50 +154,19 @@ void JsonRPCServer::setup() registerHandler(new StateHandler(this)); } -void JsonRPCServer::processData(const QUuid &clientId, const QByteArray &jsonData) +void JsonRPCServer::processData(const QUuid &clientId, const QString &targetNamespace, const QString &method, const QVariantMap &message) { - QJsonParseError error; - QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error); + // Note: id, targetNamespace and method already checked in TcpServer - if(error.error != QJsonParseError::NoError) { - qCWarning(dcJsonRpc) << "failed to parse data" << jsonData << ":" << error.errorString(); - } - - QVariantMap message = jsonDoc.toVariant().toMap(); - - bool success; - int commandId = message.value("id").toInt(&success); - if (!success) { - qCWarning(dcJsonRpc) << "Error parsing command. Missing \"id\":" << jsonData; - 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(); + int commandId = message.value("id").toInt(); QVariantMap params = message.value("params").toMap(); emit commandReceived(targetNamespace, method, params); - JsonHandler *handler = m_handlers.value(targetNamespace); - if (!handler) { - sendErrorResponse(clientId, commandId, "No such namespace"); - return; - } - if (!handler->hasMethod(method)) { - sendErrorResponse(clientId, commandId, "No such method"); - return; - } + JsonHandler *handler = m_handlers.value(targetNamespace); QPair validationResult = handler->validateParams(method, params); if (!validationResult.first) { - sendErrorResponse(clientId, commandId, "Invalid params: " + validationResult.second); + m_tcpServer->sendErrorResponse(clientId, commandId, "Invalid params: " + validationResult.second); return; } @@ -209,7 +183,7 @@ void JsonRPCServer::processData(const QUuid &clientId, const QByteArray &jsonDat } 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()); - sendResponse(clientId, commandId, reply->data()); + m_tcpServer->sendResponse(clientId, commandId, reply->data()); reply->deleteLater(); } } @@ -234,8 +208,7 @@ void JsonRPCServer::sendNotification(const QVariantMap ¶ms) notification.insert("notification", handler->name() + "." + method.name()); notification.insert("params", params); - QJsonDocument jsonDoc = QJsonDocument::fromVariant(notification); - m_tcpServer->sendData(m_clients.keys(true), jsonDoc.toJson()); + m_tcpServer->sendData(m_clients.keys(true), notification); } void JsonRPCServer::asyncReplyFinished() @@ -244,10 +217,11 @@ void JsonRPCServer::asyncReplyFinished() 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()); - sendResponse(reply->clientId(), reply->commandId(), reply->data()); + m_tcpServer->sendResponse(reply->clientId(), reply->commandId(), reply->data()); } else { - sendErrorResponse(reply->clientId(), reply->commandId(), "Command timed out"); + m_tcpServer->sendErrorResponse(reply->clientId(), reply->commandId(), "Command timed out"); } + reply->deleteLater(); } @@ -272,8 +246,7 @@ void JsonRPCServer::clientConnected(const QUuid &clientId) handshake.insert("server", "guh JSONRPC interface"); handshake.insert("version", GUH_VERSION_STRING); handshake.insert("protocol version", JSON_PROTOCOL_VERSION); - QJsonDocument jsonDoc = QJsonDocument::fromVariant(handshake); - m_tcpServer->sendData(clientId, jsonDoc.toJson()); + m_tcpServer->sendData(clientId, handshake); } void JsonRPCServer::clientDisconnected(const QUuid &clientId) @@ -281,26 +254,4 @@ void JsonRPCServer::clientDisconnected(const QUuid &clientId) m_clients.remove(clientId); } -void JsonRPCServer::sendResponse(const QUuid &clientId, int commandId, const QVariantMap ¶ms) -{ - QVariantMap rsp; - rsp.insert("id", commandId); - rsp.insert("status", "success"); - rsp.insert("params", params); - - QJsonDocument jsonDoc = QJsonDocument::fromVariant(rsp); - m_tcpServer->sendData(clientId, jsonDoc.toJson()); -} - -void JsonRPCServer::sendErrorResponse(const QUuid &clientId, int commandId, const QString &error) -{ - QVariantMap rsp; - rsp.insert("id", commandId); - rsp.insert("status", "error"); - rsp.insert("error", error); - - QJsonDocument jsonDoc = QJsonDocument::fromVariant(rsp); - m_tcpServer->sendData(clientId, jsonDoc.toJson()); -} - } diff --git a/server/jsonrpc/jsonrpcserver.h b/server/jsonrpc/jsonrpcserver.h index 5493c74f..e3d1c96d 100644 --- a/server/jsonrpc/jsonrpcserver.h +++ b/server/jsonrpc/jsonrpcserver.h @@ -54,6 +54,8 @@ public: Q_INVOKABLE JsonReply* Version(const QVariantMap ¶ms) const; Q_INVOKABLE JsonReply* SetNotificationStatus(const QVariantMap ¶ms); + QHash handlers() const; + signals: void commandReceived(const QString &targetNamespace, const QString &command, const QVariantMap ¶ms); @@ -63,7 +65,7 @@ private slots: void clientConnected(const QUuid &clientId); void clientDisconnected(const QUuid &clientId); - void processData(const QUuid &clientId, const QByteArray &jsonData); + void processData(const QUuid &clientId, const QString &targetNamespace, const QString &method, const QVariantMap &message); void sendNotification(const QVariantMap ¶ms); @@ -72,9 +74,6 @@ private slots: private: void registerHandler(JsonHandler *handler); - void sendResponse(const QUuid &clientId, int commandId, const QVariantMap ¶ms = QVariantMap()); - void sendErrorResponse(const QUuid &clientId, int commandId, const QString &error); - QString formatAssertion(const QString &targetNamespace, const QString &method, JsonHandler *handler, const QVariantMap &data) const; private: diff --git a/server/main.cpp b/server/main.cpp index f15d705a..d736109a 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -62,6 +62,8 @@ int main(int argc, char *argv[]) s_loggingFilters.insert("DeviceManager", true); s_loggingFilters.insert("RuleEngine", true); s_loggingFilters.insert("Connection", true); + s_loggingFilters.insert("TcpServer", true); + s_loggingFilters.insert("WebServer", true); s_loggingFilters.insert("JsonRpc", false); s_loggingFilters.insert("Hardware", false); s_loggingFilters.insert("LogEngine", false); diff --git a/server/server.pri b/server/server.pri index 404e113c..9c1abff3 100644 --- a/server/server.pri +++ b/server/server.pri @@ -15,6 +15,9 @@ SOURCES += $$top_srcdir/server/guhcore.cpp \ $$top_srcdir/server/logging/logengine.cpp \ $$top_srcdir/server/logging/logfilter.cpp \ $$top_srcdir/server/logging/logentry.cpp \ + $$top_srcdir/server/webserver.cpp \ + $$top_srcdir/server/transportinterface.cpp \ + HEADERS += $$top_srcdir/server/guhcore.h \ $$top_srcdir/server/tcpserver.h \ @@ -35,3 +38,6 @@ HEADERS += $$top_srcdir/server/guhcore.h \ $$top_srcdir/server/logging/logengine.h \ $$top_srcdir/server/logging/logfilter.h \ $$top_srcdir/server/logging/logentry.h \ + $$top_srcdir/server/webserver.h \ + $$top_srcdir/server/transportinterface.h \ + diff --git a/server/server.pro b/server/server.pro index f2bc616c..da8a2e07 100644 --- a/server/server.pro +++ b/server/server.pro @@ -28,4 +28,4 @@ boblight { } HEADERS += \ - guhservice.h + guhservice.h \ diff --git a/server/tcpserver.cpp b/server/tcpserver.cpp index b76d2b92..eaf2217c 100644 --- a/server/tcpserver.cpp +++ b/server/tcpserver.cpp @@ -22,6 +22,8 @@ #include "tcpserver.h" #include "loggingcategories.h" #include "guhsettings.h" +#include "guhcore.h" +#include "jsonrpcserver.h" #include #include @@ -29,7 +31,7 @@ namespace guhserver { TcpServer::TcpServer(QObject *parent) : - QObject(parent) + TransportInterface(parent) { // Timer for scanning network interfaces ever 5 seconds // Note: QNetworkConfigurationManager does not work on embedded platforms @@ -40,7 +42,7 @@ TcpServer::TcpServer(QObject *parent) : // load JSON-RPC server settings GuhSettings settings(GuhSettings::SettingsRoleGlobal); - qCDebug(dcConnection) << "Loading TCP server settings from:" << settings.fileName(); + qCDebug(dcTcpServer) << "Loading Tcp server settings from:" << settings.fileName(); settings.beginGroup("JSONRPC"); // load port @@ -63,7 +65,7 @@ TcpServer::TcpServer(QObject *parent) : settings.endGroup(); } -void TcpServer::sendData(const QList &clients, const QByteArray &data) +void TcpServer::sendData(const QList &clients, const QVariantMap &data) { foreach (const QUuid &client, clients) { sendData(client, data); @@ -92,11 +94,76 @@ void TcpServer::reloadNetworkInterfaces() } -void TcpServer::sendData(const QUuid &clientId, const QByteArray &data) +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 ¶ms) +{ + 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); if (client) { - client->write(data); + client->write(QJsonDocument::fromVariant(data).toJson()); } } @@ -105,7 +172,7 @@ void TcpServer::newClientConnected() // got a new client connected QTcpServer *server = qobject_cast(sender()); QTcpSocket *newConnection = server->nextPendingConnection(); - qCDebug(dcConnection) << "new client connected:" << newConnection->peerAddress().toString(); + qCDebug(dcConnection) << "Tcp server: new client connected:" << newConnection->peerAddress().toString(); QUuid clientId = QUuid::createUuid(); @@ -122,14 +189,14 @@ void TcpServer::newClientConnected() void TcpServer::readPackage() { QTcpSocket *client = qobject_cast(sender()); - qCDebug(dcConnection) << "data comming from" << client->peerAddress().toString(); + qCDebug(dcTcpServer) << "data comming from" << client->peerAddress().toString(); QByteArray message; while (client->canReadLine()) { QByteArray dataLine = client->readLine(); - qCDebug(dcConnection) << "line in:" << dataLine; + qCDebug(dcTcpServer) << "line in:" << dataLine; message.append(dataLine); if (dataLine.endsWith('\n')) { - emit dataAvailable(m_clientList.key(client), message); + validateMessage(m_clientList.key(client), message); message.clear(); } } @@ -138,7 +205,7 @@ void TcpServer::readPackage() void TcpServer::onClientDisconnected() { QTcpSocket *client = qobject_cast(sender()); - qCDebug(dcConnection) << "client disconnected:" << client->peerAddress().toString(); + qCDebug(dcConnection) << "Tcp server: client disconnected:" << client->peerAddress().toString(); QUuid clientId = m_clientList.key(client); m_clientList.take(clientId)->deleteLater(); } @@ -147,8 +214,8 @@ void TcpServer::onError(const QAbstractSocket::SocketError &error) { QTcpServer *server = qobject_cast(sender()); QUuid uuid = m_serverList.key(server); - qCWarning(dcConnection) << "TCP server" << server->serverAddress().toString() << "error:" << error << server->errorString(); - qCWarning(dcConnection) << "shutting down TCP server" << server->serverAddress().toString(); + qCWarning(dcTcpServer) << "Tcp server" << server->serverAddress().toString() << "error:" << error << server->errorString(); + qCWarning(dcTcpServer) << "shutting down Tcp server" << server->serverAddress().toString(); server->close(); m_serverList.take(uuid)->deleteLater(); @@ -220,7 +287,7 @@ void TcpServer::onTimeout() } if (!serversToCreate.isEmpty() || !serversToDelete.isEmpty()) { - qCDebug(dcConnection) << "current server list:"; + qCDebug(dcConnection) << "Current server list:"; foreach (QTcpServer *s, m_serverList) { qCDebug(dcConnection) << " - Tcp server on" << s->serverAddress().toString() << s->serverPort(); } @@ -230,7 +297,6 @@ void TcpServer::onTimeout() bool TcpServer::startServer() { - bool ipV4 = m_ipVersions.contains("IPv4"); bool ipV6 = m_ipVersions.contains("IPv6"); if (m_ipVersions.contains("any")) { diff --git a/server/tcpserver.h b/server/tcpserver.h index 1bf42748..4afa0a1d 100644 --- a/server/tcpserver.h +++ b/server/tcpserver.h @@ -29,16 +29,18 @@ #include #include +#include "transportinterface.h" + namespace guhserver { -class TcpServer : public QObject +class TcpServer : public TransportInterface { Q_OBJECT public: explicit TcpServer(QObject *parent = 0); - void sendData(const QUuid &clientId, const QByteArray &data); - void sendData(const QList &clients, const QByteArray &data); + void sendData(const QUuid &clientId, const QVariantMap &data); + void sendData(const QList &clients, const QVariantMap &data); private: QTimer *m_timer; @@ -51,12 +53,12 @@ private: QStringList m_ipVersions; void reloadNetworkInterfaces(); + void validateMessage(const QUuid &clientId, const QByteArray &data); + +public: + void sendResponse(const QUuid &clientId, int commandId, const QVariantMap ¶ms = QVariantMap()); + void sendErrorResponse(const QUuid &clientId, int commandId, const QString &error); -signals: - void clientConnected(const QUuid &clientId); - void clientDisconnected(const QUuid &clientId); - void dataAvailable(const QUuid &clientId, const QByteArray &data); - private slots: void newClientConnected(); void readPackage(); diff --git a/server/transportinterface.cpp b/server/transportinterface.cpp new file mode 100644 index 00000000..692b5636 --- /dev/null +++ b/server/transportinterface.cpp @@ -0,0 +1,30 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * 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 . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "transportinterface.h" + +namespace guhserver { + +TransportInterface::TransportInterface(QObject *parent) : + QObject(parent) +{ +} + +} diff --git a/server/transportinterface.h b/server/transportinterface.h new file mode 100644 index 00000000..28a352d9 --- /dev/null +++ b/server/transportinterface.h @@ -0,0 +1,48 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * 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 . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef TRANSPORTINTERFACE_H +#define TRANSPORTINTERFACE_H + +#include + +namespace guhserver { + +class TransportInterface : public QObject +{ + Q_OBJECT +public: + explicit TransportInterface(QObject *parent = 0); + virtual void sendData(const QUuid &clientId, const QVariantMap &data) = 0; + virtual void sendData(const QList &clients, const QVariantMap &data) = 0; + +signals: + void clientConnected(const QUuid &clientId); + void clientDisconnected(const QUuid &clientId); + void dataAvailable(const QUuid &clientId, const QString &targetNamespace, const QString &method, const QVariantMap &message); + +public slots: + virtual bool startServer() = 0; + virtual bool stopServer() = 0; +}; + +} + +#endif // TRANSPORTINTERFACE_H diff --git a/server/webserver.cpp b/server/webserver.cpp new file mode 100644 index 00000000..a323e134 --- /dev/null +++ b/server/webserver.cpp @@ -0,0 +1,106 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * 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 . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "webserver.h" +#include "loggingcategories.h" + +#include +#include +#include + +namespace guhserver { + +WebServer::WebServer(QObject *parent) : + TransportInterface(parent), + m_enabled(false) +{ + m_server = new QTcpServer(this); + + connect(m_server, &QTcpServer::newConnection, this, &WebServer::onNewConnection); +} + +WebServer::~WebServer() +{ + m_server->close(); +} + +void WebServer::sendData(const QUuid &clientId, const QByteArray &data) +{ + Q_UNUSED(clientId) + Q_UNUSED(data) + + +} + +void WebServer::sendData(const QList &clients, const QByteArray &data) +{ + Q_UNUSED(clients) + Q_UNUSED(data) +} + +QString WebServer::createContentHeader() +{ + QString contentHeader( + "HTTP/1.1 200 OK\r\n" + "Content-Type: application/json; charset=\"utf-8\"\r\n" + "\r\n" + ); + return contentHeader; +} + +void WebServer::onNewConnection() +{ + QTcpSocket* socket = m_server->nextPendingConnection(); + connect(socket, &QTcpSocket::readyRead, this, &WebServer::readClient); + connect(socket, &QTcpSocket::disconnected, this, &WebServer::discardClient); + qCDebug(dcConnection) << "Webserver client connected" << socket->peerName() << socket->peerAddress().toString() << socket->peerPort(); +} + +void WebServer::readClient() +{ + +} + +void WebServer::discardClient() +{ + QTcpSocket* socket = static_cast(sender()); + qCDebug(dcConnection) << "Webserver client disonnected" << socket->peerName() << socket->peerAddress().toString() << socket->peerPort(); + +} + +bool WebServer::startServer() +{ + if (!m_server->listen(QHostAddress::Any, m_port)) { + qCWarning(dcConnection) << "Webserver could not listen on" << m_server->serverAddress().toString() << m_port; + m_enabled = false; + return false; + } + qCDebug(dcConnection) << "Started webserver on" << m_server->serverAddress().toString() << m_port; + return true; +} + +bool WebServer::stopServer() +{ + m_server->close(); + qCDebug(dcConnection) << "Webserver closed."; + return true; +} + +} diff --git a/server/webserver.h b/server/webserver.h new file mode 100644 index 00000000..7da2c589 --- /dev/null +++ b/server/webserver.h @@ -0,0 +1,68 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * 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 . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef WEBSERVER_H +#define WEBSERVER_H + +#include +#include + +#include "transportinterface.h" + +class QTcpServer; +class QTcpSocket; +class QUuid; + +namespace guhserver { + +class WebServer : public TransportInterface +{ + Q_OBJECT +public: + explicit WebServer(QObject *parent = 0); + ~WebServer(); + void sendData(const QUuid &clientId, const QByteArray &data); + void sendData(const QList &clients, const QByteArray &data); + +private: + QTcpServer *m_server; + QHash m_clientList; + + bool m_enabled; + qint16 m_port; + + QString createContentHeader(); + +signals: + +private slots: + void onNewConnection(); + void readClient(); + void discardClient(); + +public slots: + bool startServer(); + bool stopServer(); + +}; + +} + +#endif // WEBSERVER_H diff --git a/tests/auto/mocktcpserver.cpp b/tests/auto/mocktcpserver.cpp index fa332283..7bd5788d 100644 --- a/tests/auto/mocktcpserver.cpp +++ b/tests/auto/mocktcpserver.cpp @@ -19,15 +19,21 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mocktcpserver.h" +#include "loggingcategories.h" +#include "jsonrpcserver.h" +#include "guhcore.h" #include +#include +#include + namespace guhserver { QList MockTcpServer::s_allServers; MockTcpServer::MockTcpServer(QObject *parent): - QObject(parent) + TransportInterface(parent) { s_allServers.append(this); } @@ -37,12 +43,12 @@ MockTcpServer::~MockTcpServer() s_allServers.removeAll(this); } -void MockTcpServer::sendData(const QUuid &clientId, const QByteArray &data) +void MockTcpServer::sendData(const QUuid &clientId, const QVariantMap &data) { - emit outgoingData(clientId, data); + emit outgoingData(clientId, QJsonDocument::fromVariant(data).toJson()); } -void MockTcpServer::sendData(const QList &clients, const QByteArray &data) +void MockTcpServer::sendData(const QList &clients, const QVariantMap &data) { foreach (const QUuid &clientId, clients) { sendData(clientId, data); @@ -56,7 +62,67 @@ QList MockTcpServer::servers() void MockTcpServer::injectData(const QUuid &clientId, const QByteArray &data) { - emit dataAvailable(clientId, 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 MockTcpServer::sendResponse(const QUuid &clientId, int commandId, const QVariantMap ¶ms) +{ + QVariantMap response; + response.insert("id", commandId); + response.insert("status", "success"); + response.insert("params", params); + + sendData(clientId, response); +} + +void MockTcpServer::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); } bool MockTcpServer::startServer() diff --git a/tests/auto/mocktcpserver.h b/tests/auto/mocktcpserver.h index a272fe8f..ba20d5e8 100644 --- a/tests/auto/mocktcpserver.h +++ b/tests/auto/mocktcpserver.h @@ -25,17 +25,21 @@ #include #include +#include "transportinterface.h" + namespace guhserver { -class MockTcpServer : public QObject +class JsonRPCServer; + +class MockTcpServer : public TransportInterface { Q_OBJECT public: explicit MockTcpServer(QObject *parent = 0); ~MockTcpServer(); - void sendData(const QUuid &clientId, const QByteArray &data); - void sendData(const QList &clients, const QByteArray &data); + void sendData(const QUuid &clientId, const QVariantMap &data); + void sendData(const QList &clients, const QVariantMap &data); /************** Used for testing **************************/ static QList servers(); @@ -44,10 +48,9 @@ signals: void outgoingData(const QUuid &clientId, const QByteArray &data); /************** Used for testing **************************/ -signals: - void clientConnected(const QUuid &clientId); - void clientDisconnected(const QUuid &clientId); - void dataAvailable(const QUuid &clientId, const QByteArray &data); +public: + void sendResponse(const QUuid &clientId, int commandId, const QVariantMap ¶ms = QVariantMap()); + void sendErrorResponse(const QUuid &clientId, int commandId, const QString &error); public slots: bool startServer();