prepared tcp server and mock server for webserver

add TransportInterface
add basic structure of webserver
pull/135/head
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
!disabletesting {
message("Building guh tests enabled")
SUBDIRS += tests
} else {
message("Building guh tests disabled")
}
server.depends = libguh plugins

View File

@ -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")

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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 &params)
return createReply(returns);
}
QHash<QString, JsonHandler *> 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<bool, QString> 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 &params)
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 &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* SetNotificationStatus(const QVariantMap &params);
QHash<QString, JsonHandler*> handlers() const;
signals:
void commandReceived(const QString &targetNamespace, const QString &command, const QVariantMap &params);
@ -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 &params);
@ -72,9 +74,6 @@ private slots:
private:
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;
private:

View File

@ -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);

View File

@ -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 \

View File

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

View File

@ -22,6 +22,8 @@
#include "tcpserver.h"
#include "loggingcategories.h"
#include "guhsettings.h"
#include "guhcore.h"
#include "jsonrpcserver.h"
#include <QDebug>
#include <QJsonDocument>
@ -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<QUuid> &clients, const QByteArray &data)
void TcpServer::sendData(const QList<QUuid> &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 &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);
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<QTcpServer*>(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<QTcpSocket*>(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<QTcpSocket*>(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<QTcpServer *>(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")) {

View File

@ -29,16 +29,18 @@
#include <QUuid>
#include <QTimer>
#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<QUuid> &clients, const QByteArray &data);
void sendData(const QUuid &clientId, const QVariantMap &data);
void sendData(const QList<QUuid> &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 &params = 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();

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 "loggingcategories.h"
#include "jsonrpcserver.h"
#include "guhcore.h"
#include <QUuid>
#include <QHash>
#include <QJsonDocument>
namespace guhserver {
QList<MockTcpServer*> 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<QUuid> &clients, const QByteArray &data)
void MockTcpServer::sendData(const QList<QUuid> &clients, const QVariantMap &data)
{
foreach (const QUuid &clientId, clients) {
sendData(clientId, data);
@ -56,7 +62,67 @@ QList<MockTcpServer *> 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 &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()

View File

@ -25,17 +25,21 @@
#include <QNetworkInterface>
#include <QDebug>
#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<QUuid> &clients, const QByteArray &data);
void sendData(const QUuid &clientId, const QVariantMap &data);
void sendData(const QList<QUuid> &clients, const QVariantMap &data);
/************** Used for testing **************************/
static QList<MockTcpServer*> 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 &params = QVariantMap());
void sendErrorResponse(const QUuid &clientId, int commandId, const QString &error);
public slots:
bool startServer();