added deviceresources and first tests

pull/135/head
Simon Stürz 2015-07-26 02:12:10 +02:00 committed by Michael Zanetti
parent 5a3c7a6cfb
commit b86a062a87
21 changed files with 517 additions and 140 deletions

View File

@ -28,5 +28,6 @@ Q_LOGGING_CATEGORY(dcConnection, "Connection")
Q_LOGGING_CATEGORY(dcTcpServer, "TcpServer")
Q_LOGGING_CATEGORY(dcWebServer, "WebServer")
Q_LOGGING_CATEGORY(dcJsonRpc, "JsonRpc")
Q_LOGGING_CATEGORY(dcRest, "Rest")
Q_LOGGING_CATEGORY(dcLogEngine, "LogEngine")

View File

@ -30,6 +30,7 @@ Q_DECLARE_LOGGING_CATEGORY(dcRuleEngine)
Q_DECLARE_LOGGING_CATEGORY(dcHardware)
Q_DECLARE_LOGGING_CATEGORY(dcConnection)
Q_DECLARE_LOGGING_CATEGORY(dcJsonRpc)
Q_DECLARE_LOGGING_CATEGORY(dcRest)
Q_DECLARE_LOGGING_CATEGORY(dcLogEngine)
Q_DECLARE_LOGGING_CATEGORY(dcTcpServer)
Q_DECLARE_LOGGING_CATEGORY(dcWebServer)

View File

@ -109,9 +109,9 @@ HttpReply::HttpReply(const HttpStatusCode &statusCode) :
{
// set known headers
setHeader(HttpHeaderType::ServerHeader, "guh/" + QByteArray(GUH_VERSION_STRING));
setHeader(HttpHeaderType::UserAgentHeader, "guh/" + QByteArray(REST_API_VERSION));
setHeader(HttpHeaderType::DateHeader, QDateTime::currentDateTime().toString("ddd, dd MMM yyyy hh:mm:ss").toUtf8() + " GMT");
setHeader(HttpHeaderType::CacheControlHeader, "no-cache");
packReply();
}
/*! Set the \a statusCode for this \l{HttpReply}.*/

View File

@ -62,7 +62,7 @@ public:
ServerHeader
};
explicit HttpReply(const HttpStatusCode &statusCode);
explicit HttpReply(const HttpStatusCode &statusCode = HttpStatusCode::Ok);
void setHttpStatusCode(const HttpStatusCode &statusCode);
HttpStatusCode httpStatusCode() const;

View File

