General improvement of memory handling and parent ownership of webserver classes

This commit is contained in:
0xFEEDC0DE64
2018-09-22 07:22:28 +02:00
parent abfd38cbd2
commit 54d4ada705
10 changed files with 229 additions and 154 deletions

View File

@@ -1,121 +1,21 @@
#include <QCoreApplication>
#include <QDir>
#include <QLibrary>
#include <QPluginLoader>
#include <QJsonObject>
#include <QJsonArray>
#include <QDebug>
#include <stdexcept>
#include <iterator>
#include "utils/jsonutils.h"
#include "webplugin.h"
#include "weblistener.h"
#include "webapplication.h"
#include "webserver.h"
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
QCoreApplication::setApplicationName("webserver");
QHash<QString, WebPlugin*> plugins;
{
QDir dir(QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(QStringLiteral("plugins/") + QCoreApplication::applicationName()));
for(const auto &fileInfo : dir.entryInfoList(QDir::Files | QDir::NoSymLinks))
{
if(!QLibrary::isLibrary(fileInfo.filePath()))
{
qWarning() << "skipping" << fileInfo.fileName() << "because no QLibrary";
continue; // to skip windows junk files
}
QPluginLoader pluginLoader(fileInfo.filePath());
if(!pluginLoader.load())
{
qCritical() << "error loading plugin" << fileInfo.fileName() << "because" << pluginLoader.errorString();
continue;
}
if(auto plugin = qobject_cast<WebPlugin*>(pluginLoader.instance()))
{
const auto pluginName = plugin->pluginName();
if(plugins.contains(pluginName))
throw std::runtime_error(QString("duplicate plugin %0").arg(pluginName).toStdString());
plugins.insert(pluginName, plugin);
}
else
qCritical() << "plugin" << fileInfo.fileName() << "could not be casted to WebPlugin";
}
}
QHash<QString, WebApplication*> applications;
const auto configPath = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(QCoreApplication::applicationName() + QStringLiteral(".json"));
const auto config = getJson<QJsonObject>(configPath);
if(!config.contains(QStringLiteral("applications")))
throw std::runtime_error("settings does not contain a applications");
{
const auto applicationsVal = config.value(QStringLiteral("applications"));
if(!applicationsVal.isObject())
throw std::runtime_error("applications is not a json object");
const auto applicationsList = applicationsVal.toObject();
for(auto iter = applicationsList.constBegin(); iter != applicationsList.constEnd(); iter++)
{
const auto applicationVal = iter.value();
if(!applicationVal.isObject())
throw std::runtime_error(QString("application %0 is not a json object").arg(iter.key()).toStdString());
auto application = applicationVal.toObject();
if(!application.contains(QStringLiteral("_pluginName")))
throw std::runtime_error(QString("application %0 does not contain a _pluginName").arg(iter.key()).toStdString());
const auto pluginNameVal = application.take(QStringLiteral("_pluginName"));
if(!pluginNameVal.isString())
throw std::runtime_error(QString("application %0 pluginName is not a string").arg(iter.key()).toStdString());
auto pluginName = pluginNameVal.toString();
const auto pluginsIter = plugins.find(pluginName);
if(pluginsIter == plugins.constEnd())
throw std::runtime_error(QString("application %0 references not installed plugin %1").arg(iter.key(), pluginName).toStdString());
applications.insert(iter.key(), pluginsIter.value()->createApplication(application));
}
}
QList<WebListener*> listeners;
if(!config.contains(QStringLiteral("listeners")))
throw std::runtime_error("settings does not contain a listeners");
{
const auto listenersVal = config.value(QStringLiteral("listeners"));
if(!listenersVal.isArray())
throw std::runtime_error("listeners is not a json array");
const auto listenersList = listenersVal.toArray();
for(auto iter = listenersList.constBegin(); iter != listenersList.constEnd(); iter++)
{
const auto listenerVal = *iter;
if(!listenerVal.isObject())
throw std::runtime_error(QString("listener %0 is not an object").arg(std::distance(listenersList.constBegin(), iter)).toStdString());
const auto listener = listenerVal.toObject();
listeners.append(new WebListener(listener, applications));
}
}
for(auto iter = applications.constBegin(); iter != applications.constEnd(); iter++)
iter.value()->start();
for(auto listener : listeners)
listener->start();
WebServer server(config);
server.start();
return app.exec();
}

