2025-07-16 20:25:01 +02:00
|
|
|
#include "webserver.h"
|
|
|
|
|
|
|
|
|
|
#include <QDebug>
|
|
|
|
|
#include <QFile>
|
|
|
|
|
#include <QMimeDatabase>
|
|
|
|
|
|
|
|
|
|
#include "client.h"
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
QHttpServerResponse serveHtmlWithPlaceholders(const QString &filePath, const QMap<QString, QString> &placeholders);
|
|
|
|
|
} // namespace
|
|
|
|
|
|
2025-07-29 23:01:33 +02:00
|
|
|
WebServer::WebServer(const QString &identity,
|
|
|
|
|
#ifdef FEATURE_REDIS
|
|
|
|
|
redisAsyncContext *redis,
|
|
|
|
|
#endif
|
|
|
|
|
QObject *parent) :
|
2025-07-16 20:25:01 +02:00
|
|
|
QObject{parent},
|
|
|
|
|
m_identity{identity}
|
2025-07-29 22:02:07 +02:00
|
|
|
#ifdef FEATURE_REDIS
|
2025-07-29 23:01:33 +02:00
|
|
|
, m_redis{redis}
|
2025-07-29 22:02:07 +02:00
|
|
|
#endif
|
2025-07-29 23:01:33 +02:00
|
|
|
{
|
2025-07-16 20:25:01 +02:00
|
|
|
m_server.route("/", [&]() {
|
|
|
|
|
return serveHtmlWithPlaceholders(":/lspjs/index.html", {
|
|
|
|
|
{"identity", identity}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
m_server.route("/<arg>", [&](int serial) {
|
|
|
|
|
return serveHtmlWithPlaceholders(":/lspjs/client.html", {
|
|
|
|
|
{"identity", identity},
|
|
|
|
|
{"serial", QString::number(serial)}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
m_server.route("/script.js", []() {
|
|
|
|
|
return QHttpServerResponse::fromFile(":/lspjs/script.js");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
m_server.route("/style.css", []() {
|
|
|
|
|
return QHttpServerResponse::fromFile(":/lspjs/style.css");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
m_server.addWebSocketUpgradeVerifier(this, &WebServer::verifySocketUpgrade);
|
|
|
|
|
|
2025-07-16 20:29:30 +02:00
|
|
|
connect(&m_server, &QHttpServer::newWebSocketConnection,
|
|
|
|
|
this, qOverload<>(&WebServer::newWebSocketConnection));
|
2025-07-16 20:25:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool WebServer::bind(QTcpServer *server)
|
|
|
|
|
{
|
|
|
|
|
return m_server.bind(server);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WebServer::~WebServer() = default;
|
|
|
|
|
|
|
|
|
|
void WebServer::newWebSocketConnection()
|
|
|
|
|
{
|
|
|
|
|
while (auto socket = m_server.nextPendingWebSocketConnection())
|
|
|
|
|
newWebSocketConnection(std::move(socket));
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-29 22:02:07 +02:00
|
|
|
#ifdef FEATURE_REDIS
|
2025-07-16 20:25:01 +02:00
|
|
|
void WebServer::createTraefikRoute(const QString &serial)
|
|
|
|
|
{
|
|
|
|
|
qDebug() << "create traefik route for" << serial << "to" << m_identity;
|
|
|
|
|
|
|
|
|
|
redisAsyncCommand(m_redis, NULL, NULL, "SET %s %s",
|
|
|
|
|
QString{"traefik/http/routers/proxyjs_%0/rule"}.arg(serial).toUtf8().constData(),
|
|
|
|
|
QString{"Host(`proxyjs.brunner.ninja`) && Path(`/%0`)"}.arg(serial).toUtf8().constData());
|
|
|
|
|
|
|
|
|
|
redisAsyncCommand(m_redis, NULL, NULL, "SET %s %s",
|
|
|
|
|
QString{"traefik/http/routers/proxyjs_%0/entrypoints/0"}.arg(serial).toUtf8().constData(),
|
|
|
|
|
"websecure");
|
|
|
|
|
|
|
|
|
|
redisAsyncCommand(m_redis, NULL, NULL, "SET %s %s",
|
|
|
|
|
QString{"traefik/http/routers/proxyjs_%0/service"}.arg(serial).toUtf8().constData(),
|
|
|
|
|
QString{"proxyjs_%0"}.arg(m_identity).toUtf8().constData());
|
|
|
|
|
|
|
|
|
|
redisAsyncCommand(m_redis, NULL, NULL, "SET %s %s",
|
|
|
|
|
QString{"traefik/http/routers/proxyjs_%0/tls/certresolver"}.arg(serial).toUtf8().constData(),
|
|
|
|
|
"myresolver");
|
|
|
|
|
|
|
|
|
|
redisAsyncCommand(m_redis, NULL, NULL, "SET %s %s",
|
|
|
|
|
QString{"traefik/http/routers/proxyjs_%0/priority"}.arg(serial).toUtf8().constData(),
|
|
|
|
|
"15");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WebServer::destroyTraefikRoute(const QString &serial)
|
|
|
|
|
{
|
|
|
|
|
qDebug() << "destroy traefik route for" << serial << "to" << m_identity;
|
|
|
|
|
|
|
|
|
|
redisAsyncCommand(m_redis, NULL, NULL, "DEL %s",
|
|
|
|
|
QString{"traefik/http/routers/proxyjs_%0/rule"}.arg(serial).toUtf8().constData());
|
|
|
|
|
|
|
|
|
|
redisAsyncCommand(m_redis, NULL, NULL, "DEL %s",
|
|
|
|
|
QString{"traefik/http/routers/proxyjs_%0/entrypoints/0"}.arg(serial).toUtf8().constData());
|
|
|
|
|
|
|
|
|
|
redisAsyncCommand(m_redis, NULL, NULL, "DEL %s",
|
|
|
|
|
QString{"traefik/http/routers/proxyjs_%0/service"}.arg(serial).toUtf8().constData());
|
|
|
|
|
|
|
|
|
|
redisAsyncCommand(m_redis, NULL, NULL, "DEL %s",
|
|
|
|
|
QString{"traefik/http/routers/proxyjs_%0/tls/certresolver"}.arg(serial).toUtf8().constData());
|
|
|
|
|
|
|
|
|
|
redisAsyncCommand(m_redis, NULL, NULL, "DEL %s",
|
|
|
|
|
QString{"traefik/http/routers/proxyjs_%0/priority"}.arg(serial).toUtf8().constData());
|
|
|
|
|
}
|
2025-07-29 22:02:07 +02:00
|
|
|
#endif
|
2025-07-16 20:25:01 +02:00
|
|
|
|
|
|
|
|
QHttpServerWebSocketUpgradeResponse WebServer::verifySocketUpgrade(const QHttpServerRequest &request)
|
|
|
|
|
{
|
|
|
|
|
auto path = request.url().path();
|
|
|
|
|
while (path.startsWith('/'))
|
|
|
|
|
path.removeFirst();
|
|
|
|
|
bool ok;
|
|
|
|
|
path.toInt(&ok);
|
|
|
|
|
if (!ok)
|
|
|
|
|
return QHttpServerWebSocketUpgradeResponse::passToNext();
|
|
|
|
|
|
|
|
|
|
return QHttpServerWebSocketUpgradeResponse::accept();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WebServer::newWebSocketConnection(std::unique_ptr<QWebSocket> &&socket)
|
|
|
|
|
{
|
|
|
|
|
auto serial = socket->requestUrl().path();
|
|
|
|
|
if (serial.startsWith('/'))
|
|
|
|
|
serial.removeFirst();
|
|
|
|
|
|
|
|
|
|
auto &set = m_clients[serial];
|
|
|
|
|
if (set.empty())
|
2025-07-29 22:02:07 +02:00
|
|
|
{
|
|
|
|
|
#ifdef FEATURE_REDIS
|
2025-07-16 20:25:01 +02:00
|
|
|
createTraefikRoute(serial);
|
2025-07-29 22:02:07 +02:00
|
|
|
#endif
|
|
|
|
|
}
|
2025-07-16 20:25:01 +02:00
|
|
|
|
|
|
|
|
set.emplace(std::make_unique<Client>(*this, std::move(socket), serial, set));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
QHttpServerResponse serveHtmlWithPlaceholders(const QString &filePath, const QMap<QString, QString> &placeholders)
|
|
|
|
|
{
|
|
|
|
|
QString content;
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
QFile file{filePath};
|
|
|
|
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
|
|
|
|
{
|
|
|
|
|
qWarning("could not open file %s because %s", qPrintable(filePath), qPrintable(file.errorString()));
|
|
|
|
|
return QHttpServerResponse{"Failed to open file", QHttpServerResponder::StatusCode::InternalServerError};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QTextStream in(&file);
|
|
|
|
|
content = in.readAll();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString modified = content;
|
|
|
|
|
for (auto it = placeholders.begin(); it != placeholders.end(); ++it)
|
|
|
|
|
modified.replace("{{" + it.key() + "}}", it.value());
|
|
|
|
|
|
|
|
|
|
QMimeDatabase db;
|
|
|
|
|
QMimeType mime = db.mimeTypeForFile(filePath);
|
|
|
|
|
|
|
|
|
|
QHttpServerResponse response(mime.name().toUtf8(), modified.toUtf8());
|
|
|
|
|
return response;
|
|
|
|
|
}
|
|
|
|
|
} // namespace
|