From c614bbd54c2666017a77ce68f1d785f9ee1545ec Mon Sep 17 00:00:00 2001 From: nymea Date: Fri, 27 Sep 2019 13:11:11 +0200 Subject: [PATCH 1/8] added http request --- httpcommander/httpcommander.pro | 2 + httpcommander/httprequest.cpp | 238 ++++++++++++++++++++++++++++++++ httpcommander/httprequest.h | 85 ++++++++++++ 3 files changed, 325 insertions(+) create mode 100644 httpcommander/httprequest.cpp create mode 100644 httpcommander/httprequest.h diff --git a/httpcommander/httpcommander.pro b/httpcommander/httpcommander.pro index 53cb8f3f..97d4e1da 100644 --- a/httpcommander/httpcommander.pro +++ b/httpcommander/httpcommander.pro @@ -6,8 +6,10 @@ TARGET = $$qtLibraryTarget(nymea_devicepluginhttpcommander) SOURCES += \ devicepluginhttpcommander.cpp \ + httprequest.cpp HEADERS += \ devicepluginhttpcommander.h \ + httprequest.h diff --git a/httpcommander/httprequest.cpp b/httpcommander/httprequest.cpp new file mode 100644 index 00000000..c8c793b5 --- /dev/null +++ b/httpcommander/httprequest.cpp @@ -0,0 +1,238 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stürz * + * * + * This file is part of nymea. * + * * + * nymea 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. * + * * + * nymea 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 nymea. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +//This file has been copied from the libnymea-core library +//TODO create a http library + +#include "httprequest.h" +#include "loggingcategories.h" + +#include + +/*! Construct an empty \l{HttpRequest}. */ +HttpRequest::HttpRequest() : + m_rawData(QByteArray()), + m_valid(false), + m_isComplete(false) +{ +} + +/*! Construct a \l{HttpRequest} with the given \a rawData. The \a rawData will be parsed in this constructor. You can check + if the data is valid with \l{isValid()}. You can check if the request is complete with \l{isComplete}. + \sa isValid(), isComplete() +*/ +HttpRequest::HttpRequest(QByteArray rawData) : + m_rawData(rawData), + m_valid(false), + m_isComplete(false) +{ + validate(); +} + +/*! Returns the raw header of this request.*/ +QByteArray HttpRequest::rawHeader() const +{ + return m_rawHeader; +} + +/*! Returns the list of raw header as key and value pairs.*/ +QHash HttpRequest::rawHeaderList() const +{ + return m_rawHeaderList; +} + +/*! Returns the \l{RequestMethod} of this request. + \sa RequestMethod +*/ +HttpRequest::RequestMethod HttpRequest::method() const +{ + return m_method; +} + +/*! Returns the method as human readable string.*/ +QString HttpRequest::methodString() const +{ + return m_methodString; +} + +/*! Returns the HTTP version of this \l{HttpRequest}.*/ +QByteArray HttpRequest::httpVersion() const +{ + return m_httpVersion; +} + +/*! Returns the URL of this \l{HttpRequest}.*/ +QUrl HttpRequest::url() const +{ + return m_url; +} + +/*! Returns the URL query of this \l{HttpRequest}.*/ +QUrlQuery HttpRequest::urlQuery() const +{ + return m_urlQuery; +} + +/*! Returns the payload (content) of this \l{HttpRequest}.*/ +QByteArray HttpRequest::payload() const +{ + return m_payload; +} + +/*! Returns true if this \l{HttpRequest} is valid. A HTTP request is valid if the header and the payload were paresed successfully without errors.*/ +bool HttpRequest::isValid() const +{ + return m_valid; +} + +/*! Returns true if this \l{HttpRequest} is complete. A HTTP request is complete if "Content-Length" header value matches the actual payload size. Bigger packages will be sent in multiple TCP packages. */ +bool HttpRequest::isComplete() const +{ + return m_isComplete; +} + +/*! Returns true if this \l{HttpRequest} has a payload.*/ +bool HttpRequest::hasPayload() const +{ + return !m_payload.isEmpty(); +} + +/*! Appends the given \a data to the current raw data of this \l{HttpRequest}. + * This method will be used if a \l{HttpRequest} is not complete yet. + * + * \sa isComplete() +*/ +void HttpRequest::appendData(const QByteArray &data) +{ + m_rawData.append(data); + validate(); +} + +void HttpRequest::validate() +{ + m_isComplete = true; m_valid = false; + + // Parese the HTTP request. The request is invalid, until the end of the parse process. + if (m_rawData.isEmpty()) + return; + + // split the data into header and payload + int headerEndIndex = m_rawData.indexOf("\r\n\r\n"); + if (headerEndIndex < 0) { + qCWarning(dcWebServer()) << "Could not parse end of HTTP header (empty line between header and body):" << m_rawData; + return; + } + + m_rawHeader = m_rawData.left(headerEndIndex); + m_payload = m_rawData.right(m_rawData.length() - headerEndIndex).simplified(); + + // parse status line + QStringList headerLines = QString(m_rawHeader).split(QRegExp("\r\n")); + QString statusLine = headerLines.takeFirst(); + QStringList statusLineTokens = statusLine.split(QRegExp("[ \r\n][ \r\n]*")); + if (statusLineTokens.count() != 3) { + qCWarning(dcWebServer()) << "Could not parse HTTP status line:" << statusLine; + return; + } + + // verify http version + m_httpVersion = statusLineTokens.at(2).toUtf8().simplified(); + if (!m_httpVersion.contains("HTTP")) { + qCWarning(dcWebServer()) << "Unknown HTTP version:" << m_httpVersion; + return; + } + m_methodString = statusLineTokens.at(0).simplified(); + m_method = getRequestMethodType(m_methodString); + + m_url = QUrl("http://example.com" + statusLineTokens.at(1).simplified()); + + if (m_url.hasQuery()) + m_urlQuery = QUrlQuery(m_url.query()); + + // verify header formating + foreach (const QString &line, headerLines) { + if (!line.contains(":")) { + qCWarning(dcWebServer()) << "Invalid HTTP header:" << line; + return; + } + int index = line.indexOf(":"); + QByteArray key = line.left(index).toUtf8().simplified(); + QByteArray value = line.right(line.count() - index - 1).toUtf8().simplified(); + m_rawHeaderList.insert(key, value); + } + + // check User-Agent + if (!m_rawHeaderList.contains("User-Agent")) + qCWarning(dcWebServer()) << "User-Agent header is missing"; + + + // verify content length with actual payload + if (m_rawHeaderList.contains("Content-Length")) { + bool ok = false; + int contentLength = m_rawHeaderList.value("Content-Length").toInt(&ok); + if (!ok) { + qCWarning(dcWebServer()) << "Could not parse Content-Length."; + return; + } + // check if we have all data + if (m_payload.size() < contentLength) { + qCDebug(dcWebServer()) << "Request incomplete:"; + qCDebug(dcWebServer()) << " -> Content-Length:" << contentLength; + qCDebug(dcWebServer()) << " -> Payload size :" << payload().size(); + m_isComplete = false; + return; + } + // check if the content length bigger than header Content-Length + if (m_payload.size() > contentLength) { + qCWarning(dcWebServer()) << "Payload size greater than header Content-Length:"; + qCWarning(dcWebServer()) << " -> Content-Length:" << contentLength; + qCWarning(dcWebServer()) << " -> Payload size :" << payload().size(); + m_isComplete = true; + return; + } + + } + m_valid = true; +} + +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; + } else if (methodString == "OPTIONS") { + return RequestMethod::Options; + } + qCWarning(dcWebServer()) << "Method" << methodString << "will not be handled."; + return RequestMethod::Unhandled; +} + +QDebug operator<<(QDebug debug, const HttpRequest &httpRequest) +{ + debug << "HttpRequest:" << endl; + debug << qUtf8Printable(httpRequest.rawHeader()); + debug << qUtf8Printable(httpRequest.payload()); + return debug; +} diff --git a/httpcommander/httprequest.h b/httpcommander/httprequest.h new file mode 100644 index 00000000..e02f17a9 --- /dev/null +++ b/httpcommander/httprequest.h @@ -0,0 +1,85 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stürz * + * * + * This file is part of nymea. * + * * + * nymea 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. * + * * + * nymea 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 nymea. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef HTTPREQUEST_H +#define HTTPREQUEST_H + +#include +#include +#include +#include + +class HttpRequest +{ +public: + enum RequestMethod { + Get, + Post, + Put, + Delete, + Options, + Unhandled + }; + + HttpRequest(); + HttpRequest(QByteArray rawData); + + QByteArray rawHeader() const; + QHash rawHeaderList() const; + + RequestMethod method() const; + QString methodString() const; + QByteArray httpVersion() const; + + QUrl url() const; + QUrlQuery urlQuery() const; + + QByteArray payload() const; + + bool isValid() const; + bool isComplete() const; + bool hasPayload() const; + + void appendData(const QByteArray &data); + +private: + QByteArray m_rawData; + QByteArray m_rawHeader; + QHash m_rawHeaderList; + + RequestMethod m_method; + QString m_methodString; + QByteArray m_httpVersion; + + QUrl m_url; + QUrlQuery m_urlQuery; + + QByteArray m_payload; + + bool m_valid; + bool m_isComplete; + + void validate(); + RequestMethod getRequestMethodType(const QString &methodString); +}; + +QDebug operator<< (QDebug debug, const HttpRequest &httpRequest); + +#endif // HTTPREQUEST_H From bd5a7c9fbcb5098cc3a0a71bca6dd0f4a9454492 Mon Sep 17 00:00:00 2001 From: nymea Date: Fri, 27 Sep 2019 17:55:46 +0200 Subject: [PATCH 2/8] 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 From 253dbecc11b4ced49064be73b3d9125f0ba73eb7 Mon Sep 17 00:00:00 2001 From: nymea Date: Mon, 14 Oct 2019 13:45:07 +0200 Subject: [PATCH 3/8] some work on simplehttp server --- httpcommander/devicepluginhttpcommander.cpp | 5 +- httpcommander/httpsimpleserver.cpp | 170 ++------------------ httpcommander/httpsimpleserver.h | 16 +- 3 files changed, 22 insertions(+), 169 deletions(-) diff --git a/httpcommander/devicepluginhttpcommander.cpp b/httpcommander/devicepluginhttpcommander.cpp index c14df5e6..42cc15e4 100644 --- a/httpcommander/devicepluginhttpcommander.cpp +++ b/httpcommander/devicepluginhttpcommander.cpp @@ -72,8 +72,9 @@ void DevicePluginHttpCommander::setupDevice(DeviceSetupInfo *info) if (device->deviceClassId() == httpServerDeviceClassId) { //TODO create a simple server - HttpSimpleServer *httpSimpleServer = new HttpSimpleServer(); - m_httpSimpleServer.insert(device, httpSimpleServer); + HttpSimpleServer *httpSimpleServer = new HttpSimpleServer(device, this); + httpSimpleServer-> + m_httpSimpleServer.insert(httpSimpleServer); return info->finish(Device::DeviceErrorNoError); } info->finish(Device::DeviceErrorNoError); diff --git a/httpcommander/httpsimpleserver.cpp b/httpcommander/httpsimpleserver.cpp index bc889f76..c56562cb 100644 --- a/httpcommander/httpsimpleserver.cpp +++ b/httpcommander/httpsimpleserver.cpp @@ -23,9 +23,6 @@ #include "httpsimpleserver.h" -#include "devices/device.h" -#include "devices/deviceplugin.h" -#include "types/deviceclass.h" #include "types/statetype.h" #include "extern-plugininfo.h" @@ -36,16 +33,9 @@ #include #include -HttpSimpleServer::HttpSimpleServer(Device *device, DevicePlugin *parent): - QTcpServer(parent), - disabled(false), - m_plugin(parent), - m_device(device) +HttpSimpleServer::HttpSimpleServer(QObject *parent): + QTcpServer(parent) { - //QHash portMap; - //portMap.insert(mockDeviceClassId, mockDeviceHttpportParamTypeId); - //portMap.insert(mockDeviceAutoDeviceClassId, mockDeviceAutoDeviceHttpportParamTypeId); - //listen(QHostAddress::Any, device->paramValue(portMap.value(device->deviceClassId())).toInt()); } HttpSimpleServer::~HttpSimpleServer() @@ -55,9 +45,6 @@ HttpSimpleServer::~HttpSimpleServer() 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 @@ -69,173 +56,48 @@ void HttpSimpleServer::incomingConnection(qintptr 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(); + 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(); + os << generateHeader(); socket->close(); if (socket->state() == QTcpSocket::UnconnectedState) delete socket; + } else if (tokens[0] == "PUT") { + + } else if (tokens[0] == "POST") { + + } else if (tokens[0] == "DELETE") { + } - }*/ + } } void HttpSimpleServer::discardClient() { - QTcpSocket* socket = (QTcpSocket*)sender(); + QTcpSocket* socket = static_cast(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" - ); + "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 index 1dfe6a46..c3c0b04c 100644 --- a/httpcommander/httpsimpleserver.h +++ b/httpcommander/httpsimpleserver.h @@ -2,6 +2,7 @@ * * * Copyright (C) 2015 Simon Stürz * * Copyright (C) 2014 Michael Zanetti * + * Copyright (C) 2019 Bernhard Trinnes * * * * This file is part of nymea. * * * @@ -37,17 +38,14 @@ class HttpSimpleServer : public QTcpServer { Q_OBJECT public: - HttpSimpleServer(Device *device, DevicePlugin* parent = nullptr); + HttpSimpleServer(QObject* 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(); + void getRequestReceied(QUrl url); private slots: void readClient(); @@ -55,15 +53,7 @@ private slots: private: QString generateHeader(); - QString generateWebPage(); -private: - bool disabled; - - DevicePlugin *m_plugin; - Device *m_device; - - QList > m_actionList; }; #endif // HttpSimpleServer_H From 94635a8977d08ba1fef4650444e4a09da7a927a0 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Sun, 10 Nov 2019 21:15:28 +0100 Subject: [PATCH 4/8] added http server --- httpcommander/devicepluginhttpcommander.cpp | 5 ++--- httpcommander/httpsimpleserver.cpp | 2 +- httpcommander/httpsimpleserver.h | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/httpcommander/devicepluginhttpcommander.cpp b/httpcommander/devicepluginhttpcommander.cpp index 42cc15e4..0b4ff8e2 100644 --- a/httpcommander/devicepluginhttpcommander.cpp +++ b/httpcommander/devicepluginhttpcommander.cpp @@ -72,9 +72,8 @@ void DevicePluginHttpCommander::setupDevice(DeviceSetupInfo *info) if (device->deviceClassId() == httpServerDeviceClassId) { //TODO create a simple server - HttpSimpleServer *httpSimpleServer = new HttpSimpleServer(device, this); - httpSimpleServer-> - m_httpSimpleServer.insert(httpSimpleServer); + HttpSimpleServer *httpSimpleServer = new HttpSimpleServer(this); + m_httpSimpleServer.insert(device, httpSimpleServer); return info->finish(Device::DeviceErrorNoError); } info->finish(Device::DeviceErrorNoError); diff --git a/httpcommander/httpsimpleserver.cpp b/httpcommander/httpsimpleserver.cpp index c56562cb..4ad0d338 100644 --- a/httpcommander/httpsimpleserver.cpp +++ b/httpcommander/httpsimpleserver.cpp @@ -61,7 +61,7 @@ void HttpSimpleServer::readClient() // 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(); + QTcpSocket* socket = static_cast(sender()); if (socket->canReadLine()) { QByteArray data = socket->readLine(); QStringList tokens = QString(data).split(QRegExp("[ \r\n][ \r\n]*")); diff --git a/httpcommander/httpsimpleserver.h b/httpcommander/httpsimpleserver.h index c3c0b04c..393037cb 100644 --- a/httpcommander/httpsimpleserver.h +++ b/httpcommander/httpsimpleserver.h @@ -39,7 +39,7 @@ class HttpSimpleServer : public QTcpServer Q_OBJECT public: HttpSimpleServer(QObject* parent = nullptr); - ~HttpSimpleServer(); + ~HttpSimpleServer() override; void incomingConnection(qintptr socket) override; signals: From 8a0d30605bb3dceafc097d32d430297ad046b96b Mon Sep 17 00:00:00 2001 From: Boernsman Date: Sun, 10 Nov 2019 22:54:43 +0100 Subject: [PATCH 5/8] first working version --- httpcommander/devicepluginhttpcommander.cpp | 30 ++++++++++- httpcommander/devicepluginhttpcommander.h | 3 ++ httpcommander/devicepluginhttpcommander.json | 10 ++++ httpcommander/httpsimpleserver.cpp | 57 +++++++++++++++----- httpcommander/httpsimpleserver.h | 10 ++-- 5 files changed, 92 insertions(+), 18 deletions(-) diff --git a/httpcommander/devicepluginhttpcommander.cpp b/httpcommander/devicepluginhttpcommander.cpp index 0b4ff8e2..a6548505 100644 --- a/httpcommander/devicepluginhttpcommander.cpp +++ b/httpcommander/devicepluginhttpcommander.cpp @@ -24,6 +24,7 @@ #include "devicepluginhttpcommander.h" #include "network/networkaccessmanager.h" #include "plugininfo.h" +#include DevicePluginHttpCommander::DevicePluginHttpCommander() { @@ -71,9 +72,20 @@ void DevicePluginHttpCommander::setupDevice(DeviceSetupInfo *info) } if (device->deviceClassId() == httpServerDeviceClassId) { - //TODO create a simple server + HttpSimpleServer *httpSimpleServer = new HttpSimpleServer(this); + connect(httpSimpleServer, &HttpSimpleServer::requestReceived, this, &DevicePluginHttpCommander::onHttpSimpleServerRequestReceived); m_httpSimpleServer.insert(device, httpSimpleServer); + + QString interfaces; + foreach (const QNetworkInterface &interface, QNetworkInterface::allInterfaces()) { + foreach (QNetworkAddressEntry entry, interface.addressEntries()) { + if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol && entry.ip().toString() != "127.0.0.1") { + interfaces.append(entry.ip().toString()); + } + } + } + device->setStateValue(httpServerServerAddressStateTypeId, interfaces); return info->finish(Device::DeviceErrorNoError); } info->finish(Device::DeviceErrorNoError); @@ -135,7 +147,7 @@ void DevicePluginHttpCommander::makeGetCall(Device *device) url.setPort(device->paramValue(httpGetCommanderDevicePortParamTypeId).toInt()); QNetworkRequest request; request.setUrl(url); - request.setRawHeader("User-Agent", "nymea 1.0"); + request.setRawHeader("User-Agent", "nymea http commander"); QNetworkReply *reply = hardwareManager()->networkManager()->get(request); connect(reply, &QNetworkReply::finished, this, &DevicePluginHttpCommander::onGetRequestFinished); @@ -221,6 +233,20 @@ void DevicePluginHttpCommander::onPutRequestFinished() reply->deleteLater(); } +void DevicePluginHttpCommander::onHttpSimpleServerRequestReceived(const QString &type, const QString &path, const QString &body) +{ + //qCDebug(dcHttpCommander()) << "Request recieved" << type << body; + HttpSimpleServer *httpServer = static_cast(sender()); + Device *device = m_httpSimpleServer.key(httpServer); + Event ev = Event(httpServerTriggeredEventTypeId, device->id()); + ParamList params; + params.append(Param(httpServerTriggeredEventRequestTypeParamTypeId, type)); + params.append(Param(httpServerTriggeredEventPathParamTypeId, path)); + params.append(Param(httpServerTriggeredEventBodyParamTypeId, body)); + ev.setParams(params); + emit emitEvent(ev); +} + void DevicePluginHttpCommander::deviceRemoved(Device *device) { diff --git a/httpcommander/devicepluginhttpcommander.h b/httpcommander/devicepluginhttpcommander.h index e2c03859..c255a397 100644 --- a/httpcommander/devicepluginhttpcommander.h +++ b/httpcommander/devicepluginhttpcommander.h @@ -40,6 +40,7 @@ class DevicePluginHttpCommander : public DevicePlugin Q_INTERFACES(DevicePlugin) public: + explicit DevicePluginHttpCommander(); void setupDevice(DeviceSetupInfo *info) override; @@ -60,6 +61,8 @@ private slots: void onGetRequestFinished(); void onPostRequestFinished(); void onPutRequestFinished(); + + void onHttpSimpleServerRequestReceived(const QString &type, const QString &path, const QString &body); }; #endif // DEVICEPLUGINHTTPCOMMANDER_H diff --git a/httpcommander/devicepluginhttpcommander.json b/httpcommander/devicepluginhttpcommander.json index 4109174e..ff3830b0 100644 --- a/httpcommander/devicepluginhttpcommander.json +++ b/httpcommander/devicepluginhttpcommander.json @@ -182,6 +182,16 @@ "defaultValue": "8000" } ], + "stateTypes": [ + { + "id": "f4458008-67f2-4ba1-bbca-e85771a30ddc", + "name": "serverAddress", + "displayName": "Server address", + "displayNameEvent": "Server address changed", + "type": "QString", + "defaultValue": "127.0.0.1" + } + ], "eventTypes": [ { "id": "86f794c6-31ad-40a8-928f-4b8802506ce1", diff --git a/httpcommander/httpsimpleserver.cpp b/httpcommander/httpsimpleserver.cpp index 4ad0d338..a13838d3 100644 --- a/httpcommander/httpsimpleserver.cpp +++ b/httpcommander/httpsimpleserver.cpp @@ -36,11 +36,13 @@ HttpSimpleServer::HttpSimpleServer(QObject *parent): QTcpServer(parent) { + listen(QHostAddress::Any, 7777); } HttpSimpleServer::~HttpSimpleServer() { close(); + } void HttpSimpleServer::incomingConnection(qintptr socket) @@ -49,10 +51,10 @@ void HttpSimpleServer::incomingConnection(qintptr socket) // 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); + QTcpSocket* tcpSocket = new QTcpSocket(this); + connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(readClient())); + connect(tcpSocket, SIGNAL(disconnected()), this, SLOT(discardClient())); + tcpSocket->setSocketDescriptor(socket); } @@ -61,27 +63,56 @@ void HttpSimpleServer::readClient() // 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 = static_cast(sender()); - if (socket->canReadLine()) { - QByteArray data = socket->readLine(); + QTcpSocket* tcpSocket = static_cast(sender()); + if (tcpSocket->canReadLine()) { + + QByteArray data = tcpSocket->readAll(); QStringList tokens = QString(data).split(QRegExp("[ \r\n][ \r\n]*")); - QUrl url("http://foo.bar" + tokens[1]); - QUrlQuery query(url); + //QUrl url("http://foo.bar" + tokens[1]); + //QUrlQuery query(url); + qCDebug(dcHttpCommander()) << "Http Request, type" << tokens[0] << "path" << tokens[1] << "body" << tokens.last(); if (tokens[0] == "GET") { - QTextStream os(socket); + QTextStream os(tcpSocket); os.setAutoDetectUnicode(true); os << generateHeader(); - socket->close(); + tcpSocket->close(); - if (socket->state() == QTcpSocket::UnconnectedState) - delete socket; + if (tcpSocket->state() == QTcpSocket::UnconnectedState) + delete tcpSocket; + + emit requestReceived(tokens[0], tokens[1], tokens.last()); } else if (tokens[0] == "PUT") { + QTextStream os(tcpSocket); + os.setAutoDetectUnicode(true); + os << generateHeader(); + tcpSocket->close(); + + if (tcpSocket->state() == QTcpSocket::UnconnectedState) + delete tcpSocket; + + emit requestReceived(tokens[0], tokens[1], tokens.last()); } else if (tokens[0] == "POST") { + QTextStream os(tcpSocket); + os.setAutoDetectUnicode(true); + os << generateHeader(); + tcpSocket->close(); + + if (tcpSocket->state() == QTcpSocket::UnconnectedState) + delete tcpSocket; + emit requestReceived(tokens[0], tokens[1], tokens.last()); } else if (tokens[0] == "DELETE") { + QTextStream os(tcpSocket); + os.setAutoDetectUnicode(true); + os << generateHeader(); + tcpSocket->close(); + if (tcpSocket->state() == QTcpSocket::UnconnectedState) + delete tcpSocket; + + emit requestReceived(tokens[0], tokens[1], tokens.last()); } } } diff --git a/httpcommander/httpsimpleserver.h b/httpcommander/httpsimpleserver.h index 393037cb..ab8c1993 100644 --- a/httpcommander/httpsimpleserver.h +++ b/httpcommander/httpsimpleserver.h @@ -22,14 +22,15 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -#ifndef HTTPSIMPLESERVER_H -#define HTTPSIMPLESERVER_H +#ifndef HTTPSIMPLESERVER1_H +#define HTTPSIMPLESERVER1_H #include "typeutils.h" #include #include #include +#include class Device; class DevicePlugin; @@ -38,6 +39,7 @@ class HttpSimpleServer : public QTcpServer { Q_OBJECT public: + HttpSimpleServer(QObject* parent = nullptr); ~HttpSimpleServer() override; void incomingConnection(qintptr socket) override; @@ -45,7 +47,9 @@ public: signals: void disappear(); void reconfigureAutodevice(); - void getRequestReceied(QUrl url); + //void getRequestReceied(QUrl url); + + void requestReceived(const QString &type, const QString &path, const QString &body); private slots: void readClient(); From e365e16537c4803e5e7dfb0cd386636d9cce6092 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Tue, 19 Nov 2019 17:51:04 +0100 Subject: [PATCH 6/8] cleaned up http server --- httpcommander/devicepluginhttpcommander.cpp | 1 - httpcommander/httpsimpleserver.cpp | 38 +++------------------ 2 files changed, 4 insertions(+), 35 deletions(-) diff --git a/httpcommander/devicepluginhttpcommander.cpp b/httpcommander/devicepluginhttpcommander.cpp index a6548505..fbcc5bd5 100644 --- a/httpcommander/devicepluginhttpcommander.cpp +++ b/httpcommander/devicepluginhttpcommander.cpp @@ -47,7 +47,6 @@ void DevicePluginHttpCommander::setupDevice(DeviceSetupInfo *info) //: Error setting up device return info->finish(Device::DeviceErrorInvalidParameter, QT_TR_NOOP("The given url is not valid.")); } - return info->finish(Device::DeviceErrorNoError); } diff --git a/httpcommander/httpsimpleserver.cpp b/httpcommander/httpsimpleserver.cpp index a13838d3..e1d2b012 100644 --- a/httpcommander/httpsimpleserver.cpp +++ b/httpcommander/httpsimpleserver.cpp @@ -42,7 +42,6 @@ HttpSimpleServer::HttpSimpleServer(QObject *parent): HttpSimpleServer::~HttpSimpleServer() { close(); - } void HttpSimpleServer::incomingConnection(qintptr socket) @@ -68,42 +67,13 @@ void HttpSimpleServer::readClient() QByteArray data = tcpSocket->readAll(); QStringList tokens = QString(data).split(QRegExp("[ \r\n][ \r\n]*")); - //QUrl url("http://foo.bar" + tokens[1]); - //QUrlQuery query(url); qCDebug(dcHttpCommander()) << "Http Request, type" << tokens[0] << "path" << tokens[1] << "body" << tokens.last(); - if (tokens[0] == "GET") { - QTextStream os(tcpSocket); - os.setAutoDetectUnicode(true); - os << generateHeader(); - tcpSocket->close(); + if ((tokens[0] == "GET") || + (tokens[0] == "PUT") || + (tokens[0] == "POST") || + (tokens[0] == "DELETE")) { - if (tcpSocket->state() == QTcpSocket::UnconnectedState) - delete tcpSocket; - - emit requestReceived(tokens[0], tokens[1], tokens.last()); - } else if (tokens[0] == "PUT") { - QTextStream os(tcpSocket); - os.setAutoDetectUnicode(true); - os << generateHeader(); - tcpSocket->close(); - - if (tcpSocket->state() == QTcpSocket::UnconnectedState) - delete tcpSocket; - - emit requestReceived(tokens[0], tokens[1], tokens.last()); - - } else if (tokens[0] == "POST") { - QTextStream os(tcpSocket); - os.setAutoDetectUnicode(true); - os << generateHeader(); - tcpSocket->close(); - - if (tcpSocket->state() == QTcpSocket::UnconnectedState) - delete tcpSocket; - emit requestReceived(tokens[0], tokens[1], tokens.last()); - - } else if (tokens[0] == "DELETE") { QTextStream os(tcpSocket); os.setAutoDetectUnicode(true); os << generateHeader(); From 747a3481524efcc8eacf5bacf7a3b2461086bbc2 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Tue, 19 Nov 2019 17:55:59 +0100 Subject: [PATCH 7/8] Updated readme --- httpcommander/README.md | 4 ++ ...2670c-6268-4487-8dff-cccca498731a-en_US.ts | 41 ++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/httpcommander/README.md b/httpcommander/README.md index 9a3d2c8d..cc6ac9df 100644 --- a/httpcommander/README.md +++ b/httpcommander/README.md @@ -1,3 +1,7 @@ # HTTP commander +## HTTP PUT/POST/GET Commander The HTTP commander allows you to execute HTTP methods on a HTTP server and is ment as a generic way to interact with a server. + +## HTTP Server +Simple HTTP Server to receive GET/POST/PUT/DELETE requests. Emits an event including HTTP request type, Url and body as parameter. diff --git a/httpcommander/translations/4e62670c-6268-4487-8dff-cccca498731a-en_US.ts b/httpcommander/translations/4e62670c-6268-4487-8dff-cccca498731a-en_US.ts index e77f76e8..73b3610c 100644 --- a/httpcommander/translations/4e62670c-6268-4487-8dff-cccca498731a-en_US.ts +++ b/httpcommander/translations/4e62670c-6268-4487-8dff-cccca498731a-en_US.ts @@ -20,7 +20,9 @@ The name of the ParamType (DeviceClass: httpPostCommander, Type: device, ID: {02 Port - The name of the ParamType (DeviceClass: httpGetCommander, Type: device, ID: {bee8b151-815a-4159-9d8a-42b76e99b42c}) + The name of the ParamType (DeviceClass: httpServer, Type: device, ID: {438117cb-c2de-49d0-9f91-5988c17225f8}) +---------- +The name of the ParamType (DeviceClass: httpGetCommander, Type: device, ID: {bee8b151-815a-4159-9d8a-42b76e99b42c}) ---------- The name of the ParamType (DeviceClass: httpPutCommander, Type: device, ID: {db994349-1105-4ce5-b6fe-6fd38fbc436a}) ---------- @@ -128,5 +130,42 @@ The name of the StateType ({8daac0e7-4c2f-4cdf-b528-02cfe04c6b39}) of DeviceClas The name of the DeviceClass ({05bf65f5-ff13-43e3-b6ae-77019e79d8a1}) + + Body + The name of the ParamType (DeviceClass: httpServer, EventType: triggered, ID: {c936810e-a73d-424f-8981-48baf0a440bb}) + + + + HTTP Server + The name of the DeviceClass ({56efcdc3-c769-4e25-8a5b-c0affe68252a}) + + + + Http request received + The name of the EventType ({86f794c6-31ad-40a8-928f-4b8802506ce1}) of DeviceClass httpServer + + + + Path + The name of the ParamType (DeviceClass: httpServer, EventType: triggered, ID: {0d4dc8f0-df0d-4fb0-b771-b62dee28a625}) + + + + Request type + The name of the ParamType (DeviceClass: httpServer, EventType: triggered, ID: {dd3c6033-0483-4237-ac15-7a64ae4dd29c}) + + + + Server address + The name of the ParamType (DeviceClass: httpServer, EventType: serverAddress, ID: {f4458008-67f2-4ba1-bbca-e85771a30ddc}) +---------- +The name of the StateType ({f4458008-67f2-4ba1-bbca-e85771a30ddc}) of DeviceClass httpServer + + + + Server address changed + The name of the EventType ({f4458008-67f2-4ba1-bbca-e85771a30ddc}) of DeviceClass httpServer + + From d43c2501cd1613ebe212d9970674585c60fb331e Mon Sep 17 00:00:00 2001 From: Boernsman Date: Thu, 21 Nov 2019 17:09:47 +0100 Subject: [PATCH 8/8] merged http requests into one device --- httpcommander/README.md | 8 +- httpcommander/devicepluginhttpcommander.cpp | 212 +++------------- httpcommander/devicepluginhttpcommander.h | 12 - httpcommander/devicepluginhttpcommander.json | 135 ++-------- httpcommander/httpcommander.pro | 2 - httpcommander/httprequest.cpp | 238 ------------------ httpcommander/httprequest.h | 85 ------- httpcommander/httpsimpleserver.cpp | 4 +- httpcommander/httpsimpleserver.h | 2 +- ...2670c-6268-4487-8dff-cccca498731a-en_US.ts | 108 ++------ 10 files changed, 85 insertions(+), 721 deletions(-) delete mode 100644 httpcommander/httprequest.cpp delete mode 100644 httpcommander/httprequest.h diff --git a/httpcommander/README.md b/httpcommander/README.md index cc6ac9df..fd13a348 100644 --- a/httpcommander/README.md +++ b/httpcommander/README.md @@ -1,7 +1,11 @@ # HTTP commander -## HTTP PUT/POST/GET Commander -The HTTP commander allows you to execute HTTP methods on a HTTP server and is ment as a generic way to interact with a server. +The HTTP commander allows you to send and reqceive generiv HTTP requests. + +## HTTP Request + +Send simple HTTP GET/POST/PUT/DELETE requests. URL and port will be defined during setup, body and HTTP method can be set within every request. ## HTTP Server + Simple HTTP Server to receive GET/POST/PUT/DELETE requests. Emits an event including HTTP request type, Url and body as parameter. diff --git a/httpcommander/devicepluginhttpcommander.cpp b/httpcommander/devicepluginhttpcommander.cpp index fbcc5bd5..962598ed 100644 --- a/httpcommander/devicepluginhttpcommander.cpp +++ b/httpcommander/devicepluginhttpcommander.cpp @@ -35,33 +35,10 @@ void DevicePluginHttpCommander::setupDevice(DeviceSetupInfo *info) Device *device = info->device(); qDebug(dcHttpCommander()) << "Setup device" << device->name() << device->params(); - if(!m_pluginTimer) { - m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60); - connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginHttpCommander::onPluginTimer); - } - if (device->deviceClassId() == httpGetCommanderDeviceClassId) { - QUrl url = device->paramValue(httpGetCommanderDeviceUrlParamTypeId).toUrl(); - if (!url.isValid()) { - qDebug(dcHttpCommander()) << "Given URL is not valid"; - //: Error setting up device - return info->finish(Device::DeviceErrorInvalidParameter, QT_TR_NOOP("The given url is not valid.")); - } - return info->finish(Device::DeviceErrorNoError); - } + if (device->deviceClassId() == httpRequestDeviceClassId) { + QUrl url = device->paramValue(httpRequestDeviceUrlParamTypeId).toUrl(); - if (device->deviceClassId() == httpPutCommanderDeviceClassId) { - QUrl url = device->paramValue(httpPutCommanderDeviceUrlParamTypeId).toUrl(); - if (!url.isValid()) { - qDebug(dcHttpCommander()) << "Given URL is not valid"; - //: Error setting up device - return info->finish(Device::DeviceErrorInvalidParameter, QT_TR_NOOP("The given url is not valid.")); - } - return info->finish(Device::DeviceErrorNoError); - } - - if (device->deviceClassId() == httpPostCommanderDeviceClassId) { - QUrl url = device->paramValue(httpPostCommanderDeviceUrlParamTypeId).toUrl(); if (!url.isValid()) { qDebug(dcHttpCommander()) << "Given URL is not valid"; //: Error setting up device @@ -71,167 +48,62 @@ void DevicePluginHttpCommander::setupDevice(DeviceSetupInfo *info) } if (device->deviceClassId() == httpServerDeviceClassId) { - - HttpSimpleServer *httpSimpleServer = new HttpSimpleServer(this); + quint16 port = static_cast(device->paramValue(httpServerDevicePortParamTypeId).toUInt()); + HttpSimpleServer *httpSimpleServer = new HttpSimpleServer(port, this); connect(httpSimpleServer, &HttpSimpleServer::requestReceived, this, &DevicePluginHttpCommander::onHttpSimpleServerRequestReceived); m_httpSimpleServer.insert(device, httpSimpleServer); - QString interfaces; - foreach (const QNetworkInterface &interface, QNetworkInterface::allInterfaces()) { - foreach (QNetworkAddressEntry entry, interface.addressEntries()) { - if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol && entry.ip().toString() != "127.0.0.1") { - interfaces.append(entry.ip().toString()); - } - } - } - device->setStateValue(httpServerServerAddressStateTypeId, interfaces); return info->finish(Device::DeviceErrorNoError); } info->finish(Device::DeviceErrorNoError); } - -void DevicePluginHttpCommander::postSetupDevice(Device *device) -{ - if (device->deviceClassId() == httpGetCommanderDeviceClassId) { - makeGetCall(device); - } -} - - void DevicePluginHttpCommander::executeAction(DeviceActionInfo *info) { Device *device = info->device(); Action action = info->action(); - if (device->deviceClassId() == httpPostCommanderDeviceClassId) { + if (device->deviceClassId() == httpRequestDeviceClassId) { - if (action.actionTypeId() == httpPostCommanderPostActionTypeId) { - QUrl url = device->paramValue(httpPostCommanderDeviceUrlParamTypeId).toUrl(); - url.setPort(device->paramValue(httpPostCommanderDevicePortParamTypeId).toInt()); - QByteArray payload = action.param(httpPostCommanderPostActionDataParamTypeId).value().toByteArray(); + if (action.actionTypeId() == httpRequestRequestActionTypeId) { + QUrl url = device->paramValue(httpRequestDeviceUrlParamTypeId).toUrl(); + url.setPort(device->paramValue(httpRequestDevicePortParamTypeId).toInt()); + QString method = action.param(httpRequestRequestActionMethodParamTypeId).value().toString(); + QByteArray payload = action.param(httpRequestRequestActionBodyParamTypeId).value().toByteArray(); - QNetworkReply *reply = hardwareManager()->networkManager()->post(QNetworkRequest(url), payload); - connect(reply, &QNetworkReply::finished, this, &DevicePluginHttpCommander::onPostRequestFinished); + QNetworkReply *reply; + if (method == "GET") { + reply = hardwareManager()->networkManager()->get(QNetworkRequest(url)); + } else if (method == "POST") { + reply = hardwareManager()->networkManager()->post(QNetworkRequest(url), payload); + } else if (method == "PUT") { + reply = hardwareManager()->networkManager()->put(QNetworkRequest(url), payload); + } else if (method == "DELETE") { + reply = hardwareManager()->networkManager()->deleteResource(QNetworkRequest(url)); + } + connect(reply, &QNetworkReply::finished, this, [device, reply, this](){ - m_httpRequests.insert(reply, device); + qDebug(dcHttpCommander()) << "POST reply finished"; + QByteArray data = reply->readAll(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + device->setStateValue(httpRequestResponseStateTypeId, data); + device->setStateValue(httpRequestStatusStateTypeId, status); + + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + qCWarning(dcHttpCommander()) << "Request error:" << status << reply->errorString(); + } + reply->deleteLater(); + }); return info->finish(Device::DeviceErrorNoError); } return info->finish(Device::DeviceErrorActionTypeNotFound); } - - if (device->deviceClassId() == httpPutCommanderDeviceClassId) { - - // check if this is the "press" action - if (action.actionTypeId() == httpPutCommanderPutActionTypeId) { - QUrl url = device->paramValue(httpPutCommanderDeviceUrlParamTypeId).toUrl(); - url.setPort(device->paramValue(httpPutCommanderDevicePortParamTypeId).toInt()); - QByteArray payload = action.param(httpPutCommanderPutActionDataParamTypeId).value().toByteArray(); - - QNetworkReply *reply = hardwareManager()->networkManager()->put(QNetworkRequest(url), payload); - connect(reply, &QNetworkReply::finished, this, &DevicePluginHttpCommander::onPutRequestFinished); - - m_httpRequests.insert(reply, device); - - return info->finish(Device::DeviceErrorNoError); - } - } return info->finish(Device::DeviceErrorDeviceClassNotFound); } -void DevicePluginHttpCommander::makeGetCall(Device *device) -{ - QUrl url = device->paramValue(httpGetCommanderDeviceUrlParamTypeId).toUrl(); - url.setPort(device->paramValue(httpGetCommanderDevicePortParamTypeId).toInt()); - QNetworkRequest request; - request.setUrl(url); - request.setRawHeader("User-Agent", "nymea http commander"); - - QNetworkReply *reply = hardwareManager()->networkManager()->get(request); - connect(reply, &QNetworkReply::finished, this, &DevicePluginHttpCommander::onGetRequestFinished); - - m_httpRequests.insert(reply, device); -} - -void DevicePluginHttpCommander::onPluginTimer() -{ - foreach (Device *device, myDevices()) { - if (device->deviceClassId() == httpGetCommanderDeviceClassId) { - makeGetCall(device); - } - } -} - -void DevicePluginHttpCommander::onGetRequestFinished() -{ - QNetworkReply *reply = static_cast(sender()); - qDebug(dcHttpCommander()) << "GET reply finished"; - QByteArray data = reply->readAll(); - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - if (!m_httpRequests.contains(reply)) { - reply->deleteLater(); - return; - } - - Device *device = m_httpRequests.take(reply); - device->setStateValue(httpGetCommanderResponseStateTypeId, data); - device->setStateValue(httpGetCommanderStatusStateTypeId, true); - - // Check HTTP status code - if (status != 200 || reply->error() != QNetworkReply::NoError) { - qCWarning(dcHttpCommander()) << "Request error:" << status << reply->errorString(); - } - reply->deleteLater(); -} - -void DevicePluginHttpCommander::onPostRequestFinished() -{ - QNetworkReply *reply = static_cast(sender()); - qDebug(dcHttpCommander()) << "POST reply finished"; - QByteArray data = reply->readAll(); - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - if (!m_httpRequests.contains(reply)) { - reply->deleteLater(); - return; - } - - Device *device = m_httpRequests.take(reply); - device->setStateValue(httpPostCommanderResponseStateTypeId, data); - device->setStateValue(httpPostCommanderStatusStateTypeId, status); - - // Check HTTP status code - if (status != 200 || reply->error() != QNetworkReply::NoError) { - qCWarning(dcHttpCommander()) << "Request error:" << status << reply->errorString(); - } - reply->deleteLater(); -} - -void DevicePluginHttpCommander::onPutRequestFinished() -{ - QNetworkReply *reply = static_cast(sender()); - qDebug(dcHttpCommander()) << "PUT reply finished"; - QByteArray data = reply->readAll(); - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - if (!m_httpRequests.contains(reply)) { - reply->deleteLater(); - return; - } - - Device *device = m_httpRequests.take(reply); - device->setStateValue(httpPutCommanderResponseStateTypeId, data); - device->setStateValue(httpPutCommanderStatusStateTypeId, status); - - // Check HTTP status code - if (status != 200 || reply->error() != QNetworkReply::NoError) { - qCWarning(dcHttpCommander()) << "Request error:" << status << reply->errorString(); - } - reply->deleteLater(); -} - void DevicePluginHttpCommander::onHttpSimpleServerRequestReceived(const QString &type, const QString &path, const QString &body) { //qCDebug(dcHttpCommander()) << "Request recieved" << type << body; @@ -249,18 +121,8 @@ void DevicePluginHttpCommander::onHttpSimpleServerRequestReceived(const QString void DevicePluginHttpCommander::deviceRemoved(Device *device) { - if ((device->deviceClassId() == httpPostCommanderDeviceClassId) || - (device->deviceClassId() == httpPutCommanderDeviceClassId) || - (device->deviceClassId() == httpGetCommanderDeviceClassId)) { - - while (m_httpRequests.values().contains(device)) { - QNetworkReply *reply = m_httpRequests.key(device); - m_httpRequests.remove(reply); - reply->deleteLater(); - } - } - - if (myDevices().empty()) { - hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); + if (device->deviceClassId() == httpServerDeviceClassId) { + HttpSimpleServer* httpSimpleServer= m_httpSimpleServer.take(device); + httpSimpleServer->deleteLater(); } } diff --git a/httpcommander/devicepluginhttpcommander.h b/httpcommander/devicepluginhttpcommander.h index c255a397..68443c5e 100644 --- a/httpcommander/devicepluginhttpcommander.h +++ b/httpcommander/devicepluginhttpcommander.h @@ -27,7 +27,6 @@ #include "devices/deviceplugin.h" #include "plugintimer.h" #include "httpsimpleserver.h" -#include "httprequest.h" #include #include @@ -44,24 +43,13 @@ public: explicit DevicePluginHttpCommander(); void setupDevice(DeviceSetupInfo *info) override; - void postSetupDevice(Device *device) override; void deviceRemoved(Device *device) override; void executeAction(DeviceActionInfo *info) override; private: - PluginTimer *m_pluginTimer = nullptr; QHash m_httpSimpleServer; - QHash m_httpRequests; - - void makeGetCall(Device *device); private slots: - void onPluginTimer(); - - void onGetRequestFinished(); - void onPostRequestFinished(); - void onPutRequestFinished(); - void onHttpSimpleServerRequestReceived(const QString &type, const QString &path, const QString &body); }; diff --git a/httpcommander/devicepluginhttpcommander.json b/httpcommander/devicepluginhttpcommander.json index ff3830b0..cf7d42cb 100644 --- a/httpcommander/devicepluginhttpcommander.json +++ b/httpcommander/devicepluginhttpcommander.json @@ -10,8 +10,8 @@ "deviceClasses": [ { "id": "b101abdf-86fd-4d2e-a657-ee76044235bd", - "name": "httpPostCommander", - "displayName": "HTTP post", + "name": "httpRequest", + "displayName": "HTTP Request", "createMethods": ["user"], "interfaces": [ ], "paramTypes": [ @@ -21,7 +21,7 @@ "displayName": "Address", "type": "QString", "inputType": "None", - "defaultValue": "https://httpbin.org/post" + "defaultValue": "https://httpbin.org/get" }, { "id": "37830ea8-2249-46e6-aaca-12164928a81a", @@ -43,7 +43,7 @@ { "id": "69f32ec8-114d-43f4-9241-1f6a57261f32", "name": "response", - "displayName": "response", + "displayName": "Response", "displayNameEvent": "Response received", "type": "QString", "defaultValue": "" @@ -52,120 +52,33 @@ "actionTypes": [ { "id": "5a97ca56-b334-411b-adba-116496ffe83d", - "name": "post", - "displayName": "Post data", + "name": "request", + "displayName": "Request", "paramTypes": [ { "id": "363119a3-c02c-4ed5-a915-11706198f3eb", - "name": "data", - "displayName": "Data", + "name": "body", + "displayName": "Body", "type": "QString", "defaultValue": "" - } - ] - } - ] - }, - { - "id": "05bf65f5-ff13-43e3-b6ae-77019e79d8a1", - "name": "httpPutCommander", - "displayName": "HTTP put", - "createMethods": ["user"], - "interfaces": [ ], - "paramTypes": [ - { - "id": "1a3fcb23-931b-4ba1-b134-c49b656c76f7", - "name": "url", - "displayName": "Address", - "type": "QString", - "inputType": "None", - "defaultValue": "https://httpbin.org/put" - }, - { - "id": "db994349-1105-4ce5-b6fe-6fd38fbc436a", - "name": "port", - "displayName": "Port", - "type": "int", - "defaultValue": "443" - } - ], - "stateTypes": [ - { - "id": "4959c589-4550-479f-ae11-c4d1097ce3d5", - "name": "status", - "displayName": "Status code", - "displayNameEvent": "Status code changed", - "type": "int", - "defaultValue": 200 - }, - { - "id": "22f8be58-be2b-4dba-b1ca-6c2a16dec533", - "name": "response", - "displayName": "Response", - "displayNameEvent": "Response received", - "type": "QString", - "defaultValue": "" - } - ], - "actionTypes": [ - { - "id": "a9f165dc-cdf1-48f0-b4b6-7c24373cb77c", - "name": "put", - "displayName": "put", - "paramTypes": [ + }, { - "id": "7742d445-8fc1-4b20-87f2-1bb35929fce1", - "name": "data", - "displayName": "Data", + "id": "9fc9948a-5995-48d2-94ce-3c1fd26f6181", + "name": "method", + "displayName": "Method", "type": "QString", - "defaultValue": "" + "defaultValue": "GET", + "allowedValues": [ + "GET", + "POST", + "PUT", + "DELETE" + ] } ] } ] }, - { - "id": "8f3f6dde-9db3-4237-800b-bb7f804098c9", - "name": "httpGetCommander", - "displayName": "HTTP get", - "createMethods": ["user"], - "interfaces": [ ], - "paramTypes": [ - { - "id": "477b544b-b631-4526-a4ef-c712ff5f955d", - "name": "url", - "displayName": "URL or IPv4 Address", - "type": "QString", - "inputType": "None", - "defaultValue": "https://httpbin.org/get" - }, - { - "id": "bee8b151-815a-4159-9d8a-42b76e99b42c", - "name": "port", - "displayName": "Port", - "type": "int", - "defaultValue": "443" - } - ], - "stateTypes":[ - { - "id": "8a60ffe0-e39a-43ec-be8c-c1ed1886aa41", - "name": "status", - "displayName": "Status code", - "displayNameEvent": "Status code changed", - "type": "int", - "defaultValue": 200 - }, - { - "id": "d81f0644-b94e-48ed-ae48-1b8ff6cebc0c", - "name": "response", - "displayName": "Response", - "type": "QString", - "defaultValue": "", - "displayNameEvent": "Response data received" - } - ] - }, { "id": "56efcdc3-c769-4e25-8a5b-c0affe68252a", @@ -182,16 +95,6 @@ "defaultValue": "8000" } ], - "stateTypes": [ - { - "id": "f4458008-67f2-4ba1-bbca-e85771a30ddc", - "name": "serverAddress", - "displayName": "Server address", - "displayNameEvent": "Server address changed", - "type": "QString", - "defaultValue": "127.0.0.1" - } - ], "eventTypes": [ { "id": "86f794c6-31ad-40a8-928f-4b8802506ce1", diff --git a/httpcommander/httpcommander.pro b/httpcommander/httpcommander.pro index 24fb6276..2e18b912 100644 --- a/httpcommander/httpcommander.pro +++ b/httpcommander/httpcommander.pro @@ -6,12 +6,10 @@ TARGET = $$qtLibraryTarget(nymea_devicepluginhttpcommander) SOURCES += \ devicepluginhttpcommander.cpp \ - httprequest.cpp \ httpsimpleserver.cpp HEADERS += \ devicepluginhttpcommander.h \ - httprequest.h \ httpsimpleserver.h diff --git a/httpcommander/httprequest.cpp b/httpcommander/httprequest.cpp deleted file mode 100644 index c8c793b5..00000000 --- a/httpcommander/httprequest.cpp +++ /dev/null @@ -1,238 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * Copyright (C) 2015 Simon Stürz * - * * - * This file is part of nymea. * - * * - * nymea 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. * - * * - * nymea 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 nymea. If not, see . * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -//This file has been copied from the libnymea-core library -//TODO create a http library - -#include "httprequest.h" -#include "loggingcategories.h" - -#include - -/*! Construct an empty \l{HttpRequest}. */ -HttpRequest::HttpRequest() : - m_rawData(QByteArray()), - m_valid(false), - m_isComplete(false) -{ -} - -/*! Construct a \l{HttpRequest} with the given \a rawData. The \a rawData will be parsed in this constructor. You can check - if the data is valid with \l{isValid()}. You can check if the request is complete with \l{isComplete}. - \sa isValid(), isComplete() -*/ -HttpRequest::HttpRequest(QByteArray rawData) : - m_rawData(rawData), - m_valid(false), - m_isComplete(false) -{ - validate(); -} - -/*! Returns the raw header of this request.*/ -QByteArray HttpRequest::rawHeader() const -{ - return m_rawHeader; -} - -/*! Returns the list of raw header as key and value pairs.*/ -QHash HttpRequest::rawHeaderList() const -{ - return m_rawHeaderList; -} - -/*! Returns the \l{RequestMethod} of this request. - \sa RequestMethod -*/ -HttpRequest::RequestMethod HttpRequest::method() const -{ - return m_method; -} - -/*! Returns the method as human readable string.*/ -QString HttpRequest::methodString() const -{ - return m_methodString; -} - -/*! Returns the HTTP version of this \l{HttpRequest}.*/ -QByteArray HttpRequest::httpVersion() const -{ - return m_httpVersion; -} - -/*! Returns the URL of this \l{HttpRequest}.*/ -QUrl HttpRequest::url() const -{ - return m_url; -} - -/*! Returns the URL query of this \l{HttpRequest}.*/ -QUrlQuery HttpRequest::urlQuery() const -{ - return m_urlQuery; -} - -/*! Returns the payload (content) of this \l{HttpRequest}.*/ -QByteArray HttpRequest::payload() const -{ - return m_payload; -} - -/*! Returns true if this \l{HttpRequest} is valid. A HTTP request is valid if the header and the payload were paresed successfully without errors.*/ -bool HttpRequest::isValid() const -{ - return m_valid; -} - -/*! Returns true if this \l{HttpRequest} is complete. A HTTP request is complete if "Content-Length" header value matches the actual payload size. Bigger packages will be sent in multiple TCP packages. */ -bool HttpRequest::isComplete() const -{ - return m_isComplete; -} - -/*! Returns true if this \l{HttpRequest} has a payload.*/ -bool HttpRequest::hasPayload() const -{ - return !m_payload.isEmpty(); -} - -/*! Appends the given \a data to the current raw data of this \l{HttpRequest}. - * This method will be used if a \l{HttpRequest} is not complete yet. - * - * \sa isComplete() -*/ -void HttpRequest::appendData(const QByteArray &data) -{ - m_rawData.append(data); - validate(); -} - -void HttpRequest::validate() -{ - m_isComplete = true; m_valid = false; - - // Parese the HTTP request. The request is invalid, until the end of the parse process. - if (m_rawData.isEmpty()) - return; - - // split the data into header and payload - int headerEndIndex = m_rawData.indexOf("\r\n\r\n"); - if (headerEndIndex < 0) { - qCWarning(dcWebServer()) << "Could not parse end of HTTP header (empty line between header and body):" << m_rawData; - return; - } - - m_rawHeader = m_rawData.left(headerEndIndex); - m_payload = m_rawData.right(m_rawData.length() - headerEndIndex).simplified(); - - // parse status line - QStringList headerLines = QString(m_rawHeader).split(QRegExp("\r\n")); - QString statusLine = headerLines.takeFirst(); - QStringList statusLineTokens = statusLine.split(QRegExp("[ \r\n][ \r\n]*")); - if (statusLineTokens.count() != 3) { - qCWarning(dcWebServer()) << "Could not parse HTTP status line:" << statusLine; - return; - } - - // verify http version - m_httpVersion = statusLineTokens.at(2).toUtf8().simplified(); - if (!m_httpVersion.contains("HTTP")) { - qCWarning(dcWebServer()) << "Unknown HTTP version:" << m_httpVersion; - return; - } - m_methodString = statusLineTokens.at(0).simplified(); - m_method = getRequestMethodType(m_methodString); - - m_url = QUrl("http://example.com" + statusLineTokens.at(1).simplified()); - - if (m_url.hasQuery()) - m_urlQuery = QUrlQuery(m_url.query()); - - // verify header formating - foreach (const QString &line, headerLines) { - if (!line.contains(":")) { - qCWarning(dcWebServer()) << "Invalid HTTP header:" << line; - return; - } - int index = line.indexOf(":"); - QByteArray key = line.left(index).toUtf8().simplified(); - QByteArray value = line.right(line.count() - index - 1).toUtf8().simplified(); - m_rawHeaderList.insert(key, value); - } - - // check User-Agent - if (!m_rawHeaderList.contains("User-Agent")) - qCWarning(dcWebServer()) << "User-Agent header is missing"; - - - // verify content length with actual payload - if (m_rawHeaderList.contains("Content-Length")) { - bool ok = false; - int contentLength = m_rawHeaderList.value("Content-Length").toInt(&ok); - if (!ok) { - qCWarning(dcWebServer()) << "Could not parse Content-Length."; - return; - } - // check if we have all data - if (m_payload.size() < contentLength) { - qCDebug(dcWebServer()) << "Request incomplete:"; - qCDebug(dcWebServer()) << " -> Content-Length:" << contentLength; - qCDebug(dcWebServer()) << " -> Payload size :" << payload().size(); - m_isComplete = false; - return; - } - // check if the content length bigger than header Content-Length - if (m_payload.size() > contentLength) { - qCWarning(dcWebServer()) << "Payload size greater than header Content-Length:"; - qCWarning(dcWebServer()) << " -> Content-Length:" << contentLength; - qCWarning(dcWebServer()) << " -> Payload size :" << payload().size(); - m_isComplete = true; - return; - } - - } - m_valid = true; -} - -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; - } else if (methodString == "OPTIONS") { - return RequestMethod::Options; - } - qCWarning(dcWebServer()) << "Method" << methodString << "will not be handled."; - return RequestMethod::Unhandled; -} - -QDebug operator<<(QDebug debug, const HttpRequest &httpRequest) -{ - debug << "HttpRequest:" << endl; - debug << qUtf8Printable(httpRequest.rawHeader()); - debug << qUtf8Printable(httpRequest.payload()); - return debug; -} diff --git a/httpcommander/httprequest.h b/httpcommander/httprequest.h deleted file mode 100644 index e02f17a9..00000000 --- a/httpcommander/httprequest.h +++ /dev/null @@ -1,85 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * Copyright (C) 2015 Simon Stürz * - * * - * This file is part of nymea. * - * * - * nymea 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. * - * * - * nymea 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 nymea. If not, see . * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#ifndef HTTPREQUEST_H -#define HTTPREQUEST_H - -#include -#include -#include -#include - -class HttpRequest -{ -public: - enum RequestMethod { - Get, - Post, - Put, - Delete, - Options, - Unhandled - }; - - HttpRequest(); - HttpRequest(QByteArray rawData); - - QByteArray rawHeader() const; - QHash rawHeaderList() const; - - RequestMethod method() const; - QString methodString() const; - QByteArray httpVersion() const; - - QUrl url() const; - QUrlQuery urlQuery() const; - - QByteArray payload() const; - - bool isValid() const; - bool isComplete() const; - bool hasPayload() const; - - void appendData(const QByteArray &data); - -private: - QByteArray m_rawData; - QByteArray m_rawHeader; - QHash m_rawHeaderList; - - RequestMethod m_method; - QString m_methodString; - QByteArray m_httpVersion; - - QUrl m_url; - QUrlQuery m_urlQuery; - - QByteArray m_payload; - - bool m_valid; - bool m_isComplete; - - void validate(); - RequestMethod getRequestMethodType(const QString &methodString); -}; - -QDebug operator<< (QDebug debug, const HttpRequest &httpRequest); - -#endif // HTTPREQUEST_H diff --git a/httpcommander/httpsimpleserver.cpp b/httpcommander/httpsimpleserver.cpp index e1d2b012..f17db230 100644 --- a/httpcommander/httpsimpleserver.cpp +++ b/httpcommander/httpsimpleserver.cpp @@ -33,10 +33,10 @@ #include #include -HttpSimpleServer::HttpSimpleServer(QObject *parent): +HttpSimpleServer::HttpSimpleServer(quint16 port, QObject *parent): QTcpServer(parent) { - listen(QHostAddress::Any, 7777); + listen(QHostAddress::Any, port); } HttpSimpleServer::~HttpSimpleServer() diff --git a/httpcommander/httpsimpleserver.h b/httpcommander/httpsimpleserver.h index ab8c1993..b8a808ac 100644 --- a/httpcommander/httpsimpleserver.h +++ b/httpcommander/httpsimpleserver.h @@ -40,7 +40,7 @@ class HttpSimpleServer : public QTcpServer Q_OBJECT public: - HttpSimpleServer(QObject* parent = nullptr); + HttpSimpleServer(quint16 port, QObject* parent = nullptr); ~HttpSimpleServer() override; void incomingConnection(qintptr socket) override; diff --git a/httpcommander/translations/4e62670c-6268-4487-8dff-cccca498731a-en_US.ts b/httpcommander/translations/4e62670c-6268-4487-8dff-cccca498731a-en_US.ts index 73b3610c..3dbb4bd8 100644 --- a/httpcommander/translations/4e62670c-6268-4487-8dff-cccca498731a-en_US.ts +++ b/httpcommander/translations/4e62670c-6268-4487-8dff-cccca498731a-en_US.ts @@ -13,66 +13,19 @@ HttpCommander Address - The name of the ParamType (DeviceClass: httpPutCommander, Type: device, ID: {1a3fcb23-931b-4ba1-b134-c49b656c76f7}) ----------- -The name of the ParamType (DeviceClass: httpPostCommander, Type: device, ID: {020f672e-cc9a-4b74-92dd-a92a93ab1d23}) + The name of the ParamType (DeviceClass: httpRequest, Type: device, ID: {020f672e-cc9a-4b74-92dd-a92a93ab1d23}) Port The name of the ParamType (DeviceClass: httpServer, Type: device, ID: {438117cb-c2de-49d0-9f91-5988c17225f8}) ---------- -The name of the ParamType (DeviceClass: httpGetCommander, Type: device, ID: {bee8b151-815a-4159-9d8a-42b76e99b42c}) ----------- -The name of the ParamType (DeviceClass: httpPutCommander, Type: device, ID: {db994349-1105-4ce5-b6fe-6fd38fbc436a}) ----------- -The name of the ParamType (DeviceClass: httpPostCommander, Type: device, ID: {37830ea8-2249-46e6-aaca-12164928a81a}) +The name of the ParamType (DeviceClass: httpRequest, Type: device, ID: {37830ea8-2249-46e6-aaca-12164928a81a}) Response received - The name of the EventType ({22f8be58-be2b-4dba-b1ca-6c2a16dec533}) of DeviceClass httpPutCommander ----------- -The name of the EventType ({69f32ec8-114d-43f4-9241-1f6a57261f32}) of DeviceClass httpPostCommander - - - - response - The name of the ParamType (DeviceClass: httpPostCommander, EventType: response, ID: {69f32ec8-114d-43f4-9241-1f6a57261f32}) ----------- -The name of the StateType ({69f32ec8-114d-43f4-9241-1f6a57261f32}) of DeviceClass httpPostCommander - - - - Post data - The name of the ActionType ({5a97ca56-b334-411b-adba-116496ffe83d}) of DeviceClass httpPostCommander - - - - Data - The name of the ParamType (DeviceClass: httpPutCommander, ActionType: put, ID: {7742d445-8fc1-4b20-87f2-1bb35929fce1}) ----------- -The name of the ParamType (DeviceClass: httpPostCommander, ActionType: post, ID: {363119a3-c02c-4ed5-a915-11706198f3eb}) - - - - put - The name of the ActionType ({a9f165dc-cdf1-48f0-b4b6-7c24373cb77c}) of DeviceClass httpPutCommander - - - - HTTP get - The name of the DeviceClass ({8f3f6dde-9db3-4237-800b-bb7f804098c9}) - - - - URL or IPv4 Address - The name of the ParamType (DeviceClass: httpGetCommander, Type: device, ID: {477b544b-b631-4526-a4ef-c712ff5f955d}) - - - - Response data received - The name of the EventType ({d81f0644-b94e-48ed-ae48-1b8ff6cebc0c}) of DeviceClass httpGetCommander + The name of the EventType ({69f32ec8-114d-43f4-9241-1f6a57261f32}) of DeviceClass httpRequest @@ -82,13 +35,9 @@ The name of the ParamType (DeviceClass: httpPostCommander, ActionType: post, ID: Response - The name of the ParamType (DeviceClass: httpGetCommander, EventType: response, ID: {d81f0644-b94e-48ed-ae48-1b8ff6cebc0c}) + The name of the ParamType (DeviceClass: httpRequest, EventType: response, ID: {69f32ec8-114d-43f4-9241-1f6a57261f32}) ---------- -The name of the StateType ({d81f0644-b94e-48ed-ae48-1b8ff6cebc0c}) of DeviceClass httpGetCommander ----------- -The name of the ParamType (DeviceClass: httpPutCommander, EventType: response, ID: {22f8be58-be2b-4dba-b1ca-6c2a16dec533}) ----------- -The name of the StateType ({22f8be58-be2b-4dba-b1ca-6c2a16dec533}) of DeviceClass httpPutCommander +The name of the StateType ({69f32ec8-114d-43f4-9241-1f6a57261f32}) of DeviceClass httpRequest @@ -96,43 +45,23 @@ The name of the StateType ({22f8be58-be2b-4dba-b1ca-6c2a16dec533}) of DeviceClas The name of the vendor ({2062d64d-3232-433c-88bc-0d33c0ba2ba6}) - - HTTP post - The name of the DeviceClass ({b101abdf-86fd-4d2e-a657-ee76044235bd}) - - Status code changed - The name of the EventType ({8a60ffe0-e39a-43ec-be8c-c1ed1886aa41}) of DeviceClass httpGetCommander ----------- -The name of the EventType ({4959c589-4550-479f-ae11-c4d1097ce3d5}) of DeviceClass httpPutCommander ----------- -The name of the EventType ({8daac0e7-4c2f-4cdf-b528-02cfe04c6b39}) of DeviceClass httpPostCommander + The name of the EventType ({8daac0e7-4c2f-4cdf-b528-02cfe04c6b39}) of DeviceClass httpRequest Status code - The name of the ParamType (DeviceClass: httpGetCommander, EventType: status, ID: {8a60ffe0-e39a-43ec-be8c-c1ed1886aa41}) + The name of the ParamType (DeviceClass: httpRequest, EventType: status, ID: {8daac0e7-4c2f-4cdf-b528-02cfe04c6b39}) ---------- -The name of the StateType ({8a60ffe0-e39a-43ec-be8c-c1ed1886aa41}) of DeviceClass httpGetCommander ----------- -The name of the ParamType (DeviceClass: httpPutCommander, EventType: status, ID: {4959c589-4550-479f-ae11-c4d1097ce3d5}) ----------- -The name of the StateType ({4959c589-4550-479f-ae11-c4d1097ce3d5}) of DeviceClass httpPutCommander ----------- -The name of the ParamType (DeviceClass: httpPostCommander, EventType: status, ID: {8daac0e7-4c2f-4cdf-b528-02cfe04c6b39}) ----------- -The name of the StateType ({8daac0e7-4c2f-4cdf-b528-02cfe04c6b39}) of DeviceClass httpPostCommander - - - - HTTP put - The name of the DeviceClass ({05bf65f5-ff13-43e3-b6ae-77019e79d8a1}) +The name of the StateType ({8daac0e7-4c2f-4cdf-b528-02cfe04c6b39}) of DeviceClass httpRequest Body - The name of the ParamType (DeviceClass: httpServer, EventType: triggered, ID: {c936810e-a73d-424f-8981-48baf0a440bb}) + The name of the ParamType (DeviceClass: httpServer, EventType: triggered, ID: {c936810e-a73d-424f-8981-48baf0a440bb}) +---------- +The name of the ParamType (DeviceClass: httpRequest, ActionType: request, ID: {363119a3-c02c-4ed5-a915-11706198f3eb}) @@ -156,15 +85,18 @@ The name of the StateType ({8daac0e7-4c2f-4cdf-b528-02cfe04c6b39}) of DeviceClas - Server address - The name of the ParamType (DeviceClass: httpServer, EventType: serverAddress, ID: {f4458008-67f2-4ba1-bbca-e85771a30ddc}) ----------- -The name of the StateType ({f4458008-67f2-4ba1-bbca-e85771a30ddc}) of DeviceClass httpServer + HTTP Request + The name of the DeviceClass ({b101abdf-86fd-4d2e-a657-ee76044235bd}) - Server address changed - The name of the EventType ({f4458008-67f2-4ba1-bbca-e85771a30ddc}) of DeviceClass httpServer + Method + The name of the ParamType (DeviceClass: httpRequest, ActionType: request, ID: {9fc9948a-5995-48d2-94ce-3c1fd26f6181}) + + + + Request + The name of the ActionType ({5a97ca56-b334-411b-adba-116496ffe83d}) of DeviceClass httpRequest