prepared tcp server and mock server for webserver

add TransportInterface
add basic structure of webserver
This commit is contained in:
Simon Stürz 2015-07-15 13:14:13 +02:00 committed by Michael Zanetti
parent df99a2f438
commit f1dd14527e
18 changed files with 467 additions and 106 deletions

View File

@ -5,7 +5,10 @@ TEMPLATE=subdirs
SUBDIRS += libguh server plugins SUBDIRS += libguh server plugins
!disabletesting { !disabletesting {
message("Building guh tests enabled")
SUBDIRS += tests SUBDIRS += tests
} else {
message("Building guh tests disabled")
} }
server.depends = libguh plugins server.depends = libguh plugins

View File

@ -25,6 +25,8 @@ Q_LOGGING_CATEGORY(dcDeviceManager, "DeviceManager")
Q_LOGGING_CATEGORY(dcRuleEngine, "RuleEngine") Q_LOGGING_CATEGORY(dcRuleEngine, "RuleEngine")
Q_LOGGING_CATEGORY(dcHardware, "Hardware") Q_LOGGING_CATEGORY(dcHardware, "Hardware")
Q_LOGGING_CATEGORY(dcConnection, "Connection") Q_LOGGING_CATEGORY(dcConnection, "Connection")
Q_LOGGING_CATEGORY(dcTcpServer, "TcpServer")
Q_LOGGING_CATEGORY(dcWebServer, "WebServer")
Q_LOGGING_CATEGORY(dcJsonRpc, "JsonRpc") Q_LOGGING_CATEGORY(dcJsonRpc, "JsonRpc")
Q_LOGGING_CATEGORY(dcLogEngine, "LogEngine") Q_LOGGING_CATEGORY(dcLogEngine, "LogEngine")

View File

@ -31,5 +31,7 @@ Q_DECLARE_LOGGING_CATEGORY(dcHardware)
Q_DECLARE_LOGGING_CATEGORY(dcConnection) Q_DECLARE_LOGGING_CATEGORY(dcConnection)
Q_DECLARE_LOGGING_CATEGORY(dcJsonRpc) Q_DECLARE_LOGGING_CATEGORY(dcJsonRpc)
Q_DECLARE_LOGGING_CATEGORY(dcLogEngine) Q_DECLARE_LOGGING_CATEGORY(dcLogEngine)
Q_DECLARE_LOGGING_CATEGORY(dcTcpServer)
Q_DECLARE_LOGGING_CATEGORY(dcWebServer)
#endif // LOGGINGCATEGORYS_H #endif // LOGGINGCATEGORYS_H

View File