View File

@@ -58,7 +58,7 @@
},
"WifiLamp": {
"_pluginName": "wifilamp",
"controlHostAdress": "QHostAddress::Any",
"controlHostAddress": "QHostAddress::Any",
"controlPort": 1234
}
},
@@ -67,26 +67,26 @@
"port": 8080,
"vhosts": {
"*": "Fallback",
"1000serien.com": "1000serien.com",
"www.1000serien.com": "1000serien.com",
"cdn.1000serien.com": "cdn.1000serien.com",
"brunner.ninja": "brunner.ninja",
"www.brunner.ninja": "brunner.ninja",
"telegram.brunner.ninja": "telegram.brunner.ninja",
"transmission.brunner.ninja": "transmission.brunner.ninja",
"findtheinvisiblegspot.com": "findtheinvisiblegspot.com",
"www.findtheinvisiblegspot.com": "findtheinvisiblegspot.com",
"flucky.xyz": "flucky.xyz",
"www.flucky.xyz": "flucky.xyz",
"mail.flucky.xyz": "mail.flucky.xyz",
"phpmyadmin.flucky.xyz": "phpmyadmin.flucky.xyz",
"localhorst.xyz": "localhorst.xyz",
"www.localhorst.xyz": "localhorst.xyz",
"maik-mahlow.de": "maik-mahlow.de",
"www.maik-mahlow.de": "maik-mahlow.de",
"flucky-server": "HelloWorld",
"192.168.0.2": "HelloWorld",
"lampen": "WifiLamp"
"1000serien.com:8080": "1000serien.com",
"www.1000serien.com:8080": "1000serien.com",
"cdn.1000serien.com:8080": "cdn.1000serien.com",
"brunner.ninja:8080": "brunner.ninja",
"www.brunner.ninja:8080": "brunner.ninja",
"telegram.brunner.ninja:8080": "telegram.brunner.ninja",
"transmission.brunner.ninja:8080": "transmission.brunner.ninja",
"findtheinvisiblegspot.com:8080": "findtheinvisiblegspot.com",
"www.findtheinvisiblegspot.com:8080": "findtheinvisiblegspot.com",
"flucky.xyz:8080": "flucky.xyz",
"www.flucky.xyz:8080": "flucky.xyz",
"mail.flucky.xyz:8080": "mail.flucky.xyz",
"phpmyadmin.flucky.xyz:8080": "phpmyadmin.flucky.xyz",
"localhorst.xyz:8080": "localhorst.xyz",
"www.localhorst.xyz:8080": "localhorst.xyz",
"maik-mahlow.de:8080": "maik-mahlow.de",
"www.maik-mahlow.de:8080": "maik-mahlow.de",
"flucky-server:8080": "HelloWorld",
"192.168.0.2:8080": "HelloWorld",
"lampen:8080": "WifiLamp"
}
}]
}

View File

