first working REST call
add httpreply and httprequest to lib
This commit is contained in:
parent
478c832ec8
commit
5a3c7a6cfb
6
guh.pri
6
guh.pri
@ -26,5 +26,11 @@ enable433gpio {
|
||||
DEFINES += GPIO433
|
||||
}
|
||||
|
||||
# check webserver support
|
||||
equals(QT_MAJOR_VERSION, 5):greaterThan(QT_MINOR_VERSION, 3) {
|
||||
DEFINES += WEBSERVER
|
||||
}
|
||||
|
||||
|
||||
top_srcdir=$$PWD
|
||||
top_builddir=$$shadowed($$PWD)
|
||||
|
||||
7
guh.pro
7
guh.pro
@ -25,6 +25,7 @@ test.commands = LD_LIBRARY_PATH=$$top_builddir/libguh make check
|
||||
|
||||
QMAKE_EXTRA_TARGETS += licensecheck doc test
|
||||
|
||||
message(Qt version: $$[QT_VERSION])
|
||||
message("Building guh version $${GUH_VERSION_STRING}")
|
||||
message("JSON-RPC API version $${JSON_PROTOCOL_VERSION}")
|
||||
message("REST API version $${REST_API_VERSION}")
|
||||
@ -33,6 +34,12 @@ coverage {
|
||||
message("Building coverage.")
|
||||
}
|
||||
|
||||
contains(DEFINES, WEBSERVER){
|
||||
message("Building guh with webserver.")
|
||||
} else {
|
||||
message("Building guh without webserver.")
|
||||
}
|
||||
|
||||
contains(DEFINES, GPIO433){
|
||||
message("Radio 433 for GPIO's enabled")
|
||||
} else {
|
||||
|
||||
@ -39,7 +39,8 @@ SOURCES += plugin/device.cpp \
|
||||
types/statedescriptor.cpp \
|
||||
loggingcategories.cpp \
|
||||
guhsettings.cpp \
|
||||
network/httpreply.cpp
|
||||
network/httpreply.cpp \
|
||||
network/httprequest.cpp
|
||||
|
||||
HEADERS += plugin/device.h \
|
||||
plugin/deviceclass.h \
|
||||
@ -64,7 +65,6 @@ HEADERS += plugin/device.h \
|
||||
types/event.h \
|
||||
types/eventdescriptor.h \
|
||||
types/vendor.h \
|
||||
types/typeutils.h \
|
||||
types/paramtype.h \
|
||||
types/param.h \
|
||||
types/paramdescriptor.h \
|
||||
@ -74,5 +74,6 @@ HEADERS += plugin/device.h \
|
||||
typeutils.h \
|
||||
loggingcategories.h \
|
||||
guhsettings.h \
|
||||
network/httpreply.h
|
||||
network/httpreply.h \
|
||||
network/httprequest.h
|
||||
|
||||
|
||||
@ -187,11 +187,13 @@ void HttpReply::clear()
|
||||
m_payload.clear();
|
||||
m_rawHeaderList.clear();
|
||||
}
|
||||
|
||||
/*! Returns the whole reply data of this \l{HttpReply}. The data contain the HTTP header and the payload. */
|
||||
QByteArray HttpReply::packReply()
|
||||
/*! Packs the whole reply data of this \l{HttpReply}. The data can be accessed with \l{HttpReply::data()}.
|
||||
\sa data()
|
||||
*/
|
||||
void HttpReply::packReply()
|
||||
{
|
||||
// set status code
|
||||
m_data.clear();
|
||||
m_rawHeader.clear();
|
||||
m_rawHeader.append("HTTP/1.1 " + QByteArray::number(m_statusCode) + " " + getHttpReasonPhrase(m_statusCode) + "\r\n");
|
||||
|
||||
@ -201,8 +203,12 @@ QByteArray HttpReply::packReply()
|
||||
}
|
||||
|
||||
m_rawHeader.append("\r\n");
|
||||
m_rawHeader.append(m_payload);
|
||||
return m_rawHeader;
|
||||
m_data = m_rawHeader.append(m_payload);
|
||||
}
|
||||
|
||||
QByteArray HttpReply::data() const
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
QByteArray HttpReply::getHttpReasonPhrase(const HttpReply::HttpStatusCode &statusCode)
|
||||
|
||||
@ -78,12 +78,15 @@ public:
|
||||
bool isEmpty() const;
|
||||
void clear();
|
||||
|
||||
QByteArray packReply();
|
||||
void packReply();
|
||||
|
||||
QByteArray data() const;
|
||||
|
||||
private:
|
||||
HttpStatusCode m_statusCode;
|
||||
QByteArray m_rawHeader;
|
||||
QByteArray m_payload;
|
||||
QByteArray m_data;
|
||||
|
||||
QHash<QByteArray, QByteArray> m_rawHeaderList;
|
||||
|
||||
|
||||
129
libguh/network/httprequest.cpp
Normal file
129
libguh/network/httprequest.cpp
Normal file
@ -0,0 +1,129 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* 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 "httprequest.h"
|
||||
#include "loggingcategories.h"
|
||||
|
||||
#include <QUrlQuery>
|
||||
|
||||
|
||||
HttpRequest::HttpRequest(QByteArray rawData) :
|
||||
m_rawData(rawData),
|
||||
m_valid(false)
|
||||
{
|
||||
// Parese the HTTP request. The request is invalid, until the end of the parse process.
|
||||
if (m_rawData.isEmpty())
|
||||
return;
|
||||
|
||||
// split the data into header and payload
|
||||
int headerEndIndex = m_rawData.indexOf("\r\n\r\n");
|
||||
if (headerEndIndex < 0) {
|
||||
qCWarning(dcWebServer) << "Could not parse end of HTTP header (empty line between header and body):" << rawData;
|
||||
return;
|
||||
}
|
||||
m_rawHeader = m_rawData.left(headerEndIndex);
|
||||
m_payload = m_rawData.right(m_rawData.length() - headerEndIndex).simplified();
|
||||
|
||||
// parse status line
|
||||
QStringList headerLines = QString(m_rawHeader).split(QRegExp("\r\n"));
|
||||
QString statusLine = headerLines.takeFirst();
|
||||
QStringList statusLineTokens = statusLine.split(QRegExp("[ \r\n][ \r\n]*"));
|
||||
if (statusLineTokens.count() != 3) {
|
||||
qCWarning(dcWebServer) << "Could not parse HTTP status line:" << statusLine;
|
||||
return;
|
||||
}
|
||||
|
||||
m_method = statusLineTokens.at(0).toUtf8().simplified();
|
||||
m_urlQuery = QUrlQuery(statusLineTokens.at(1).simplified());
|
||||
m_httpVersion = statusLineTokens.at(2).toUtf8().simplified();
|
||||
|
||||
if (!m_httpVersion.contains("HTTP")) {
|
||||
qCWarning(dcWebServer) << "Unknown HTTP version:" << m_httpVersion;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (const QString &line, headerLines) {
|
||||
if (!line.contains(":")) {
|
||||
qCWarning(dcWebServer) << "Invalid HTTP header:" << line;
|
||||
return;
|
||||
}
|
||||
int index = line.indexOf(":");
|
||||
QByteArray key = line.left(index).toUtf8().simplified();
|
||||
QByteArray value = line.right(line.count() - index - 1).toUtf8().simplified();
|
||||
m_rawHeaderList.insert(key, value);
|
||||
}
|
||||
|
||||
m_valid = true;
|
||||
}
|
||||
|
||||
QByteArray HttpRequest::rawHeader() const
|
||||
{
|
||||
return m_rawHeader;
|
||||
}
|
||||
|
||||
QHash<QByteArray, QByteArray> HttpRequest::rawHeaderList() const
|
||||
{
|
||||
return m_rawHeaderList;
|
||||
}
|
||||
|
||||
QByteArray HttpRequest::method() const
|
||||
{
|
||||
return m_method;
|
||||
}
|
||||
|
||||
QByteArray HttpRequest::httpVersion() const
|
||||
{
|
||||
return m_httpVersion;
|
||||
}
|
||||
|
||||
QUrlQuery HttpRequest::urlQuery() const
|
||||
{
|
||||
return m_urlQuery;
|
||||
}
|
||||
|
||||
QByteArray HttpRequest::payload() const
|
||||
{
|
||||
return m_payload;
|
||||
}
|
||||
|
||||
bool HttpRequest::isValid() const
|
||||
{
|
||||
return m_valid;
|
||||
}
|
||||
|
||||
bool HttpRequest::hasPayload() const
|
||||
{
|
||||
return !m_payload.isEmpty();
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, const HttpRequest &httpRequest)
|
||||
{
|
||||
debug << "===================================" << "\n";
|
||||
debug << " http version: " << httpRequest.httpVersion() << "\n";
|
||||
debug << " method: " << httpRequest.method() << "\n";
|
||||
debug << " URL query: " << httpRequest.urlQuery().query() << "\n";
|
||||
debug << " is valid: " << httpRequest.isValid() << "\n";
|
||||
debug << "-----------------------------------" << "\n";
|
||||
debug << httpRequest.rawHeader() << "\n";
|
||||
debug << "-----------------------------------" << "\n";
|
||||
debug << httpRequest.payload() << "\n";
|
||||
debug << "-----------------------------------" << "\n";
|
||||
return debug;
|
||||
}
|
||||
62
libguh/network/httprequest.h
Normal file
62
libguh/network/httprequest.h
Normal file
@ -0,0 +1,62 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* 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 HTTPREQUEST_H
|
||||
#define HTTPREQUEST_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QUrlQuery>
|
||||
#include <QString>
|
||||
#include <QHash>
|
||||
|
||||
class HttpRequest
|
||||
{
|
||||
public:
|
||||
explicit HttpRequest(QByteArray rawData);
|
||||
|
||||
QByteArray rawHeader() const;
|
||||
QHash<QByteArray, QByteArray> rawHeaderList() const;
|
||||
|
||||
QByteArray method() const;
|
||||
QByteArray httpVersion() const;
|
||||
QUrlQuery urlQuery() const;
|
||||
|
||||
QByteArray payload() const;
|
||||
|
||||
bool isValid() const;
|
||||
bool hasPayload() const;
|
||||
|
||||
private:
|
||||
QByteArray m_rawData;
|
||||
QByteArray m_rawHeader;
|
||||
QHash<QByteArray, QByteArray> m_rawHeaderList;
|
||||
|
||||
QByteArray m_method;
|
||||
QByteArray m_httpVersion;
|
||||
QUrlQuery m_urlQuery;
|
||||
|
||||
QByteArray m_payload;
|
||||
|
||||
bool m_valid;
|
||||
};
|
||||
|
||||
QDebug operator<< (QDebug debug, const HttpRequest &httpRequest);
|
||||
|
||||
#endif // HTTPREQUEST_H
|
||||
@ -416,11 +416,8 @@ GuhCore::GuhCore(QObject *parent) :
|
||||
qCDebug(dcApplication) << "Creating Rule Engine";
|
||||
m_ruleEngine = new RuleEngine(this);
|
||||
|
||||
qCDebug(dcApplication) << "Starting JSON RPC Server";
|
||||
m_jsonServer = new JsonRPCServer(this);
|
||||
|
||||
qCDebug(dcApplication) << "Starting REST Webserver";
|
||||
m_webServer = new WebServer(this);
|
||||
m_serverManager = new ServerManager(this);
|
||||
|
||||
connect(m_deviceManager, &DeviceManager::eventTriggered, this, &GuhCore::gotEvent);
|
||||
connect(m_deviceManager, &DeviceManager::deviceStateChanged, this, &GuhCore::deviceStateChanged);
|
||||
@ -438,7 +435,6 @@ GuhCore::GuhCore(QObject *parent) :
|
||||
connect(m_ruleEngine, &RuleEngine::ruleConfigurationChanged, this, &GuhCore::ruleConfigurationChanged);
|
||||
|
||||
m_logger->logSystemEvent(true);
|
||||
m_webServer->startServer();
|
||||
}
|
||||
|
||||
/*! Connected to the DeviceManager's emitEvent signal. Events received in
|
||||
@ -530,7 +526,12 @@ LogEngine* GuhCore::logEngine() const
|
||||
|
||||
JsonRPCServer *GuhCore::jsonRPCServer() const
|
||||
{
|
||||
return m_jsonServer;
|
||||
return m_serverManager->jsonServer();
|
||||
}
|
||||
|
||||
RestServer *GuhCore::restServer() const
|
||||
{
|
||||
return m_serverManager->restServer();
|
||||
}
|
||||
|
||||
void GuhCore::actionExecutionFinished(const ActionId &id, DeviceManager::DeviceError status)
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
|
||||
#include "devicemanager.h"
|
||||
#include "ruleengine.h"
|
||||
#include "webserver.h"
|
||||
#include "servermanager.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
@ -95,6 +95,7 @@ public:
|
||||
|
||||
LogEngine* logEngine() const;
|
||||
JsonRPCServer *jsonRPCServer() const;
|
||||
RestServer *restServer() const;
|
||||
|
||||
signals:
|
||||
void eventTriggered(const Event &event);
|
||||
@ -123,8 +124,7 @@ private:
|
||||
static GuhCore *s_instance;
|
||||
RunningMode m_runningMode;
|
||||
|
||||
WebServer *m_webServer;
|
||||
JsonRPCServer *m_jsonServer;
|
||||
ServerManager *m_serverManager;
|
||||
DeviceManager *m_deviceManager;
|
||||
RuleEngine *m_ruleEngine;
|
||||
|
||||
|
||||
@ -52,11 +52,11 @@ namespace guhserver {
|
||||
|
||||
JsonRPCServer::JsonRPCServer(QObject *parent):
|
||||
JsonHandler(parent),
|
||||
#ifdef TESTING_ENABLED
|
||||
#ifdef TESTING_ENABLED
|
||||
m_tcpServer(new MockTcpServer(this)),
|
||||
#else
|
||||
#else
|
||||
m_tcpServer(new TcpServer(this)),
|
||||
#endif
|
||||
#endif
|
||||
m_notificationId(0)
|
||||
{
|
||||
// First, define our own JSONRPC methods
|
||||
@ -162,7 +162,7 @@ void JsonRPCServer::processData(const QUuid &clientId, const QString &targetName
|
||||
|
||||
emit commandReceived(targetNamespace, method, params);
|
||||
|
||||
JsonHandler *handler = m_handlers.value(targetNamespace);
|
||||
JsonHandler *handler = m_handlers.value(targetNamespace);
|
||||
QPair<bool, QString> validationResult = handler->validateParams(method, params);
|
||||
if (!validationResult.first) {
|
||||
m_tcpServer->sendErrorResponse(clientId, commandId, "Invalid params: " + validationResult.second);
|
||||
@ -207,6 +207,8 @@ void JsonRPCServer::sendNotification(const QVariantMap ¶ms)
|
||||
notification.insert("notification", handler->name() + "." + method.name());
|
||||
notification.insert("params", params);
|
||||
|
||||
emit notificationDataReady(notification);
|
||||
|
||||
m_tcpServer->sendData(m_clients.keys(true), notification);
|
||||
}
|
||||
|
||||
|
||||
@ -58,6 +58,7 @@ public:
|
||||
|
||||
signals:
|
||||
void commandReceived(const QString &targetNamespace, const QString &command, const QVariantMap ¶ms);
|
||||
void notificationDataReady(const QVariantMap ¬ification);
|
||||
|
||||
private slots:
|
||||
void setup();
|
||||
|
||||
136
server/rest/restserver.cpp
Normal file
136
server/rest/restserver.cpp
Normal file
@ -0,0 +1,136 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* 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 "restserver.h"
|
||||
#include "loggingcategories.h"
|
||||
#include "network/httprequest.h"
|
||||
#include "network/httpreply.h"
|
||||
#include "guhcore.h"
|
||||
|
||||
#include "devicehandler.h"
|
||||
#include "actionhandler.h"
|
||||
#include "ruleshandler.h"
|
||||
#include "eventhandler.h"
|
||||
#include "logginghandler.h"
|
||||
#include "statehandler.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
|
||||
namespace guhserver {
|
||||
|
||||
RestServer::RestServer(QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
m_webserver = new WebServer(this);
|
||||
connect(m_webserver, &WebServer::clientConnected, this, &RestServer::clientConnected);
|
||||
connect(m_webserver, &WebServer::clientDisconnected, this, &RestServer::clientDisconnected);
|
||||
connect(m_webserver, &WebServer::httpRequestReady, this, &RestServer::processHttpRequest);
|
||||
|
||||
m_webserver->startServer();
|
||||
}
|
||||
|
||||
void RestServer::clientConnected(const QUuid &clientId)
|
||||
{
|
||||
m_clientList.append(clientId);
|
||||
}
|
||||
|
||||
void RestServer::clientDisconnected(const QUuid &clientId)
|
||||
{
|
||||
m_clientList.removeAll(clientId);
|
||||
}
|
||||
|
||||
void RestServer::processHttpRequest(const QUuid &clientId, const HttpRequest &request)
|
||||
{
|
||||
qCDebug(dcWebServer) << "process http request" << clientId << request.method() << request.urlQuery().query();
|
||||
|
||||
QString targetNamespace;
|
||||
QString method;
|
||||
QVariantMap params;
|
||||
|
||||
if (request.urlQuery().hasQueryItem("devices")) {
|
||||
qCDebug(dcWebServer) << "devices resource";
|
||||
|
||||
}
|
||||
|
||||
if (request.method() == "GET" && request.urlQuery().query() == "/api/v1/devices.json") {
|
||||
targetNamespace = "Devices";
|
||||
method = "GetConfiguredDevices";
|
||||
} else if (request.method() == "GET" && request.urlQuery().query() == "/api/v1/devices.json") {
|
||||
targetNamespace = "Devices";
|
||||
method = "GetConfiguredDevices";
|
||||
} else {
|
||||
HttpReply httpReply(HttpReply::BadRequest);
|
||||
httpReply.setPayload("400 Bad Request.");
|
||||
httpReply.packReply();
|
||||
m_webserver->sendHttpReply(clientId, httpReply);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonHandler *handler = GuhCore::instance()->jsonRPCServer()->handlers().value(targetNamespace);
|
||||
|
||||
QPair<bool, QString> validationResult = handler->validateParams(method, params);
|
||||
if (!validationResult.first) {
|
||||
qCWarning(dcWebServer) << "Invalid params: " << validationResult.second;
|
||||
return;
|
||||
}
|
||||
|
||||
JsonReply *jsonReply;
|
||||
QMetaObject::invokeMethod(handler, method.toLatin1().data(), Q_RETURN_ARG(JsonReply*, jsonReply), Q_ARG(QVariantMap, params));
|
||||
if (jsonReply->type() == JsonReply::TypeAsync) {
|
||||
jsonReply->setClientId(clientId);
|
||||
connect(jsonReply, &JsonReply::finished, this, &RestServer::asyncReplyFinished);
|
||||
jsonReply->startWait();
|
||||
m_asyncReplies.insert(clientId, jsonReply);
|
||||
return;
|
||||
}
|
||||
|
||||
HttpReply httpReply(HttpReply::Ok);
|
||||
httpReply.setHeader(HttpReply::ContentTypeHeader, "application/json; charset=\"utf-8\";");
|
||||
httpReply.setPayload(QJsonDocument::fromVariant(jsonReply->data()).toJson());
|
||||
httpReply.packReply();
|
||||
|
||||
m_webserver->sendHttpReply(clientId, httpReply);
|
||||
|
||||
jsonReply->deleteLater();
|
||||
}
|
||||
|
||||
void RestServer::asyncReplyFinished()
|
||||
{
|
||||
JsonReply *jsonReply = qobject_cast<JsonReply*>(sender());
|
||||
QUuid clientId = m_asyncReplies.key(jsonReply);
|
||||
|
||||
if (!jsonReply->timedOut()) {
|
||||
HttpReply httpReply(HttpReply::Ok);
|
||||
httpReply.setHeader(HttpReply::ContentTypeHeader, "application/json; charset=\"utf-8\";");
|
||||
httpReply.setPayload(QJsonDocument::fromVariant(jsonReply->data()).toJson());
|
||||
httpReply.packReply();
|
||||
m_webserver->sendHttpReply(clientId, httpReply);
|
||||
} else {
|
||||
HttpReply httpReply(HttpReply::GatewayTimeout);
|
||||
httpReply.setHeader(HttpReply::ContentTypeHeader, "application/json; charset=\"utf-8\";");
|
||||
httpReply.setPayload(QJsonDocument::fromVariant(jsonReply->data()).toJson());
|
||||
httpReply.packReply();
|
||||
m_webserver->sendHttpReply(clientId, httpReply);
|
||||
}
|
||||
jsonReply->deleteLater();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
59
server/rest/restserver.h
Normal file
59
server/rest/restserver.h
Normal file
@ -0,0 +1,59 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* 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 RESTSERVER_H
|
||||
#define RESTSERVER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "webserver.h"
|
||||
#include "jsonhandler.h"
|
||||
|
||||
class HttpRequest;
|
||||
class HttpReply;
|
||||
|
||||
namespace guhserver {
|
||||
|
||||
class RestServer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit RestServer(QObject *parent = 0);
|
||||
|
||||
private:
|
||||
WebServer *m_webserver;
|
||||
QList<QUuid> m_clientList;
|
||||
QHash<QUuid, JsonReply *> m_asyncReplies;
|
||||
|
||||
signals:
|
||||
void httpReplyReady(const HttpReply &httpReply);
|
||||
|
||||
private slots:
|
||||
void clientConnected(const QUuid &clientId);
|
||||
void clientDisconnected(const QUuid &clientId);
|
||||
|
||||
void processHttpRequest(const QUuid &clientId, const HttpRequest &request);
|
||||
void asyncReplyFinished();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // RESTSERVER_H
|
||||
@ -1,3 +1,9 @@
|
||||
|
||||
contains(DEFINES, WEBSERVER){
|
||||
QT += websockets
|
||||
}
|
||||
|
||||
|
||||
SOURCES += $$top_srcdir/server/guhcore.cpp \
|
||||
$$top_srcdir/server/tcpserver.cpp \
|
||||
$$top_srcdir/server/ruleengine.cpp \
|
||||
@ -17,6 +23,9 @@ SOURCES += $$top_srcdir/server/guhcore.cpp \
|
||||
$$top_srcdir/server/logging/logentry.cpp \
|
||||
$$top_srcdir/server/webserver.cpp \
|
||||
$$top_srcdir/server/transportinterface.cpp \
|
||||
$$top_srcdir/server/servermanager.cpp \
|
||||
$$top_srcdir/server/websocketserver.cpp \
|
||||
$$top_srcdir/server/rest/restserver.cpp
|
||||
|
||||
|
||||
HEADERS += $$top_srcdir/server/guhcore.h \
|
||||
@ -33,11 +42,14 @@ HEADERS += $$top_srcdir/server/guhcore.h \
|
||||
$$top_srcdir/server/jsonrpc/statehandler.h \
|
||||
$$top_srcdir/server/jsonrpc/logginghandler.h \
|
||||
$$top_srcdir/server/stateevaluator.h \
|
||||
$$top_srcdir/server/jsontypes.h \
|
||||
$$top_srcdir/server/logging/logging.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 \
|
||||
$$top_srcdir/server/servermanager.h \
|
||||
$$top_srcdir/server/websocketserver.h \
|
||||
$$top_srcdir/server/rest/restserver.h \
|
||||
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ INCLUDEPATH += ../libguh jsonrpc
|
||||
target.path = /usr/bin
|
||||
INSTALLS += target
|
||||
|
||||
QT += network sql
|
||||
QT += sql
|
||||
|
||||
LIBS += -L$$top_builddir/libguh/ -lguh
|
||||
|
||||
@ -16,7 +16,7 @@ include(server.pri)
|
||||
include(qtservice/qtservice.pri)
|
||||
|
||||
SOURCES += main.cpp \
|
||||
guhservice.cpp
|
||||
guhservice.cpp \
|
||||
|
||||
boblight {
|
||||
xcompile {
|
||||
|
||||
45
server/servermanager.cpp
Normal file
45
server/servermanager.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* 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 "servermanager.h"
|
||||
|
||||
namespace guhserver {
|
||||
|
||||
ServerManager::ServerManager(QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
qCDebug(dcApplication) << "Starting JSON RPC Server";
|
||||
m_jsonServer = new JsonRPCServer(this);
|
||||
|
||||
qCDebug(dcApplication) << "Starting REST Webserver";
|
||||
m_restServer = new RestServer(this);
|
||||
}
|
||||
|
||||
JsonRPCServer *ServerManager::jsonServer() const
|
||||
{
|
||||
return m_jsonServer;
|
||||
}
|
||||
|
||||
RestServer *ServerManager::restServer() const
|
||||
{
|
||||
return m_restServer;
|
||||
}
|
||||
|
||||
}
|
||||
51
server/servermanager.h
Normal file
51
server/servermanager.h
Normal file
@ -0,0 +1,51 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* 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 SERVERMANAGER_H
|
||||
#define SERVERMANAGER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "loggingcategories.h"
|
||||
#include "jsonrpc/jsonrpcserver.h"
|
||||
#include "rest/restserver.h"
|
||||
|
||||
|
||||
|
||||
namespace guhserver {
|
||||
|
||||
class ServerManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ServerManager(QObject *parent = 0);
|
||||
|
||||
JsonRPCServer *jsonServer() const;
|
||||
RestServer *restServer() const;
|
||||
|
||||
private:
|
||||
JsonRPCServer *m_jsonServer;
|
||||
RestServer *m_restServer;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // SERVERMANAGER_H
|
||||
@ -167,7 +167,7 @@ void TcpServer::sendData(const QUuid &clientId, const QVariantMap &data)
|
||||
}
|
||||
}
|
||||
|
||||
void TcpServer::newClientConnected()
|
||||
void TcpServer::onClientConnected()
|
||||
{
|
||||
// got a new client connected
|
||||
QTcpServer *server = qobject_cast<QTcpServer*>(sender());
|
||||
@ -185,7 +185,6 @@ void TcpServer::newClientConnected()
|
||||
emit clientConnected(clientId);
|
||||
}
|
||||
|
||||
|
||||
void TcpServer::readPackage()
|
||||
{
|
||||
QTcpSocket *client = qobject_cast<QTcpSocket*>(sender());
|
||||
@ -278,7 +277,7 @@ void TcpServer::onTimeout()
|
||||
QTcpServer *server = new QTcpServer(this);
|
||||
if(server->listen(address, m_port)) {
|
||||
qCDebug(dcConnection) << "Started TCP server on" << address.toString() << m_port;
|
||||
connect(server, SIGNAL(newConnection()), SLOT(newClientConnected()));
|
||||
connect(server, &QTcpServer::newConnection, this, &TcpServer::onClientConnected);
|
||||
m_serverList.insert(QUuid::createUuid(), server);
|
||||
} else {
|
||||
qCWarning(dcConnection) << "Tcp server error: can not listen on" << address.toString() << m_port;
|
||||
@ -292,7 +291,6 @@ void TcpServer::onTimeout()
|
||||
qCDebug(dcConnection) << " - Tcp server on" << s->serverAddress().toString() << s->serverPort();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool TcpServer::startServer()
|
||||
@ -321,7 +319,7 @@ bool TcpServer::startServer()
|
||||
QTcpServer *server = new QTcpServer(this);
|
||||
if(server->listen(address, m_port)) {
|
||||
qCDebug(dcConnection) << "Started Tcp server on" << server->serverAddress().toString() << server->serverPort();
|
||||
connect(server, SIGNAL(newConnection()), SLOT(newClientConnected()));
|
||||
connect(server, SIGNAL(newConnection()), SLOT(onClientConnected()));
|
||||
m_serverList.insert(QUuid::createUuid(), server);
|
||||
} else {
|
||||
qCWarning(dcConnection) << "Tcp server error: can not listen on" << interface.name() << address.toString() << m_port;
|
||||
|
||||
@ -60,9 +60,9 @@ public:
|
||||
void sendErrorResponse(const QUuid &clientId, int commandId, const QString &error);
|
||||
|
||||
private slots:
|
||||
void newClientConnected();
|
||||
void readPackage();
|
||||
void onClientConnected();
|
||||
void onClientDisconnected();
|
||||
void readPackage();
|
||||
void onError(const QAbstractSocket::SocketError &error);
|
||||
void onTimeout();
|
||||
|
||||
|
||||
@ -27,4 +27,8 @@ TransportInterface::TransportInterface(QObject *parent) :
|
||||
{
|
||||
}
|
||||
|
||||
TransportInterface::~TransportInterface()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -22,6 +22,9 @@
|
||||
#define TRANSPORTINTERFACE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
#include <QUuid>
|
||||
|
||||
namespace guhserver {
|
||||
|
||||
@ -30,7 +33,8 @@ class TransportInterface : public QObject
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TransportInterface(QObject *parent = 0);
|
||||
virtual ~TransportInterface() = default;
|
||||
virtual ~TransportInterface();
|
||||
|
||||
virtual void sendData(const QUuid &clientId, const QVariantMap &data) = 0;
|
||||
virtual void sendData(const QList<QUuid> &clients, const QVariantMap &data) = 0;
|
||||
|
||||
|
||||
@ -22,7 +22,9 @@
|
||||
#include "loggingcategories.h"
|
||||
#include "guhsettings.h"
|
||||
#include "network/httpreply.h"
|
||||
#include "network/httprequest.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
#include <QUrlQuery>
|
||||
@ -36,21 +38,22 @@ WebServer::WebServer(QObject *parent) :
|
||||
TransportInterface(parent),
|
||||
m_enabled(false)
|
||||
{
|
||||
m_server = new QTcpServer(this);
|
||||
|
||||
// load webserver settings
|
||||
GuhSettings settings(GuhSettings::SettingsRoleGlobal);
|
||||
qCDebug(dcTcpServer) << "Loading Webserver settings from:" << settings.fileName();
|
||||
|
||||
settings.beginGroup("Webserver");
|
||||
m_port = settings.value("port", 3000).toUInt();
|
||||
// load the path to the webinterface public folder (qdir to make shore there is no "/" at the end)
|
||||
m_port = settings.value("port", 3000).toInt();
|
||||
m_webinterfaceDir = QDir(settings.value("publicFolder", "/usr/share/guh-webinterface/public/").toString());
|
||||
settings.endGroup();
|
||||
|
||||
qCDebug(dcTcpServer) << "Using port" << m_port;
|
||||
qCDebug(dcTcpServer) << "Publish webinterface from" << m_webinterfaceDir.path();
|
||||
|
||||
if (!m_webinterfaceDir.exists())
|
||||
qCWarning(dcWebServer) << "Web interface path" << m_webinterfaceDir.path() << "does not exist.";
|
||||
|
||||
// create webserver
|
||||
m_server = new QTcpServer(this);
|
||||
connect(m_server, &QTcpServer::newConnection, this, &WebServer::onNewConnection);
|
||||
}
|
||||
|
||||
@ -61,19 +64,30 @@ WebServer::~WebServer()
|
||||
|
||||
void WebServer::sendData(const QUuid &clientId, const QVariantMap &data)
|
||||
{
|
||||
Q_UNUSED(clientId)
|
||||
Q_UNUSED(data)
|
||||
|
||||
// TODO: reply.setHeader(HttpReply::ContentTypeHeader, "application/json; charset=\"utf-8\";");
|
||||
|
||||
QTcpSocket *socket = m_clientList.value(clientId);
|
||||
HttpReply reply(HttpReply::Ok);
|
||||
reply.setHeader(HttpReply::ContentTypeHeader, "application/json; charset=\"utf-8\";");
|
||||
reply.setPayload(QJsonDocument::fromVariant(data).toJson());
|
||||
reply.packReply();
|
||||
writeData(socket, reply.data());
|
||||
}
|
||||
|
||||
void WebServer::sendData(const QList<QUuid> &clients, const QVariantMap &data)
|
||||
{
|
||||
Q_UNUSED(clients)
|
||||
Q_UNUSED(data)
|
||||
foreach (const QUuid &client, clients) {
|
||||
QTcpSocket *socket = m_clientList.value(client);
|
||||
HttpReply reply(HttpReply::Ok);
|
||||
reply.setHeader(HttpReply::ContentTypeHeader, "application/json; charset=\"utf-8\";");
|
||||
reply.setPayload(QJsonDocument::fromVariant(data).toJson());
|
||||
reply.packReply();
|
||||
writeData(socket, reply.data());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: reply.setHeader(HttpReply::ContentTypeHeader, "application/json; charset=\"utf-8\";");
|
||||
void WebServer::sendHttpReply(const QUuid &clientId, const HttpReply &reply)
|
||||
{
|
||||
QTcpSocket *socket = m_clientList.value(clientId);
|
||||
writeData(socket, reply.data());
|
||||
}
|
||||
|
||||
bool WebServer::verifyFile(QTcpSocket *socket, const QString &fileName)
|
||||
@ -85,7 +99,8 @@ bool WebServer::verifyFile(QTcpSocket *socket, const QString &fileName)
|
||||
qCWarning(dcWebServer) << "requested file" << checkFile.fileName() << "does not exist.";
|
||||
HttpReply reply(HttpReply::NotFound);
|
||||
reply.setPayload("404 Not found.");
|
||||
writeData(socket, reply.packReply());
|
||||
reply.packReply();
|
||||
writeData(socket, reply.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -94,7 +109,8 @@ bool WebServer::verifyFile(QTcpSocket *socket, const QString &fileName)
|
||||
qCWarning(dcWebServer) << "requested file" << checkFile.fileName() << "is outside the public folder.";
|
||||
HttpReply reply(HttpReply::Forbidden);
|
||||
reply.setPayload("403 Forbidden.");
|
||||
writeData(socket, reply.packReply());
|
||||
reply.packReply();
|
||||
writeData(socket, reply.data());
|
||||
socket->close();
|
||||
return false;
|
||||
}
|
||||
@ -104,7 +120,8 @@ bool WebServer::verifyFile(QTcpSocket *socket, const QString &fileName)
|
||||
qCWarning(dcWebServer) << "requested file" << checkFile.fileName() << "is not readable.";
|
||||
HttpReply reply(HttpReply::Forbidden);
|
||||
reply.setPayload("403 Forbidden. Page not readable.");
|
||||
writeData(socket, reply.packReply());
|
||||
reply.packReply();
|
||||
writeData(socket, reply.data());
|
||||
socket->close();
|
||||
return false;
|
||||
}
|
||||
@ -157,10 +174,8 @@ void WebServer::onNewConnection()
|
||||
QUuid clientId = QUuid::createUuid();
|
||||
m_clientList.insert(clientId, socket);
|
||||
|
||||
// TODO: maby check already at this point if this is a ws connection or not
|
||||
|
||||
connect(socket, &QTcpSocket::readyRead, this, &WebServer::readClient);
|
||||
connect(socket, &QTcpSocket::disconnected, this, &WebServer::discardClient);
|
||||
connect(socket, &QTcpSocket::disconnected, this, &WebServer::onDisconnected);
|
||||
qCDebug(dcConnection) << "Webserver client connected" << socket->peerName() << socket->peerAddress().toString() << socket->peerPort();
|
||||
|
||||
emit clientConnected(clientId);
|
||||
@ -171,75 +186,63 @@ void WebServer::readClient()
|
||||
if (!m_enabled)
|
||||
return;
|
||||
|
||||
QTcpSocket* socket = static_cast<QTcpSocket *>(sender());
|
||||
QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
|
||||
QUuid clientId = m_clientList.key(socket);
|
||||
|
||||
// read data
|
||||
QByteArray data = socket->readAll();
|
||||
// check client
|
||||
if (clientId.isNull()) {
|
||||
qCWarning(dcWebServer) << "Client not recognized";
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList lines = QString(data).split("\r\n");
|
||||
QStringList tokens = QString(data).split(QRegExp("[ \r\n][ \r\n]*"));
|
||||
// read http request
|
||||
HttpRequest request = HttpRequest(socket->readAll());
|
||||
if (!request.isValid()) {
|
||||
qCWarning(dcWebServer) << "Invalid request.";
|
||||
HttpReply reply(HttpReply::BadRequest);
|
||||
reply.setPayload("400 Bad Request.");
|
||||
reply.packReply();
|
||||
writeData(socket, reply.data());
|
||||
return;
|
||||
}
|
||||
|
||||
// verify HTTP version
|
||||
if (!lines.first().contains("HTTP/1.1")) {
|
||||
if (request.httpVersion() != "HTTP/1.1") {
|
||||
qCWarning(dcWebServer) << "HTTP version is not supported." ;
|
||||
HttpReply reply(HttpReply::HttpVersionNotSupported);
|
||||
reply.setPayload("505 HTTP version is not supported.");
|
||||
writeData(socket, reply.packReply());
|
||||
reply.packReply();
|
||||
writeData(socket, reply.data());
|
||||
return;
|
||||
}
|
||||
|
||||
if (tokens.isEmpty() || tokens.count() < 2)
|
||||
return;
|
||||
|
||||
QString methodString = tokens.at(0);
|
||||
QString queryString = tokens.at(1);
|
||||
|
||||
qCDebug(dcWebServer) << QString("Got request from %1:%2").arg(socket->peerAddress().toString()).arg(socket->peerPort());
|
||||
qCDebug(dcWebServer) << "Request method:" << methodString;
|
||||
qCDebug(dcWebServer) << "Request query :" << queryString;
|
||||
qCDebug(dcWebServer) << QString("Got valid request from %1:%2").arg(socket->peerAddress().toString()).arg(socket->peerPort());
|
||||
qCDebug(dcWebServer) << request;
|
||||
|
||||
// verify method
|
||||
RequestMethod requestMethod = getRequestMethodType(methodString);
|
||||
RequestMethod requestMethod = getRequestMethodType(request.method());
|
||||
if (requestMethod == RequestMethod::Unhandled) {
|
||||
qCWarning(dcWebServer) << "method" << methodString << "not allowed";
|
||||
qCWarning(dcWebServer) << "method" << request.method() << "not allowed";
|
||||
HttpReply reply(HttpReply::MethodNotAllowed);
|
||||
reply.setHeader(HttpReply::AllowHeader, "GET, PUT, POST, DELETE");
|
||||
reply.setPayload("405 Method not allowed.");
|
||||
writeData(socket, reply.packReply());
|
||||
reply.packReply();
|
||||
writeData(socket, reply.data());
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: authentification check
|
||||
|
||||
// TODO: parse payload and header
|
||||
|
||||
// TODO: verify header to make shore this is a valid HTTP request
|
||||
|
||||
if (queryString.startsWith("/api/v1")) {
|
||||
// TODO: check if this is an API call
|
||||
qCDebug(dcWebServer) << "got api call";
|
||||
HttpReply reply(HttpReply::Ok);
|
||||
reply.setPayload("Got api call. This is not implemented yet...");
|
||||
writeData(socket, reply.packReply());
|
||||
// verify query
|
||||
if (request.urlQuery().query().startsWith("/api/v1")) {
|
||||
emit httpRequestReady(clientId, request);
|
||||
return;
|
||||
}
|
||||
|
||||
if (queryString.startsWith("/ws")) {
|
||||
qCDebug(dcWebServer) << "got websocket request";
|
||||
HttpReply reply(HttpReply::Ok);
|
||||
reply.setPayload("Got api call. This is not implemented yet...");
|
||||
writeData(socket, reply.packReply());
|
||||
|
||||
// TODO: move the ws client to a separat websocket client list and redirect
|
||||
// the notification stream to thouse clients
|
||||
}
|
||||
|
||||
// request for a file...
|
||||
if (requestMethod == RequestMethod::Get) {
|
||||
if (!verifyFile(socket, fileName(queryString)))
|
||||
if (!verifyFile(socket, fileName(request.urlQuery().query())))
|
||||
return;
|
||||
|
||||
QFile file(fileName(queryString));
|
||||
QFile file(fileName(request.urlQuery().query()));
|
||||
if (file.open(QFile::ReadOnly | QFile::Truncate)) {
|
||||
qCDebug(dcWebServer) << "load file" << file.fileName();
|
||||
HttpReply reply(HttpReply::Ok);
|
||||
@ -247,26 +250,29 @@ void WebServer::readClient()
|
||||
reply.setHeader(HttpReply::ContentTypeHeader, "text/html; charset=\"utf-8\";");
|
||||
}
|
||||
reply.setPayload(file.readAll());
|
||||
writeData(socket, reply.packReply());
|
||||
reply.packReply();
|
||||
writeData(socket, reply.data());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
qCWarning(dcWebServer) << "Not recognized request.";
|
||||
// reject everything else...
|
||||
qCWarning(dcWebServer) << "Unknown message received. Respond client with 501: Not Implemented.";
|
||||
HttpReply reply(HttpReply::NotImplemented);
|
||||
reply.setPayload("501 Not Implemented.");
|
||||
writeData(socket, reply.packReply());
|
||||
return;
|
||||
reply.setPayload("501 Not implemented.");
|
||||
reply.packReply();
|
||||
writeData(socket, reply.data());
|
||||
}
|
||||
|
||||
void WebServer::discardClient()
|
||||
void WebServer::onDisconnected()
|
||||
{
|
||||
QTcpSocket* socket = static_cast<QTcpSocket *>(sender());
|
||||
QTcpSocket* socket = qobject_cast<QTcpSocket *>(sender());
|
||||
qCDebug(dcConnection) << "Webserver client disonnected.";
|
||||
|
||||
// clean up
|
||||
QUuid clientId = m_clientList.key(socket);
|
||||
m_clientList.take(clientId)->deleteLater();
|
||||
m_clientList.remove(clientId);
|
||||
socket->deleteLater();
|
||||
|
||||
emit clientDisconnected(clientId);
|
||||
}
|
||||
@ -278,8 +284,9 @@ bool WebServer::startServer()
|
||||
m_enabled = false;
|
||||
return false;
|
||||
}
|
||||
m_enabled = true;
|
||||
qCDebug(dcConnection) << "Started webserver on" << QString("http://%1:%2").arg(m_server->serverAddress().toString()).arg(m_port);
|
||||
m_enabled = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -24,18 +24,22 @@
|
||||
#include <QObject>
|
||||
#include <QHash>
|
||||
#include <QDir>
|
||||
#include <QUuid>
|
||||
|
||||
|
||||
#include "transportinterface.h"
|
||||
|
||||
class QTcpServer;
|
||||
class QTcpSocket;
|
||||
class QUuid;
|
||||
class HttpRequest;
|
||||
class HttpReply;
|
||||
|
||||
// Note: Status codes according to HTTP 1.1: https://tools.ietf.org/html/rfc7231
|
||||
// Note: Hypertext Transfer Protocol (HTTP/1.1) from the Internet Engineering Task Force (IETF):
|
||||
// https://tools.ietf.org/html/rfc7231
|
||||
|
||||
namespace guhserver {
|
||||
|
||||
class WebServer : public TransportInterface
|
||||
class WebServer : public TransportInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
@ -52,25 +56,31 @@ public:
|
||||
|
||||
void sendData(const QUuid &clientId, const QVariantMap &data) override;
|
||||
void sendData(const QList<QUuid> &clients, const QVariantMap &data) override;
|
||||
void sendHttpReply(const QUuid &clientId, const HttpReply &reply);
|
||||
|
||||
private:
|
||||
QTcpServer *m_server;
|
||||
|
||||
QHash<QUuid, QTcpSocket *> m_clientList;
|
||||
|
||||
bool m_enabled;
|
||||
qint16 m_port;
|
||||
QDir m_webinterfaceDir;
|
||||
|
||||
bool verifyFile(QTcpSocket *socket, const QString &fileName);
|
||||
|
||||
QString fileName(const QString &query);
|
||||
RequestMethod getRequestMethodType(const QString &methodString);
|
||||
|
||||
void writeData(QTcpSocket *socket, const QByteArray &data);
|
||||
|
||||
signals:
|
||||
void httpRequestReady(const QUuid &clientId, const HttpRequest &httpRequest);
|
||||
|
||||
private slots:
|
||||
void onNewConnection();
|
||||
void readClient();
|
||||
void discardClient();
|
||||
void onDisconnected();
|
||||
|
||||
public slots:
|
||||
bool startServer() override;
|
||||
|
||||
30
server/websocketserver.cpp
Normal file
30
server/websocketserver.cpp
Normal 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 "websocketserver.h"
|
||||
|
||||
namespace guhserver {
|
||||
|
||||
WebSocketServer::WebSocketServer(QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
45
server/websocketserver.h
Normal file
45
server/websocketserver.h
Normal file
@ -0,0 +1,45 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* 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 WEBSOCKETSERVER_H
|
||||
#define WEBSOCKETSERVER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
// Note: WebSocket Protocol from the Internet Engineering Task Force (IETF) -> RFC6455 V13:
|
||||
// http://tools.ietf.org/html/rfc6455
|
||||
|
||||
namespace guhserver {
|
||||
|
||||
class WebSocketServer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WebSocketServer(QObject *parent = 0);
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // WEBSOCKETSERVER_H
|
||||
@ -69,15 +69,13 @@ void TestWebserver::httpVersion()
|
||||
|
||||
QSignalSpy clientSpy(socket, SIGNAL(readyRead()));
|
||||
|
||||
socket->write("Confusing, non HTTP protocol stuff which should not be accepted.");
|
||||
socket->write("GET /hello/guh HTTP/1.0\r\n\r\n");
|
||||
bool filesWritten = socket->waitForBytesWritten(500);
|
||||
QVERIFY2(filesWritten, "could not write to webserver.");
|
||||
|
||||
clientSpy.wait(500);
|
||||
QVERIFY2(clientSpy.count() == 1, "expected exactly 1 response from webserver");
|
||||
|
||||
QByteArray data = socket->readAll();
|
||||
|
||||
QVERIFY2(!data.isEmpty(), "got no response");
|
||||
|
||||
QStringList lines = QString(data).split("\r\n");
|
||||
|
||||
Reference in New Issue
Block a user