Initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
build/
|
||||
*.user*
|
16
README.md
Normal file
16
README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# less_shitty_proxyjs implementation using redis and traefik
|
||||
|
||||
Install the traefik proxyjs.yml file (using the file provider!) to register the default handler that does load balancing to all proxyjs instances
|
||||
Install redis in traefik, to allow for dynamic route updates
|
||||
|
||||
## Build and run
|
||||
|
||||
```
|
||||
git clone https://code.brunner.ninja/feedc0de/less_shitty_proxyjs.git
|
||||
cd less_shitty_proxyjs/
|
||||
mkdir build
|
||||
cd build/
|
||||
qmake6 ..
|
||||
make -j8
|
||||
parallel -j10 --linebuffer ./less_shitty_proxyjs 'test{}' '950{}' ::: {0..9}
|
||||
```
|
86
client.cpp
Normal file
86
client.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#include "client.h"
|
||||
|
||||
#include <QWebSocket>
|
||||
|
||||
#include "webserver.h"
|
||||
|
||||
Client::Client(WebServer &server, std::unique_ptr<QWebSocket> &&socket,
|
||||
const QString &serial, const std::set<std::unique_ptr<Client>> &serialClients) :
|
||||
QObject{&server},
|
||||
m_server{server},
|
||||
m_socket{std::move(socket)},
|
||||
m_serial{serial}
|
||||
{
|
||||
qDebug() << "new ws connection!!" << m_socket->requestUrl();
|
||||
|
||||
m_socket->sendTextMessage(QString{"Hello from server %0, you requested serial %1, there are %2 clients connected"}
|
||||
.arg(m_server.m_identity, serial).arg(serialClients.size()));
|
||||
|
||||
for (auto &other : serialClients)
|
||||
{
|
||||
other->sendTextMessage(QString{"A new client connected, number of connected clients: %0"}.arg(serialClients.size() + 1));
|
||||
|
||||
connect(this, &Client::sendTextMessageToOthers,
|
||||
other.get(), &Client::sendTextMessage);
|
||||
connect(other.get(), &Client::sendTextMessageToOthers,
|
||||
this, &Client::sendTextMessage);
|
||||
}
|
||||
|
||||
QObject::connect(m_socket.get(), &QWebSocket::disconnected,
|
||||
this, &Client::socketDisconnected);
|
||||
|
||||
QObject::connect(m_socket.get(), &QWebSocket::destroyed,
|
||||
this, &Client::socketDestroyed);
|
||||
}
|
||||
|
||||
Client::~Client() = default;
|
||||
|
||||
void Client::sendTextMessage(const QString &text)
|
||||
{
|
||||
qDebug() << text;
|
||||
if (m_socket)
|
||||
m_socket->sendTextMessage(text);
|
||||
else
|
||||
qWarning() << "tried to send with invalid socket";
|
||||
}
|
||||
|
||||
void Client::textMessageReceived(const QString &text)
|
||||
{
|
||||
qDebug() << text;
|
||||
emit sendTextMessageToOthers(text);
|
||||
}
|
||||
|
||||
void Client::socketDestroyed()
|
||||
{
|
||||
qDebug() << "destroyed";
|
||||
m_socket.release();
|
||||
assert(!m_socket);
|
||||
}
|
||||
|
||||
void Client::socketDisconnected()
|
||||
{
|
||||
qDebug() << "disconnected";
|
||||
|
||||
std::set<std::unique_ptr<Client>> &serialClients = m_server.m_clients[m_serial];
|
||||
|
||||
{
|
||||
auto iter = std::find_if(std::begin(serialClients), std::end(serialClients), [this](const auto &ptr){
|
||||
return ptr.get() == this;
|
||||
});
|
||||
if (iter == std::end(serialClients))
|
||||
qWarning() << "couldnt fint ourself when trying to erase us";
|
||||
else
|
||||
{
|
||||
qDebug() << serialClients.size();
|
||||
serialClients.extract(iter).value().release()->deleteLater();
|
||||
qDebug() << serialClients.size();
|
||||
}
|
||||
}
|
||||
|
||||
if (serialClients.empty())
|
||||
m_server.destroyTraefikRoute(m_serial);
|
||||
else for (auto &ptr : serialClients)
|
||||
ptr->sendTextMessage(QString{"A client disconnected, number of remaining connected clients: %0"}.arg(serialClients.size()));
|
||||
|
||||
qDebug() << "done with sending...";
|
||||
}
|
35
client.h
Normal file
35
client.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
class WebServer;
|
||||
class QWebSocket;
|
||||
|
||||
class Client : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Client(WebServer &server, std::unique_ptr<QWebSocket> &&socket,
|
||||
const QString &serial, const std::set<std::unique_ptr<Client>> &serialClients);
|
||||
~Client() override;
|
||||
|
||||
signals:
|
||||
void sendTextMessageToOthers(const QString &text);
|
||||
|
||||
public slots:
|
||||
void sendTextMessage(const QString &text);
|
||||
|
||||
private slots:
|
||||
void textMessageReceived(const QString &text);
|
||||
void socketDestroyed();
|
||||
void socketDisconnected();
|
||||
|
||||
private:
|
||||
WebServer &m_server;
|
||||
std::unique_ptr<QWebSocket> m_socket;
|
||||
const QString m_serial;
|
||||
};
|
29
client.html
Normal file
29
client.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>less shitty proxyjs</title>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-LN+7fdVzj6u52u30Kp6M/trliBMCMKTyK833zpbD+pXdCLuTusPj697FH4R/5mcr" crossorigin="anonymous" />
|
||||
<link href="style.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>client.html</h1>
|
||||
<p><b>Server-Identity:</b> {{identity}}</p>
|
||||
<p><b>Request-Serial:</b> {{serial}}</p>
|
||||
<p><b>WS status:</b> <span id="wsStatus">Disconnected</span></p>
|
||||
<button id="reconnectButton">Connect</button>
|
||||
|
||||
<div id="logView"></div>
|
||||
<form id="sendForm">
|
||||
<label>Text to send: <input type="text" required /></label>
|
||||
<button type="submit">Send</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js" integrity="sha384-ndDqU0Gzau9qJ1lfW4pNLlhNTkCfHzAVBReH9diLvGRem5+R9g2FzA8ZGN954O5Q" crossorigin="anonymous"></script>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
16
index.html
Normal file
16
index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>less shitty proxyjs</title>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-LN+7fdVzj6u52u30Kp6M/trliBMCMKTyK833zpbD+pXdCLuTusPj697FH4R/5mcr" crossorigin="anonymous" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>index.html</h1>
|
||||
<p><b>Server-Identity:</b> {{identity}}</p>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js" integrity="sha384-ndDqU0Gzau9qJ1lfW4pNLlhNTkCfHzAVBReH9diLvGRem5+R9g2FzA8ZGN954O5Q" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
28
less_shitty_proxyjs.pro
Normal file
28
less_shitty_proxyjs.pro
Normal file
@@ -0,0 +1,28 @@
|
||||
QT = core network websockets httpserver
|
||||
|
||||
CONFIG += c++latest cmdline
|
||||
|
||||
DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000
|
||||
|
||||
SOURCES += \
|
||||
client.cpp \
|
||||
main.cpp \
|
||||
redisqtadapter.cpp \
|
||||
webserver.cpp
|
||||
|
||||
RESOURCES += \
|
||||
resources.qrc
|
||||
|
||||
OTHER_FILES += \
|
||||
client.html \
|
||||
index.html \
|
||||
script.js \
|
||||
style.css
|
||||
|
||||
HEADERS += \
|
||||
client.h \
|
||||
redisqtadapter.h \
|
||||
webserver.h
|
||||
|
||||
LIBS += \
|
||||
-L/usr/lib -lhiredis
|
85
main.cpp
Normal file
85
main.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QCommandLineParser>
|
||||
|
||||
#include "webserver.h"
|
||||
|
||||
#include <QTcpServer>
|
||||
#include <QDebug>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
qSetMessagePattern("%{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};
|
||||
QCoreApplication::setApplicationName("less_shitty_proxyjs");
|
||||
QCoreApplication::setApplicationVersion("1.0");
|
||||
|
||||
QString identity = "test";
|
||||
uint16_t port = 8000;
|
||||
|
||||
{
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription("Test helper");
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
parser.addPositionalArgument("identiy", QCoreApplication::translate("main", "The name of this service instance"));
|
||||
parser.addPositionalArgument("port", QCoreApplication::translate("main", "The port to listen on (will only listen on localhost)."));
|
||||
parser.process(app);
|
||||
|
||||
const auto &args = parser.positionalArguments();
|
||||
if (args.size() > 0)
|
||||
identity = args.first();
|
||||
if (args.size() > 1)
|
||||
{
|
||||
bool ok;
|
||||
port = args.at(1).toInt(&ok);
|
||||
if (!ok)
|
||||
{
|
||||
qCritical("could not parse port: %s", qPrintable(args.at(1)));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
qSetMessagePattern(QString{"%{time dd.MM.yyyy HH:mm:ss.zzz} %0 "
|
||||
"["
|
||||
"%{if-debug}D%{endif}"
|
||||
"%{if-info}I%{endif}"
|
||||
"%{if-warning}W%{endif}"
|
||||
"%{if-critical}C%{endif}"
|
||||
"%{if-fatal}F%{endif}"
|
||||
"] "
|
||||
"%{function}(): "
|
||||
"%{message}"}.arg(identity));
|
||||
|
||||
WebServer server{identity, QString{"http://localhost:%0"}.arg(port)};
|
||||
|
||||
QTcpServer tcpServer;
|
||||
if (!tcpServer.listen(QHostAddress::LocalHost, port))
|
||||
{
|
||||
qCritical("failed to start listening on port %hu: %s", port, qPrintable(tcpServer.errorString()));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!server.bind(&tcpServer))
|
||||
{
|
||||
qCritical("failed to webserver on socket!");
|
||||
return -2;
|
||||
}
|
||||
|
||||
qDebug() << "server started";
|
||||
|
||||
auto result = app.exec();
|
||||
qDebug() << "bey bey!";
|
||||
return result;
|
||||
}
|
||||
|
25
proxyjs.yml
Normal file
25
proxyjs.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
http:
|
||||
routers:
|
||||
proxyjs:
|
||||
rule: "Host(`proxyjs.brunner.ninja`)"
|
||||
service: proxyjs
|
||||
entryPoints:
|
||||
- websecure
|
||||
tls:
|
||||
certResolver: myresolver
|
||||
priority: 10
|
||||
|
||||
services:
|
||||
proxyjs:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://localhost:9500"
|
||||
- url: "http://localhost:9501"
|
||||
- url: "http://localhost:9502"
|
||||
- url: "http://localhost:9503"
|
||||
- url: "http://localhost:9504"
|
||||
- url: "http://localhost:9505"
|
||||
- url: "http://localhost:9506"
|
||||
- url: "http://localhost:9507"
|
||||
- url: "http://localhost:9508"
|
||||
- url: "http://localhost:9509"
|
2
redisqtadapter.cpp
Normal file
2
redisqtadapter.cpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#include "redisqtadapter.h"
|
||||
|
108
redisqtadapter.h
Normal file
108
redisqtadapter.h
Normal file
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include <QSocketNotifier>
|
||||
#include <hiredis/async.h>
|
||||
|
||||
static void RedisQtAddRead(void *);
|
||||
static void RedisQtDelRead(void *);
|
||||
static void RedisQtAddWrite(void *);
|
||||
static void RedisQtDelWrite(void *);
|
||||
static void RedisQtCleanup(void *);
|
||||
|
||||
class RedisQtAdapter : public QObject {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
friend
|
||||
void RedisQtAddRead(void * adapter) {
|
||||
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
|
||||
a->addRead();
|
||||
}
|
||||
|
||||
friend
|
||||
void RedisQtDelRead(void * adapter) {
|
||||
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
|
||||
a->delRead();
|
||||
}
|
||||
|
||||
friend
|
||||
void RedisQtAddWrite(void * adapter) {
|
||||
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
|
||||
a->addWrite();
|
||||
}
|
||||
|
||||
friend
|
||||
void RedisQtDelWrite(void * adapter) {
|
||||
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
|
||||
a->delWrite();
|
||||
}
|
||||
|
||||
friend
|
||||
void RedisQtCleanup(void * adapter) {
|
||||
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
|
||||
a->cleanup();
|
||||
}
|
||||
|
||||
public:
|
||||
RedisQtAdapter(QObject * parent = 0)
|
||||
: QObject(parent), m_ctx(0), m_read(0), m_write(0) { }
|
||||
|
||||
~RedisQtAdapter() {
|
||||
if (m_ctx != 0) {
|
||||
m_ctx->ev.data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int setContext(redisAsyncContext * ac) {
|
||||
if (ac->ev.data != NULL) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
m_ctx = ac;
|
||||
m_ctx->ev.data = this;
|
||||
m_ctx->ev.addRead = RedisQtAddRead;
|
||||
m_ctx->ev.delRead = RedisQtDelRead;
|
||||
m_ctx->ev.addWrite = RedisQtAddWrite;
|
||||
m_ctx->ev.delWrite = RedisQtDelWrite;
|
||||
m_ctx->ev.cleanup = RedisQtCleanup;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
void addRead() {
|
||||
if (m_read) return;
|
||||
m_read = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Read, 0);
|
||||
connect(m_read, SIGNAL(activated(int)), this, SLOT(read()));
|
||||
}
|
||||
|
||||
void delRead() {
|
||||
if (!m_read) return;
|
||||
delete m_read;
|
||||
m_read = 0;
|
||||
}
|
||||
|
||||
void addWrite() {
|
||||
if (m_write) return;
|
||||
m_write = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Write, 0);
|
||||
connect(m_write, SIGNAL(activated(int)), this, SLOT(write()));
|
||||
}
|
||||
|
||||
void delWrite() {
|
||||
if (!m_write) return;
|
||||
delete m_write;
|
||||
m_write = 0;
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
delRead();
|
||||
delWrite();
|
||||
}
|
||||
|
||||
private slots:
|
||||
void read() { redisAsyncHandleRead(m_ctx); }
|
||||
void write() { redisAsyncHandleWrite(m_ctx); }
|
||||
|
||||
private:
|
||||
redisAsyncContext * m_ctx;
|
||||
QSocketNotifier * m_read;
|
||||
QSocketNotifier * m_write;
|
||||
};
|
8
resources.qrc
Normal file
8
resources.qrc
Normal file
@@ -0,0 +1,8 @@
|
||||
<RCC>
|
||||
<qresource prefix="/lspjs">
|
||||
<file>client.html</file>
|
||||
<file>index.html</file>
|
||||
<file>script.js</file>
|
||||
<file>style.css</file>
|
||||
</qresource>
|
||||
</RCC>
|
63
script.js
Normal file
63
script.js
Normal file
@@ -0,0 +1,63 @@
|
||||
jQuery(document).ready(function($){
|
||||
let ws = null;
|
||||
|
||||
$('#reconnectButton').click(function(ev){
|
||||
ev.preventDefault();
|
||||
|
||||
if (ws) {
|
||||
$('#reconnectButton').text('Connect');
|
||||
ws.close();
|
||||
ws = null;
|
||||
} else {
|
||||
$('#reconnectButton').text('Disconnect');
|
||||
setWsStatus('Connecting');
|
||||
ws = new WebSocket(window.location.href.replace(/^http/, "ws"));
|
||||
ws.onopen = function(event) {
|
||||
setWsStatus('Connected');
|
||||
$('#sendForm input').focus();
|
||||
}
|
||||
ws.onclose = function(event) {
|
||||
setWsStatus('Closed');
|
||||
$('#reconnectButton').text('Connect');
|
||||
ws.close();
|
||||
ws = null;
|
||||
}
|
||||
ws.onerror = function(event) {
|
||||
setWsStatus('Error');
|
||||
}
|
||||
ws.onmessage = function(event) {
|
||||
if (typeof event.data == 'string') {
|
||||
pushLine('Received ' + event.data);
|
||||
} else if (typeof event.data == 'object') {
|
||||
pushLine('Received binary message!');
|
||||
} else {
|
||||
pushLine('Unknown msg data type ' + typeof event.data);
|
||||
}
|
||||
};
|
||||
}
|
||||
}).click();
|
||||
|
||||
$('#sendForm').submit(function(ev){
|
||||
ev.preventDefault();
|
||||
|
||||
if (!ws) {
|
||||
alert('please connect first');
|
||||
return;
|
||||
}
|
||||
|
||||
const input = $('#sendForm input');
|
||||
const val = input.val();
|
||||
pushLine('Sending ' + val);
|
||||
ws.send(val);
|
||||
input.val('').focus();
|
||||
});
|
||||
});
|
||||
|
||||
function pushLine(line) {
|
||||
$('#logView').append($('<p>').text(new Date().toLocaleTimeString() + ': ' + line));
|
||||
}
|
||||
|
||||
function setWsStatus(status) {
|
||||
$('#wsStatus').text(status);
|
||||
pushLine('Status changed to ' + status);
|
||||
}
|
6
style.css
Normal file
6
style.css
Normal file
@@ -0,0 +1,6 @@
|
||||
#logView {
|
||||
width: 100%;
|
||||
min-height: 400px;
|
||||
|
||||
background-color: yellow;
|
||||
}
|
166
webserver.cpp
Normal file
166
webserver.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
#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, const QString &url, QObject *parent) :
|
||||
QObject{parent},
|
||||
m_identity{identity}
|
||||
{
|
||||
m_redis = redisAsyncConnect("localhost", 6379);
|
||||
|
||||
if (m_redis->err)
|
||||
qFatal("error with redis: %s", m_redis->errstr);
|
||||
|
||||
m_redisAdapter.setContext(m_redis);
|
||||
|
||||
redisAsyncCommand(m_redis, NULL, NULL, "SET %s %s",
|
||||
QString{"traefik/http/services/proxyjs_%0/loadbalancer/servers/0/url"}.arg(m_identity).toUtf8().constData(),
|
||||
url.toUtf8().constData());
|
||||
|
||||
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);
|
||||
|
||||
QObject::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));
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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())
|
||||
createTraefikRoute(serial);
|
||||
|
||||
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
|
45
webserver.h
Normal file
45
webserver.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QHttpServer>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
|
||||
#include <hiredis/async.h>
|
||||
#include "redisqtadapter.h"
|
||||
|
||||
class QWebSocket;
|
||||
class Client;
|
||||
|
||||
class WebServer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
friend class Client;
|
||||
|
||||
public:
|
||||
explicit WebServer(const QString &identity, const QString &url, QObject *parent = nullptr);
|
||||
~WebServer() override;
|
||||
|
||||
bool bind(QTcpServer *server);
|
||||
|
||||
private slots:
|
||||
void newWebSocketConnection();
|
||||
|
||||
private:
|
||||
void createTraefikRoute(const QString &serial);
|
||||
void destroyTraefikRoute(const QString &serial);
|
||||
|
||||
QHttpServerWebSocketUpgradeResponse verifySocketUpgrade(const QHttpServerRequest &request);
|
||||
void newWebSocketConnection(std::unique_ptr<QWebSocket> &&socket);
|
||||
|
||||
QHttpServer m_server;
|
||||
|
||||
QString m_identity;
|
||||
|
||||
std::unordered_map<QString, std::set<std::unique_ptr<Client>>> m_clients;
|
||||
|
||||
redisAsyncContext *m_redis;
|
||||
RedisQtAdapter m_redisAdapter;
|
||||
};
|
Reference in New Issue
Block a user