diff --git a/debian-qt5/libnymea-dev.install.in b/debian-qt5/libnymea-dev.install.in index 32ff475d..7748efc8 100644 --- a/debian-qt5/libnymea-dev.install.in +++ b/debian-qt5/libnymea-dev.install.in @@ -5,9 +5,10 @@ usr/include/nymea/experiences/* usr/include/nymea/hardware/* usr/include/nymea/integrations/* usr/include/nymea/jsonrpc/* +usr/include/nymea/logging/* usr/include/nymea/network/* usr/include/nymea/platform/* usr/include/nymea/time/* usr/include/nymea/types/* -usr/include/nymea/logging/* +usr/include/nymea/webserver/* usr/lib/@DEB_HOST_MULTIARCH@/pkgconfig/nymea.pc diff --git a/libnymea-core/debugserverhandler.cpp b/libnymea-core/debugserverhandler.cpp index 9a642862..105413a6 100644 --- a/libnymea-core/debugserverhandler.cpp +++ b/libnymea-core/debugserverhandler.cpp @@ -23,14 +23,12 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "nymeacore.h" -#include "servers/httpreply.h" #include "nymeasettings.h" #include "loggingcategories.h" #include "debugserverhandler.h" #include "nymeaconfiguration.h" #include "version.h" -#include #include #include #include @@ -41,19 +39,38 @@ #include #include - namespace nymeaserver { -QList DebugServerHandler::s_websocketClients; +QList DebugServerHandler::s_websocketClients; QMutex DebugServerHandler::s_loggingMutex; DebugServerHandler::DebugServerHandler(QObject *parent) : - QObject(parent) + WebServerResource("/debug", parent) { connect(NymeaCore::instance()->configuration(), &NymeaConfiguration::debugServerEnabledChanged, this, &DebugServerHandler::onDebugServerEnabledChanged); onDebugServerEnabledChanged(NymeaCore::instance()->configuration()->debugServerEnabled()); } +HttpReply *DebugServerHandler::processRequest(const HttpRequest &request) +{ + if (m_enabled) { + + // Verify methods + if (request.method() != HttpRequest::Get && request.method() != HttpRequest::Options) { + HttpReply *reply = HttpReply::createErrorReply(HttpReply::MethodNotAllowed); + reply->setHeader(HttpReply::AllowHeader, "GET, OPTIONS"); + return reply; + } + + qCDebug(dcDebugServer()) << "Request:" << request.url().toString(); + return processDebugRequest(request.url().path(), request.urlQuery()); + + } else { + qCWarning(dcWebServer()) << "The debug server handler is disabled. You can enable it by adding \'debugServerEnabled=true\' in the \'nymead\' section of the nymead.conf file."; + return HttpReply::createErrorReply(HttpReply::NotFound); + } +} + HttpReply *DebugServerHandler::processDebugRequest(const QString &requestPath, const QUrlQuery &requestQuery) { qCDebug(dcDebugServer()) << "Debug request for" << requestPath; @@ -636,6 +653,8 @@ void DebugServerHandler::onDebugServerEnabledChanged(bool enabled) m_websocketServer = nullptr; } } + + setEnabled(enabled); } void DebugServerHandler::onWebsocketClientConnected() diff --git a/libnymea-core/debugserverhandler.h b/libnymea-core/debugserverhandler.h index b4083fba..dd9777f6 100644 --- a/libnymea-core/debugserverhandler.h +++ b/libnymea-core/debugserverhandler.h @@ -33,17 +33,17 @@ #include #include "debugreportgenerator.h" -#include "servers/httpreply.h" +#include "webserver/webserverresource.h" namespace nymeaserver { -class DebugServerHandler : public QObject +class DebugServerHandler : public WebServerResource { Q_OBJECT public: explicit DebugServerHandler(QObject *parent = nullptr); - HttpReply *processDebugRequest(const QString &requestPath, const QUrlQuery &requestQuery); + HttpReply *processRequest(const HttpRequest &request) override; private: static QList s_websocketClients; @@ -63,6 +63,8 @@ private: DebugReportGenerator *m_debugReportGenerator = nullptr; + HttpReply *processDebugRequest(const QString &requestPath, const QUrlQuery &requestQuery); + QByteArray loadResourceData(const QString &resourceFileName); QString getResourceFileName(const QString &requestPath); bool resourceFileExits(const QString &requestPath); diff --git a/libnymea-core/experiences/experiencemanager.cpp b/libnymea-core/experiences/experiencemanager.cpp index 479ada50..8b0fb746 100644 --- a/libnymea-core/experiences/experiencemanager.cpp +++ b/libnymea-core/experiences/experiencemanager.cpp @@ -24,6 +24,7 @@ #include "experiencemanager.h" #include "experiences/experienceplugin.h" +#include "servermanager.h" #include "jsonrpc/jsonrpcserverimplementation.h" #include "loggingcategories.h" @@ -35,9 +36,12 @@ namespace nymeaserver { -ExperienceManager::ExperienceManager(ThingManager *thingManager, JsonRPCServer *jsonRpcServer, QObject *parent) : QObject(parent), - m_thingManager(thingManager), - m_jsonRpcServer(jsonRpcServer) +ExperienceManager::ExperienceManager(ThingManager *thingManager, JsonRPCServer *jsonRpcServer, ServerManager *serverManager, QObject *parent) : + QObject{parent}, + m_thingManager{thingManager}, + m_jsonRpcServer{jsonRpcServer}, + m_serverManager{serverManager} + { staticMetaObject.invokeMethod(this, "loadPlugins", Qt::QueuedConnection); } @@ -122,6 +126,9 @@ void ExperienceManager::loadExperiencePlugin(const QString &file) plugin->setParent(this); plugin->initPlugin(m_thingManager, m_jsonRpcServer); + if (plugin->webServerResource()) { + m_serverManager->registerWebServerResource(plugin->webServerResource()); + } } void ExperienceManager::loadExperiencePlugin(ExperiencePlugin *experiencePlugin) @@ -130,6 +137,10 @@ void ExperienceManager::loadExperiencePlugin(ExperiencePlugin *experiencePlugin) m_plugins.append(experiencePlugin); experiencePlugin->setParent(this); experiencePlugin->initPlugin(m_thingManager, m_jsonRpcServer); + + if (experiencePlugin->webServerResource()) { + m_serverManager->registerWebServerResource(experiencePlugin->webServerResource()); + } } } diff --git a/libnymea-core/experiences/experiencemanager.h b/libnymea-core/experiences/experiencemanager.h index f3a0ad86..47eb3439 100644 --- a/libnymea-core/experiences/experiencemanager.h +++ b/libnymea-core/experiences/experiencemanager.h @@ -33,11 +33,13 @@ class ThingManager; namespace nymeaserver { +class ServerManager; + class ExperienceManager : public QObject { Q_OBJECT public: - explicit ExperienceManager(ThingManager *thingManager, JsonRPCServer *jsonRpcServer, QObject *parent = nullptr); + explicit ExperienceManager(ThingManager *thingManager, JsonRPCServer *jsonRpcServer, ServerManager *serverManager, QObject *parent = nullptr); QList plugins() const; @@ -50,6 +52,8 @@ private slots: private: ThingManager *m_thingManager = nullptr; JsonRPCServer *m_jsonRpcServer = nullptr; + ServerManager *m_serverManager = nullptr; + QList m_plugins; QStringList pluginSearchDirs() const; diff --git a/libnymea-core/libnymea-core.pro b/libnymea-core/libnymea-core.pro index 09259ab5..849968f4 100644 --- a/libnymea-core/libnymea-core.pro +++ b/libnymea-core/libnymea-core.pro @@ -107,8 +107,6 @@ HEADERS += nymeacore.h \ servers/tcpserver.h \ servers/mocktcpserver.h \ servers/webserver.h \ - servers/httprequest.h \ - servers/httpreply.h \ servers/bluetoothserver.h \ servers/websocketserver.h \ servers/mqttbroker.h \ @@ -210,8 +208,6 @@ SOURCES += nymeacore.cpp \ servers/tcpserver.cpp \ servers/mocktcpserver.cpp \ servers/webserver.cpp \ - servers/httprequest.cpp \ - servers/httpreply.cpp \ servers/websocketserver.cpp \ servers/bluetoothserver.cpp \ servers/mqttbroker.cpp \ diff --git a/libnymea-core/nymeacore.cpp b/libnymea-core/nymeacore.cpp index 0872fd4a..30d1191d 100644 --- a/libnymea-core/nymeacore.cpp +++ b/libnymea-core/nymeacore.cpp @@ -37,6 +37,7 @@ #include "jsonrpc/scriptshandler.h" #include "jsonrpc/debughandler.h" #include "usermanager/usermanager.h" +#include "debugserverhandler.h" #include "version.h" #include "integrations/thingmanagerimplementation.h" @@ -152,12 +153,13 @@ void NymeaCore::init(const QStringList &additionalInterfaces, bool disableLogEng qCDebug(dcCore) << "Creating Debug Server Handler"; m_debugServerHandler = new DebugServerHandler(this); + m_serverManager->registerWebServerResource(m_debugServerHandler); qCDebug(dcCore) << "Register Debug Handler"; m_serverManager->jsonServer()->registerHandler(new DebugHandler(m_serverManager->jsonServer())); qCDebug(dcCore()) << "Loading experiences"; - m_experienceManager = new ExperienceManager(m_thingManager, m_serverManager->jsonServer(), this); + m_experienceManager = new ExperienceManager(m_thingManager, m_serverManager->jsonServer(), m_serverManager, this); connect(m_configuration, &NymeaConfiguration::serverNameChanged, m_serverManager, &ServerManager::setServerName); connect(m_thingManager, &ThingManagerImplementation::loaded, this, &NymeaCore::thingManagerLoaded); diff --git a/libnymea-core/nymeacore.h b/libnymea-core/nymeacore.h index ea6c9785..3d9e5e9a 100644 --- a/libnymea-core/nymeacore.h +++ b/libnymea-core/nymeacore.h @@ -39,7 +39,6 @@ #include "time/timemanager.h" #include "hardwaremanagerimplementation.h" -#include "debugserverhandler.h" #include @@ -63,6 +62,7 @@ class ZigbeeManager; class ZWaveManager; class ModbusRtuManager; class SerialPortMonitor; +class DebugServerHandler; namespace scriptengine { class ScriptEngine; diff --git a/libnymea-core/servermanager.cpp b/libnymea-core/servermanager.cpp index 6304679c..8aaf748d 100644 --- a/libnymea-core/servermanager.cpp +++ b/libnymea-core/servermanager.cpp @@ -56,6 +56,8 @@ #include "network/zeroconf/zeroconfservicepublisher.h" +#include + #include #include #include @@ -214,6 +216,13 @@ ServerManager::ServerManager(Platform *platform, NymeaConfiguration *configurati foreach (const WebServerConfiguration &config, configuration->webServerConfigurations()) { WebServer *webServer = new WebServer(config, m_sslConfiguration, this); m_webServers.insert(config.id, webServer); + + foreach (WebServerResource *resource, m_webServerResources) { + if (!webServer->registerResource(resource)) { + qCWarning(dcServerManager()) << "Unable to register resource" << resource->basePath() << "on webserver" << webServer->serverUrl().toString(); + } + } + if (webServer->startServer()) { registerZeroConfService(config, "http", "_http._tcp"); } @@ -268,6 +277,29 @@ MqttBroker *ServerManager::mqttBroker() const return m_mqttBroker; } +bool ServerManager::registerWebServerResource(WebServerResource *resource) +{ + if (m_webServerResources.contains(resource->basePath())) { + qCDebug(dcServerManager()) << "Could not register web server resource" << resource->basePath() << "because a resource with this path has already been registered"; + return false; + } + + m_webServerResources.insert(resource->basePath(), resource); + foreach (WebServer *webserver, m_webServers) + webserver->registerResource(resource); + + return true; +} + +void ServerManager::unregisterWebServerResource(WebServerResource *resource) +{ + m_webServerResources.remove(resource->basePath()); + + foreach (WebServer *webserver, m_webServers) + webserver->unregisterResource(resource); + +} + void ServerManager::tcpServerConfigurationChanged(const QString &id) { ServerConfiguration config = NymeaCore::instance()->configuration()->tcpServerConfigurations().value(id); @@ -347,6 +379,11 @@ void ServerManager::webServerConfigurationChanged(const QString &id) qCDebug(dcServerManager()) << "Received a Web Server config change event but don't have a Web Server instance for it. Creating new WebServer instance on" << config.address << config.port << "(SSL:" << config.sslEnabled << ")"; server = new WebServer(config, m_sslConfiguration, this); m_webServers.insert(config.id, server); + foreach (WebServerResource *resource, m_webServerResources) { + if (!server->registerResource(resource)) { + qCWarning(dcServerManager()) << "Unable to register resource" << resource->basePath() << "on webserver" << server->serverUrl().toString(); + } + } } if (server->startServer()) { registerZeroConfService(config, "http", "_http._tcp"); @@ -359,7 +396,12 @@ void ServerManager::webServerConfigurationRemoved(const QString &id) qCWarning(dcServerManager()) << "Received a Web Server config removed event but don't have a Web Server instance for it."; return; } + WebServer *server = m_webServers.take(id); + + foreach (WebServerResource *resource, m_webServerResources) + server->unregisterResource(resource); + unregisterZeroConfService(id, "http"); server->stopServer(); server->deleteLater(); diff --git a/libnymea-core/servermanager.h b/libnymea-core/servermanager.h index fb127bdb..5fe6355a 100644 --- a/libnymea-core/servermanager.h +++ b/libnymea-core/servermanager.h @@ -32,6 +32,7 @@ #include #include +class WebServerResource; namespace nymeaserver { @@ -55,13 +56,14 @@ public: // Interfaces JsonRPCServerImplementation *jsonServer() const; - - BluetoothServer* bluetoothServer() const; - + BluetoothServer *bluetoothServer() const; MockTcpServer *mockTcpServer() const; - MqttBroker *mqttBroker() const; + // Resources for the webservers + bool registerWebServerResource(WebServerResource *resource); + void unregisterWebServerResource(WebServerResource *resource); + public slots: void setServerName(const QString &serverName); @@ -94,14 +96,16 @@ private: JsonRPCServerImplementation *m_jsonServer; BluetoothServer *m_bluetoothServer; - QHash m_tcpServers; - QHash m_webSocketServers; - QHash m_webServers; + QHash m_tcpServers; + QHash m_webSocketServers; + QHash m_webServers; QHash m_tunnelProxyServers; MockTcpServer *m_mockTcpServer; MqttBroker *m_mqttBroker; + QHash m_webServerResources; + // Encrytption and stuff QSslConfiguration m_sslConfiguration; QSslKey m_certificateKey; diff --git a/libnymea-core/servers/webserver.cpp b/libnymea-core/servers/webserver.cpp index b60fd573..25eaae5b 100644 --- a/libnymea-core/servers/webserver.cpp +++ b/libnymea-core/servers/webserver.cpp @@ -76,11 +76,10 @@ #include "webserver.h" #include "loggingcategories.h" -#include "nymeasettings.h" #include "nymeacore.h" -#include "httpreply.h" -#include "httprequest.h" -#include "debugserverhandler.h" +#include "webserver/httpreply.h" +#include "webserver/httprequest.h" +#include "webserver/webserverresource.h" #include "version.h" #include @@ -107,16 +106,20 @@ WebServer::WebServer(const WebServerConfiguration &configuration, const QSslConf m_configuration(configuration), m_sslConfiguration(sslConfiguration) { - if (QCoreApplication::instance()->organizationName() == "nymea-test") { + if (QCoreApplication::instance()->organizationName() == "nymea-test") m_configuration.publicFolder = QCoreApplication::applicationDirPath(); - } - qCDebug(dcWebServer()) << "Starting WebServer. Interface:" << m_configuration.address << "Port:" << m_configuration.port << "SSL:" << m_configuration.sslEnabled << "AUTH:" << m_configuration.authenticationEnabled << "Public folder:" << QDir(m_configuration.publicFolder).canonicalPath(); + + qCInfo(dcWebServer()) << "Starting WebServer. Interface:" << m_configuration.address + << "Port:" << m_configuration.port + << "SSL:" << (m_configuration.sslEnabled ? "enabled" : "disabled") + << "AUTH:" << (m_configuration.authenticationEnabled ? "enabled" : "disabled") + << "Public folder:" << QDir(m_configuration.publicFolder).canonicalPath(); } /*! Destructor of this \l{WebServer}. */ WebServer::~WebServer() { - qCDebug(dcWebServer()) << "Shutting down \"Webserver\"" << serverUrl().toString(); + qCInfo(dcWebServer()) << "Shutting down \"Webserver\"" << serverUrl().toString(); this->close(); } @@ -124,7 +127,17 @@ WebServer::~WebServer() /*! Returns the server URL of this WebServer. */ QUrl WebServer::serverUrl() const { - return QUrl(QString("%1://%2:%3").arg((m_configuration.sslEnabled ? "https" : "http")).arg(m_configuration.address).arg(m_configuration.port)); + QUrl url; + url.setScheme(m_configuration.sslEnabled ? "https" : "http"); + url.setHost(m_configuration.address); + url.setPort(m_configuration.port); + return url; +} + +/*! Returns the configuration of this WebServer. */ +WebServerConfiguration WebServer::configuration() const +{ + return m_configuration; } /*! Send the given \a reply map to the corresponding client. @@ -133,7 +146,6 @@ QUrl WebServer::serverUrl() const */ void WebServer::sendHttpReply(HttpReply *reply) { - // get the right socket QSslSocket *socket = nullptr; socket = m_clientList.value(reply->clientId()); if (!socket) { @@ -148,13 +160,40 @@ void WebServer::sendHttpReply(HttpReply *reply) socket->write(reply->data()); } +QList WebServer::resources() const +{ + return m_resources.values(); +} + +bool WebServer::registerResource(WebServerResource *resource) +{ + qCDebug(dcWebServer()) << "Register resource" << resource->basePath() << "on server" << serverUrl().toString(); + + if (m_resources.contains(resource->basePath())) { + qCWarning(dcWebServer()) << "Could not register resource" << resource->basePath() << "because there is already a resource resistered for this base path."; + return false; + } + + m_resources.insert(resource->basePath(), resource); + return true; +} + +void WebServer::unregisterResource(WebServerResource *resource) +{ + qCDebug(dcWebServer()) << "Unregister resource" << resource->basePath() << "from server" << serverUrl().toString(); + if (!m_resources.contains(resource->basePath())) { + qCWarning(dcWebServer()) << "Could not unregister resource" << resource->basePath() << "because there is no resource resistered with this base path."; + return; + } + + m_resources.remove(resource->basePath()); +} + bool WebServer::verifyFile(QSslSocket *socket, const QString &fileName) { QFileInfo file(fileName); - - // make sure the file exists if (!file.exists()) { - qCDebug(dcWebServer()) << "requested file" << file.filePath() << "does not exist."; + qCDebug(dcWebServer()) << "Requested file" << file.filePath() << "does not exist."; HttpReply *reply = HttpReply::createErrorReply(HttpReply::NotFound); reply->setClientId(m_clientList.key(socket)); sendHttpReply(reply); @@ -182,6 +221,7 @@ bool WebServer::verifyFile(QSslSocket *socket, const QString &fileName) reply->deleteLater(); return false; } + return true; } @@ -269,16 +309,7 @@ void WebServer::incomingConnection(qintptr socketDescriptor) return; } - connect(socket, &QSslSocket::readyRead, this, &WebServer::readClient); - connect(socket, &QSslSocket::disconnected, this, &WebServer::onDisconnected); - -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - connect(socket, &QSslSocket::errorOccurred, this, &WebServer::onError); -#else - connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError))); -#endif - - emit clientConnected(clientId); + setupClient(clientId, socket); } void WebServer::readClient() @@ -345,6 +376,37 @@ void WebServer::readClient() } } + // Verify if we habe a resource for this request + foreach (WebServerResource *resource, m_resources) { + if (request.url().path().startsWith(resource->basePath())) { + if (!resource->enabled()) { + qCDebug(dcWebServer()) << "The corresponding resource exists but is not enabled. Respond with 404 Not Found."; + HttpReply *reply = HttpReply::createErrorReply(HttpReply::NotFound); + reply->setClientId(clientId); + sendHttpReply(reply); + reply->deleteLater(); + return; + } + + qCDebug(dcDebugServer()) << "Request:" << request.url().toString(); + HttpReply *reply = resource->processRequest(request); + reply->setClientId(clientId); + + // Handle async replies + if (reply->type() == HttpReply::TypeAsync) { + connect(reply, &HttpReply::finished, this, &WebServer::onAsyncReplyFinished); + reply->startWait(); + } else { + sendHttpReply(reply); + reply->deleteLater(); + } + + return; + } + } + + // No resource handled this request, let the webserver itself handle it + // Verify method if (request.method() == HttpRequest::Unhandled) { HttpReply *reply = HttpReply::createErrorReply(HttpReply::MethodNotAllowed); @@ -364,43 +426,6 @@ void WebServer::readClient() return; } - // Check if this is a debug call - if (request.url().path().startsWith("/debug")) { - // Check if debug server is enabled - if (NymeaCore::instance()->configuration()->debugServerEnabled()) { - // Verify methods - if (request.method() != HttpRequest::Get && request.method() != HttpRequest::Options) { - HttpReply *reply = HttpReply::createErrorReply(HttpReply::MethodNotAllowed); - reply->setClientId(clientId); - reply->setHeader(HttpReply::AllowHeader, "GET, OPTIONS"); - sendHttpReply(reply); - reply->deleteLater(); - return; - } - - qCDebug(dcDebugServer()) << "Request:" << request.url().toString(); - HttpReply *reply = NymeaCore::instance()->debugServerHandler()->processDebugRequest(request.url().path(), request.urlQuery()); - reply->setClientId(clientId); - - // Handle async replies - if (reply->type() == HttpReply::TypeAsync) { - connect(reply, &HttpReply::finished, this, &WebServer::onAsyncReplyFinished); - reply->startWait(); - } else { - sendHttpReply(reply); - reply->deleteLater(); - } - return; - } else { - qCWarning(dcWebServer()) << "The debug server handler is disabled. You can enable it by adding \'debugServerEnabled=true\' in the \'nymead\' section of the nymead.conf file."; - HttpReply *reply = HttpReply::createErrorReply(HttpReply::NotFound); - reply->setClientId(clientId); - sendHttpReply(reply); - reply->deleteLater(); - return; - } - } - // Check server.xml call if (request.url().path() == "/server.xml" && request.method() == HttpRequest::Get) { qCDebug(dcWebServer()) << "Server XML request call"; @@ -413,7 +438,6 @@ void WebServer::readClient() return; } - // Request for a file... if (request.method() == HttpRequest::Get) { // Check if the webinterface dir does exist, otherwise a filerequest is not relevant @@ -431,42 +455,12 @@ void WebServer::readClient() if (!verifyFile(socket, path)) return; - QFile file(path); - if (file.open(QFile::ReadOnly)) { - qCDebug(dcWebServer()) << "Load file" << file.fileName(); - HttpReply *reply = HttpReply::createSuccessReply(); - // Check content type - if (file.fileName().endsWith(".html")) { - reply->setHeader(HttpReply::ContentTypeHeader, "text/html; charset=\"utf-8\";"); - } else if (file.fileName().endsWith(".css")) { - reply->setHeader(HttpReply::ContentTypeHeader, "text/css; charset=\"utf-8\";"); - } else if (file.fileName().endsWith(".pdf")) { - reply->setHeader(HttpReply::ContentTypeHeader, "application/pdf"); - } else if (file.fileName().endsWith(".js")) { - reply->setHeader(HttpReply::ContentTypeHeader, "text/javascript; charset=\"utf-8\";"); - } else if (file.fileName().endsWith(".ttf")) { - reply->setHeader(HttpReply::ContentTypeHeader, "application/x-font-ttf"); - } else if (file.fileName().endsWith(".eot")) { - reply->setHeader(HttpReply::ContentTypeHeader, "application/vnd.ms-fontobject"); - } else if (file.fileName().endsWith(".woff")) { - reply->setHeader(HttpReply::ContentTypeHeader, "application/x-font-woff"); - } else if (file.fileName().endsWith(".jpg") || file.fileName().endsWith(".jpeg")) { - reply->setHeader(HttpReply::ContentTypeHeader, "image/jpeg"); - } else if (file.fileName().endsWith(".png") || file.fileName().endsWith(".PNG")) { - reply->setHeader(HttpReply::ContentTypeHeader, "image/png"); - } else if (file.fileName().endsWith(".ico")) { - reply->setHeader(HttpReply::ContentTypeHeader, "image/x-icon"); - } else if (file.fileName().endsWith(".svg")) { - reply->setHeader(HttpReply::ContentTypeHeader, "image/svg+xml; charset=\"utf-8\";"); - } + HttpReply *reply = WebServerResource::createFileReply(path); + reply->setClientId(clientId); + sendHttpReply(reply); + reply->deleteLater(); - reply->setPayload(file.readAll()); - reply->setClientId(clientId); - sendHttpReply(reply); - reply->deleteLater(); - return; - } } // Reject everything else... @@ -508,15 +502,7 @@ void WebServer::onEncrypted() { QSslSocket* socket = static_cast(sender()); qCDebug(dcWebServer()).noquote() << QString("Encrypted connection %1:%2 successfully established.").arg(socket->peerAddress().toString()).arg(socket->peerPort()); - connect(socket, &QSslSocket::readyRead, this, &WebServer::readClient); - connect(socket, &QSslSocket::disconnected, this, &WebServer::onDisconnected); - -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - connect(socket, &QSslSocket::errorOccurred, this, &WebServer::onError); -#else - connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError))); -#endif - emit clientConnected(m_clientList.key(socket)); + setupClient(m_clientList.key(socket), socket); } void WebServer::onError(QAbstractSocket::SocketError error) @@ -547,6 +533,20 @@ void WebServer::onAsyncReplyFinished() reply->deleteLater(); } +void WebServer::setupClient(const QUuid &clientId, QSslSocket *socket) +{ + connect(socket, &QSslSocket::readyRead, this, &WebServer::readClient); + connect(socket, &QSslSocket::disconnected, this, &WebServer::onDisconnected); + +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + connect(socket, &QSslSocket::errorOccurred, this, &WebServer::onError); +#else + connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError))); +#endif + + emit clientConnected(clientId); +} + /*! Set the configuration of this \l{WebServer} to the given \a config. * * \sa WebServerConfiguration @@ -581,7 +581,7 @@ bool WebServer::startServer() bool WebServer::stopServer() { foreach (QSslSocket *client, m_clientList.values()) - client->close(); + client->close(); close(); m_enabled = false; @@ -589,12 +589,6 @@ bool WebServer::stopServer() return true; } -WebServerConfiguration WebServer::configuration() const -{ - return m_configuration; -} - - QByteArray WebServer::createServerXmlDocument(QHostAddress address) { QByteArray uuid = NymeaCore::instance()->configuration()->serverUuid().toString().remove(QRegularExpression("[{}]")).toUtf8(); @@ -612,9 +606,9 @@ QByteArray WebServer::createServerXmlDocument(QHostAddress address) writer.writeEndElement(); // specVersion QString presentationUrl = QString("%1://%2:%3") - .arg(m_configuration.sslEnabled ? "https" : "http") - .arg(address.toString()) - .arg(m_configuration.port); + .arg(m_configuration.sslEnabled ? "https" : "http") + .arg(address.toString()) + .arg(m_configuration.port); writer.writeStartElement("device"); writer.writeTextElement("presentationURL", presentationUrl); writer.writeTextElement("deviceType", "urn:schemas-upnp-org:device:Basic:1"); @@ -815,8 +809,7 @@ void WebServerClient::removeConnection(QSslSocket *socket) */ void WebServerClient::resetTimout(QSslSocket *socket) { - QTimer *timer = nullptr; - timer = m_runningConnections.key(socket); + QTimer *timer = m_runningConnections.key(socket); if (timer) timer->start(); } diff --git a/libnymea-core/servers/webserver.h b/libnymea-core/servers/webserver.h index 9b609f31..8938b671 100644 --- a/libnymea-core/servers/webserver.h +++ b/libnymea-core/servers/webserver.h @@ -43,10 +43,11 @@ // Note: Hypertext Transfer Protocol (HTTP/1.1) from the Internet Engineering Task Force (IETF): // https://tools.ietf.org/html/rfc7231 -namespace nymeaserver { - class HttpReply; class HttpRequest; +class WebServerResource; + +namespace nymeaserver { class WebServerClient : public QObject { @@ -80,9 +81,14 @@ public: ~WebServer() override; QUrl serverUrl() const; + WebServerConfiguration configuration() const; void sendHttpReply(HttpReply *reply); + QList resources() const; + bool registerResource(WebServerResource *resource); + void unregisterResource(WebServerResource *resource); + private: QHash m_clientList; QList m_webServerClients; @@ -92,6 +98,8 @@ private: WebServerConfiguration m_configuration; QSslConfiguration m_sslConfiguration; + QHash m_resources; + bool m_enabled = false; bool verifyFile(QSslSocket *socket, const QString &fileName); @@ -99,16 +107,16 @@ private: QByteArray createServerXmlDocument(QHostAddress address); HttpReply *processIconRequest(const QString &fileName); - HttpReply *processDebugRequest(const QString &requestPath); protected: void incomingConnection(qintptr socketDescriptor) override; signals: - void httpRequestReady(const QUuid &clientId, const HttpRequest &httpRequest); void clientConnected(const QUuid &clientId); void clientDisconnected(const QUuid &clientId); + void httpRequestReady(const QUuid &clientId, const HttpRequest &httpRequest); + private slots: void readClient(); void onDisconnected(); @@ -116,12 +124,14 @@ private slots: void onError(QAbstractSocket::SocketError error); void onAsyncReplyFinished(); + void setupClient(const QUuid &clientId, QSslSocket *socket); + public slots: - void setConfiguration(const WebServerConfiguration &config); + void setConfiguration(const nymeaserver::WebServerConfiguration &config); void setServerName(const QString &serverName); bool startServer(); bool stopServer(); - WebServerConfiguration configuration() const; + }; diff --git a/libnymea-core/usermanager/usermanager.cpp b/libnymea-core/usermanager/usermanager.cpp index 11a5b40f..88882f5a 100644 --- a/libnymea-core/usermanager/usermanager.cpp +++ b/libnymea-core/usermanager/usermanager.cpp @@ -631,6 +631,7 @@ bool UserManager::verifyToken(const QByteArray &token) return false; } + //qCDebug(dcUserManager) << "Token authorized for user" << result.value("username").toString(); return true; } diff --git a/libnymea-core/zwave/zwavemanager.cpp b/libnymea-core/zwave/zwavemanager.cpp index 239f8d43..a0f0af17 100644 --- a/libnymea-core/zwave/zwavemanager.cpp +++ b/libnymea-core/zwave/zwavemanager.cpp @@ -122,7 +122,7 @@ void ZWaveManager::loadZWaveNetworks() NymeaSettings settings(NymeaSettings::SettingsRoleZWave); qCDebug(dcZWave()) << "Loading ZWave networks from" << settings.fileName(); settings.beginGroup("Networks"); - foreach (const QString &uuidString, settings.childGroups()) { + foreach (const QString &uuidString, settings.childGroups()) { settings.beginGroup(uuidString); QString serialPort = settings.value("serialPort").toString(); quint32 homeId = settings.value("homeId").toULongLong(); diff --git a/libnymea/experiences/experienceplugin.cpp b/libnymea/experiences/experienceplugin.cpp index f32df07a..504fb166 100644 --- a/libnymea/experiences/experienceplugin.cpp +++ b/libnymea/experiences/experienceplugin.cpp @@ -36,6 +36,13 @@ void ExperiencePlugin::init() } +/*! This method will can be used to provide a web server resource to the core. + Override this method and provide an object. The resource will be added to the webserver after the init() method has been called. */ +WebServerResource *ExperiencePlugin::webServerResource() const +{ + return nullptr; +} + /*! Returns a pointer to the DeviceManager. The pointer won't be valid unless init() has been called. */ ThingManager *ExperiencePlugin::thingManager() { diff --git a/libnymea/experiences/experienceplugin.h b/libnymea/experiences/experienceplugin.h index 88f25d2e..a4b4ee79 100644 --- a/libnymea/experiences/experienceplugin.h +++ b/libnymea/experiences/experienceplugin.h @@ -29,8 +29,10 @@ class ThingManager; class JsonRPCServer; +class WebServerResource; namespace nymeaserver { + class ExperienceManager; } class ExperiencePlugin : public QObject @@ -38,12 +40,15 @@ class ExperiencePlugin : public QObject Q_OBJECT public: explicit ExperiencePlugin(QObject *parent = nullptr); + virtual ~ExperiencePlugin() = default; virtual void init() = 0; + virtual WebServerResource *webServerResource() const; + protected: - ThingManager* thingManager(); - JsonRPCServer* jsonRpcServer(); + ThingManager *thingManager(); + JsonRPCServer *jsonRpcServer(); private: friend class nymeaserver::ExperienceManager; @@ -56,5 +61,4 @@ private: Q_DECLARE_INTERFACE(ExperiencePlugin, "io.nymea.ExperiencePlugin") - #endif // EXPERIENCEPLUGIN_H diff --git a/libnymea/integrations/thing.cpp b/libnymea/integrations/thing.cpp index c70f8a69..7963fddc 100644 --- a/libnymea/integrations/thing.cpp +++ b/libnymea/integrations/thing.cpp @@ -317,6 +317,15 @@ States Thing::states() const return m_states; } + +/*! Returns true, a \l{Param} with the given \a paramTypeId exists for this thing. */ +bool Thing::hasParam(const QString ¶mName) const +{ + ParamTypeId paramTypeId = m_thingClass.paramTypes().findByName(paramName).id(); + return m_params.hasParam(paramTypeId); +} + + /*! Returns true, a \l{Param} with the given \a paramTypeId exists for this thing. */ bool Thing::hasParam(const ParamTypeId ¶mTypeId) const { diff --git a/libnymea/integrations/thing.h b/libnymea/integrations/thing.h index 3c862213..c28e3337 100644 --- a/libnymea/integrations/thing.h +++ b/libnymea/integrations/thing.h @@ -112,6 +112,7 @@ public: ParamList params() const; bool hasParam(const ParamTypeId ¶mTypeId) const; + bool hasParam(const QString ¶mName) const; void setParams(const ParamList ¶ms); QVariant paramValue(const ParamTypeId ¶mTypeId) const; diff --git a/libnymea/libnymea.pro b/libnymea/libnymea.pro index e7995063..8eb1196f 100644 --- a/libnymea/libnymea.pro +++ b/libnymea/libnymea.pro @@ -139,6 +139,9 @@ HEADERS += \ platform/platformupdatecontroller.h \ platform/platformzeroconfcontroller.h \ experiences/experienceplugin.h \ + webserver/httprequest.h \ + webserver/httpreply.h \ + webserver/webserverresource.h \ SOURCES += \ hardware/modbus/modbusrtuhardwareresource.cpp \ @@ -257,6 +260,9 @@ SOURCES += \ platform/platformupdatecontroller.cpp \ platform/platformzeroconfcontroller.cpp \ experiences/experienceplugin.cpp \ + webserver/httprequest.cpp \ + webserver/httpreply.cpp \ + webserver/webserverresource.cpp \ RESOURCES += \ diff --git a/libnymea-core/servers/httpreply.cpp b/libnymea/webserver/httpreply.cpp similarity index 94% rename from libnymea-core/servers/httpreply.cpp rename to libnymea/webserver/httpreply.cpp index 4d277a99..a76d8a4a 100644 --- a/libnymea-core/servers/httpreply.cpp +++ b/libnymea/webserver/httpreply.cpp @@ -137,15 +137,12 @@ #include "httpreply.h" #include "loggingcategories.h" -#include "nymeacore.h" -#include "version.h" +#include "../version.h" #include #include #include -namespace nymeaserver { - HttpReply::HttpReply(QObject *parent) : QObject(parent), m_statusCode(HttpReply::Ok), @@ -162,7 +159,7 @@ HttpReply::HttpReply(QObject *parent) : // set known headers setHeader(HttpReply::ContentTypeHeader, "text/plain; charset=\"utf-8\";"); setHeader(HttpHeaderType::ServerHeader, "nymea/" + QByteArray(NYMEA_VERSION_STRING)); - setHeader(HttpHeaderType::DateHeader, NymeaCore::instance()->timeManager()->currentDateTime().toString("ddd, dd MMM yyyy hh:mm:ss").toUtf8()); + setHeader(HttpHeaderType::DateHeader, QDateTime::currentDateTime().toString("ddd, dd MMM yyyy hh:mm:ss").toUtf8()); setHeader(HttpHeaderType::CacheControlHeader, "no-cache"); setHeader(HttpHeaderType::ConnectionHeader, "Keep-Alive"); setRawHeader("Access-Control-Allow-Origin","*"); @@ -185,7 +182,7 @@ HttpReply::HttpReply(const HttpReply::HttpStatusCode &statusCode, const HttpRepl // set known / default headers setHeader(HttpReply::ContentTypeHeader, "text/plain; charset=\"utf-8\";"); setHeader(HttpHeaderType::ServerHeader, "nymea/" + QByteArray(NYMEA_VERSION_STRING)); - setHeader(HttpHeaderType::DateHeader, NymeaCore::instance()->timeManager()->currentDateTime().toString("ddd, dd MMM yyyy hh:mm:ss").toUtf8()); + setHeader(HttpHeaderType::DateHeader, QDateTime::currentDateTime().toString("ddd, dd MMM yyyy hh:mm:ss").toUtf8()); setHeader(HttpHeaderType::CacheControlHeader, "no-cache"); setHeader(HttpHeaderType::ConnectionHeader, "Keep-Alive"); setRawHeader("Access-Control-Allow-Origin","*"); @@ -200,6 +197,14 @@ HttpReply *HttpReply::createSuccessReply() return reply; } +HttpReply *HttpReply::createJsonReply(const QJsonDocument &jsonDoc, const HttpReply::HttpStatusCode &statusCode) +{ + HttpReply *reply = new HttpReply(statusCode, HttpReply::TypeSync); + reply->setPayload(jsonDoc.toJson(QJsonDocument::Compact)); + reply->setHeader(HttpReply::ContentTypeHeader, "application/json; charset=\"utf-8\";"); + return reply; +} + HttpReply *HttpReply::createErrorReply(const HttpReply::HttpStatusCode &statusCode) { HttpReply *reply = new HttpReply(statusCode, HttpReply::TypeSync); @@ -392,6 +397,12 @@ QByteArray HttpReply::getHttpReasonPhrase(const HttpReply::HttpStatusCode &statu case BadRequest: response = QString("Bad Request").toUtf8(); break; + case Unauthorized: + response = QString("Unauthorized").toUtf8(); + break; + case PaymentRequired: + response = QString("Payment required").toUtf8(); + break; case Forbidden: response = QString("Forbidden").toUtf8(); break; @@ -401,6 +412,9 @@ QByteArray HttpReply::getHttpReasonPhrase(const HttpReply::HttpStatusCode &statu case MethodNotAllowed: response = QString("Method Not Allowed").toUtf8(); break; + case NotAcceptable: + response = QString("Not Acceptable").toUtf8(); + break; case RequestTimeout: response = QString("Request Timeout").toUtf8(); break; @@ -490,5 +504,3 @@ QDebug operator<<(QDebug debug, HttpReply *httpReply) debug << qUtf8Printable(httpReply->payload()); return debug; } - -} diff --git a/libnymea-core/servers/httpreply.h b/libnymea/webserver/httpreply.h similarity index 86% rename from libnymea-core/servers/httpreply.h rename to libnymea/webserver/httpreply.h index 681652ad..c584a6a4 100644 --- a/libnymea-core/servers/httpreply.h +++ b/libnymea/webserver/httpreply.h @@ -30,16 +30,14 @@ #include #include #include +#include // Note: RFC 7231 HTTP/1.1 Semantics and Content -> http://tools.ietf.org/html/rfc7231 -namespace nymeaserver { - class HttpReply: public QObject { Q_OBJECT public: - enum HttpStatusCode { Ok = 200, Created = 201, @@ -48,9 +46,12 @@ public: Found = 302, PermanentRedirect = 308, BadRequest = 400, + Unauthorized = 401, + PaymentRequired = 402, Forbidden = 403, NotFound = 404, MethodNotAllowed = 405, + NotAcceptable = 406, RequestTimeout = 408, Conflict = 409, InternalServerError = 500, @@ -81,9 +82,10 @@ public: HttpReply(QObject *parent = nullptr); HttpReply(const HttpStatusCode &statusCode = HttpStatusCode::Ok, const Type &type = TypeSync, QObject *parent = nullptr); - static HttpReply* createSuccessReply(); - static HttpReply* createErrorReply(const HttpReply::HttpStatusCode &statusCode); - static HttpReply* createAsyncReply(); + static HttpReply *createSuccessReply(); + static HttpReply *createErrorReply(const HttpReply::HttpStatusCode &statusCode); + static HttpReply *createJsonReply(const QJsonDocument &jsonDoc, const HttpReply::HttpStatusCode &statusCode = HttpStatusCode::Ok); + static HttpReply *createAsyncReply(); void setHttpStatusCode(const HttpStatusCode &statusCode); HttpStatusCode httpStatusCode() const; @@ -116,9 +118,9 @@ public: bool timedOut() const; private: - HttpStatusCode m_statusCode; + HttpStatusCode m_statusCode = HttpReply::Ok; QByteArray m_reasonPhrase; - Type m_type; + Type m_type = HttpReply::TypeSync; QUuid m_clientId; QByteArray m_rawHeader; @@ -127,11 +129,11 @@ private: QHash m_rawHeaderList; - bool m_closeConnection; + bool m_closeConnection = false; QTimer *m_timer = nullptr; int m_timeout = 60000; - bool m_timedOut; + bool m_timedOut = false; QByteArray getHttpReasonPhrase(const HttpStatusCode &statusCode); QByteArray getHeaderType(const HttpHeaderType &headerType); @@ -149,6 +151,4 @@ signals: QDebug operator<<(QDebug debug, HttpReply *httpReply); -} - #endif // HTTPREPLY_H diff --git a/libnymea-core/servers/httprequest.cpp b/libnymea/webserver/httprequest.cpp similarity index 99% rename from libnymea-core/servers/httprequest.cpp rename to libnymea/webserver/httprequest.cpp index ac7158b0..3c53a2e6 100644 --- a/libnymea-core/servers/httprequest.cpp +++ b/libnymea/webserver/httprequest.cpp @@ -63,8 +63,6 @@ #include #include -namespace nymeaserver { - /*! Construct an empty \l{HttpRequest}. */ HttpRequest::HttpRequest() : m_rawData(QByteArray()), @@ -279,4 +277,3 @@ QDebug operator<<(QDebug debug, const HttpRequest &httpRequest) return debug; } -} diff --git a/libnymea-core/servers/httprequest.h b/libnymea/webserver/httprequest.h similarity index 95% rename from libnymea-core/servers/httprequest.h rename to libnymea/webserver/httprequest.h index 8c3f800b..6b33ea59 100644 --- a/libnymea-core/servers/httprequest.h +++ b/libnymea/webserver/httprequest.h @@ -30,8 +30,6 @@ #include #include -namespace nymeaserver { - class HttpRequest { public: @@ -86,7 +84,6 @@ private: RequestMethod getRequestMethodType(const QString &methodString); }; -QDebug operator<< (QDebug debug, const HttpRequest &httpRequest); +QDebug operator<<(QDebug debug, const HttpRequest &httpRequest); -} #endif // HTTPREQUEST_H diff --git a/libnymea/webserver/webserverresource.cpp b/libnymea/webserver/webserverresource.cpp new file mode 100644 index 00000000..3c4aa720 --- /dev/null +++ b/libnymea/webserver/webserverresource.cpp @@ -0,0 +1,102 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2025, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU version 3. This project 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 +* this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "webserverresource.h" +#include "loggingcategories.h" + +#include + +WebServerResource::WebServerResource(const QString &basePath, QObject *parent) + : QObject{parent}, + m_basePath{basePath} +{ + +} + +QString WebServerResource::basePath() const +{ + return m_basePath; +} + +bool WebServerResource::enabled() const +{ + return m_enabled; +} + +void WebServerResource::setEnabled(bool enabled) +{ + if (m_enabled == enabled) + return; + + qCDebug(dcWebServer()) << "The resource" << m_basePath << "is now" << (enabled ? "enabled" : "disabled"); + m_enabled = enabled; + emit enabledChanged(m_enabled); +} + +HttpReply *WebServerResource::createFileReply(const QString fileName) +{ + qCDebug(dcWebServer()) << "Create file reply for" << fileName; + + QFile file(fileName); + if (!file.open(QFile::ReadOnly)) { + qCWarning(dcWebServer()) << "Unable to generate file reply. The file" << fileName << "could not be opened. Respond with 403 Forbidden."; + return HttpReply::createErrorReply(HttpReply::Forbidden); + } + + HttpReply *reply = HttpReply::createSuccessReply(); + + // Check content type + if (file.fileName().endsWith(".html")) { + reply->setHeader(HttpReply::ContentTypeHeader, "text/html; charset=\"utf-8\";"); + } else if (file.fileName().endsWith(".css")) { + reply->setHeader(HttpReply::ContentTypeHeader, "text/css; charset=\"utf-8\";"); + } else if (file.fileName().endsWith(".pdf")) { + reply->setHeader(HttpReply::ContentTypeHeader, "application/pdf"); + } else if (file.fileName().endsWith(".js")) { + reply->setHeader(HttpReply::ContentTypeHeader, "text/javascript; charset=\"utf-8\";"); + } else if (file.fileName().endsWith(".ttf")) { + reply->setHeader(HttpReply::ContentTypeHeader, "application/x-font-ttf"); + } else if (file.fileName().endsWith(".eot")) { + reply->setHeader(HttpReply::ContentTypeHeader, "application/vnd.ms-fontobject"); + } else if (file.fileName().endsWith(".woff")) { + reply->setHeader(HttpReply::ContentTypeHeader, "application/x-font-woff"); + } else if (file.fileName().endsWith(".jpg") || file.fileName().endsWith(".jpeg")) { + reply->setHeader(HttpReply::ContentTypeHeader, "image/jpeg"); + } else if (file.fileName().endsWith(".png") || file.fileName().endsWith(".PNG")) { + reply->setHeader(HttpReply::ContentTypeHeader, "image/png"); + } else if (file.fileName().endsWith(".ico")) { + reply->setHeader(HttpReply::ContentTypeHeader, "image/x-icon"); + } else if (file.fileName().endsWith(".svg")) { + reply->setHeader(HttpReply::ContentTypeHeader, "image/svg+xml; charset=\"utf-8\";"); + } + + reply->setPayload(file.readAll()); + return reply; +} diff --git a/libnymea/webserver/webserverresource.h b/libnymea/webserver/webserverresource.h new file mode 100644 index 00000000..879e84fb --- /dev/null +++ b/libnymea/webserver/webserverresource.h @@ -0,0 +1,65 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2025, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU version 3. This project 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 +* this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef WebServerResource_H +#define WebServerResource_H + +#include +#include + +#include "httpreply.h" +#include "httprequest.h" + +class WebServerResource : public QObject +{ + Q_OBJECT +public: + explicit WebServerResource(const QString &basePath, QObject *parent = nullptr); + virtual ~WebServerResource() = default; + + QString basePath() const; + + bool enabled() const; + void setEnabled(bool enabled); + + virtual HttpReply *processRequest(const HttpRequest &request) = 0; + + static HttpReply *createFileReply(const QString fileName); + +signals: + void enabledChanged(bool enabled); + +protected: + QString m_basePath; + bool m_enabled = true; + +}; + +#endif // WebServerResource_H diff --git a/plugins/mock/httpdaemon.h b/plugins/mock/httpdaemon.h index fedafb43..0b5e2377 100644 --- a/plugins/mock/httpdaemon.h +++ b/plugins/mock/httpdaemon.h @@ -41,8 +41,8 @@ class HttpDaemon : public QTcpServer public: HttpDaemon(Thing *thing, IntegrationPlugin* parent = nullptr); ~HttpDaemon(); - void incomingConnection(qintptr socket) override; + void incomingConnection(qintptr socket) override; void actionExecuted(const ActionTypeId &actionTypeId); signals: diff --git a/tests/auto/webserver/testwebserver.cpp b/tests/auto/webserver/testwebserver.cpp index c81a0a27..0ee59264 100644 --- a/tests/auto/webserver/testwebserver.cpp +++ b/tests/auto/webserver/testwebserver.cpp @@ -25,6 +25,8 @@ #include "nymeatestbase.h" #include "nymeacore.h" +#include + #include #include