@ -524,6 +524,11 @@ LogEngine* GuhCore::logEngine() const
return m_logger; return m_logger;
} }
JsonRPCServer *GuhCore::jsonRPCServer() const
{
return m_jsonServer;
}
void GuhCore::actionExecutionFinished(const ActionId &id, DeviceManager::DeviceError status) void GuhCore::actionExecutionFinished(const ActionId &id, DeviceManager::DeviceError status)
{ {
emit actionExecuted(id, status); emit actionExecuted(id, status);

View File

@ -93,6 +93,7 @@ public:
RuleEngine::RuleError disableRule(const RuleId &ruleId); RuleEngine::RuleError disableRule(const RuleId &ruleId);
LogEngine* logEngine() const; LogEngine* logEngine() const;
JsonRPCServer *jsonRPCServer() const;
signals: signals:
void eventTriggered(const Event &event); void eventTriggered(const Event &event);
@ -115,7 +116,8 @@ signals:
private: private:
RuleEngine *ruleEngine() const; RuleEngine *ruleEngine() const;
DeviceManager* deviceManager() const; DeviceManager *deviceManager() const;
explicit GuhCore(QObject *parent = 0); explicit GuhCore(QObject *parent = 0);
static GuhCore *s_instance; static GuhCore *s_instance;
RunningMode m_runningMode; RunningMode m_runningMode;

View File

@ -87,7 +87,7 @@ JsonRPCServer::JsonRPCServer(QObject *parent):
// Now set up the logic // Now set up the logic
connect(m_tcpServer, SIGNAL(clientConnected(const QUuid &)), this, SLOT(clientConnected(const QUuid &))); 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(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(); m_tcpServer->startServer();
QMetaObject::invokeMethod(this, "setup", Qt::QueuedConnection); QMetaObject::invokeMethod(this, "setup", Qt::QueuedConnection);
@ -138,6 +138,11 @@ JsonReply* JsonRPCServer::SetNotificationStatus(const QVariantMap &params)
return createReply(returns); return createReply(returns);
} }
QHash<QString, JsonHandler *> JsonRPCServer::handlers() const
{
return m_handlers;
}
void JsonRPCServer::setup() void JsonRPCServer::setup()
{ {
registerHandler(this); registerHandler(this);
@ -149,50 +154,19 @@ void JsonRPCServer::setup()
registerHandler(new StateHandler(this)); 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; // Note: id, targetNamespace and method already checked in TcpServer
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error);
if(error.error != QJsonParseError::NoError) { int commandId = message.value("id").toInt();
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();
QVariantMap params = message.value("params").toMap(); QVariantMap params = message.value("params").toMap();
emit commandReceived(targetNamespace, method, params); emit commandReceived(targetNamespace, method, params);
JsonHandler *handler = m_handlers.value(targetNamespace); 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;
}
QPair<bool, QString> validationResult = handler->validateParams(method, params); QPair<bool, QString> validationResult = handler->validateParams(method, params);
if (!validationResult.first) { if (!validationResult.first) {
sendErrorResponse(clientId, commandId, "Invalid params: " + validationResult.second); m_tcpServer->sendErrorResponse(clientId, commandId, "Invalid params: " + validationResult.second);
return; return;
} }
@ -209,7 +183,7 @@ void JsonRPCServer::processData(const QUuid &clientId, const QByteArray &jsonDat
} else { } else {
Q_ASSERT_X((targetNamespace == "JSONRPC" && method == "Introspect") || handler->validateReturns(method, reply->data()).first Q_ASSERT_X((targetNamespace == "JSONRPC" && method == "Introspect") || handler->validateReturns(method, reply->data()).first
,"validating return value", formatAssertion(targetNamespace, method, handler, reply->data()).toLatin1().data()); ,"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(); reply->deleteLater();
} }
} }
@ -234,8 +208,7 @@ void JsonRPCServer::sendNotification(const QVariantMap &params)
notification.insert("notification", handler->name() + "." + method.name()); notification.insert("notification", handler->name() + "." + method.name());
notification.insert("params", params); notification.insert("params", params);
QJsonDocument jsonDoc = QJsonDocument::fromVariant(notification); m_tcpServer->sendData(m_clients.keys(true), notification);
m_tcpServer->sendData(m_clients.keys(true), jsonDoc.toJson());
} }
void JsonRPCServer::asyncReplyFinished() void JsonRPCServer::asyncReplyFinished()
@ -244,10 +217,11 @@ void JsonRPCServer::asyncReplyFinished()
if (!reply->timedOut()) { if (!reply->timedOut()) {
Q_ASSERT_X(reply->handler()->validateReturns(reply->method(), reply->data()).first 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()); ,"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 { } else {
sendErrorResponse(reply->clientId(), reply->commandId(), "Command timed out"); m_tcpServer->sendErrorResponse(reply->clientId(), reply->commandId(), "Command timed out");
} }
reply->deleteLater(); reply->deleteLater();
} }
@ -272,8 +246,7 @@ void JsonRPCServer::clientConnected(const QUuid &clientId)
handshake.insert("server", "guh JSONRPC interface"); handshake.insert("server", "guh JSONRPC interface");
handshake.insert("version", GUH_VERSION_STRING); handshake.insert("version", GUH_VERSION_STRING);
handshake.insert("protocol version", JSON_PROTOCOL_VERSION); handshake.insert("protocol version", JSON_PROTOCOL_VERSION);
QJsonDocument jsonDoc = QJsonDocument::fromVariant(handshake); m_tcpServer->sendData(clientId, handshake);
m_tcpServer->sendData(clientId, jsonDoc.toJson());
} }
void JsonRPCServer::clientDisconnected(const QUuid &clientId) void JsonRPCServer::clientDisconnected(const QUuid &clientId)
@ -281,26 +254,4 @@ void JsonRPCServer::clientDisconnected(const QUuid &clientId)
m_clients.remove(clientId); m_clients.remove(clientId);
} }
void JsonRPCServer::sendResponse(const QUuid &clientId, int commandId, const QVariantMap &params)
{
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());
}
} }

