added deviceresources and first tests

This commit is contained in:
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(dcTcpServer, "TcpServer")
Q_LOGGING_CATEGORY(dcWebServer, "WebServer") Q_LOGGING_CATEGORY(dcWebServer, "WebServer")
Q_LOGGING_CATEGORY(dcJsonRpc, "JsonRpc") Q_LOGGING_CATEGORY(dcJsonRpc, "JsonRpc")
Q_LOGGING_CATEGORY(dcRest, "Rest")
Q_LOGGING_CATEGORY(dcLogEngine, "LogEngine") 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(dcHardware)
Q_DECLARE_LOGGING_CATEGORY(dcConnection) Q_DECLARE_LOGGING_CATEGORY(dcConnection)
Q_DECLARE_LOGGING_CATEGORY(dcJsonRpc) Q_DECLARE_LOGGING_CATEGORY(dcJsonRpc)
Q_DECLARE_LOGGING_CATEGORY(dcRest)
Q_DECLARE_LOGGING_CATEGORY(dcLogEngine) Q_DECLARE_LOGGING_CATEGORY(dcLogEngine)
Q_DECLARE_LOGGING_CATEGORY(dcTcpServer) Q_DECLARE_LOGGING_CATEGORY(dcTcpServer)
Q_DECLARE_LOGGING_CATEGORY(dcWebServer) Q_DECLARE_LOGGING_CATEGORY(dcWebServer)

View File

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

View File

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

View File

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

View File

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

View File

@ -262,29 +262,16 @@ QString DeviceHandler::name() const
JsonReply* DeviceHandler::GetSupportedVendors(const QVariantMap &params) const JsonReply* DeviceHandler::GetSupportedVendors(const QVariantMap &params) const
{ {
Q_UNUSED(params) Q_UNUSED(params)
QVariantMap returns; QVariantMap returns;
QVariantList supportedVendors; returns.insert("vendors", JsonTypes::packSupportedVendors());
foreach (const Vendor &vendor, GuhCore::instance()->supportedVendors()) {
supportedVendors.append(JsonTypes::packVendor(vendor));
}
returns.insert("vendors", supportedVendors);
return createReply(returns); return createReply(returns);
} }
JsonReply* DeviceHandler::GetSupportedDevices(const QVariantMap &params) const JsonReply* DeviceHandler::GetSupportedDevices(const QVariantMap &params) const
{ {
QVariantMap returns; QVariantMap returns;
QVariantList supportedDeviceList; returns.insert("deviceClasses", JsonTypes::packSupportedDevices(VendorId(params.value("vendorId").toString())));
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);
return createReply(returns); return createReply(returns);
} }
@ -309,20 +296,9 @@ JsonReply *DeviceHandler::GetDiscoveredDevices(const QVariantMap &params) const
JsonReply* DeviceHandler::GetPlugins(const QVariantMap &params) const JsonReply* DeviceHandler::GetPlugins(const QVariantMap &params) const
{ {
Q_UNUSED(params) Q_UNUSED(params)
QVariantMap returns; QVariantMap returns;
QVariantList plugins; returns.insert("plugins", JsonTypes::packPlugins());
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);
return createReply(returns); return createReply(returns);
} }
@ -531,14 +507,14 @@ JsonReply* DeviceHandler::GetStateValue(const QVariantMap &params) const
returns.insert("deviceError", JsonTypes::deviceErrorToString(DeviceManager::DeviceErrorDeviceNotFound)); returns.insert("deviceError", JsonTypes::deviceErrorToString(DeviceManager::DeviceErrorDeviceNotFound));
return createReply(returns); 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)); returns.insert("deviceError", JsonTypes::deviceErrorToString(DeviceManager::DeviceErrorStateTypeNotFound));
return createReply(returns); return createReply(returns);
} }
QVariant stateValue = device->stateValue(StateTypeId(params.value("stateTypeId").toString()));
returns.insert("deviceError", JsonTypes::deviceErrorToString(DeviceManager::DeviceErrorNoError)); returns.insert("deviceError", JsonTypes::deviceErrorToString(DeviceManager::DeviceErrorNoError));
returns.insert("value", stateValue); returns.insert("value", device->state(stateTypeId).value());
return createReply(returns); return createReply(returns);
} }
@ -552,20 +528,8 @@ JsonReply *DeviceHandler::GetStateValues(const QVariantMap &params) const
return createReply(returns); 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("deviceError", JsonTypes::deviceErrorToString(DeviceManager::DeviceErrorNoError));
returns.insert("values", values); returns.insert("values", JsonTypes::packDeviceStates(device));
return createReply(returns); return createReply(returns);
} }

