From bd5a7c9fbcb5098cc3a0a71bca6dd0f4a9454492 Mon Sep 17 00:00:00 2001 From: nymea Date: Fri, 27 Sep 2019 17:55:46 +0200 Subject: [PATCH] added simple http server --- httpcommander/devicepluginhttpcommander.cpp | 7 + httpcommander/devicepluginhttpcommander.h | 3 + httpcommander/devicepluginhttpcommander.json | 53 ++++ httpcommander/httpcommander.pro | 6 +- httpcommander/httpsimpleserver.cpp | 241 +++++++++++++++++++ httpcommander/httpsimpleserver.h | 69 ++++++ 6 files changed, 377 insertions(+), 2 deletions(-) create mode 100644 httpcommander/httpsimpleserver.cpp create mode 100644 httpcommander/httpsimpleserver.h diff --git a/httpcommander/devicepluginhttpcommander.cpp b/httpcommander/devicepluginhttpcommander.cpp index 8d18e6db..c14df5e6 100644 --- a/httpcommander/devicepluginhttpcommander.cpp +++ b/httpcommander/devicepluginhttpcommander.cpp @@ -69,6 +69,13 @@ void DevicePluginHttpCommander::setupDevice(DeviceSetupInfo *info) } return info->finish(Device::DeviceErrorNoError); } + + if (device->deviceClassId() == httpServerDeviceClassId) { + //TODO create a simple server + HttpSimpleServer *httpSimpleServer = new HttpSimpleServer(); + m_httpSimpleServer.insert(device, httpSimpleServer); + return info->finish(Device::DeviceErrorNoError); + } info->finish(Device::DeviceErrorNoError); } diff --git a/httpcommander/devicepluginhttpcommander.h b/httpcommander/devicepluginhttpcommander.h index 48770dc5..e2c03859 100644 --- a/httpcommander/devicepluginhttpcommander.h +++ b/httpcommander/devicepluginhttpcommander.h @@ -26,6 +26,8 @@ #include "devices/deviceplugin.h" #include "plugintimer.h" +#include "httpsimpleserver.h" +#include "httprequest.h" #include #include @@ -47,6 +49,7 @@ public: private: PluginTimer *m_pluginTimer = nullptr; + QHash m_httpSimpleServer; QHash m_httpRequests; void makeGetCall(Device *device); diff --git a/httpcommander/devicepluginhttpcommander.json b/httpcommander/devicepluginhttpcommander.json index 1f38407e..4109174e 100644 --- a/httpcommander/devicepluginhttpcommander.json +++ b/httpcommander/devicepluginhttpcommander.json @@ -165,6 +165,59 @@ "displayNameEvent": "Response data received" } ] + }, + { + + "id": "56efcdc3-c769-4e25-8a5b-c0affe68252a", + "name": "httpServer", + "displayName": "HTTP Server", + "createMethods": ["user"], + "interfaces": [ "inputtrigger" ], + "paramTypes": [ + { + "id": "438117cb-c2de-49d0-9f91-5988c17225f8", + "name": "port", + "displayName": "Port", + "type": "int", + "defaultValue": "8000" + } + ], + "eventTypes": [ + { + "id": "86f794c6-31ad-40a8-928f-4b8802506ce1", + "name": "triggered", + "displayName": "Http request received", + "paramTypes": [ + { + "id": "dd3c6033-0483-4237-ac15-7a64ae4dd29c", + "name": "requestType", + "displayName": "Request type", + "type": "QString", + "allowedValues": [ + "GET", + "POST", + "PUT", + "DELETE" + ], + "defaultValue": "GET" + }, + { + "id": "c936810e-a73d-424f-8981-48baf0a440bb", + "name": "body", + "displayName": "Body", + "type": "QString", + "defaultValue": "" + } , + { + "id": "0d4dc8f0-df0d-4fb0-b771-b62dee28a625", + "name": "path", + "displayName": "Path", + "type": "QString", + "defaultValue": "" + } + ] + } + ] } ] } diff --git a/httpcommander/httpcommander.pro b/httpcommander/httpcommander.pro index 97d4e1da..24fb6276 100644 --- a/httpcommander/httpcommander.pro +++ b/httpcommander/httpcommander.pro @@ -6,10 +6,12 @@ TARGET = $$qtLibraryTarget(nymea_devicepluginhttpcommander) SOURCES += \ devicepluginhttpcommander.cpp \ - httprequest.cpp + httprequest.cpp \ + httpsimpleserver.cpp HEADERS += \ devicepluginhttpcommander.h \ - httprequest.h + httprequest.h \ + httpsimpleserver.h diff --git a/httpcommander/httpsimpleserver.cpp b/httpcommander/httpsimpleserver.cpp new file mode 100644 index 00000000..bc889f76 --- /dev/null +++ b/httpcommander/httpsimpleserver.cpp @@ -0,0 +1,241 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stürz * + * Copyright (C) 2014 Michael Zanetti * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "httpsimpleserver.h" + +#include "devices/device.h" +#include "devices/deviceplugin.h" +#include "types/deviceclass.h" +#include "types/statetype.h" +#include "extern-plugininfo.h" + +#include +#include +#include +#include +#include +#include + +HttpSimpleServer::HttpSimpleServer(Device *device, DevicePlugin *parent): + QTcpServer(parent), + disabled(false), + m_plugin(parent), + m_device(device) +{ + //QHash portMap; + //portMap.insert(mockDeviceClassId, mockDeviceHttpportParamTypeId); + //portMap.insert(mockDeviceAutoDeviceClassId, mockDeviceAutoDeviceHttpportParamTypeId); + //listen(QHostAddress::Any, device->paramValue(portMap.value(device->deviceClassId())).toInt()); +} + +HttpSimpleServer::~HttpSimpleServer() +{ + close(); +} + +void HttpSimpleServer::incomingConnection(qintptr socket) +{ + if (disabled) + return; + + // When a new client connects, the server constructs a QTcpSocket and all + // communication with the client is done over this QTcpSocket. QTcpSocket + // works asynchronously, this means that all the communication is done + // in the two slots readClient() and discardClient(). + QTcpSocket* s = new QTcpSocket(this); + connect(s, SIGNAL(readyRead()), this, SLOT(readClient())); + connect(s, SIGNAL(disconnected()), this, SLOT(discardClient())); + s->setSocketDescriptor(socket); + +} + +void HttpSimpleServer::actionExecuted(const ActionTypeId &actionTypeId) +{ + m_actionList.append(qMakePair(actionTypeId, QDateTime::currentDateTime())); +} + +void HttpSimpleServer::readClient() +{ + if (disabled) + return; + + // This slot is called when the client sent data to the server. The + // server looks if it was a get request and sends a very simple HTML + // document back. + /*QTcpSocket* socket = (QTcpSocket*)sender(); + if (socket->canReadLine()) { + QByteArray data = socket->readLine(); + QStringList tokens = QString(data).split(QRegExp("[ \r\n][ \r\n]*")); + QUrl url("http://foo.bar" + tokens[1]); + QUrlQuery query(url); + if (url.path() == "/setstate") { + StateTypeId stateTypeId = StateTypeId(query.queryItems().first().first); + QVariant stateValue = query.queryItems().first().second; + if (stateTypeId == mockBoolStateTypeId || stateTypeId == mockBatteryCriticalStateTypeId) { + stateValue.convert(QVariant::Bool); + } else if (stateTypeId == mockIntStateTypeId) { + stateValue.convert(QVariant::Int); + } else if (stateTypeId == mockDoubleStateTypeId) { + stateValue.convert(QVariant::Double); + } + qCDebug(dcHttpCommander) << "Set state value" << stateValue; + emit setState(stateTypeId, stateValue); + } else if (url.path() == "/generateevent") { + emit triggerEvent(EventTypeId(query.queryItemValue("eventtypeid"))); + } else if (url.path() == "/actionhistory") { + qCDebug(dcHttpCommander) << "Get action history called"; + + QTextStream os(socket); + os.setAutoDetectUnicode(true); + os << generateHeader(); + for (int i = 0; i < m_actionList.count(); ++i) { + os << m_actionList.at(i).first.toString() << '\n'; + qCDebug(dcHttpCommander) << " " << m_actionList.at(i).first.toString(); + } + socket->close(); + return; + } else if (url.path() == "/clearactionhistory") { + qCDebug(dcHttpCommander) << "Clear action history"; + m_actionList.clear(); + } else if (url.path() == "/disappear") { + qCDebug(dcHttpCommander) << "Should disappear"; + emit disappear(); + } else if (url.path() == "/reconfigureautodevice") { + qCDebug(dcHttpCommander) << "Reconfigure auto device"; + emit reconfigureAutodevice(); + } + + if (tokens[0] == "GET") { + QTextStream os(socket); + os.setAutoDetectUnicode(true); + os << generateWebPage(); + socket->close(); + + if (socket->state() == QTcpSocket::UnconnectedState) + delete socket; + } + }*/ +} + +void HttpSimpleServer::discardClient() +{ + QTcpSocket* socket = (QTcpSocket*)sender(); + socket->deleteLater(); +} + +QString HttpSimpleServer::generateHeader() +{ + QString contentHeader( + "HTTP/1.0 200 Ok\r\n" + "Content-Type: text/html; charset=\"utf-8\"\r\n" + "\r\n" + ); + return contentHeader; +} + +QString HttpSimpleServer::generateWebPage() +{ + DeviceClass deviceClass; + foreach (const DeviceClass &dc, m_plugin->supportedDevices()) { + if (dc.id() == m_device->deviceClassId()) { + deviceClass = dc; + } + } + Q_ASSERT(deviceClass.isValid()); + + QString body = QString( + "" + "" + "

Mock device Controller

\n" + "
" + "

Device Information

" + "Name: %1
" + "ID: %2
" + "DeviceClass ID: %3
").arg(m_device->name()).arg(m_device->id().toString()).arg(deviceClass.id().toString()); + + body.append("
"); + body.append("

States

"); + + body.append(""); + for (int i = 0; i < deviceClass.stateTypes().count(); ++i) { + body.append(""); + body.append(""); + StateType stateType = deviceClass.stateTypes().at(i); + body.append(""); + body.append(QString("").arg(stateType.id().toString()).arg(m_device->states().at(i).value().toString())); + body.append(""); + body.append(""); + body.append(""); + } + body.append("
" + stateType.name() + "
"); + + body.append("
"); + body.append("

Events

"); + + body.append(""); + for (int i = 0; i < deviceClass.eventTypes().count(); ++i) { + EventType eventType = deviceClass.eventTypes().at(i); + body.append(QString( + "" + "" + "" + "" + "" + "" + ); + } + body.append("
%1").arg(eventType.name()).arg(eventType.id().toString())); + if (!eventType.displayName().endsWith(" changed")) { + body.append(QStringLiteral("")); + } + body.append("
"); + + body.append("
"); + body.append("

Actions

"); + + body.append(""); + body.append(""); + for (int i = 0; i < m_actionList.count(); ++i) { + ActionTypeId actionTypeId = ActionTypeId(m_actionList.at(i).first); + QDateTime timestamp = m_actionList.at(i).second; + QString actionName; + foreach (const ActionType &at, deviceClass.actionTypes()) { + if (at.id() == actionTypeId) { + actionName = at.name(); + break; + } + } + body.append(QString( + "" + "" + "" + "" + "" + ).arg(actionName).arg(actionTypeId.toString()).arg(timestamp.toString())); + } + body.append("
NameType IDTimestamp
%1%2%3
"); + + body.append("\n"); + + return generateHeader() + body; +} diff --git a/httpcommander/httpsimpleserver.h b/httpcommander/httpsimpleserver.h new file mode 100644 index 00000000..1dfe6a46 --- /dev/null +++ b/httpcommander/httpsimpleserver.h @@ -0,0 +1,69 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stürz * + * Copyright (C) 2014 Michael Zanetti * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef HTTPSIMPLESERVER_H +#define HTTPSIMPLESERVER_H + +#include "typeutils.h" + +#include +#include +#include + +class Device; +class DevicePlugin; + +class HttpSimpleServer : public QTcpServer +{ + Q_OBJECT +public: + HttpSimpleServer(Device *device, DevicePlugin* parent = nullptr); + ~HttpSimpleServer(); + void incomingConnection(qintptr socket) override; + + void actionExecuted(const ActionTypeId &actionTypeId); + +signals: + void setState(const StateTypeId &stateTypeId, const QVariant &value); + void triggerEvent(const EventTypeId &eventTypeId); + void disappear(); + void reconfigureAutodevice(); + +private slots: + void readClient(); + void discardClient(); + +private: + QString generateHeader(); + QString generateWebPage(); + +private: + bool disabled; + + DevicePlugin *m_plugin; + Device *m_device; + + QList > m_actionList; +}; + +#endif // HttpSimpleServer_H