View File

@ -54,6 +54,8 @@ public:
Q_INVOKABLE JsonReply* Version(const QVariantMap &params) const; Q_INVOKABLE JsonReply* Version(const QVariantMap &params) const;
Q_INVOKABLE JsonReply* SetNotificationStatus(const QVariantMap &params); Q_INVOKABLE JsonReply* SetNotificationStatus(const QVariantMap &params);
QHash<QString, JsonHandler*> handlers() const;
signals: signals:
void commandReceived(const QString &targetNamespace, const QString &command, const QVariantMap &params); void commandReceived(const QString &targetNamespace, const QString &command, const QVariantMap &params);
@ -63,7 +65,7 @@ private slots:
void clientConnected(const QUuid &clientId); void clientConnected(const QUuid &clientId);
void clientDisconnected(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 &params); void sendNotification(const QVariantMap &params);
@ -72,9 +74,6 @@ private slots:
private: private:
void registerHandler(JsonHandler *handler); void registerHandler(JsonHandler *handler);
void sendResponse(const QUuid &clientId, int commandId, const QVariantMap &params = 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; QString formatAssertion(const QString &targetNamespace, const QString &method, JsonHandler *handler, const QVariantMap &data) const;
private: private:

View File

@ -62,6 +62,8 @@ int main(int argc, char *argv[])
s_loggingFilters.insert("DeviceManager", true); s_loggingFilters.insert("DeviceManager", true);
s_loggingFilters.insert("RuleEngine", true); s_loggingFilters.insert("RuleEngine", true);
s_loggingFilters.insert("Connection", true); s_loggingFilters.insert("Connection", true);
s_loggingFilters.insert("TcpServer", true);
s_loggingFilters.insert("WebServer", true);
s_loggingFilters.insert("JsonRpc", false); s_loggingFilters.insert("JsonRpc", false);
s_loggingFilters.insert("Hardware", false); s_loggingFilters.insert("Hardware", false);
s_loggingFilters.insert("LogEngine", false); s_loggingFilters.insert("LogEngine", false);

View File

@ -15,6 +15,9 @@ SOURCES += $$top_srcdir/server/guhcore.cpp \
$$top_srcdir/server/logging/logengine.cpp \ $$top_srcdir/server/logging/logengine.cpp \
$$top_srcdir/server/logging/logfilter.cpp \ $$top_srcdir/server/logging/logfilter.cpp \
$$top_srcdir/server/logging/logentry.cpp \ $$top_srcdir/server/logging/logentry.cpp \
$$top_srcdir/server/webserver.cpp \
$$top_srcdir/server/transportinterface.cpp \
HEADERS += $$top_srcdir/server/guhcore.h \ HEADERS += $$top_srcdir/server/guhcore.h \
$$top_srcdir/server/tcpserver.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/logengine.h \
$$top_srcdir/server/logging/logfilter.h \ $$top_srcdir/server/logging/logfilter.h \
$$top_srcdir/server/logging/logentry.h \ $$top_srcdir/server/logging/logentry.h \
$$top_srcdir/server/webserver.h \
$$top_srcdir/server/transportinterface.h \

View File

@ -28,4 +28,4 @@ boblight {
} }
HEADERS += \ HEADERS += \
guhservice.h guhservice.h \

