Files
less_shitty_proxyjs/webserver.cpp

169 lines
5.4 KiB
C++
Raw Permalink Normal View History

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
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}
#ifdef FEATURE_REDIS
, m_redis{redis}
#endif
{
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));
}
#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());
}
#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())
{
#ifdef FEATURE_REDIS
2025-07-16 20:25:01 +02:00
createTraefikRoute(serial);
#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