@ -50,15 +50,17 @@ HttpRequest::HttpRequest(QByteArray rawData) :
return;
}
m_method = statusLineTokens.at(0).toUtf8().simplified();
m_urlQuery = QUrlQuery(statusLineTokens.at(1).simplified());
// verify http version
m_httpVersion = statusLineTokens.at(2).toUtf8().simplified();
if (!m_httpVersion.contains("HTTP")) {
qCWarning(dcWebServer) << "Unknown HTTP version:" << m_httpVersion;
return;
}
m_method = getRequestMethodType(statusLineTokens.at(0).simplified());
m_urlQuery = QUrlQuery(statusLineTokens.at(1).simplified());
// verify headers
foreach (const QString &line, headerLines) {
if (!line.contains(":")) {
qCWarning(dcWebServer) << "Invalid HTTP header:" << line;
@ -70,6 +72,8 @@ HttpRequest::HttpRequest(QByteArray rawData) :
m_rawHeaderList.insert(key, value);
}
// TODO: check content size
m_valid = true;
}
@ -83,7 +87,7 @@ QHash<QByteArray, QByteArray> HttpRequest::rawHeaderList() const
return m_rawHeaderList;
}
QByteArray HttpRequest::method() const
HttpRequest::RequestMethod HttpRequest::method() const
{
return m_method;
}
@ -113,6 +117,21 @@ bool HttpRequest::hasPayload() const
return !m_payload.isEmpty();
}
HttpRequest::RequestMethod HttpRequest::getRequestMethodType(const QString &methodString)
{
if (methodString == "GET") {
return RequestMethod::Get;
} else if (methodString == "POST") {
return RequestMethod::Post;
} else if (methodString == "PUT") {
return RequestMethod::Put;
} else if (methodString == "DELETE") {
return RequestMethod::Delete;
}
qCWarning(dcWebServer) << "Method" << methodString << "will not be handled.";
return RequestMethod::Unhandled;
}
QDebug operator<<(QDebug debug, const HttpRequest &httpRequest)
{
debug << "===================================" << "\n";

View File

@ -29,12 +29,20 @@
class HttpRequest
{
public:
enum RequestMethod {
Get,
Post,
Put,
Delete,
Unhandled
};
explicit HttpRequest(QByteArray rawData);
QByteArray rawHeader() const;
QHash<QByteArray, QByteArray> rawHeaderList() const;
QByteArray method() const;
RequestMethod method() const;
QByteArray httpVersion() const;
QUrlQuery urlQuery() const;
@ -48,13 +56,15 @@ private:
QByteArray m_rawHeader;
QHash<QByteArray, QByteArray> m_rawHeaderList;
QByteArray m_method;
RequestMethod m_method;
QByteArray m_httpVersion;
QUrlQuery m_urlQuery;
QByteArray m_payload;
bool m_valid;
RequestMethod getRequestMethodType(const QString &methodString);
};
QDebug operator<< (QDebug debug, const HttpRequest &httpRequest);

View File

@ -262,29 +262,16 @@ QString DeviceHandler::name() const
JsonReply* DeviceHandler::GetSupportedVendors(const QVariantMap &params) const
{
Q_UNUSED(params)
QVariantMap returns;
QVariantList supportedVendors;
foreach (const Vendor &vendor, GuhCore::instance()->supportedVendors()) {
supportedVendors.append(JsonTypes::packVendor(vendor));
}
returns.insert("vendors", supportedVendors);
returns.insert("vendors", JsonTypes::packSupportedVendors());
return createReply(returns);
}
JsonReply* DeviceHandler::GetSupportedDevices(const QVariantMap &params) const
{
QVariantMap returns;
QVariantList supportedDeviceList;
QList<DeviceClass> supportedDevices;
if (params.contains("vendorId")) {
supportedDevices = GuhCore::instance()->supportedDevices(VendorId(params.value("vendorId").toString()));
} else {
supportedDevices = GuhCore::instance()->supportedDevices();
}
foreach (const DeviceClass &deviceClass, supportedDevices) {
supportedDeviceList.append(JsonTypes::packDeviceClass(deviceClass));
}
returns.insert("deviceClasses", supportedDeviceList);
returns.insert("deviceClasses", JsonTypes::packSupportedDevices(VendorId(params.value("vendorId").toString())));
return createReply(returns);
}
@ -309,20 +296,9 @@ JsonReply *DeviceHandler::GetDiscoveredDevices(const QVariantMap &params) const
JsonReply* DeviceHandler::GetPlugins(const QVariantMap &params) const
{
Q_UNUSED(params)
QVariantMap returns;
QVariantList plugins;
foreach (DevicePlugin *plugin, GuhCore::instance()->plugins()) {
QVariantMap pluginMap;
pluginMap.insert("id", plugin->pluginId());
pluginMap.insert("name", plugin->pluginName());
QVariantList params;
foreach (const ParamType &param, plugin->configurationDescription()) {
params.append(JsonTypes::packParamType(param));
}
pluginMap.insert("params", params);
plugins.append(pluginMap);
}
returns.insert("plugins", plugins);
returns.insert("plugins", JsonTypes::packPlugins());
return createReply(returns);
}
@ -531,14 +507,14 @@ JsonReply* DeviceHandler::GetStateValue(const QVariantMap &params) const
returns.insert("deviceError", JsonTypes::deviceErrorToString(DeviceManager::DeviceErrorDeviceNotFound));
return createReply(returns);
}
if (!device->hasState(StateTypeId(params.value("stateTypeId").toString()))) {
StateTypeId stateTypeId = StateTypeId(params.value("stateTypeId").toString());
if (!device->hasState(stateTypeId)) {
returns.insert("deviceError", JsonTypes::deviceErrorToString(DeviceManager::DeviceErrorStateTypeNotFound));
return createReply(returns);
}
QVariant stateValue = device->stateValue(StateTypeId(params.value("stateTypeId").toString()));
returns.insert("deviceError", JsonTypes::deviceErrorToString(DeviceManager::DeviceErrorNoError));
returns.insert("value", stateValue);
returns.insert("value", device->state(stateTypeId).value());
return createReply(returns);
}
@ -552,20 +528,8 @@ JsonReply *DeviceHandler::GetStateValues(const QVariantMap &params) const
return createReply(returns);
}
DeviceClass deviceClass = GuhCore::instance()->findDeviceClass(device->deviceClassId());
if (!deviceClass.isValid()) {
returns.insert("deviceError", JsonTypes::deviceErrorToString(DeviceManager::DeviceErrorDeviceClassNotFound));
return createReply(returns);
}
QVariantList values;
foreach (const StateType &stateType, deviceClass.stateTypes()) {
QVariantMap stateValue;
stateValue.insert("stateTypeId", stateType.id().toString());
stateValue.insert("value", device->stateValue(stateType.id()));
values.append(stateValue);
}
returns.insert("deviceError", JsonTypes::deviceErrorToString(DeviceManager::DeviceErrorNoError));
returns.insert("values", values);
returns.insert("values", JsonTypes::packDeviceStates(device));
return createReply(returns);
}