View File

@ -22,6 +22,8 @@
#include "tcpserver.h" #include "tcpserver.h"
#include "loggingcategories.h" #include "loggingcategories.h"
#include "guhsettings.h" #include "guhsettings.h"
#include "guhcore.h"
#include "jsonrpcserver.h"
#include <QDebug> #include <QDebug>
#include <QJsonDocument> #include <QJsonDocument>
@ -29,7 +31,7 @@
namespace guhserver { namespace guhserver {
TcpServer::TcpServer(QObject *parent) : TcpServer::TcpServer(QObject *parent) :
QObject(parent) TransportInterface(parent)
{ {
// Timer for scanning network interfaces ever 5 seconds // Timer for scanning network interfaces ever 5 seconds
// Note: QNetworkConfigurationManager does not work on embedded platforms // Note: QNetworkConfigurationManager does not work on embedded platforms
@ -40,7 +42,7 @@ TcpServer::TcpServer(QObject *parent) :
// load JSON-RPC server settings // load JSON-RPC server settings
GuhSettings settings(GuhSettings::SettingsRoleGlobal); 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"); settings.beginGroup("JSONRPC");
// load port // load port
@ -63,7 +65,7 @@ TcpServer::TcpServer(QObject *parent) :
settings.endGroup(); settings.endGroup();
} }
void TcpServer::sendData(const QList<QUuid> &clients, const QByteArray &data) void TcpServer::sendData(const QList<QUuid> &clients, const QVariantMap &data)
{ {
foreach (const QUuid &client, clients) { foreach (const QUuid &client, clients) {
sendData(client, data); 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 &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 = m_clientList.value(clientId);
if (client) { if (client) {
client->write(data); client->write(QJsonDocument::fromVariant(data).toJson());
} }
} }
@ -105,7 +172,7 @@ void TcpServer::newClientConnected()
// got a new client connected // got a new client connected
QTcpServer *server = qobject_cast<QTcpServer*>(sender()); QTcpServer *server = qobject_cast<QTcpServer*>(sender());
QTcpSocket *newConnection = server->nextPendingConnection(); 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(); QUuid clientId = QUuid::createUuid();
@ -122,14 +189,14 @@ void TcpServer::newClientConnected()
void TcpServer::readPackage() void TcpServer::readPackage()
{ {
QTcpSocket *client = qobject_cast<QTcpSocket*>(sender()); QTcpSocket *client = qobject_cast<QTcpSocket*>(sender());
qCDebug(dcConnection) << "data comming from" << client->peerAddress().toString(); qCDebug(dcTcpServer) << "data comming from" << client->peerAddress().toString();
QByteArray message; QByteArray message;
while (client->canReadLine()) { while (client->canReadLine()) {
QByteArray dataLine = client->readLine(); QByteArray dataLine = client->readLine();
qCDebug(dcConnection) << "line in:" << dataLine; qCDebug(dcTcpServer) << "line in:" << dataLine;
message.append(dataLine); message.append(dataLine);
if (dataLine.endsWith('\n')) { if (dataLine.endsWith('\n')) {
emit dataAvailable(m_clientList.key(client), message); validateMessage(m_clientList.key(client), message);
message.clear(); message.clear();
} }
} }
@ -138,7 +205,7 @@ void TcpServer::readPackage()
void TcpServer::onClientDisconnected() void TcpServer::onClientDisconnected()
{ {
QTcpSocket *client = qobject_cast<QTcpSocket*>(sender()); QTcpSocket *client = qobject_cast<QTcpSocket*>(sender());
qCDebug(dcConnection) << "client disconnected:" << client->peerAddress().toString(); qCDebug(dcConnection) << "Tcp server: client disconnected:" << client->peerAddress().toString();
QUuid clientId = m_clientList.key(client); QUuid clientId = m_clientList.key(client);
m_clientList.take(clientId)->deleteLater(); m_clientList.take(clientId)->deleteLater();
} }
@ -147,8 +214,8 @@ void TcpServer::onError(const QAbstractSocket::SocketError &error)
{ {
QTcpServer *server = qobject_cast<QTcpServer *>(sender()); QTcpServer *server = qobject_cast<QTcpServer *>(sender());
QUuid uuid = m_serverList.key(server); QUuid uuid = m_serverList.key(server);
qCWarning(dcConnection) << "TCP server" << server->serverAddress().toString() << "error:" << error << server->errorString(); qCWarning(dcTcpServer) << "Tcp server" << server->serverAddress().toString() << "error:" << error << server->errorString();
qCWarning(dcConnection) << "shutting down TCP server" << server->serverAddress().toString(); qCWarning(dcTcpServer) << "shutting down Tcp server" << server->serverAddress().toString();
server->close(); server->close();
m_serverList.take(uuid)->deleteLater(); m_serverList.take(uuid)->deleteLater();
@ -220,7 +287,7 @@ void TcpServer::onTimeout()
} }
if (!serversToCreate.isEmpty() || !serversToDelete.isEmpty()) { if (!serversToCreate.isEmpty() || !serversToDelete.isEmpty()) {
qCDebug(dcConnection) << "current server list:"; qCDebug(dcConnection) << "Current server list:";
foreach (QTcpServer *s, m_serverList) { foreach (QTcpServer *s, m_serverList) {
qCDebug(dcConnection) << " - Tcp server on" << s->serverAddress().toString() << s->serverPort(); qCDebug(dcConnection) << " - Tcp server on" << s->serverAddress().toString() << s->serverPort();
} }
@ -230,7 +297,6 @@ void TcpServer::onTimeout()
bool TcpServer::startServer() bool TcpServer::startServer()
{ {
bool ipV4 = m_ipVersions.contains("IPv4"); bool ipV4 = m_ipVersions.contains("IPv4");
bool ipV6 = m_ipVersions.contains("IPv6"); bool ipV6 = m_ipVersions.contains("IPv6");
if (m_ipVersions.contains("any")) { if (m_ipVersions.contains("any")) {

View File

@ -29,16 +29,18 @@
#include <QUuid> #include <QUuid>
#include <QTimer> #include <QTimer>
#include "transportinterface.h"
namespace guhserver { namespace guhserver {
class TcpServer : public QObject class TcpServer : public TransportInterface
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit TcpServer(QObject *parent = 0); explicit TcpServer(QObject *parent = 0);
void sendData(const QUuid &clientId, const QByteArray &data); void sendData(const QUuid &clientId, const QVariantMap &data);
void sendData(const QList<QUuid> &clients, const QByteArray &data); void sendData(const QList<QUuid> &clients, const QVariantMap &data);
private: private:
QTimer *m_timer; QTimer *m_timer;
@ -51,11 +53,11 @@ private:
QStringList m_ipVersions; QStringList m_ipVersions;
void reloadNetworkInterfaces(); void reloadNetworkInterfaces();
void validateMessage(const QUuid &clientId, const QByteArray &data);
signals: public:
void clientConnected(const QUuid &clientId); void sendResponse(const QUuid &clientId, int commandId, const QVariantMap &params = QVariantMap());
void clientDisconnected(const QUuid &clientId); void sendErrorResponse(const QUuid &clientId, int commandId, const QString &error);
void dataAvailable(const QUuid &clientId, const QByteArray &data);
private slots: private slots:
void newClientConnected(); void newClientConnected();

View File

@ -0,0 +1,30 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* 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 "transportinterface.h"
namespace guhserver {
TransportInterface::TransportInterface(QObject *parent) :
QObject(parent)
{
}
}

View File

@ -0,0 +1,48 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* 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/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef TRANSPORTINTERFACE_H
#define TRANSPORTINTERFACE_H
#include <QObject>
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<QUuid> &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

106
server/webserver.cpp Normal file
View File

@ -0,0 +1,106 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* 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 "webserver.h"
#include "loggingcategories.h"
#include <QTcpServer>
#include <QTcpSocket>
#include <QUuid>
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<QUuid> &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<QTcpSocket *>(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;
}
}

68
server/webserver.h Normal file
View File

@ -0,0 +1,68 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* 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/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef WEBSERVER_H
#define WEBSERVER_H
#include <QObject>
#include <QHash>
#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<QUuid> &clients, const QByteArray &data);
private:
QTcpServer *m_server;
QHash<QUuid, QTcpSocket *> 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

View File

@ -19,15 +19,21 @@
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "mocktcpserver.h" #include "mocktcpserver.h"
#include "loggingcategories.h"
#include "jsonrpcserver.h"
#include "guhcore.h"
#include <QUuid> #include <QUuid>
#include <QHash>
#include <QJsonDocument>
namespace guhserver { namespace guhserver {
QList<MockTcpServer*> MockTcpServer::s_allServers; QList<MockTcpServer*> MockTcpServer::s_allServers;
MockTcpServer::MockTcpServer(QObject *parent): MockTcpServer::MockTcpServer(QObject *parent):
QObject(parent) TransportInterface(parent)
{ {
s_allServers.append(this); s_allServers.append(this);
} }
@ -37,12 +43,12 @@ MockTcpServer::~MockTcpServer()
s_allServers.removeAll(this); 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<QUuid> &clients, const QByteArray &data) void MockTcpServer::sendData(const QList<QUuid> &clients, const QVariantMap &data)
{ {
foreach (const QUuid &clientId, clients) { foreach (const QUuid &clientId, clients) {
sendData(clientId, data); sendData(clientId, data);
@ -56,7 +62,67 @@ QList<MockTcpServer *> MockTcpServer::servers()
void MockTcpServer::injectData(const QUuid &clientId, const QByteArray &data) 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 &params)
{
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() bool MockTcpServer::startServer()

View File

@ -25,17 +25,21 @@
#include <QNetworkInterface> #include <QNetworkInterface>
#include <QDebug> #include <QDebug>
#include "transportinterface.h"
namespace guhserver { namespace guhserver {
class MockTcpServer : public QObject class JsonRPCServer;
class MockTcpServer : public TransportInterface
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit MockTcpServer(QObject *parent = 0); explicit MockTcpServer(QObject *parent = 0);
~MockTcpServer(); ~MockTcpServer();
void sendData(const QUuid &clientId, const QByteArray &data); void sendData(const QUuid &clientId, const QVariantMap &data);
void sendData(const QList<QUuid> &clients, const QByteArray &data); void sendData(const QList<QUuid> &clients, const QVariantMap &data);
/************** Used for testing **************************/ /************** Used for testing **************************/
static QList<MockTcpServer*> servers(); static QList<MockTcpServer*> servers();
@ -44,10 +48,9 @@ signals:
void outgoingData(const QUuid &clientId, const QByteArray &data); void outgoingData(const QUuid &clientId, const QByteArray &data);
/************** Used for testing **************************/ /************** Used for testing **************************/
signals: public:
void clientConnected(const QUuid &clientId); void sendResponse(const QUuid &clientId, int commandId, const QVariantMap &params = QVariantMap());
void clientDisconnected(const QUuid &clientId); void sendErrorResponse(const QUuid &clientId, int commandId, const QString &error);
void dataAvailable(const QUuid &clientId, const QByteArray &data);
public slots: public slots:
bool startServer(); bool startServer();