From 68a17e24420d985326bc6564da7abfc1e16ef3eb Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 Date: Sun, 25 Sep 2022 01:31:36 +0200 Subject: [PATCH] Import existing sources --- espremoteagent/espremoteagent.cpp | 331 ++++++++++++++++++++++ espremoteagent/espremoteagent.h | 35 +++ espremoteagent/espremoteagent.ini | 13 + espremoteagent/espremoteagent.pro | 31 ++ espremoteagent/espremoteagentcontainers.h | 11 + espremoteagent/espremoteport.cpp | 225 +++++++++++++++ espremoteagent/espremoteport.h | 56 ++++ espremoteagent/main.cpp | 67 +++++ espremotemanager.pro | 9 + espremotemanager/espremoteclient.cpp | 119 ++++++++ espremotemanager/espremoteclient.h | 36 +++ espremotemanager/espremotemanager.cpp | 136 +++++++++ espremotemanager/espremotemanager.h | 32 +++ espremotemanager/espremotemanager.ini | 7 + espremotemanager/espremotemanager.pro | 30 ++ espremotemanager/main.cpp | 54 ++++ project.pri | 27 ++ webserver/abstractwebserver.cpp | 101 +++++++ webserver/abstractwebserver.h | 62 ++++ webserver/webserver.pro | 23 ++ webserver/webserver_global.h | 9 + webserver/webserverclientconnection.cpp | 143 ++++++++++ webserver/webserverclientconnection.h | 42 +++ webserver/webservercontainer.h | 22 ++ webserver/webserverutils.cpp | 17 ++ webserver/webserverutils.h | 22 ++ 26 files changed, 1660 insertions(+) create mode 100644 espremoteagent/espremoteagent.cpp create mode 100644 espremoteagent/espremoteagent.h create mode 100644 espremoteagent/espremoteagent.ini create mode 100644 espremoteagent/espremoteagent.pro create mode 100644 espremoteagent/espremoteagentcontainers.h create mode 100644 espremoteagent/espremoteport.cpp create mode 100644 espremoteagent/espremoteport.h create mode 100644 espremoteagent/main.cpp create mode 100644 espremotemanager.pro create mode 100644 espremotemanager/espremoteclient.cpp create mode 100644 espremotemanager/espremoteclient.h create mode 100644 espremotemanager/espremotemanager.cpp create mode 100644 espremotemanager/espremotemanager.h create mode 100644 espremotemanager/espremotemanager.ini create mode 100644 espremotemanager/espremotemanager.pro create mode 100644 espremotemanager/main.cpp create mode 100644 project.pri create mode 100644 webserver/abstractwebserver.cpp create mode 100644 webserver/abstractwebserver.h create mode 100644 webserver/webserver.pro create mode 100644 webserver/webserver_global.h create mode 100644 webserver/webserverclientconnection.cpp create mode 100644 webserver/webserverclientconnection.h create mode 100644 webserver/webservercontainer.h create mode 100644 webserver/webserverutils.cpp create mode 100644 webserver/webserverutils.h diff --git a/espremoteagent/espremoteagent.cpp b/espremoteagent/espremoteagent.cpp new file mode 100644 index 0000000..0f0b931 --- /dev/null +++ b/espremoteagent/espremoteagent.cpp @@ -0,0 +1,331 @@ +#include "espremoteagent.h" + +#include +#include +#include + +#include "webservercontainer.h" +#include "webserverclientconnection.h" +#include "espremoteagentcontainers.h" +#include "espremoteport.h" + +EspRemoteAgent::EspRemoteAgent(std::vector &&serialPortConfigs, QObject *parent) : + AbstractWebserver{parent} +{ + m_ports.reserve(serialPortConfigs.size()); + + for (auto &config : serialPortConfigs) + m_ports.emplace_back(std::make_unique(std::move(config), this)); +} + +EspRemoteAgent::~EspRemoteAgent() = default; + +void EspRemoteAgent::requestReceived(WebserverClientConnection &client, const Request &request) +{ + const QUrl url{request.path}; + const QUrlQuery query{url}; + + if (url.path() == "/") + { + sendRootResponse(client, url, query); + } + else if (url.path() == "/open") + { + sendOpenResponse(client, url, query); + } + else if (url.path() == "/close") + { + sendCloseResponse(client, url, query); + } + else if (url.path() == "/reboot") + { + sendRebootResponse(client, url, query); + } + else if (url.path() == "/setDTR") + { + sendSetDTRResponse(client, url, query); + } + else if (url.path() == "/setRTS") + { + sendSetRTSResponse(client, url, query); + } + else + if (!client.sendFullResponse(404, "Not Found", {{"Content-Type", "text/plain"}}, "The requested path \"" + request.path + "\" was not found.")) + qWarning() << "sending response failed"; +} + +void EspRemoteAgent::sendRootResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query) +{ + QString content = + "" + "" + "ESP Remote Agent" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + std::size_t i{}; + for (const auto &port : m_ports) + { + const auto currentId = i++; + content += QStringLiteral("" + "" + "" + "" + "" + "" + "" + "") + .arg(currentId) + .arg(port->port()) + .arg(port->status()) + .arg(port->message()) + .arg(port->isDataTerminalReady() ? "On" : "Off") + .arg(port->isDataTerminalReady() ? "false" : "true") + .arg(port->isRequestToSend() ? "On" : "Off") + .arg(port->isRequestToSend() ? "false" : "true") + .arg(port->logOutput()); + } + + content += "" + "
IDPortStatusMessageActionsLog output
%0%1%2%3" + "Open " + "Close
" + "Reboot
" + "DTR %4 Toggle
" + "RTS %6 Toggle" + "
%8
" + "" + ""; + + if (!client.sendFullResponse(200, "Ok", {{"Content-Type", "text/html"}}, content.toUtf8())) + qWarning() << "sending response failed"; +} + +void EspRemoteAgent::sendOpenResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query) +{ + if (!query.hasQueryItem("id")) + { + if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "id missing")) + qWarning() << "sending response failed"; + return; + } + const auto idStr = query.queryItemValue("id"); + bool ok{}; + const auto id = idStr.toInt(&ok); + if (!ok) + { + if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "could not parse id")) + qWarning() << "sending response failed"; + return; + } + if (id < 0 || id >= m_ports.size()) + { + if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "id out of range")) + qWarning() << "sending response failed"; + return; + } + + if ((*std::next(std::begin(m_ports), id))->tryOpen()) + { + if (!client.sendFullResponse(200, "Ok", {{"Content-Type", "text/plain"}}, "Port opened successfully!")) + qWarning() << "sending response failed"; + return; + } + else + { + if (!client.sendFullResponse(500, "Internal Server Error", {{"Content-Type", "text/plain"}}, "Opening port failed!")) + qWarning() << "sending response failed"; + return; + } +} + +void EspRemoteAgent::sendCloseResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query) +{ + if (!query.hasQueryItem("id")) + { + if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "id missing")) + qWarning() << "sending response failed"; + return; + } + const auto idStr = query.queryItemValue("id"); + bool ok{}; + const auto id = idStr.toInt(&ok); + if (!ok) + { + if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "could not parse id")) + qWarning() << "sending response failed"; + return; + } + if (id < 0 || id >= m_ports.size()) + { + if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "id out of range")) + qWarning() << "sending response failed"; + return; + } + + (*std::next(std::begin(m_ports), id))->close(); + if (!client.sendFullResponse(200, "Ok", {{"Content-Type", "text/plain"}}, "Port closed successfully!")) + qWarning() << "sending response failed"; +} + +void EspRemoteAgent::sendRebootResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query) +{ + if (!query.hasQueryItem("id")) + { + if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "id missing")) + qWarning() << "sending response failed"; + return; + } + const auto idStr = query.queryItemValue("id"); + bool ok{}; + const auto id = idStr.toInt(&ok); + if (!ok) + { + if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "could not parse id")) + qWarning() << "sending response failed"; + return; + } + if (id < 0 || id >= m_ports.size()) + { + if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "id out of range")) + qWarning() << "sending response failed"; + return; + } + + if ((*std::next(std::begin(m_ports), id))->reboot()) + { + if (!client.sendFullResponse(200, "Ok", {{"Content-Type", "text/plain"}}, "Reboot successfully!")) + qWarning() << "sending response failed"; + return; + } + else + { + if (!client.sendFullResponse(500, "Internal Server Error", {{"Content-Type", "text/plain"}}, "Reboot failed!")) + qWarning() << "sending response failed"; + return; + } +} + +void EspRemoteAgent::sendSetDTRResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query) +{ + if (!query.hasQueryItem("id")) + { + if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "id missing")) + qWarning() << "sending response failed"; + return; + } + const auto idStr = query.queryItemValue("id"); + bool ok{}; + const auto id = idStr.toInt(&ok); + if (!ok) + { + if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "could not parse id")) + qWarning() << "sending response failed"; + return; + } + if (id < 0 || id >= m_ports.size()) + { + if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "id out of range")) + qWarning() << "sending response failed"; + return; + } + + if (!query.hasQueryItem("set")) + { + if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "set missing")) + qWarning() << "sending response failed"; + return; + } + bool set; + if (const auto setStr = query.queryItemValue("set"); setStr == "true") + set = true; + else if (setStr == "false") + set = false; + else + { + if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "could not parse set")) + qWarning() << "sending response failed"; + return; + } + + if ((*std::next(std::begin(m_ports), id))->setDataTerminalReady(set)) + { + if (!client.sendFullResponse(200, "Ok", {{"Content-Type", "text/plain"}}, "Port set DTR successfully!")) + qWarning() << "sending response failed"; + return; + } + else + { + if (!client.sendFullResponse(500, "Internal Server Error", {{"Content-Type", "text/plain"}}, "Set port DTR failed!")) + qWarning() << "sending response failed"; + return; + } +} + +void EspRemoteAgent::sendSetRTSResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query) +{ + if (!query.hasQueryItem("id")) + { + if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "id missing")) + qWarning() << "sending response failed"; + return; + } + const auto idStr = query.queryItemValue("id"); + bool ok{}; + const auto id = idStr.toInt(&ok); + if (!ok) + { + if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "could not parse id")) + qWarning() << "sending response failed"; + return; + } + if (id < 0 || id >= m_ports.size()) + { + if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "id out of range")) + qWarning() << "sending response failed"; + return; + } + + if (!query.hasQueryItem("set")) + { + if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "set missing")) + qWarning() << "sending response failed"; + return; + } + bool set; + if (const auto setStr = query.queryItemValue("set"); setStr == "true") + set = true; + else if (setStr == "false") + set = false; + else + { + if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "could not parse set")) + qWarning() << "sending response failed"; + return; + } + + if ((*std::next(std::begin(m_ports), id))->setRequestToSend(set)) + { + if (!client.sendFullResponse(200, "Ok", {{"Content-Type", "text/plain"}}, "Port set RTS successfully!")) + qWarning() << "sending response failed"; + return; + } + else + { + if (!client.sendFullResponse(500, "Internal Server Error", {{"Content-Type", "text/plain"}}, "Set port RTS failed!")) + qWarning() << "sending response failed"; + return; + } +} diff --git a/espremoteagent/espremoteagent.h b/espremoteagent/espremoteagent.h new file mode 100644 index 0000000..49298c0 --- /dev/null +++ b/espremoteagent/espremoteagent.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +#include "abstractwebserver.h" + +class SerialPortConfig; +class WebserverClientConnection; +class Request; +class EspRemotePort; +class QUrl; +class QUrlQuery; + +class EspRemoteAgent : public AbstractWebserver +{ + Q_OBJECT + +public: + explicit EspRemoteAgent(std::vector &&serialPortConfigs, QObject *parent = nullptr); + ~EspRemoteAgent() override; + +protected: + void requestReceived(WebserverClientConnection &client, const Request &request) override; + +private: + void sendRootResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query); + void sendOpenResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query); + void sendCloseResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query); + void sendRebootResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query); + void sendSetDTRResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query); + void sendSetRTSResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query); + + std::vector> m_ports; +}; diff --git a/espremoteagent/espremoteagent.ini b/espremoteagent/espremoteagent.ini new file mode 100644 index 0000000..08ef0ea --- /dev/null +++ b/espremoteagent/espremoteagent.ini @@ -0,0 +1,13 @@ +[Webserver] +listen=Any +port=80 + +[PortA] +port=/dev/ttyUSB0 +baudrate=115200 +url=ws://office-pi:1235/charger0 + +[PortB] +port=/dev/ttyUSB1 +baudrate=115200 +url=ws://office-pi:1235/charger1 diff --git a/espremoteagent/espremoteagent.pro b/espremoteagent/espremoteagent.pro new file mode 100644 index 0000000..592d4d7 --- /dev/null +++ b/espremoteagent/espremoteagent.pro @@ -0,0 +1,31 @@ +QT = core network serialport websockets + +TARGET = espremoteagent + +TEMPLATE = app + +CONFIG += console +CONFIG -= app_bundle + +PROJECT_ROOT = .. + +DESTDIR = $${OUT_PWD}/$${PROJECT_ROOT}/bin + +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +DBLIBS += webserver + +HEADERS += \ + espremoteagent.h \ + espremoteagentcontainers.h \ + espremoteport.h + +SOURCES += \ + espremoteport.cpp \ + main.cpp \ + espremoteagent.cpp + +OTHER_FILES += \ + espremoteagent.ini + +include($${PROJECT_ROOT}/project.pri) diff --git a/espremoteagent/espremoteagentcontainers.h b/espremoteagent/espremoteagentcontainers.h new file mode 100644 index 0000000..195b657 --- /dev/null +++ b/espremoteagent/espremoteagentcontainers.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +struct SerialPortConfig +{ + QString port; + int baudrate; + QUrl url; +}; diff --git a/espremoteagent/espremoteport.cpp b/espremoteagent/espremoteport.cpp new file mode 100644 index 0000000..7b48592 --- /dev/null +++ b/espremoteagent/espremoteport.cpp @@ -0,0 +1,225 @@ +#include "espremoteport.h" + +#include +#include +#include +#include +#include +#include +#include + +EspRemotePort::EspRemotePort(SerialPortConfig &&config, QObject *parent) : + QObject{parent}, + m_config{std::move(config)}, + m_port{std::make_unique(m_config.port)}, + m_websocket{m_config.url.isEmpty() ? nullptr : std::make_unique(QString{}, QWebSocketProtocol::VersionLatest, this)} +{ + connect(m_port.get(), &QSerialPort::readyRead, this, &EspRemotePort::serialReadyRead); + + if (m_websocket) + { + connect(m_websocket.get(), &QWebSocket::connected, this, &EspRemotePort::websocketConnected); + connect(m_websocket.get(), &QWebSocket::disconnected, this, &EspRemotePort::websocketDisconnected); + connect(m_websocket.get(), qOverload(&QWebSocket::error), this, &EspRemotePort::websocketError); + connect(m_websocket.get(), &QWebSocket::textMessageReceived, this, &EspRemotePort::websocketTextMessageReceived); + qDebug() << "connecting to" << m_config.url; + m_websocket->open(m_config.url); + } + + tryOpen(); +} + +EspRemotePort::~EspRemotePort() = default; + +QString EspRemotePort::status() const +{ + if (m_port->isOpen()) + return tr("Open"); + else + return tr("Not open"); +} + +QString EspRemotePort::logOutput() const +{ + QString str; + for (const auto &line : m_logOutput) + { + if (!str.isEmpty()) + str += "\n"; + str += line.toHtmlEscaped(); + } + return str; +} + +bool EspRemotePort::reboot() +{ + bool set = m_port->isDataTerminalReady(); + + Q_ASSERT(m_port); + + if (!m_port->setDataTerminalReady(!set)) + return false; + + QTimer::singleShot(100, m_port.get(), [set,port=m_port.get()](){ + if (!port->setDataTerminalReady(set)) + qWarning() << "reboot failed"; + }); + + return true; +} + +bool EspRemotePort::setDataTerminalReady(bool set) +{ + return m_port->setDataTerminalReady(set); +} + +bool EspRemotePort::isDataTerminalReady() +{ + return m_port->isDataTerminalReady(); +} + +bool EspRemotePort::setRequestToSend(bool set) +{ + return m_port->setRequestToSend(set); +} + +bool EspRemotePort::isRequestToSend() +{ + return m_port->isRequestToSend(); +} + +bool EspRemotePort::tryOpen() +{ + m_port->close(); + + if (!m_port->setBaudRate(m_config.baudrate)) + qWarning() << "could not set baud rate" << m_config.baudrate; + + if (!m_port->open(QIODevice::ReadWrite)) + { + m_message = tr("Could not open port because %0").arg(m_port->errorString()); + qWarning() << m_message; + return false; + } + + return true; +} + +void EspRemotePort::close() +{ + m_port->close(); +} + +void EspRemotePort::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == m_reconnectTimerId) + { + m_reconnectTimerId = -1; + + if (!m_config.url.isEmpty()) + { + Q_ASSERT(m_websocket); + qDebug() << "reconnecting to" << m_config.url; + m_websocket->open(m_config.url); + } + } + else + QObject::timerEvent(event); +} + +void EspRemotePort::serialReadyRead() +{ + while (m_port->canReadLine()) + { + auto line = m_port->readLine(); + + if (line.endsWith('\n')) + { + line.chop(1); + if (line.endsWith('\r')) + line.chop(1); + } + +// qDebug() << line; + + if (m_websocket) + { + m_websocket->sendTextMessage(QJsonDocument{QJsonObject{ + {"type", "log"}, + {"line", QString{line}}, + }}.toJson()); + } + + m_logOutput.push(std::move(line)); + + while (m_logOutput.size() > 10) + m_logOutput.pop(); + } +} + +void EspRemotePort::websocketConnected() +{ + qDebug() << "called"; + + if (m_reconnectTimerId != -1) + killTimer(m_reconnectTimerId); +} + +void EspRemotePort::websocketDisconnected() +{ + qDebug() << "called"; +} + +void EspRemotePort::websocketError(QAbstractSocket::SocketError error) +{ + qDebug() << "called" << error; + + if (m_reconnectTimerId != -1) + killTimer(m_reconnectTimerId); + + m_reconnectTimerId = startTimer(5000); +} + +void EspRemotePort::websocketTextMessageReceived(const QString &message) +{ +// qDebug() << message; + + QJsonParseError error; + const auto doc = QJsonDocument::fromJson(message.toUtf8(), &error); + if (error.error != QJsonParseError::NoError) + { + qWarning() << "could not parse json command:" << error.errorString(); + return; + } + + if (!doc.isObject()) + { + qWarning() << "json command is not an object"; + return; + } + + const auto obj = doc.object(); + + if (!obj.contains("type")) + { + qWarning() << "json command does not contain a type"; + return; + } + + const auto typeVal = obj.value("type"); + + if (!typeVal.isString()) + { + qWarning() << "json command type is not a string"; + return; + } + + const auto type = typeVal.toString(); + + if (type == "reboot") + { + reboot(); + } + else + qWarning() << "unknown command type" << type; +} diff --git a/espremoteagent/espremoteport.h b/espremoteagent/espremoteport.h new file mode 100644 index 0000000..c8d4f45 --- /dev/null +++ b/espremoteagent/espremoteport.h @@ -0,0 +1,56 @@ +#pragma once + +#include + +#include +#include + +#include "espremoteagentcontainers.h" +#include "webserverutils.h" + +class QSerialPort; +class QWebSocket; + +class EspRemotePort : public QObject +{ + Q_OBJECT + +public: + explicit EspRemotePort(SerialPortConfig &&config, QObject *parent = nullptr); + ~EspRemotePort() override; + + QString port() const { return m_config.port; } + QString status() const; + QString message() const { return m_message; } + QString logOutput() const; + + bool reboot(); + + bool setDataTerminalReady(bool set); + bool isDataTerminalReady(); + + bool setRequestToSend(bool set); + bool isRequestToSend(); + + bool tryOpen(); + void close(); + +protected: + void timerEvent(QTimerEvent *event) override; + +private slots: + void serialReadyRead(); + void websocketConnected(); + void websocketDisconnected(); + void websocketError(QAbstractSocket::SocketError error); + void websocketTextMessageReceived(const QString &message); + +private: + SerialPortConfig m_config; + const std::unique_ptr m_port; + const std::unique_ptr m_websocket; + QString m_message; + iterable_queue m_logOutput; + + int m_reconnectTimerId{-1}; +}; diff --git a/espremoteagent/main.cpp b/espremoteagent/main.cpp new file mode 100644 index 0000000..649a0ca --- /dev/null +++ b/espremoteagent/main.cpp @@ -0,0 +1,67 @@ +#include +#include +#include + +#include "espremoteagent.h" +#include "espremoteagentcontainers.h" +#include "webserverutils.h" + +int main(int argc, char *argv[]) +{ + qSetMessagePattern(QStringLiteral("%{time dd.MM.yyyy HH:mm:ss.zzz} " + "[" + "%{if-debug}D%{endif}" + "%{if-info}I%{endif}" + "%{if-warning}W%{endif}" + "%{if-critical}C%{endif}" + "%{if-fatal}F%{endif}" + "] " + "%{function}(): " + "%{message}")); + + QCoreApplication app{argc, argv}; + + QSettings settings{"espremoteagent.ini", QSettings::IniFormat}; + + std::vector serialPortConfigs; + + const auto probePort = [&](auto group){ + auto port = settings.value(QStringLiteral("%0/port").arg(group)).toString(); + if (port.isEmpty()) + return; + + QUrl url; + if (auto urlStr = settings.value(QStringLiteral("%0/url").arg(group)).toString(); !urlStr.isEmpty()) + url = QUrl{std::move(urlStr)}; + + int baudrate{}; + bool ok{}; + baudrate = settings.value(QStringLiteral("%0/baudrate").arg(group)).toInt(&ok); + if (!ok) + qFatal("could not parse baudrate for %s", qPrintable(group)); + + serialPortConfigs.emplace_back(SerialPortConfig{ + .port=std::move(port), + .baudrate=baudrate, + .url=std::move(url) + }); + }; + + probePort("PortA"); + probePort("PortB"); + + QHostAddress webserverListen = parseHostAddress(settings.value("Webserver/listen").toString()); + int webserverPort; + { + bool ok{}; + webserverPort = settings.value("Webserver/port", 1234).toInt(&ok); + if (!ok) + qFatal("could not parse webserver port"); + } + + EspRemoteAgent agent{std::move(serialPortConfigs)}; + if (!agent.listen(webserverListen, webserverPort)) + qFatal("could not start listening %s", qPrintable(agent.errorString())); + + return app.exec(); +} diff --git a/espremotemanager.pro b/espremotemanager.pro new file mode 100644 index 0000000..2887f89 --- /dev/null +++ b/espremotemanager.pro @@ -0,0 +1,9 @@ +TEMPLATE = subdirs + +SUBDIRS += \ + espremoteagent \ + espremotemanager \ + webserver + +espremoteagent.depends += webserver +espremotemanager.depends += webserver diff --git a/espremotemanager/espremoteclient.cpp b/espremotemanager/espremoteclient.cpp new file mode 100644 index 0000000..b4c6452 --- /dev/null +++ b/espremotemanager/espremoteclient.cpp @@ -0,0 +1,119 @@ +#include "espremoteclient.h" + +#include +#include +#include +#include + +EspRemoteClient::EspRemoteClient(QWebSocket *websocket, EspRemoteManager &manager, QObject *parent) : + QObject{parent}, + m_websocket{websocket}, + m_manager{manager} +{ + qDebug() << "connected" << m_websocket->peerAddress().toString() << m_websocket->peerPort(); + + connect(m_websocket.get(), &QWebSocket::disconnected, this, &QObject::deleteLater); + connect(m_websocket.get(), &QWebSocket::textMessageReceived, this, &EspRemoteClient::textMessageReceived); +} + +EspRemoteClient::~EspRemoteClient() +{ + qDebug() << "disconnected" << m_websocket->peerAddress().toString() << m_websocket->peerPort(); +} + +QString EspRemoteClient::peer() const +{ + return QStringLiteral("%0:%1").arg(m_websocket->peerAddress().toString()).arg(m_websocket->peerPort()); +} + +QString EspRemoteClient::path() const +{ + return m_websocket->requestUrl().path(); +} + +QString EspRemoteClient::logOutput() const +{ + QString str; + for (const auto &line : m_logOutput) + { + if (!str.isEmpty()) + str += "\n"; + str += line.toHtmlEscaped(); + } + return str; +} + +void EspRemoteClient::reboot() +{ + m_websocket->sendTextMessage(QJsonDocument{QJsonObject{ + {"type", "reboot"} + }}.toJson()); +} + +void EspRemoteClient::textMessageReceived(const QString &message) +{ +// qDebug() << message; + + QJsonParseError error; + const auto doc = QJsonDocument::fromJson(message.toUtf8(), &error); + if (error.error != QJsonParseError::NoError) + { + qWarning() << "could not parse json command:" << error.errorString(); + return; + } + + if (!doc.isObject()) + { + qWarning() << "json command is not an object"; + return; + } + + const auto obj = doc.object(); + + if (!obj.contains("type")) + { + qWarning() << "json command does not contain a type"; + return; + } + + const auto typeVal = obj.value("type"); + + if (!typeVal.isString()) + { + qWarning() << "json command type is not a string"; + return; + } + + const auto type = typeVal.toString(); + + if (type == "log") + { + if (!obj.contains("line")) + { + qWarning() << "json command does not contain a line"; + return; + } + + const auto lineVal = obj.value("line"); + + if (!lineVal.isString()) + { + qWarning() << "json command line is not a string"; + return; + } + + auto line = lineVal.toString(); + + logReceived(std::move(line)); + } + else + qWarning() << "unknown command type" << type; +} + +void EspRemoteClient::logReceived(QString &&line) +{ + m_logOutput.push(std::move(line)); + + while (m_logOutput.size() > 10) + m_logOutput.pop(); +} diff --git a/espremotemanager/espremoteclient.h b/espremotemanager/espremoteclient.h new file mode 100644 index 0000000..c84582d --- /dev/null +++ b/espremotemanager/espremoteclient.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include + +#include "webserverutils.h" + +class QWebSocket; +class EspRemoteManager; + +class EspRemoteClient : public QObject +{ + Q_OBJECT + +public: + explicit EspRemoteClient(QWebSocket *websocket, EspRemoteManager &manager, QObject *parent = nullptr); + ~EspRemoteClient() override; + + QString peer() const; + QString path() const; + QString logOutput() const; + + void reboot(); + +private slots: + void textMessageReceived(const QString &message); + +private: + void logReceived(QString &&line); + + const std::unique_ptr m_websocket; + EspRemoteManager &m_manager; + + iterable_queue m_logOutput; +}; diff --git a/espremotemanager/espremotemanager.cpp b/espremotemanager/espremotemanager.cpp new file mode 100644 index 0000000..2c04b47 --- /dev/null +++ b/espremotemanager/espremotemanager.cpp @@ -0,0 +1,136 @@ +#include "espremotemanager.h" + +#include +#include +#include +#include + +#include "webservercontainer.h" +#include "webserverclientconnection.h" +#include "espremoteclient.h" + +EspRemoteManager::EspRemoteManager(QWebSocketServer &websocketServer, QObject *parent) : + AbstractWebserver{parent}, + m_websocketServer{websocketServer} +{ + connect(&m_websocketServer, &QWebSocketServer::newConnection, this, &EspRemoteManager::newConnection); +} + +EspRemoteManager::~EspRemoteManager() = default; + +void EspRemoteManager::requestReceived(WebserverClientConnection &client, const Request &request) +{ + QUrl url{request.path}; + QUrlQuery query{url}; + + if (url.path() == "/") + { + sendRootResponse(client, url, query); + } + else if (url.path() == "/reboot") + { + sendRebootResponse(client, url, query); + } + else + if (!client.sendFullResponse(404, "Not Found", {{"Content-Type", "text/plain"}}, "The requested path \"" + request.path + "\" was not found.")) + qWarning() << "sending response failed"; +} + +void EspRemoteManager::newConnection() +{ + while (const auto socket = m_websocketServer.nextPendingConnection()) + { + auto ö = new EspRemoteClient(socket, *this, this); + connect(ö, &QObject::destroyed, this, &EspRemoteManager::clientDestroyed); + m_clients.emplace_back(ö); + } +} + +void EspRemoteManager::clientDestroyed(QObject *object) +{ + m_clients.erase(std::remove(std::begin(m_clients), std::end(m_clients), object), std::end(m_clients)); +} + +void EspRemoteManager::sendRootResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query) +{ + QString content = + "" + "" + "" + "" + "" + "ESP Remote Manager" + "" + "" + "" + "" + "

ESP Remote Manager

" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + for (auto port : m_clients) + { + content += QStringLiteral("" + "" + "" + "" + "" + "") + .arg(port->peer()) + .arg(port->path()) + .arg(port->logOutput()); + } + + content += "" + "
PeerWS PathActionsLog
%0%1Reboot
%2
" + "" + "" + "" + "" + "" + "" + ""; + + if (!client.sendFullResponse(200, "Ok", {{"Content-Type", "text/html"}}, content.toUtf8())) + qWarning() << "sending response failed"; +} + +void EspRemoteManager::sendRebootResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query) +{ + if (!query.hasQueryItem("peer")) + { + if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "peer missing")) + qWarning() << "sending response failed"; + return; + } + const auto peer = query.queryItemValue("peer"); + auto iter = std::find_if(std::begin(m_clients), std::end(m_clients), [&peer](EspRemoteClient *client){ return client->peer() == peer; }); + if (iter == std::end(m_clients)) + { + if (!client.sendFullResponse(404, "Bad Request", {{"Content-Type", "text/plain"}}, "peer not found")) + qWarning() << "sending response failed"; + return; + } + + (*iter)->reboot(); + + if (!client.sendFullResponse(200, "Ok", {{"Content-Type", "text/plain"}}, "peer reboot command sent!")) + qWarning() << "sending response failed"; +} diff --git a/espremotemanager/espremotemanager.h b/espremotemanager/espremotemanager.h new file mode 100644 index 0000000..2dac816 --- /dev/null +++ b/espremotemanager/espremotemanager.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include "abstractwebserver.h" + +class QWebSocketServer; +class EspRemoteClient; +class QUrl; +class QUrlQuery; + +class EspRemoteManager : public AbstractWebserver +{ +public: + explicit EspRemoteManager(QWebSocketServer &websocketServer, QObject *parent = nullptr); + ~EspRemoteManager() override; + +protected: + void requestReceived(WebserverClientConnection &client, const Request &request) override; + +private slots: + void newConnection(); + void clientDestroyed(QObject *object); + +private: + void sendRootResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query); + void sendRebootResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query); + + QWebSocketServer &m_websocketServer; + + std::vector m_clients; +}; diff --git a/espremotemanager/espremotemanager.ini b/espremotemanager/espremotemanager.ini new file mode 100644 index 0000000..42c6f5a --- /dev/null +++ b/espremotemanager/espremotemanager.ini @@ -0,0 +1,7 @@ +[Webserver] +listen=Any +port=80 + +[Websocket] +listen=Any +port=81 diff --git a/espremotemanager/espremotemanager.pro b/espremotemanager/espremotemanager.pro new file mode 100644 index 0000000..c6eed99 --- /dev/null +++ b/espremotemanager/espremotemanager.pro @@ -0,0 +1,30 @@ +QT = core network websockets + +TARGET = espremotemanager + +TEMPLATE = app + +CONFIG += console +CONFIG -= app_bundle + +PROJECT_ROOT = .. + +DESTDIR = $${OUT_PWD}/$${PROJECT_ROOT}/bin + +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +DBLIBS += webserver + +HEADERS += \ + espremoteclient.h \ + espremotemanager.h + +SOURCES += \ + espremoteclient.cpp \ + espremotemanager.cpp \ + main.cpp + +OTHER_FILES += \ + espremotemanager.ini + +include($${PROJECT_ROOT}/project.pri) diff --git a/espremotemanager/main.cpp b/espremotemanager/main.cpp new file mode 100644 index 0000000..aaf6a6e --- /dev/null +++ b/espremotemanager/main.cpp @@ -0,0 +1,54 @@ +#include +#include +#include + +#include "webserverutils.h" +#include "espremotemanager.h" + +int main(int argc, char *argv[]) +{ + qSetMessagePattern(QStringLiteral("%{time dd.MM.yyyy HH:mm:ss.zzz} " + "[" + "%{if-debug}D%{endif}" + "%{if-info}I%{endif}" + "%{if-warning}W%{endif}" + "%{if-critical}C%{endif}" + "%{if-fatal}F%{endif}" + "] " + "%{function}(): " + "%{message}")); + + QCoreApplication app{argc, argv}; + + QSettings settings{"espremotemanager.ini", QSettings::IniFormat}; + + QHostAddress webserverListen = parseHostAddress(settings.value("Webserver/listen").toString()); + int webserverPort; + { + bool ok{}; + webserverPort = settings.value("Webserver/port", 1234).toInt(&ok); + if (!ok) + qFatal("could not parse webserver port"); + } + + QHostAddress websocketListen = parseHostAddress(settings.value("Websocket/listen").toString()); + int websocketPort; + { + bool ok{}; + websocketPort = settings.value("Websocket/port", 1234).toInt(&ok); + if (!ok) + qFatal("could not parse webserver port"); + } + + QWebSocketServer websocketServer{"dafuq", QWebSocketServer::NonSecureMode}; + + EspRemoteManager manager{websocketServer}; + + if (!manager.listen(webserverListen, webserverPort)) + qFatal("could not start webserver listening %s", qPrintable(manager.errorString())); + + if (!websocketServer.listen(websocketListen, websocketPort)) + qFatal("could not start webserver listening %s", qPrintable(manager.errorString())); + + return app.exec(); +} diff --git a/project.pri b/project.pri new file mode 100644 index 0000000..c9f7d8a --- /dev/null +++ b/project.pri @@ -0,0 +1,27 @@ +CONFIG += c++17 + +DEFINES += QT_DEPRECATED_WARNINGS \ + QT_DISABLE_DEPRECATED_BEFORE=0x060000 \ + QT_MESSAGELOGCONTEXT + +equals(TEMPLATE, "lib") { + win32: DESTDIR = $${OUT_PWD}/$${PROJECT_ROOT}/bin + else: DESTDIR = $${OUT_PWD}/$${PROJECT_ROOT}/lib +} + +!isEmpty(DBLIBS) { + win32: LIBS += -L$${OUT_PWD}/$${PROJECT_ROOT}/bin + else: LIBS += -Wl,-rpath=\\\$$ORIGIN/../lib -L$${OUT_PWD}/$${PROJECT_ROOT}/lib +} + +contains(DBLIBS, webserver) { + LIBS += -lwebserver + + INCLUDEPATH += $$PWD/webserver + DEPENDPATH += $$PWD/webserver +} + +isEmpty(QMAKE_LRELEASE) { + win32:QMAKE_LRELEASE = $$[QT_INSTALL_BINS]\lrelease.exe + else:QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease +} diff --git a/webserver/abstractwebserver.cpp b/webserver/abstractwebserver.cpp new file mode 100644 index 0000000..a629158 --- /dev/null +++ b/webserver/abstractwebserver.cpp @@ -0,0 +1,101 @@ +#include "abstractwebserver.h" + +#include +#ifndef QT_NO_NETWORKPROXY +#include +#endif + +#include "webserverclientconnection.h" + +AbstractWebserver::AbstractWebserver(QObject *parent) : + QObject{parent}, + m_server{std::make_unique(this)} +{ + connect(m_server.get(), &QTcpServer::newConnection, this, &AbstractWebserver::newConnection); + connect(m_server.get(), &QTcpServer::acceptError, this, &AbstractWebserver::acceptError); +} + +AbstractWebserver::~AbstractWebserver() = default; + +bool AbstractWebserver::listen(const QHostAddress &address, quint16 port) +{ + return m_server->listen(address, port); +} + +void AbstractWebserver::close() +{ + m_server->close(); +} + +bool AbstractWebserver::isListening() const +{ + return m_server->isListening(); +} + +void AbstractWebserver::setMaxPendingConnections(int numConnections) +{ + m_server->setMaxPendingConnections(numConnections); +} + +int AbstractWebserver::maxPendingConnections() const +{ + return m_server->maxPendingConnections(); +} + +quint16 AbstractWebserver::serverPort() const +{ + return m_server->serverPort(); +} + +QHostAddress AbstractWebserver::serverAddress() const +{ + return m_server->serverAddress(); +} + +qintptr AbstractWebserver::socketDescriptor() const +{ + return m_server->socketDescriptor(); +} + +bool AbstractWebserver::setSocketDescriptor(qintptr socketDescriptor) +{ + return m_server->setSocketDescriptor(socketDescriptor); +} + +QAbstractSocket::SocketError AbstractWebserver::serverError() const +{ + return m_server->serverError(); +} + +QString AbstractWebserver::errorString() const +{ + return m_server->errorString(); +} + +void AbstractWebserver::pauseAccepting() +{ + m_server->pauseAccepting(); +} + +void AbstractWebserver::resumeAccepting() +{ + m_server->resumeAccepting(); +} + +#ifndef QT_NO_NETWORKPROXY +void AbstractWebserver::setProxy(const QNetworkProxy &networkProxy) +{ + m_server->setProxy(networkProxy); +} + +QNetworkProxy AbstractWebserver::proxy() const +{ + return m_server->proxy(); +} +#endif + +void AbstractWebserver::newConnection() +{ + while (const auto socket = m_server->nextPendingConnection()) + new WebserverClientConnection{*socket, *this, this}; +} diff --git a/webserver/abstractwebserver.h b/webserver/abstractwebserver.h new file mode 100644 index 0000000..22ff92a --- /dev/null +++ b/webserver/abstractwebserver.h @@ -0,0 +1,62 @@ +#pragma once + +#include + +#include +#include +#include + +#include "webserver_global.h" + +class QTcpServer; + +class WebserverClientConnection; +class Request; + +class WEBSERVER_EXPORT AbstractWebserver : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(AbstractWebserver) + +public: + explicit AbstractWebserver(QObject *parent = nullptr); + ~AbstractWebserver() override; + + bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0); + void close(); + + bool isListening() const; + + void setMaxPendingConnections(int numConnections); + int maxPendingConnections() const; + + quint16 serverPort() const; + QHostAddress serverAddress() const; + + qintptr socketDescriptor() const; + bool setSocketDescriptor(qintptr socketDescriptor); + + QAbstractSocket::SocketError serverError() const; + QString errorString() const; + + void pauseAccepting(); + void resumeAccepting(); + +#ifndef QT_NO_NETWORKPROXY + void setProxy(const QNetworkProxy &networkProxy); + QNetworkProxy proxy() const; +#endif + +protected: + friend class WebserverClientConnection; + virtual void requestReceived(WebserverClientConnection &client, const Request &request) = 0; + +Q_SIGNALS: + void acceptError(QAbstractSocket::SocketError socketError); + +private slots: + void newConnection(); + +private: + const std::unique_ptr m_server; +}; diff --git a/webserver/webserver.pro b/webserver/webserver.pro new file mode 100644 index 0000000..1cd1d04 --- /dev/null +++ b/webserver/webserver.pro @@ -0,0 +1,23 @@ +QT += core network +QT -= gui widgets + +TARGET = webserver +TEMPLATE = lib + +PROJECT_ROOT = .. + +DEFINES += WEBSERVER_LIBRARY + +HEADERS += \ + abstractwebserver.h \ + webservercontainer.h \ + webserverclientconnection.h \ + webserver_global.h \ + webserverutils.h + +SOURCES += \ + abstractwebserver.cpp \ + webserverclientconnection.cpp \ + webserverutils.cpp + +include($${PROJECT_ROOT}/project.pri) diff --git a/webserver/webserver_global.h b/webserver/webserver_global.h new file mode 100644 index 0000000..fa6f01b --- /dev/null +++ b/webserver/webserver_global.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#if defined(WEBSERVER_LIBRARY) +# define WEBSERVER_EXPORT Q_DECL_EXPORT +#else +# define WEBSERVER_EXPORT Q_DECL_IMPORT +#endif diff --git a/webserver/webserverclientconnection.cpp b/webserver/webserverclientconnection.cpp new file mode 100644 index 0000000..593b6d6 --- /dev/null +++ b/webserver/webserverclientconnection.cpp @@ -0,0 +1,143 @@ +#include "webserverclientconnection.h" + +#include + +#include + +#include "abstractwebserver.h" + +WebserverClientConnection::WebserverClientConnection(QTcpSocket &socket, AbstractWebserver &webserver, QObject *parent) : + QObject{parent}, + m_socket{&socket}, + m_webserver{webserver} +{ +// qDebug() << "connected"; + m_socket->setParent(this); + + connect(m_socket.get(), &QTcpSocket::readyRead, this, &WebserverClientConnection::readyRead); + connect(m_socket.get(), &QTcpSocket::disconnected, this, &QObject::deleteLater); +} + +WebserverClientConnection::~WebserverClientConnection() +{ +// qDebug() << "disconnected"; +} + +bool WebserverClientConnection::sendResponseHeaders(int status, const QByteArray &message, const QMap &responseHeaders) +{ + if (m_status != Response) + return false; + + if (!writeLine(m_request.protocol + ' ' + QString::number(status).toUtf8() + ' ' + message)) + return false; + + for (auto iter = std::begin(responseHeaders); iter != std::end(responseHeaders); iter++) + if (!writeLine(iter.key() + ": " + iter.value())) + return false; + + if (!writeLine({})) + return false; + + return true; +} + +bool WebserverClientConnection::sendFullResponse(int status, const QByteArray &message, + QMap responseHeaders, const QByteArray &response) +{ + if (m_status != Response) + { + qWarning() << "status not response"; + return false; + } + + const auto containsKey = [&](auto key){ + for (auto iter = std::begin(responseHeaders); iter != std::end(responseHeaders); iter++) + if (iter.key().compare(key, Qt::CaseInsensitive) == 0) + return true; + return false; + }; + + if (!containsKey("Connection")) + responseHeaders.insert("Connection", "keep"); + + if (!response.isEmpty() && !containsKey("Content-Length")) + responseHeaders.insert("Content-Length", QString::number(response.size()).toUtf8()); + + if (!sendResponseHeaders(status, message, responseHeaders)) + return false; + + m_socket->write(response); + m_socket->flush(); + + m_request.clear(); + m_status = RequestLine; + + return true; +} + +void WebserverClientConnection::readyRead() +{ + while (m_socket->canReadLine()) + { + auto line = m_socket->readLine(); + if (line.endsWith('\n')) + { + line.chop(1); + if (line.endsWith('\r')) + line.chop(1); + } + +// qDebug() << line; + + switch (m_status) + { + case RequestLine: + { + auto parts = line.split(' '); + if (parts.size() < 3) + { + qWarning() << "invalid request line" << line; + m_socket->close(); + return; + } + + m_request.method = parts.takeFirst(); + m_request.path = parts.takeFirst(); + m_request.protocol = parts.join(' '); + + m_status = RequestHeaders; + continue; + } + case RequestHeaders: + { + if (line.isEmpty()) + { + m_status = Response; + + m_webserver.requestReceived(*this, m_request); + } + else + { + const auto index = line.indexOf(": "); + if (index == -1) + qWarning() << "could not parse request header" << line; + else + m_request.headers.insert(line.left(index), line.mid(index + 2)); + } + + continue; + default: + qWarning() << "received data in unexpected state" << m_status; + } + } + } +} + +bool WebserverClientConnection::writeLine(const QByteArray &buf) +{ +// qDebug() << buf; + m_socket->write(buf); + m_socket->write("\r\n"); + + return true; +} diff --git a/webserver/webserverclientconnection.h b/webserver/webserverclientconnection.h new file mode 100644 index 0000000..f8a8673 --- /dev/null +++ b/webserver/webserverclientconnection.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include + +#include "webserver_global.h" +#include "webservercontainer.h" + +class QTcpSocket; + +class AbstractWebserver; + +class WEBSERVER_EXPORT WebserverClientConnection : public QObject +{ + Q_OBJECT + +public: + explicit WebserverClientConnection(QTcpSocket &socket, AbstractWebserver &webserver, QObject *parent = nullptr); + ~WebserverClientConnection() override; + + bool sendResponseHeaders(int status, const QByteArray &message, + const QMap &responseHeaders); + bool sendFullResponse(int status, const QByteArray &message, + QMap responseHeaders, const QByteArray &response); + +private slots: + void readyRead(); + +private: + bool writeLine(const QByteArray &buf); + + const std::unique_ptr m_socket; + AbstractWebserver &m_webserver; + + enum Status { + RequestLine, RequestHeaders, Response + }; + Status m_status{RequestLine}; + + Request m_request; +}; diff --git a/webserver/webservercontainer.h b/webserver/webservercontainer.h new file mode 100644 index 0000000..89ce7d5 --- /dev/null +++ b/webserver/webservercontainer.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#include "webserver_global.h" + +struct WEBSERVER_EXPORT Request +{ + QByteArray method; + QByteArray path; + QByteArray protocol; + QMap headers; + + void clear() + { + method.clear(); + path.clear(); + protocol.clear(); + headers.clear(); + } +}; diff --git a/webserver/webserverutils.cpp b/webserver/webserverutils.cpp new file mode 100644 index 0000000..ec28e27 --- /dev/null +++ b/webserver/webserverutils.cpp @@ -0,0 +1,17 @@ +#include "webserverutils.h" + +QHostAddress parseHostAddress(const QString &str) +{ + if (str.isEmpty() || str == "Any") + return QHostAddress::Any; + else if (str == "AnyIPv6") + return QHostAddress::AnyIPv6; + else if (str == "AnyIPv4") + return QHostAddress::AnyIPv4; + else if (str == "LocalHost") + return QHostAddress::LocalHost; + else if (str == "LocalHostIPv6") + return QHostAddress::LocalHostIPv6; + else + return QHostAddress{str}; +} diff --git a/webserver/webserverutils.h b/webserver/webserverutils.h new file mode 100644 index 0000000..67770a7 --- /dev/null +++ b/webserver/webserverutils.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +#include "webserver_global.h" + +QHostAddress WEBSERVER_EXPORT parseHostAddress(const QString &str); + +template > +class WEBSERVER_EXPORT iterable_queue : public std::queue +{ +public: + typedef typename Container::iterator iterator; + typedef typename Container::const_iterator const_iterator; + + iterator begin() { return this->c.begin(); } + iterator end() { return this->c.end(); } + const_iterator begin() const { return this->c.begin(); } + const_iterator end() const { return this->c.end(); } +};