View File

@ -23,6 +23,7 @@
#include "plugin/device.h"
#include "devicemanager.h"
#include "guhcore.h"
#include "ruleengine.h"
#include "loggingcategories.h"
@ -387,6 +388,14 @@ QVariantMap JsonTypes::packRuleActionParam(const RuleActionParam &ruleActionPara
return variantMap;
}
QVariantMap JsonTypes::packState(const State &state)
{
QVariantMap stateMap;
stateMap.insert("stateTypeId", state.stateTypeId().toString());
stateMap.insert("value", state.value());
return stateMap;
}
QVariantMap JsonTypes::packStateType(const StateType &stateType)
{
QVariantMap variantMap;
@ -526,9 +535,17 @@ QVariantMap JsonTypes::packDeviceClass(const DeviceClass &deviceClass)
QVariantMap JsonTypes::packPlugin(DevicePlugin *plugin)
{
Q_UNUSED(plugin)
qCWarning(dcDeviceManager) << "packPlugin not implemented yet!";
return QVariantMap();
QVariantMap pluginMap;
pluginMap.insert("id", plugin->pluginId());
pluginMap.insert("name", plugin->pluginName());
QVariantList params;
foreach (const ParamType &param, plugin->configurationDescription()) {
params.append(packParamType(param));
}
pluginMap.insert("params", params);
return pluginMap;
}
QVariantMap JsonTypes::packDevice(Device *device)
@ -656,6 +673,56 @@ QVariantList JsonTypes::packCreateMethods(DeviceClass::CreateMethods createMetho
return ret;
}
QVariantList JsonTypes::packSupportedVendors()
{
QVariantList supportedVendors;
foreach (const Vendor &vendor, GuhCore::instance()->supportedVendors()) {
supportedVendors.append(packVendor(vendor));
}
return supportedVendors;
}
QVariantList JsonTypes::packSupportedDevices(const VendorId &vendorId)
{
QVariantList supportedDeviceList;
foreach (const DeviceClass &deviceClass, GuhCore::instance()->supportedDevices(vendorId)) {
supportedDeviceList.append(packDeviceClass(deviceClass));
}
return supportedDeviceList;
}
QVariantList JsonTypes::packConfiguredDevices()
{
QVariantList configuredDeviceList;
foreach (Device *device, GuhCore::instance()->configuredDevices()) {
configuredDeviceList.append(packDevice(device));
}
return configuredDeviceList;
}
QVariantList JsonTypes::packDeviceStates(Device *device)
{
DeviceClass deviceClass = GuhCore::instance()->findDeviceClass(device->deviceClassId());
QVariantList stateValues;
foreach (const StateType &stateType, deviceClass.stateTypes()) {
QVariantMap stateValue;
stateValue.insert("stateTypeId", stateType.id().toString());
stateValue.insert("value", device->stateValue(stateType.id()));
stateValues.append(stateValue);
}
return stateValues;
}
QVariantList JsonTypes::packPlugins()
{
QVariantList pluginsList;
foreach (DevicePlugin *plugin, GuhCore::instance()->plugins()) {
QVariantMap pluginMap = packPlugin(plugin);
pluginsList.append(pluginMap);
}
return pluginsList;
}
Param JsonTypes::unpackParam(const QVariantMap &paramMap)
{
if (paramMap.keys().count() == 0) {

View File

@ -135,6 +135,7 @@ public:
DECLARE_OBJECT(ruleDescription, "RuleDescription")
DECLARE_OBJECT(logEntry, "LogEntry")
// pack types
static QVariantMap packEventType(const EventType &eventType);
static QVariantMap packEvent(const Event &event);
static QVariantMap packEventDescriptor(const EventDescriptor &event);
@ -142,6 +143,7 @@ public:
static QVariantMap packAction(const Action &action);
static QVariantMap packRuleAction(const RuleAction &ruleAction);
static QVariantMap packRuleActionParam(const RuleActionParam &ruleActionParam);
static QVariantMap packState(const State &state);
static QVariantMap packStateType(const StateType &stateType);
static QVariantMap packStateDescriptor(const StateDescriptor &stateDescriptor);
static QVariantMap packStateEvaluator(const StateEvaluator &stateEvaluator);
@ -158,6 +160,16 @@ public:
static QVariantMap packLogEntry(const LogEntry &logEntry);
static QVariantList packCreateMethods(DeviceClass::CreateMethods createMethods);
// pack resources
static QVariantList packSupportedVendors();
static QVariantList packSupportedDevices(const VendorId &vendorId);
static QVariantList packConfiguredDevices();
static QVariantList packDeviceStates(Device *device);
static QVariantList packPlugins();
// unpack Types
static Param unpackParam(const QVariantMap &paramMap);
static ParamList unpackParams(const QVariantList &paramList);
static RuleActionParam unpackRuleActionParam(const QVariantMap &ruleActionParamMap);
@ -169,6 +181,7 @@ public:
static StateDescriptor unpackStateDescriptor(const QVariantMap &stateDescriptorMap);
static LogFilter unpackLogFilter(const QVariantMap &logFilterMap);
// validate
static QPair<bool, QString> validateMap(const QVariantMap &templateMap, const QVariantMap &map);
static QPair<bool, QString> validateProperty(const QVariant &templateValue, const QVariant &value);
static QPair<bool, QString> validateList(const QVariantList &templateList, const QVariantList &list);

View File

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

View File

@ -0,0 +1,153 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* 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 "devicesresource.h"
#include "jsontypes.h"
#include "guhcore.h"
#include "network/httpreply.h"
#include "network/httprequest.h"
#include <QJsonDocument>
namespace guhserver {
DevicesResource::DevicesResource(QObject *parent) :
QObject(parent)
{
}
HttpReply DevicesResource::proccessDeviceRequest(const HttpRequest &request, const QStringList &urlTokens)
{
DeviceId deviceId;
StateTypeId stateTypeId;
Device *device = 0;
// first parse device, stateTypeId
if (urlTokens.count() >= 4) {
deviceId = DeviceId(urlTokens.at(3));
if (deviceId.isNull()) {
qCWarning(dcRest) << "Could not parse DeviceId:" << urlTokens.at(3);
return HttpReply(HttpReply::BadRequest);
}
device = GuhCore::instance()->findConfiguredDevice(deviceId);
if (!device) {
qCWarning(dcRest) << "Could find any device with DeviceId:" << urlTokens.at(3);
return HttpReply(HttpReply::NotFound);
}
// /api/v1/devices/{deviceId}/states/{stateTypeId}
if (urlTokens.count() >= 6 && urlTokens.at(4) == "states") {
stateTypeId = StateTypeId(urlTokens.at(5));
if (stateTypeId.isNull()) {
qCWarning(dcRest) << "Could not parse StateTypeId:" << urlTokens.at(5);
return HttpReply(HttpReply::BadRequest);
}
if (!device->hasState(stateTypeId)){
qCWarning(dcRest) << "This device has no StateTypeId:" << urlTokens.at(5);
return HttpReply(HttpReply::NotFound);
}
}
}
// check methods
if (request.method() == HttpRequest::Get) {
// /api/v1/devices
if (urlTokens.count() == 3)
return getConfiguredDevices();
// /api/v1/devices/{deviceId}
if (urlTokens.count() == 4)
return getConfiguredDevice(device);
// /api/v1/devices/{deviceId}/states
if (urlTokens.count() == 5 && urlTokens.at(4) == "states")
return getDeviceStateValues(device);
// /api/v1/devices/{deviceId}/states/{stateTypeId}
if (urlTokens.count() == 6 && urlTokens.at(4) == "states")
return getDeviceStateValue(device, stateTypeId);
} else if (request.method() == HttpRequest::Delete) {
// /api/v1/devices
if (urlTokens.count() == 3)
return HttpReply(HttpReply::BadRequest);
if (urlTokens.count() == 4)
return removeDevice(device);
}
return HttpReply(HttpReply::BadRequest);
}
HttpReply DevicesResource::getConfiguredDevices()
{
HttpReply httpReply(HttpReply::Ok);
httpReply.setHeader(HttpReply::ContentTypeHeader, "application/json; charset=\"utf-8\";");
httpReply.setPayload(QJsonDocument::fromVariant(JsonTypes::packConfiguredDevices()).toJson());
httpReply.packReply();
return httpReply;
}
HttpReply DevicesResource::getConfiguredDevice(Device *device)
{
HttpReply httpReply(HttpReply::Ok);
httpReply.setHeader(HttpReply::ContentTypeHeader, "application/json; charset=\"utf-8\";");
httpReply.setPayload(QJsonDocument::fromVariant(JsonTypes::packDevice(device)).toJson());
httpReply.packReply();
return httpReply;
}
HttpReply DevicesResource::getDeviceStateValues(Device *device)
{
HttpReply httpReply(HttpReply::Ok);
httpReply.setHeader(HttpReply::ContentTypeHeader, "application/json; charset=\"utf-8\";");
httpReply.setPayload(QJsonDocument::fromVariant(JsonTypes::packDeviceStates(device)).toJson());
httpReply.packReply();
return httpReply;
}
HttpReply DevicesResource::getDeviceStateValue(Device *device, const StateTypeId &stateTypeId)
{
HttpReply httpReply(HttpReply::Ok);
httpReply.setHeader(HttpReply::ContentTypeHeader, "application/json; charset=\"utf-8\";");
QVariantMap stateValue;
stateValue.insert("value", device->state(stateTypeId).value());
httpReply.setPayload(QJsonDocument::fromVariant(stateValue).toJson());
httpReply.packReply();
return httpReply;
}
HttpReply DevicesResource::removeDevice(Device *device)
{
DeviceManager::DeviceError result = GuhCore::instance()->removeConfiguredDevice(device->id(), QHash<RuleId, RuleEngine::RemovePolicy>());
// TODO: parse removepolicy query params
if (result == DeviceManager::DeviceErrorNoError)
return HttpReply(HttpReply::Ok);
return HttpReply(HttpReply::Forbidden);
}
}

View File

@ -0,0 +1,56 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* 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 DEVICESRESOURCE_H
#define DEVICESRESOURCE_H
#include <QObject>
#include "jsontypes.h"
class HttpReply;
class HttpRequest;
namespace guhserver {
class DevicesResource : public QObject
{
Q_OBJECT
public:
explicit DevicesResource(QObject *parent = 0);
HttpReply proccessDeviceRequest(const HttpRequest &request, const QStringList &urlTokens);
private:
HttpReply getConfiguredDevices();
HttpReply getConfiguredDevice(Device *device);
HttpReply getDeviceStateValues(Device *device);
HttpReply getDeviceStateValue(Device *device, const StateTypeId &stateTypeId);
HttpReply removeDevice(Device *device);
signals:
public slots:
};
}
#endif // DEVICESRESOURCE_H

View File

@ -24,13 +24,6 @@
#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 {
@ -43,6 +36,10 @@ RestServer::RestServer(QObject *parent) :
connect(m_webserver, &WebServer::clientDisconnected, this, &RestServer::clientDisconnected);
connect(m_webserver, &WebServer::httpRequestReady, this, &RestServer::processHttpRequest);
// Resources
m_deviceResource = new DevicesResource(this);
m_webserver->startServer();
}
@ -58,57 +55,87 @@ void RestServer::clientDisconnected(const QUuid &clientId)
void RestServer::processHttpRequest(const QUuid &clientId, const HttpRequest &request)
{
qCDebug(dcWebServer) << "process http request" << clientId << request.method() << request.urlQuery().query();
qCDebug(dcRest) << "Process HTTP request" << clientId << request.method() << request.urlQuery().query();
QString targetNamespace;
QString method;
QVariantMap params;
QStringList urlTokens = request.urlQuery().query(QUrl::FullyDecoded).split("/");
urlTokens.removeAll(QString());
if (request.urlQuery().hasQueryItem("devices")) {
qCDebug(dcWebServer) << "devices resource";
qCDebug(dcRest) << urlTokens;
if (urlTokens.count() < 3) {
m_webserver->sendHttpReply(clientId, HttpReply(HttpReply::BadRequest));
return;
}
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();
if (urlTokens.at(2) == "devices") {
HttpReply httpReply = m_deviceResource->proccessDeviceRequest(request, urlTokens);
qCDebug(dcRest) << "sending header" << httpReply.rawHeader();
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;
}
// QString targetNamespace;
// QString method;
// QVariantMap params;
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;
}
// // check filter
// QVariantList deviceList;
// if (!request.urlQuery().hasQueryItem("id")) {
// HttpReply httpReply = m_deviceResource->proccessDeviceRequest(request);
// m_webserver->sendHttpReply(clientId, httpReply);
// return;
// } else {
// foreach (const QString& idString, request.urlQuery().allQueryItemValues("id")) {
// Device *device = GuhCore::instance()->deviceManager()->findConfiguredDevice(DeviceId(idString));
// if (device == Device()) {
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();
// if (request.method() == HttpRequest::Get && request.urlQuery().query() == "/api/v1/devices.json") {
// targetNamespace = "Devices";
// method = "GetConfiguredDevices";
// } else if (request.method() == HttpRequest::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()

View File

@ -25,6 +25,7 @@
#include "webserver.h"
#include "jsonhandler.h"
#include "devicesresource.h"
class HttpRequest;
class HttpReply;
@ -42,6 +43,8 @@ private:
QList<QUuid> m_clientList;
QHash<QUuid, JsonReply *> m_asyncReplies;
DevicesResource *m_deviceResource;
signals:
void httpReplyReady(const HttpReply &httpReply);

View File

@ -25,7 +25,8 @@ SOURCES += $$top_srcdir/server/guhcore.cpp \
$$top_srcdir/server/transportinterface.cpp \
$$top_srcdir/server/servermanager.cpp \
$$top_srcdir/server/websocketserver.cpp \
$$top_srcdir/server/rest/restserver.cpp
$$top_srcdir/server/rest/restserver.cpp \
$$top_srcdir/server/rest/devicesresource.cpp \
HEADERS += $$top_srcdir/server/guhcore.h \
@ -51,5 +52,6 @@ HEADERS += $$top_srcdir/server/guhcore.h \
$$top_srcdir/server/servermanager.h \
$$top_srcdir/server/websocketserver.h \
$$top_srcdir/server/rest/restserver.h \
$$top_srcdir/server/rest/devicesresource.h \

View File

@ -40,7 +40,7 @@ WebServer::WebServer(QObject *parent) :
{
// load webserver settings
GuhSettings settings(GuhSettings::SettingsRoleGlobal);
qCDebug(dcTcpServer) << "Loading Webserver settings from:" << settings.fileName();
qCDebug(dcTcpServer) << "Loading webserver settings from:" << settings.fileName();
settings.beginGroup("Webserver");
m_port = settings.value("port", 3000).toInt();
@ -140,21 +140,6 @@ QString WebServer::fileName(const QString &query)
return QFileInfo(m_webinterfaceDir.path() + fileName).canonicalFilePath();
}
WebServer::RequestMethod WebServer::getRequestMethodType(const QString &methodString)
{
if (methodString == "GET") {
return RequestMethod::Get;
} else if (methodString == "POST") {
return RequestMethod::Post;
} else if (methodString == "PUT") {
return RequestMethod::Put;
} else if (methodString == "DELETE") {
return RequestMethod::Delete;
}
return RequestMethod::Unhandled;
}
void WebServer::writeData(QTcpSocket *socket, const QByteArray &data)
{
QTextStream os(socket);
@ -195,10 +180,10 @@ void WebServer::readClient()
return;
}
// read http request
// read HTTP request
HttpRequest request = HttpRequest(socket->readAll());
if (!request.isValid()) {
qCWarning(dcWebServer) << "Invalid request.";
qCWarning(dcWebServer) << "Got invalid request.";
HttpReply reply(HttpReply::BadRequest);
reply.setPayload("400 Bad Request.");
reply.packReply();
@ -220,9 +205,7 @@ void WebServer::readClient()
qCDebug(dcWebServer) << request;
// verify method
RequestMethod requestMethod = getRequestMethodType(request.method());
if (requestMethod == RequestMethod::Unhandled) {
qCWarning(dcWebServer) << "method" << request.method() << "not allowed";
if (request.method() == HttpRequest::Unhandled) {
HttpReply reply(HttpReply::MethodNotAllowed);
reply.setHeader(HttpReply::AllowHeader, "GET, PUT, POST, DELETE");
reply.setPayload("405 Method not allowed.");
@ -238,7 +221,7 @@ void WebServer::readClient()
}
// request for a file...
if (requestMethod == RequestMethod::Get) {
if (request.method() == HttpRequest::Get) {
if (!verifyFile(socket, fileName(request.urlQuery().query())))
return;

View File

@ -43,13 +43,7 @@ class WebServer : public TransportInterface
{
Q_OBJECT
public:
enum RequestMethod {
Get,
Post,
Put,
Delete,
Unhandled
};
explicit WebServer(QObject *parent = 0);
~WebServer();
@ -70,7 +64,6 @@ private:
bool verifyFile(QTcpSocket *socket, const QString &fileName);
QString fileName(const QString &query);
RequestMethod getRequestMethodType(const QString &methodString);
void writeData(QTcpSocket *socket, const QByteArray &data);

View File

@ -1,2 +1,2 @@
TEMPLATE=subdirs
SUBDIRS=versioning devices jsonrpc events states actions rules plugins webserver
SUBDIRS=versioning devices jsonrpc events states actions rules plugins webserver restdevices

View File

@ -0,0 +1,5 @@
include(../../../guh.pri)
include(../autotests.pri)
TARGET = restdevices
SOURCES += testrestdevices.cpp

View File

@ -0,0 +1,87 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of guh. *
* *
* Guh is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, version 2 of the License. *
* *
* Guh is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with guh. If not, see <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "guhtestbase.h"
#include "guhcore.h"
#include "devicemanager.h"
#include "mocktcpserver.h"
#include "webserver.h"
#include <QtTest/QtTest>
#include <QCoreApplication>
#include <QTcpSocket>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QCoreApplication>
#include <QJsonDocument>
#include <QMetaType>
using namespace guhserver;
class TestRestDevices: public GuhTestBase
{
Q_OBJECT
private slots:
void getConfiguredDevices();
private:
// for debugging
void printResponse(QNetworkReply *reply);
};
void TestRestDevices::getConfiguredDevices()
{
QNetworkAccessManager *nam = new QNetworkAccessManager(this);
QSignalSpy clientSpy(nam, SIGNAL(finished(QNetworkReply*)));
QNetworkRequest request;
request.setUrl(QUrl("http://localhost:3000/api/v1/devices"));
QNetworkReply *reply;
reply = nam->get(request);
clientSpy.wait(200);
QVERIFY2(clientSpy.count() == 1, "expected exactly 1 response from webserver");
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll(), &error);
QCOMPARE(error.error, QJsonParseError::NoError);
QVariantList deviceList = jsonDoc.toVariant().toList();
QCOMPARE(deviceList.count(), 3);
reply->deleteLater();
}
void TestRestDevices::printResponse(QNetworkReply *reply)
{
qDebug() << "-------------------------------";
qDebug() << reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
foreach (const QNetworkReply::RawHeaderPair &headerPair, reply->rawHeaderPairs()) {
qDebug() << headerPair.first << ":" << headerPair.second;
}
qDebug() << "-------------------------------";
qDebug() << reply->readAll();
qDebug() << "-------------------------------";
}
#include "testrestdevices.moc"
QTEST_MAIN(TestRestDevices)

View File

@ -40,7 +40,6 @@ class TestWebserver: public GuhTestBase
Q_OBJECT
private slots:
void pingServer();
void httpVersion();
void checkAllowedMethodCall_data();
@ -55,11 +54,6 @@ private:
};
void TestWebserver::pingServer()
{
// TODO: when QWebsocket will be used
}
void TestWebserver::httpVersion()
{
QTcpSocket *socket = new QTcpSocket(this);
@ -156,8 +150,6 @@ void TestWebserver::getFiles_data()
QTest::newRow("get /etc/guh/guhd.conf") << "/etc/guh/guhd.conf" << 404;
QTest::newRow("get /etc/sudoers") << "/etc/sudoers" << 404;
QTest::newRow("get /root/.ssh/id_rsa.pub") << "/root/.ssh/id_rsa.pub" << 404;
}
void TestWebserver::getFiles()