From b86a062a878e87ac1da1e849e2c06976f63fee21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Sun, 26 Jul 2015 02:12:10 +0200 Subject: [PATCH] added deviceresources and first tests --- libguh/loggingcategories.cpp | 1 + libguh/loggingcategories.h | 1 + libguh/network/httpreply.cpp | 2 +- libguh/network/httpreply.h | 2 +- libguh/network/httprequest.cpp | 27 +++- libguh/network/httprequest.h | 14 +- server/jsonrpc/devicehandler.cpp | 54 ++------ server/jsonrpc/jsontypes.cpp | 73 +++++++++- server/jsonrpc/jsontypes.h | 13 ++ server/main.cpp | 1 + server/rest/devicesresource.cpp | 153 +++++++++++++++++++++ server/rest/devicesresource.h | 56 ++++++++ server/rest/restserver.cpp | 115 ++++++++++------ server/rest/restserver.h | 3 + server/server.pri | 4 +- server/webserver.cpp | 27 +--- server/webserver.h | 9 +- tests/auto/auto.pro | 2 +- tests/auto/restdevices/restdevices.pro | 5 + tests/auto/restdevices/testrestdevices.cpp | 87 ++++++++++++ tests/auto/webserver/testwebserver.cpp | 8 -- 21 files changed, 517 insertions(+), 140 deletions(-) create mode 100644 server/rest/devicesresource.cpp create mode 100644 server/rest/devicesresource.h create mode 100644 tests/auto/restdevices/restdevices.pro create mode 100644 tests/auto/restdevices/testrestdevices.cpp diff --git a/libguh/loggingcategories.cpp b/libguh/loggingcategories.cpp index be01d891..4f840769 100644 --- a/libguh/loggingcategories.cpp +++ b/libguh/loggingcategories.cpp @@ -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") diff --git a/libguh/loggingcategories.h b/libguh/loggingcategories.h index e4ea2d91..a7f6aa5c 100644 --- a/libguh/loggingcategories.h +++ b/libguh/loggingcategories.h @@ -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) diff --git a/libguh/network/httpreply.cpp b/libguh/network/httpreply.cpp index 7603ea2a..32d00c93 100644 --- a/libguh/network/httpreply.cpp +++ b/libguh/network/httpreply.cpp @@ -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}.*/ diff --git a/libguh/network/httpreply.h b/libguh/network/httpreply.h index a3507fe7..3f66746a 100644 --- a/libguh/network/httpreply.h +++ b/libguh/network/httpreply.h @@ -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; diff --git a/libguh/network/httprequest.cpp b/libguh/network/httprequest.cpp index 416e41e1..cba273bc 100644 --- a/libguh/network/httprequest.cpp +++ b/libguh/network/httprequest.cpp @@ -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 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"; diff --git a/libguh/network/httprequest.h b/libguh/network/httprequest.h index 705b07cb..ce0eda28 100644 --- a/libguh/network/httprequest.h +++ b/libguh/network/httprequest.h @@ -29,12 +29,20 @@ class HttpRequest { public: + enum RequestMethod { + Get, + Post, + Put, + Delete, + Unhandled + }; + explicit HttpRequest(QByteArray rawData); QByteArray rawHeader() const; QHash rawHeaderList() const; - QByteArray method() const; + RequestMethod method() const; QByteArray httpVersion() const; QUrlQuery urlQuery() const; @@ -48,13 +56,15 @@ private: QByteArray m_rawHeader; QHash 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); diff --git a/server/jsonrpc/devicehandler.cpp b/server/jsonrpc/devicehandler.cpp index 06600645..b6b4d007 100644 --- a/server/jsonrpc/devicehandler.cpp +++ b/server/jsonrpc/devicehandler.cpp @@ -262,29 +262,16 @@ QString DeviceHandler::name() const JsonReply* DeviceHandler::GetSupportedVendors(const QVariantMap ¶ms) 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 ¶ms) const { QVariantMap returns; - QVariantList supportedDeviceList; - QList 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 ¶ms) const JsonReply* DeviceHandler::GetPlugins(const QVariantMap ¶ms) 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 ¶m, 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 ¶ms) 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 ¶ms) 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); } diff --git a/server/jsonrpc/jsontypes.cpp b/server/jsonrpc/jsontypes.cpp index 11f05a86..9d795ae5 100644 --- a/server/jsonrpc/jsontypes.cpp +++ b/server/jsonrpc/jsontypes.cpp @@ -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 ¶m, 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 ¶mMap) { if (paramMap.keys().count() == 0) { diff --git a/server/jsonrpc/jsontypes.h b/server/jsonrpc/jsontypes.h index 39b1b78f..a7e247eb 100644 --- a/server/jsonrpc/jsontypes.h +++ b/server/jsonrpc/jsontypes.h @@ -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 ¶mMap); static ParamList unpackParams(const QVariantList ¶mList); 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 validateMap(const QVariantMap &templateMap, const QVariantMap &map); static QPair validateProperty(const QVariant &templateValue, const QVariant &value); static QPair validateList(const QVariantList &templateList, const QVariantList &list); diff --git a/server/main.cpp b/server/main.cpp index d736109a..af0334e6 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -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); diff --git a/server/rest/devicesresource.cpp b/server/rest/devicesresource.cpp new file mode 100644 index 00000000..e79ff658 --- /dev/null +++ b/server/rest/devicesresource.cpp @@ -0,0 +1,153 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of guh. * + * * + * Guh is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 2 of the License. * + * * + * Guh is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with guh. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "devicesresource.h" +#include "jsontypes.h" +#include "guhcore.h" +#include "network/httpreply.h" +#include "network/httprequest.h" + +#include + +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()); + + // TODO: parse removepolicy query params + + if (result == DeviceManager::DeviceErrorNoError) + return HttpReply(HttpReply::Ok); + + return HttpReply(HttpReply::Forbidden); +} + +} diff --git a/server/rest/devicesresource.h b/server/rest/devicesresource.h new file mode 100644 index 00000000..e3dcbe3f --- /dev/null +++ b/server/rest/devicesresource.h @@ -0,0 +1,56 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of guh. * + * * + * Guh is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 2 of the License. * + * * + * Guh is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with guh. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef DEVICESRESOURCE_H +#define DEVICESRESOURCE_H + +#include +#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 diff --git a/server/rest/restserver.cpp b/server/rest/restserver.cpp index 4c40ecb0..79a4eb5d 100644 --- a/server/rest/restserver.cpp +++ b/server/rest/restserver.cpp @@ -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 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 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 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() diff --git a/server/rest/restserver.h b/server/rest/restserver.h index bfcd1257..f49e59ba 100644 --- a/server/rest/restserver.h +++ b/server/rest/restserver.h @@ -25,6 +25,7 @@ #include "webserver.h" #include "jsonhandler.h" +#include "devicesresource.h" class HttpRequest; class HttpReply; @@ -42,6 +43,8 @@ private: QList m_clientList; QHash m_asyncReplies; + DevicesResource *m_deviceResource; + signals: void httpReplyReady(const HttpReply &httpReply); diff --git a/server/server.pri b/server/server.pri index 3ba52857..9e19d953 100644 --- a/server/server.pri +++ b/server/server.pri @@ -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 \ diff --git a/server/webserver.cpp b/server/webserver.cpp index 9eba1a51..88722933 100644 --- a/server/webserver.cpp +++ b/server/webserver.cpp @@ -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; diff --git a/server/webserver.h b/server/webserver.h index a626a5c8..9e3d68cd 100644 --- a/server/webserver.h +++ b/server/webserver.h @@ -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); diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index e1682623..07956968 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -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 diff --git a/tests/auto/restdevices/restdevices.pro b/tests/auto/restdevices/restdevices.pro new file mode 100644 index 00000000..f7d2bb84 --- /dev/null +++ b/tests/auto/restdevices/restdevices.pro @@ -0,0 +1,5 @@ +include(../../../guh.pri) +include(../autotests.pri) + +TARGET = restdevices +SOURCES += testrestdevices.cpp diff --git a/tests/auto/restdevices/testrestdevices.cpp b/tests/auto/restdevices/testrestdevices.cpp new file mode 100644 index 00000000..2ae1284d --- /dev/null +++ b/tests/auto/restdevices/testrestdevices.cpp @@ -0,0 +1,87 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of guh. * + * * + * Guh is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 2 of the License. * + * * + * Guh is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with guh. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "guhtestbase.h" +#include "guhcore.h" +#include "devicemanager.h" +#include "mocktcpserver.h" +#include "webserver.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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) diff --git a/tests/auto/webserver/testwebserver.cpp b/tests/auto/webserver/testwebserver.cpp index 257b96e6..71ab774a 100644 --- a/tests/auto/webserver/testwebserver.cpp +++ b/tests/auto/webserver/testwebserver.cpp @@ -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()