@@ -6,10 +6,10 @@
#include "weblistener.h"
HttpClientConnection::HttpClientConnection(QTcpSocket &socket, WebListener &httpServer) :
QObject(&httpServer),
HttpClientConnection::HttpClientConnection(QTcpSocket &socket, WebListener &webServer) :
QObject(&webServer),
m_socket(socket),
m_webListener(httpServer),
m_webListener(webServer),
m_state(RequestLine),
m_bodyLength(-1)
{
@@ -105,7 +105,11 @@ void HttpClientConnection::readyRead()
case RequestLine:
{
auto parts = line.split(' ');
Q_ASSERT(parts.count() == 3);
if(parts.count() != 3)
{
m_socket.close();
return;
}
m_request.method = parts.at(0);
m_request.path = parts.at(1);

View File

@@ -19,7 +19,7 @@ class WEBSERVERLIB_EXPORT HttpClientConnection : public QObject
Q_OBJECT
public:
explicit HttpClientConnection(QTcpSocket &socket, WebListener &WebListener);
explicit HttpClientConnection(QTcpSocket &socket, WebListener &webListener);
void sendResponse(const HttpResponse &response);
void sendResponse(HttpResponse response, const QByteArray &byteArray);

View File

@@ -8,11 +8,13 @@
#include <stdexcept>
#include "utils/netutils.h"
#include "webserver.h"
#include "httpclientconnection.h"
#include "webapplication.h"
WebListener::WebListener(const QJsonObject &config, const QHash<QString, WebApplication*> &applications, QObject *parent) :
QObject(parent)
WebListener::WebListener(const QJsonObject &config, WebServer &webServer) :
QObject(&webServer), m_webServer(webServer)
{
if(!config.contains(QStringLiteral("hostAddress")))
throw std::runtime_error("listener does not contain hostAddress");
@@ -21,7 +23,7 @@ WebListener::WebListener(const QJsonObject &config, const QHash<QString, WebAppl
if(!hostAddressVal.isString())
throw std::runtime_error("listener hostAddress is not a string");
m_address = parseHostAddress(hostAddressVal.toString());
m_hostAddress = parseHostAddress(hostAddressVal.toString());
if(!config.contains(QStringLiteral("port")))
throw std::runtime_error("listener does not contain port");
@@ -32,7 +34,7 @@ WebListener::WebListener(const QJsonObject &config, const QHash<QString, WebAppl
m_port = portVal.toInt();
m_tcpServer = new QTcpServer(this);
m_server = new QTcpServer(this);
if(!config.contains(QStringLiteral("vhosts")))
throw std::runtime_error("listener does not contain vhosts");
@@ -47,14 +49,14 @@ WebListener::WebListener(const QJsonObject &config, const QHash<QString, WebAppl
const auto applicationNameVal = iter.value();
if(!applicationNameVal.isString())
throw std::runtime_error(QString("listener %0:%1 vhost %2 is not a string")
.arg(m_address.toString()).arg(m_port).arg(iter.key()).toStdString());
.arg(m_hostAddress.toString()).arg(m_port).arg(iter.key()).toStdString());
const auto applicationName = applicationNameVal.toString();
const auto applicationsIter = applications.find(applicationName);
if(applicationsIter == applications.constEnd())
const auto applicationsIter = m_webServer.applications().find(applicationName);
if(applicationsIter == m_webServer.applications().constEnd())
throw std::runtime_error(QString("listener %0:%1 vhost %2 references unknown application %3")
.arg(m_address.toString()).arg(m_port).arg(iter.key(), applicationName).toStdString());
.arg(m_hostAddress.toString()).arg(m_port).arg(iter.key(), applicationName).toStdString());
m_hosts.insert(iter.key(), *applicationsIter);
}
@@ -62,13 +64,12 @@ WebListener::WebListener(const QJsonObject &config, const QHash<QString, WebAppl
void WebListener::start()
{
qDebug() << "starting listening" << m_address << m_port;
if(!m_tcpServer->listen(m_address, m_port))
if(!m_server->listen(m_hostAddress, m_port))
throw std::runtime_error(QString("Could not start listening on %0:%1 because %2")
.arg(m_address.toString()).arg(m_port).arg(m_tcpServer->errorString()).toStdString());
.arg(m_hostAddress.toString()).arg(m_port).arg(m_server->errorString()).toStdString());
connect(m_tcpServer, &QTcpServer::acceptError, this, &WebListener::acceptError);
connect(m_tcpServer, &QTcpServer::newConnection, this, &WebListener::newConnection);
connect(m_server, &QTcpServer::acceptError, this, &WebListener::acceptError);
connect(m_server, &QTcpServer::newConnection, this, &WebListener::newConnection);
}
void WebListener::handleRequest(HttpClientConnection *connection, const HttpRequest &request)
@@ -121,9 +122,16 @@ void WebListener::acceptError(QAbstractSocket::SocketError socketError)
void WebListener::newConnection()
{
auto connection = m_tcpServer->nextPendingConnection();
if(!connection)
auto socket = m_server->nextPendingConnection();
if(!socket)
{
qWarning() << "null socket received";
return;
}
new HttpClientConnection(*connection, *this);
auto client = new HttpClientConnection(*socket, *this);
m_clients.insert(client);
connect(client, &QObject::destroyed, this, [this, client](){
m_clients.remove(client);
});
}

View File

@@ -6,10 +6,12 @@
#include <QAbstractSocket>
#include <QHostAddress>
#include <QHash>
#include <QSet>
class QJsonObject;
class QTcpServer;
class WebServer;
class WebApplication;
class HttpClientConnection;
class HttpRequest;
@@ -19,7 +21,7 @@ class WEBSERVERLIB_EXPORT WebListener : public QObject
Q_OBJECT
public:
WebListener(const QJsonObject &config, const QHash<QString, WebApplication*> &applications, QObject *parent = Q_NULLPTR);
WebListener(const QJsonObject &config, WebServer &webServer);
void start();
void handleRequest(HttpClientConnection *connection, const HttpRequest &request);
@@ -29,10 +31,11 @@ private Q_SLOTS:
void newConnection();
private:
using HostsContainer = QHash<QString, WebApplication*>;
WebServer &m_webServer;
QTcpServer *m_tcpServer;
QHostAddress m_address;
QHostAddress m_hostAddress;
quint16 m_port;
HostsContainer m_hosts;
QTcpServer *m_server;
QHash<QString, WebApplication*> m_hosts;
QSet<HttpClientConnection*> m_clients;
};

View File

@@ -6,6 +6,7 @@
class QJsonObject;
class WebApplication;
class WebServer;
class WEBSERVERLIB_EXPORT WebPlugin : public QObject
{
@@ -15,7 +16,7 @@ public:
WebPlugin(QObject *parent = Q_NULLPTR);
virtual QString pluginName() const = 0;
virtual WebApplication *createApplication(const QJsonObject &config) const = 0;
virtual WebApplication *createApplication(const QJsonObject &config, WebServer &webServer) const = 0;
};
Q_DECLARE_INTERFACE(WebPlugin, "dbsoftware.webserver.plugin/1.0")

127
webserverlib/webserver.cpp Normal file
View File

@@ -0,0 +1,127 @@
#include "webserver.h"
#include <QDebug>
#include <QDir>
#include <QCoreApplication>
#include <QLibrary>
#include <QPluginLoader>
#include <QJsonObject>
#include <QJsonValue>
#include <QJsonArray>
#include <stdexcept>
#include "webplugin.h"
#include "webapplication.h"
#include "weblistener.h"
WebServer::WebServer(const QJsonObject &config, QObject *parent) :
QObject(parent)
{
{
QDir dir(QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(QStringLiteral("plugins/") + QCoreApplication::applicationName()));
for(const auto &fileInfo : dir.entryInfoList(QDir::Files | QDir::NoSymLinks))
{
if(!QLibrary::isLibrary(fileInfo.filePath()))
{
qWarning() << "skipping" << fileInfo.fileName() << "because no QLibrary";
continue; // to skip windows junk files
}
QPluginLoader pluginLoader(fileInfo.filePath());
if(!pluginLoader.load())
{
qCritical() << "error loading plugin" << fileInfo.fileName() << "because" << pluginLoader.errorString();
continue;
}
if(auto plugin = qobject_cast<WebPlugin*>(pluginLoader.instance()))
{
const auto pluginName = plugin->pluginName();
if(m_plugins.contains(pluginName))
throw std::runtime_error(QString("duplicate plugin %0").arg(pluginName).toStdString());
m_plugins.insert(pluginName, plugin);
}
else
qCritical() << "plugin" << fileInfo.fileName() << "could not be casted to WebPlugin";
}
}
if(!config.contains(QStringLiteral("applications")))
throw std::runtime_error("settings does not contain a applications");
{
const auto applicationsVal = config.value(QStringLiteral("applications"));
if(!applicationsVal.isObject())
throw std::runtime_error("applications is not a json object");
const auto applicationsList = applicationsVal.toObject();
for(auto iter = applicationsList.constBegin(); iter != applicationsList.constEnd(); iter++)
{
const auto applicationVal = iter.value();
if(!applicationVal.isObject())
throw std::runtime_error(QString("application %0 is not a json object").arg(iter.key()).toStdString());
auto application = applicationVal.toObject();
if(!application.contains(QStringLiteral("_pluginName")))
throw std::runtime_error(QString("application %0 does not contain a _pluginName").arg(iter.key()).toStdString());
const auto pluginNameVal = application.take(QStringLiteral("_pluginName"));
if(!pluginNameVal.isString())
throw std::runtime_error(QString("application %0 pluginName is not a string").arg(iter.key()).toStdString());
auto pluginName = pluginNameVal.toString();
const auto pluginsIter = m_plugins.find(pluginName);
if(pluginsIter == m_plugins.constEnd())
throw std::runtime_error(QString("application %0 references not installed plugin %1").arg(iter.key(), pluginName).toStdString());
m_applications.insert(iter.key(), pluginsIter.value()->createApplication(application, *this));
}
}
if(!config.contains(QStringLiteral("listeners")))
throw std::runtime_error("settings does not contain a listeners");
{
const auto listenersVal = config.value(QStringLiteral("listeners"));
if(!listenersVal.isArray())
throw std::runtime_error("listeners is not a json array");
const auto listenersList = listenersVal.toArray();
for(auto iter = listenersList.constBegin(); iter != listenersList.constEnd(); iter++)
{
const auto listenerVal = *iter;
if(!listenerVal.isObject())
throw std::runtime_error(QString("listener %0 is not an object").arg(std::distance(listenersList.constBegin(), iter)).toStdString());
const auto listener = listenerVal.toObject();
m_listeners.insert(new WebListener(listener, *this));
}
}
}
void WebServer::start()
{
for(auto iter = m_applications.constBegin(); iter != m_applications.constEnd(); iter++)
iter.value()->start();
for(auto listener : m_listeners)
listener->start();
}
const QHash<QString, WebPlugin *> &WebServer::plugins() const
{
return m_plugins;
}
const QHash<QString, WebApplication *> &WebServer::applications() const
{
return m_applications;
}
const QSet<WebListener *> &WebServer::listeners() const
{
return m_listeners;
}

30
webserverlib/webserver.h Normal file
View File

@@ -0,0 +1,30 @@
#pragma once
#include "webserverlib_global.h"
#include <QObject>
#include <QHash>
#include <QSet>
class QJsonObject;
class WebPlugin;
class WebApplication;
class WebListener;
class WEBSERVERLIB_EXPORT WebServer : public QObject
{
public:
explicit WebServer(const QJsonObject &config, QObject *parent = Q_NULLPTR);
void start();
const QHash<QString, WebPlugin*> &plugins() const;
const QHash<QString, WebApplication*> &applications() const;
const QSet<WebListener*> &listeners() const;
private:
QHash<QString, WebPlugin*> m_plugins;
QHash<QString, WebApplication*> m_applications;
QSet<WebListener*> m_listeners;
};

View File

@@ -13,7 +13,8 @@ SOURCES += \
webplugin.cpp \
httpclientconnection.cpp \
httprequest.cpp \
httpresponse.cpp
httpresponse.cpp \
webserver.cpp
HEADERS += webserverlib_global.h \
weblistener.h \
@@ -21,7 +22,8 @@ HEADERS += webserverlib_global.h \
webplugin.h \
httpclientconnection.h \
httprequest.h \
httpresponse.h
httpresponse.h \
webserver.h
FORMS +=