#include "webserver.h" #include #include #include #include "client.h" namespace { QHttpServerResponse serveHtmlWithPlaceholders(const QString &filePath, const QMap &placeholders); } // namespace WebServer::WebServer(const QString &identity, #ifdef FEATURE_REDIS redisAsyncContext *redis, #endif QObject *parent) : QObject{parent}, m_identity{identity} #ifdef FEATURE_REDIS , m_redis{redis} #endif { m_server.route("/", [&]() { return serveHtmlWithPlaceholders(":/lspjs/index.html", { {"identity", identity} }); }); m_server.route("/", [&](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); connect(&m_server, &QHttpServer::newWebSocketConnection, this, qOverload<>(&WebServer::newWebSocketConnection)); } 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 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(), 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(), "999999"); } 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 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 &&socket) { auto serial = socket->requestUrl().path(); if (serial.startsWith('/')) serial.removeFirst(); auto &set = m_clients[serial]; if (set.empty()) { #ifdef FEATURE_REDIS createTraefikRoute(serial); #endif } set.emplace(std::make_unique(*this, std::move(socket), serial, set)); } namespace { QHttpServerResponse serveHtmlWithPlaceholders(const QString &filePath, const QMap &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