View File

@ -23,6 +23,7 @@
#include "plugin/device.h" #include "plugin/device.h"
#include "devicemanager.h" #include "devicemanager.h"
#include "guhcore.h"
#include "ruleengine.h" #include "ruleengine.h"
#include "loggingcategories.h" #include "loggingcategories.h"
@ -387,6 +388,14 @@ QVariantMap JsonTypes::packRuleActionParam(const RuleActionParam &ruleActionPara
return variantMap; 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 JsonTypes::packStateType(const StateType &stateType)
{ {
QVariantMap variantMap; QVariantMap variantMap;
@ -526,9 +535,17 @@ QVariantMap JsonTypes::packDeviceClass(const DeviceClass &deviceClass)
QVariantMap JsonTypes::packPlugin(DevicePlugin *plugin) QVariantMap JsonTypes::packPlugin(DevicePlugin *plugin)
{ {
Q_UNUSED(plugin) QVariantMap pluginMap;
qCWarning(dcDeviceManager) << "packPlugin not implemented yet!"; pluginMap.insert("id", plugin->pluginId());
return QVariantMap(); 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) QVariantMap JsonTypes::packDevice(Device *device)
@ -656,6 +673,56 @@ QVariantList JsonTypes::packCreateMethods(DeviceClass::CreateMethods createMetho
return ret; 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) Param JsonTypes::unpackParam(const QVariantMap &paramMap)
{ {
if (paramMap.keys().count() == 0) { if (paramMap.keys().count() == 0) {

View File

@ -135,6 +135,7 @@ public:
DECLARE_OBJECT(ruleDescription, "RuleDescription") DECLARE_OBJECT(ruleDescription, "RuleDescription")
DECLARE_OBJECT(logEntry, "LogEntry") DECLARE_OBJECT(logEntry, "LogEntry")
// pack types
static QVariantMap packEventType(const EventType &eventType); static QVariantMap packEventType(const EventType &eventType);
static QVariantMap packEvent(const Event &event); static QVariantMap packEvent(const Event &event);
static QVariantMap packEventDescriptor(const EventDescriptor &event); static QVariantMap packEventDescriptor(const EventDescriptor &event);
@ -142,6 +143,7 @@ public:
static QVariantMap packAction(const Action &action); static QVariantMap packAction(const Action &action);
static QVariantMap packRuleAction(const RuleAction &ruleAction); static QVariantMap packRuleAction(const RuleAction &ruleAction);
static QVariantMap packRuleActionParam(const RuleActionParam &ruleActionParam); static QVariantMap packRuleActionParam(const RuleActionParam &ruleActionParam);
static QVariantMap packState(const State &state);
static QVariantMap packStateType(const StateType &stateType); static QVariantMap packStateType(const StateType &stateType);
static QVariantMap packStateDescriptor(const StateDescriptor &stateDescriptor); static QVariantMap packStateDescriptor(const StateDescriptor &stateDescriptor);
static QVariantMap packStateEvaluator(const StateEvaluator &stateEvaluator); static QVariantMap packStateEvaluator(const StateEvaluator &stateEvaluator);
@ -158,6 +160,16 @@ public:
static QVariantMap packLogEntry(const LogEntry &logEntry); static QVariantMap packLogEntry(const LogEntry &logEntry);
static QVariantList packCreateMethods(DeviceClass::CreateMethods createMethods); 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 Param unpackParam(const QVariantMap &paramMap);
static ParamList unpackParams(const QVariantList &paramList); static ParamList unpackParams(const QVariantList &paramList);
static RuleActionParam unpackRuleActionParam(const QVariantMap &ruleActionParamMap); static RuleActionParam unpackRuleActionParam(const QVariantMap &ruleActionParamMap);
@ -169,6 +181,7 @@ public:
static StateDescriptor unpackStateDescriptor(const QVariantMap &stateDescriptorMap); static StateDescriptor unpackStateDescriptor(const QVariantMap &stateDescriptorMap);
static LogFilter unpackLogFilter(const QVariantMap &logFilterMap); static LogFilter unpackLogFilter(const QVariantMap &logFilterMap);
// validate
static QPair<bool, QString> validateMap(const QVariantMap &templateMap, const QVariantMap &map); 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> validateProperty(const QVariant &templateValue, const QVariant &value);
static QPair<bool, QString> validateList(const QVariantList &templateList, const QVariantList &list); 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("TcpServer", true);
s_loggingFilters.insert("WebServer", true); s_loggingFilters.insert("WebServer", true);
s_loggingFilters.insert("JsonRpc", false); s_loggingFilters.insert("JsonRpc", false);
s_loggingFilters.insert("Rest", true);
s_loggingFilters.insert("Hardware", false); s_loggingFilters.insert("Hardware", false);
s_loggingFilters.insert("LogEngine", 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 "network/httpreply.h"
#include "guhcore.h" #include "guhcore.h"
#include "devicehandler.h"
#include "actionhandler.h"
#include "ruleshandler.h"
#include "eventhandler.h"
#include "logginghandler.h"
#include "statehandler.h"
#include <QJsonDocument> #include <QJsonDocument>
namespace guhserver { namespace guhserver {
@ -43,6 +36,10 @@ RestServer::RestServer(QObject *parent) :
connect(m_webserver, &WebServer::clientDisconnected, this, &RestServer::clientDisconnected); connect(m_webserver, &WebServer::clientDisconnected, this, &RestServer::clientDisconnected);
connect(m_webserver, &WebServer::httpRequestReady, this, &RestServer::processHttpRequest); connect(m_webserver, &WebServer::httpRequestReady, this, &RestServer::processHttpRequest);
// Resources
m_deviceResource = new DevicesResource(this);
m_webserver->startServer(); m_webserver->startServer();
} }
@ -58,57 +55,87 @@ void RestServer::clientDisconnected(const QUuid &clientId)
void RestServer::processHttpRequest(const QUuid &clientId, const HttpRequest &request) 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; QStringList urlTokens = request.urlQuery().query(QUrl::FullyDecoded).split("/");
QString method; urlTokens.removeAll(QString());
QVariantMap params;
if (request.urlQuery().hasQueryItem("devices")) { qCDebug(dcRest) << urlTokens;
qCDebug(dcWebServer) << "devices resource";
if (urlTokens.count() < 3) {
m_webserver->sendHttpReply(clientId, HttpReply(HttpReply::BadRequest));
return;
} }
if (request.method() == "GET" && request.urlQuery().query() == "/api/v1/devices.json") { if (urlTokens.at(2) == "devices") {
targetNamespace = "Devices"; HttpReply httpReply = m_deviceResource->proccessDeviceRequest(request, urlTokens);
method = "GetConfiguredDevices"; qCDebug(dcRest) << "sending header" << httpReply.rawHeader();
} 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); m_webserver->sendHttpReply(clientId, httpReply);
return; return;
} }
JsonHandler *handler = GuhCore::instance()->jsonRPCServer()->handlers().value(targetNamespace);
QPair<bool, QString> validationResult = handler->validateParams(method, params); // QString targetNamespace;
if (!validationResult.first) { // QString method;
qCWarning(dcWebServer) << "Invalid params: " << validationResult.second; // QVariantMap params;
return;
}
JsonReply *jsonReply; // // check filter
QMetaObject::invokeMethod(handler, method.toLatin1().data(), Q_RETURN_ARG(JsonReply*, jsonReply), Q_ARG(QVariantMap, params)); // QVariantList deviceList;
if (jsonReply->type() == JsonReply::TypeAsync) { // if (!request.urlQuery().hasQueryItem("id")) {
jsonReply->setClientId(clientId); // HttpReply httpReply = m_deviceResource->proccessDeviceRequest(request);
connect(jsonReply, &JsonReply::finished, this, &RestServer::asyncReplyFinished); // m_webserver->sendHttpReply(clientId, httpReply);
jsonReply->startWait(); // return;
m_asyncReplies.insert(clientId, jsonReply); // } else {
return; // 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() void RestServer::asyncReplyFinished()

View File

@ -25,6 +25,7 @@
#include "webserver.h" #include "webserver.h"
#include "jsonhandler.h" #include "jsonhandler.h"
#include "devicesresource.h"
class HttpRequest; class HttpRequest;
class HttpReply; class HttpReply;
@ -42,6 +43,8 @@ private:
QList<QUuid> m_clientList; QList<QUuid> m_clientList;
QHash<QUuid, JsonReply *> m_asyncReplies; QHash<QUuid, JsonReply *> m_asyncReplies;
DevicesResource *m_deviceResource;
signals: signals:
void httpReplyReady(const HttpReply &httpReply); 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/transportinterface.cpp \
$$top_srcdir/server/servermanager.cpp \ $$top_srcdir/server/servermanager.cpp \
$$top_srcdir/server/websocketserver.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 \ HEADERS += $$top_srcdir/server/guhcore.h \
@ -51,5 +52,6 @@ HEADERS += $$top_srcdir/server/guhcore.h \
$$top_srcdir/server/servermanager.h \ $$top_srcdir/server/servermanager.h \
$$top_srcdir/server/websocketserver.h \ $$top_srcdir/server/websocketserver.h \
$$top_srcdir/server/rest/restserver.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 // load webserver settings
GuhSettings settings(GuhSettings::SettingsRoleGlobal); GuhSettings settings(GuhSettings::SettingsRoleGlobal);
qCDebug(dcTcpServer) << "Loading Webserver settings from:" << settings.fileName(); qCDebug(dcTcpServer) << "Loading webserver settings from:" << settings.fileName();
settings.beginGroup("Webserver"); settings.beginGroup("Webserver");
m_port = settings.value("port", 3000).toInt(); m_port = settings.value("port", 3000).toInt();
@ -140,21 +140,6 @@ QString WebServer::fileName(const QString &query)
return QFileInfo(m_webinterfaceDir.path() + fileName).canonicalFilePath(); 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) void WebServer::writeData(QTcpSocket *socket, const QByteArray &data)
{ {
QTextStream os(socket); QTextStream os(socket);
@ -195,10 +180,10 @@ void WebServer::readClient()
return; return;
} }
// read http request // read HTTP request
HttpRequest request = HttpRequest(socket->readAll()); HttpRequest request = HttpRequest(socket->readAll());
if (!request.isValid()) { if (!request.isValid()) {
qCWarning(dcWebServer) << "Invalid request."; qCWarning(dcWebServer) << "Got invalid request.";
HttpReply reply(HttpReply::BadRequest); HttpReply reply(HttpReply::BadRequest);
reply.setPayload("400 Bad Request."); reply.setPayload("400 Bad Request.");
reply.packReply(); reply.packReply();
@ -220,9 +205,7 @@ void WebServer::readClient()
qCDebug(dcWebServer) << request; qCDebug(dcWebServer) << request;
// verify method // verify method
RequestMethod requestMethod = getRequestMethodType(request.method()); if (request.method() == HttpRequest::Unhandled) {
if (requestMethod == RequestMethod::Unhandled) {
qCWarning(dcWebServer) << "method" << request.method() << "not allowed";
HttpReply reply(HttpReply::MethodNotAllowed); HttpReply reply(HttpReply::MethodNotAllowed);
reply.setHeader(HttpReply::AllowHeader, "GET, PUT, POST, DELETE"); reply.setHeader(HttpReply::AllowHeader, "GET, PUT, POST, DELETE");
reply.setPayload("405 Method not allowed."); reply.setPayload("405 Method not allowed.");
@ -238,7 +221,7 @@ void WebServer::readClient()
} }
// request for a file... // request for a file...
if (requestMethod == RequestMethod::Get) { if (request.method() == HttpRequest::Get) {
if (!verifyFile(socket, fileName(request.urlQuery().query()))) if (!verifyFile(socket, fileName(request.urlQuery().query())))
return; return;

View File

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

View File

@ -1,2 +1,2 @@
TEMPLATE=subdirs 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 Q_OBJECT
private slots: private slots:
void pingServer();
void httpVersion(); void httpVersion();
void checkAllowedMethodCall_data(); void checkAllowedMethodCall_data();
@ -55,11 +54,6 @@ private:
}; };
void TestWebserver::pingServer()
{
// TODO: when QWebsocket will be used
}
void TestWebserver::httpVersion() void TestWebserver::httpVersion()
{ {
QTcpSocket *socket = new QTcpSocket(this); 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/guh/guhd.conf") << "/etc/guh/guhd.conf" << 404;
QTest::newRow("get /etc/sudoers") << "/etc/sudoers" << 404; QTest::newRow("get /etc/sudoers") << "/etc/sudoers" << 404;
QTest::newRow("get /root/.ssh/id_rsa.pub") << "/root/.ssh/id_rsa.pub" << 404; QTest::newRow("get /root/.ssh/id_rsa.pub") << "/root/.ssh/id_rsa.pub" << 404;
} }
void TestWebserver::getFiles() void TestWebserver::getFiles()