Imported existing sources

This commit is contained in:
0xFEEDC0DE64
2018-09-17 19:29:29 +02:00
parent 5634a187a0
commit 3426433a22
26 changed files with 1011 additions and 0 deletions

15
.gitmodules vendored Normal file
View File

@@ -0,0 +1,15 @@
[submodule "plugins/fileserverplugin"]
path = plugins/fileserverplugin
url = https://github.com/0xFEEDC0DE64/DbWebserver-fileserverplugin.git
[submodule "plugins/helloworldplugin"]
path = plugins/helloworldplugin
url = https://github.com/0xFEEDC0DE64/DbWebserver-helloworldplugin.git
[submodule "plugins/proxyplugin"]
path = plugins/proxyplugin
url = https://github.com/0xFEEDC0DE64/DbWebserver-proxyplugin.git
[submodule "plugins/seriesplugin"]
path = plugins/seriesplugin
url = https://github.com/0xFEEDC0DE64/DbWebserver-seriesplugin.git
[submodule "plugins/wifilampplugin"]
path = plugins/wifilampplugin
url = https://github.com/0xFEEDC0DE64/DbWebserver-wifilampplugin.git

8
DbWebserver.pro Normal file
View File

@@ -0,0 +1,8 @@
TEMPLATE = subdirs
SUBDIRS += webserver \
webserverlib \
plugins
webserver.depends += webserverlib
plugins.depends += webserverlib

5
plugins/plugin.pri Normal file
View File

@@ -0,0 +1,5 @@
PROJECT_ROOT = ../../..
TEMPLATE = lib
CONFIG += shared
DESTDIR = $${OUT_PWD}/$${PROJECT_ROOT}/bin/plugins/webserver
include(../../project.pri)

9
plugins/plugins.pro Normal file
View File

@@ -0,0 +1,9 @@
TEMPLATE = subdirs
SUBDIRS += fileserverplugin \
helloworldplugin \
proxyplugin \
seriesplugin \
wifilampplugin
OTHER_FILES += plugin.pri

1
plugins/proxyplugin Submodule

Submodule plugins/proxyplugin added at 795d191f27

1
plugins/seriesplugin Submodule

Submodule plugins/seriesplugin added at 5bcabbdce0

121
webserver/main.cpp Normal file
View File

@@ -0,0 +1,121 @@
#include <QCoreApplication>
#include <QDir>
#include <QLibrary>
#include <QPluginLoader>
#include <QJsonObject>
#include <QJsonArray>
#include <QDebug>
#include <stdexcept>
#include <iterator>
#include "utils.h"
#include "webplugin.h"
#include "weblistener.h"
#include "webapplication.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();
return app.exec();
}

92
webserver/webserver.json Normal file
View File

@@ -0,0 +1,92 @@
{
"applications": {
"Fallback": {
"_pluginName": "fileserver",
"rootPath": "htdocs/__fallback"
},
"HelloWorld": {
"_pluginName": "helloworld"
},
"1000serien.com": {
"_pluginName": "series",
"mysql": {
"hostname": "localhost",
"username": "series",
"password": "_stripped_",
"database": "series"
}
},
"cdn.1000serien.com": {
"_pluginName": "fileserver",
"rootPath": "/komposthaufen/multimedia/Videos"
},
"brunner.ninja": {
"_pluginName": "fileserver",
"rootPath": "htdocs/brunner.ninja"
},
"telegram.brunner.ninja": {
"_pluginName": "fileserver",
"rootPath": "htdocs/telegram.brunner.ninja/dist"
},
"transmission.brunner.ninja": {
"_pluginName": "proxy",
"url": "http://127.0.0.1:9091/"
},
"findtheinvisiblegspot.com": {
"_pluginName": "fileserver",
"rootPath": "htdocs/findtheinvisiblegspot.com"
},
"flucky.xyz": {
"_pluginName": "fileserver",
"rootPath": "htdocs/flucky.xyz"
},
"mail.flucky.xyz": {
"_pluginName": "fileserver",
"rootPath": "/etc/webapps/roundcubemail"
},
"phpmyadmin.flucky.xyz": {
"_pluginName": "fileserver",
"rootPath": "/usr/share/webapps/phpMyAdmin"
},
"localhorst.xyz": {
"_pluginName": "fileserver",
"rootPath": "htdocs/localhorst.xyz"
},
"maik-mahlow.de": {
"_pluginName": "fileserver",
"rootPath": "htdocs/maik-mahlow.de"
},
"WifiLamp": {
"_pluginName": "wifilamp",
"controlHostAdress": "QHostAddress::Any",
"controlPort": 1234
}
},
"listeners": [{
"hostAddress": "QHostAddress::Any",
"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"
}
}]
}

22
webserver/webserver.pro Normal file
View File

@@ -0,0 +1,22 @@
QT += core network
QT -= gui widgets
DBLIBS += webserverlib
PROJECT_ROOT = ../..
SOURCES += main.cpp
HEADERS +=
FORMS +=
RESOURCES +=
TRANSLATIONS +=
configinstall.path = $${OUT_PWD}/$${PROJECT_ROOT}/bin
configinstall.files = webserver.json
INSTALLS += configinstall
include($${PROJECT_ROOT}/app.pri)

View File

@@ -0,0 +1,193 @@
#include "httpclientconnection.h"
#include <QTcpSocket>
#include <QTextStream>
#include <QRegularExpression>
#include "weblistener.h"
HttpClientConnection::HttpClientConnection(QTcpSocket &socket, WebListener &httpServer) :
QObject(&httpServer),
m_socket(socket),
m_webListener(httpServer),
m_state(RequestLine),
m_bodyLength(-1)
{
m_socket.setParent(this);
connect(&m_socket, &QIODevice::readyRead, this, &HttpClientConnection::readyRead);
connect(&m_socket, &QTcpSocket::disconnected, this, &QObject::deleteLater);
}
void HttpClientConnection::sendResponse(const HttpResponse &response)
{
if(m_state != WaitingForResponse)
{
qCritical() << "sending a response now is not allowed!";
return;
}
QTextStream stream(&m_socket);
stream << response.protocol << ' ' << int(response.statusCode) << ' ' << response.statusString() << endl;
for(auto iter = response.headers.constBegin(); iter != response.headers.constEnd(); iter++)
stream << iter.key() << ": " << iter.value() << endl;
stream << endl;
}
void HttpClientConnection::sendResponse(HttpResponse response, const QByteArray &byteArray)
{
if(m_state != WaitingForResponse)
{
qCritical() << "sending a response now is not allowed!";
return;
}
response.headers.insert(QStringLiteral("Content-Length"), QString::number(byteArray.length()));
sendResponse(response);
m_socket.write(byteArray);
m_state = RequestLine;
}
void HttpClientConnection::sendResponse(HttpResponse response, const QString &string)
{
if(m_state != WaitingForResponse)
{
qCritical() << "sending a response now is not allowed!";
return;
}
sendResponse(response, string.toUtf8());
m_state = RequestLine;
}
void HttpClientConnection::sendResponse(HttpResponse response, std::unique_ptr<QIODevice> &&device)
{
if(m_state != WaitingForResponse)
throw std::runtime_error("sending a response now is not allowed!");
if(!device->isReadable())
throw std::runtime_error("device is not readable");
if(device->isSequential())
throw std::runtime_error("sequental device not supported yet");
m_sendingDeivce = std::move(device);
response.headers.insert(QStringLiteral("Content-Length"), QString::number(m_sendingDeivce->size()));
sendResponse(response);
connect(&m_socket, &QIODevice::bytesWritten, this, &HttpClientConnection::bytesWritten);
bytesWritten();
m_state = SendingResponse;
}
void HttpClientConnection::readyRead()
{
m_buffer.append(m_socket.readAll());
switch(m_state)
{
case RequestLine:
case Headers:
{
int index;
while((index = m_buffer.indexOf(QByteArrayLiteral("\r\n"))) != -1)
{
QString line(m_buffer.left(index));
m_buffer.remove(0, index + 2);
switch(m_state)
{
case RequestLine:
{
auto parts = line.split(' ');
Q_ASSERT(parts.count() == 3);
m_request.method = parts.at(0);
m_request.path = parts.at(1);
m_request.protocol = parts.at(2);
m_state = Headers;
continue;
}
case Headers:
{
if(!line.isEmpty())
{
static const QRegularExpression regex(QStringLiteral("^ *([^ :]+) *: *(.*) *$"));
auto match = regex.match(line);
if(!match.hasMatch())
qWarning() << "ignoring invalid" << line;
if(m_request.headers.contains(match.captured(1)))
qWarning() << "duplicate header" << match.captured(1);
m_request.headers.insert(match.captured(1), match.captured(2));
}
else
{
if(m_request.headers.contains(QStringLiteral("Content-Length")))
{
m_bodyLength = m_request.headers.value(QStringLiteral("Content-Length")).toInt();
m_state = RequestBody;
goto hatschi;
}
else
{
m_state = WaitingForResponse;
m_webListener.handleRequest(this, m_request);
clearRequest();
}
}
}
}
}
return;
}
case RequestBody:
hatschi:
{
auto length = qMin(m_bodyLength - m_request.body.count(), m_buffer.count());
m_request.body.append(m_buffer.left(length));
m_buffer.remove(0, length);
if(m_request.body.count() == m_bodyLength)
{
if(!m_buffer.isEmpty())
qCritical() << "received more than expected!";
m_state = WaitingForResponse;
m_webListener.handleRequest(this, m_request);
clearRequest();
}
}
}
}
void HttpClientConnection::bytesWritten()
{
if(m_socket.bytesToWrite() >= 1024*1024*4)
return;
if(m_socket.bytesToWrite() == 0 && m_sendingDeivce->bytesAvailable() == 0)
{
m_state = RequestLine;
disconnect(&m_socket, &QIODevice::bytesWritten, this, &HttpClientConnection::bytesWritten);
m_sendingDeivce.reset();
return;
}
auto buffer = m_sendingDeivce->read(1024*1024*4);
m_socket.write(buffer);
}
void HttpClientConnection::clearRequest()
{
m_request.headers.clear();
m_request.body.clear();
}

View File

@@ -0,0 +1,43 @@
#pragma once
#include <QObject>
#include <QIODevice>
#include <memory>
#include "httpcontainers.h"
class QTcpSocket;
class WebListener;
class HttpClientConnection : public QObject
{
Q_OBJECT
public:
explicit HttpClientConnection(QTcpSocket &socket, WebListener &WebListener);
void sendResponse(const HttpResponse &response);
void sendResponse(HttpResponse response, const QByteArray &byteArray);
void sendResponse(HttpResponse response, const QString &string);
void sendResponse(HttpResponse response, std::unique_ptr<QIODevice> &&device);
private Q_SLOTS:
void readyRead();
void bytesWritten();
private:
void clearRequest();
QTcpSocket &m_socket;
WebListener &m_webListener;
QByteArray m_buffer;
enum { RequestLine, Headers, RequestBody, WaitingForResponse, SendingResponse } m_state;
int m_bodyLength;
HttpRequest m_request;
std::unique_ptr<QIODevice> m_sendingDeivce;
};

View File

@@ -0,0 +1,75 @@
#include "httpcontainers.h"
QString HttpResponse::statusString() const
{
switch(statusCode) {
case HttpResponse::StatusCode::Continue: return QStringLiteral("Continue");
case HttpResponse::StatusCode::SwitchingProtocols: return QStringLiteral("Switching Protocols");
case HttpResponse::StatusCode::Processing: return QStringLiteral("Processing");
case HttpResponse::StatusCode::EarlyHints: return QStringLiteral("Early Hints");
case HttpResponse::StatusCode::OK: return QStringLiteral("OK");
case HttpResponse::StatusCode::Created: return QStringLiteral("Created");
case HttpResponse::StatusCode::Accepted: return QStringLiteral("Accepted");
case HttpResponse::StatusCode::NonAuthoritativeInformation: return QStringLiteral("Non-Authoritative Information");
case HttpResponse::StatusCode::NoContent: return QStringLiteral("No Content");
case HttpResponse::StatusCode::ResetContent: return QStringLiteral("Reset Content");
case HttpResponse::StatusCode::PartialContent: return QStringLiteral("Partial Content");
case HttpResponse::StatusCode::MultiStatus: return QStringLiteral("Multi-Status");
case HttpResponse::StatusCode::AlreadyReported: return QStringLiteral("Already Reported");
case HttpResponse::StatusCode::IMUsed: return QStringLiteral("IM Used");
case HttpResponse::StatusCode::MultipleChoices: return QStringLiteral("Multiple Choices");
case HttpResponse::StatusCode::MovedPermanently: return QStringLiteral("Moved Permanently");
case HttpResponse::StatusCode::Found: return QStringLiteral("Found (Moved Temporarily)");
case HttpResponse::StatusCode::SeeOther: return QStringLiteral("See Other");
case HttpResponse::StatusCode::NotModified: return QStringLiteral("Not Modified");
case HttpResponse::StatusCode::UseProxy: return QStringLiteral("Use Proxy");
case HttpResponse::StatusCode::SwitchProxy: return QStringLiteral("(reserviert)");
case HttpResponse::StatusCode::TemporaryRedirect: return QStringLiteral("Temporary Redirect");
case HttpResponse::StatusCode::PermanentRedirect: return QStringLiteral("Permanent Redirect");
case HttpResponse::StatusCode::BadRequest: return QStringLiteral("Bad Request");
case HttpResponse::StatusCode::Unauthorized: return QStringLiteral("Unauthorized");
case HttpResponse::StatusCode::PaymentRequired: return QStringLiteral("Payment Required");
case HttpResponse::StatusCode::Forbidden: return QStringLiteral("Forbidden");
case HttpResponse::StatusCode::NotFound: return QStringLiteral("Not Found");
case HttpResponse::StatusCode::MethodNotAllowed: return QStringLiteral("Method Not Allowed");
case HttpResponse::StatusCode::NotAcceptable: return QStringLiteral("Not Acceptable");
case HttpResponse::StatusCode::ProxyAuthenticationRequired: return QStringLiteral("Proxy Authentication Required");
case HttpResponse::StatusCode::RequestTimeout: return QStringLiteral("Request Timeout");
case HttpResponse::StatusCode::Conflict: return QStringLiteral("Conflict");
case HttpResponse::StatusCode::Gone: return QStringLiteral("Gone");
case HttpResponse::StatusCode::LengthRequired: return QStringLiteral("Length Required");
case HttpResponse::StatusCode::PreconditionFailed: return QStringLiteral("Precondition Failed");
case HttpResponse::StatusCode::RequestEntityTooLarge: return QStringLiteral("Request Entity Too Large");
case HttpResponse::StatusCode::URITooLong: return QStringLiteral("URI Too Long");
case HttpResponse::StatusCode::UnsupportedMediaType: return QStringLiteral("Unsupported Media Type");
case HttpResponse::StatusCode::Requestedrangenotsatisfiable: return QStringLiteral("Requested range not satisfiable");
case HttpResponse::StatusCode::ExpectationFailed: return QStringLiteral("Expectation Failed");
case HttpResponse::StatusCode::Imateapot: return QStringLiteral("Im a teapot");
case HttpResponse::StatusCode::PolicyNotFulfilled: return QStringLiteral("Policy Not Fulfilled");
case HttpResponse::StatusCode::MisdirectedRequest: return QStringLiteral("Misdirected Request");
case HttpResponse::StatusCode::UnprocessableEntity: return QStringLiteral("Unprocessable Entity");
case HttpResponse::StatusCode::Locked: return QStringLiteral("Locked");
case HttpResponse::StatusCode::FailedDependency: return QStringLiteral("Failed Dependency");
case HttpResponse::StatusCode::UnorderedCollection: return QStringLiteral("Unordered Collection");
case HttpResponse::StatusCode::UpgradeRequired: return QStringLiteral("Upgrade Required");
case HttpResponse::StatusCode::PreconditionRequired: return QStringLiteral("Precondition Required");
case HttpResponse::StatusCode::TooManyRequests: return QStringLiteral("Too Many Requests");
case HttpResponse::StatusCode::RequestHeaderFieldsTooLarge: return QStringLiteral("Request Header Fields Too Large");
case HttpResponse::StatusCode::NoResponse: return QStringLiteral("No Response");
case HttpResponse::StatusCode::Therequestshouldberetriedafterdoingtheappropriateaction: return QStringLiteral("The request should be retried after doing the appropriate action");
case HttpResponse::StatusCode::UnavailableForLegalReasons: return QStringLiteral("Unavailable For Legal Reasons");
case HttpResponse::StatusCode::ClientClosedRequest: return QStringLiteral("Client Closed Request");
case HttpResponse::StatusCode::InternalServerError: return QStringLiteral("Internal Server Error");
case HttpResponse::StatusCode::NotImplemented: return QStringLiteral("Not Implemented");
case HttpResponse::StatusCode::BadGateway: return QStringLiteral("Bad Gateway");
case HttpResponse::StatusCode::ServiceUnavailable: return QStringLiteral("Service Unavailable");
case HttpResponse::StatusCode::GatewayTimeout: return QStringLiteral("Gateway Timeout");
case HttpResponse::StatusCode::HTTPVersionnotsupported: return QStringLiteral("HTTP Version not supported");
case HttpResponse::StatusCode::VariantAlsoNegotiates: return QStringLiteral("Variant Also Negotiates");
case HttpResponse::StatusCode::InsufficientStorage: return QStringLiteral("Insufficient Storage");
case HttpResponse::StatusCode::LoopDetected: return QStringLiteral("Loop Detected");
case HttpResponse::StatusCode::BandwidthLimitExceeded: return QStringLiteral("Bandwidth Limit Exceeded");
case HttpResponse::StatusCode::NotExtended: return QStringLiteral("Not Extended");
case HttpResponse::StatusCode::NetworkAuthenticationRequired: return QStringLiteral("Network Authentication Required");
}
}

View File

@@ -0,0 +1,101 @@
#pragma once
#include <QString>
#include <QHash>
#include <QByteArray>
struct HttpRequest {
QString method;
QString path;
QString protocol;
QHash<QString, QString> headers;
QByteArray body;
};
struct HttpResponse {
enum class StatusCode {
// 1xx Informational responses
Continue = 100, // Continue
SwitchingProtocols = 101, // Switching Protocols
Processing = 102, // Processing
EarlyHints = 103, // Early Hints
// 2xx Success
OK = 200, // OK
Created = 201, // Created
Accepted = 202, // Accepted
NonAuthoritativeInformation = 203, // Non-Authoritative Information
NoContent = 204, // No Content
ResetContent = 205, // Reset Content
PartialContent = 206, // Partial Content
MultiStatus = 207, // Multi-Status
AlreadyReported = 208, // Already Reported
IMUsed = 226, // IM Used
// 3xx Redirection
MultipleChoices = 300, // Multiple Choices
MovedPermanently = 301, // Moved Permanently
Found = 302, // Found (Moved Temporarily)
SeeOther = 303, // See Other
NotModified = 304, // Not Modified
UseProxy = 305, // Use Proxy
SwitchProxy = 306, // (reserviert)
TemporaryRedirect = 307, // Temporary Redirect
PermanentRedirect = 308, // Permanent Redirect
// 4xx Client errors
BadRequest = 400, // Bad Request
Unauthorized = 401, // Unauthorized
PaymentRequired = 402, // Payment Required
Forbidden = 403, // Forbidden
NotFound = 404, // Not Found
MethodNotAllowed = 405, // Method Not Allowed
NotAcceptable = 406, // Not Acceptable
ProxyAuthenticationRequired = 407, // Proxy Authentication Required
RequestTimeout = 408, // Request Timeout
Conflict = 409, // Conflict
Gone = 410, // Gone
LengthRequired = 411, // Length Required
PreconditionFailed = 412, // Precondition Failed
RequestEntityTooLarge = 413, // Request Entity Too Large
URITooLong = 414, // URI Too Long
UnsupportedMediaType = 415, // Unsupported Media Type
Requestedrangenotsatisfiable = 416, // Requested range not satisfiable
ExpectationFailed = 417, // Expectation Failed
Imateapot = 418, // Im a teapot
PolicyNotFulfilled = 420, // Policy Not Fulfilled
MisdirectedRequest = 421, // Misdirected Request
UnprocessableEntity = 422, // Unprocessable Entity
Locked = 423, // Locked
FailedDependency = 424, // Failed Dependency
UnorderedCollection = 425, // Unordered Collection
UpgradeRequired = 426, // Upgrade Required
PreconditionRequired = 428, // Precondition Required
TooManyRequests = 429, // Too Many Requests
RequestHeaderFieldsTooLarge = 431, // Request Header Fields Too Large
NoResponse = 444, // No Response
Therequestshouldberetriedafterdoingtheappropriateaction = 449, // The request should be retried after doing the appropriate action
UnavailableForLegalReasons = 451, // Unavailable For Legal Reasons
ClientClosedRequest = 499, // Client Closed Request
// 5xx Server errors
InternalServerError = 500, // Internal Server Error
NotImplemented = 501, // Not Implemented
BadGateway = 502, // Bad Gateway
ServiceUnavailable = 503, // Service Unavailable
GatewayTimeout = 504, // Gateway Timeout
HTTPVersionnotsupported = 505, // HTTP Version not supported
VariantAlsoNegotiates = 506, // Variant Also Negotiates
InsufficientStorage = 507, // Insufficient Storage
LoopDetected = 508, // Loop Detected
BandwidthLimitExceeded = 509, // Bandwidth Limit Exceeded
NotExtended = 510, // Not Extended
NetworkAuthenticationRequired = 511, // Network Authentication Required
};
QString protocol;
StatusCode statusCode;
QHash<QString, QString> headers;
QString statusString() const;
};

47
webserverlib/utils.cpp Normal file
View File

@@ -0,0 +1,47 @@
#include "utils.h"
#include <QJsonObject>
#include <QJsonArray>
QHostAddress parseHostAddress(const QString &hostAddress)
{
static const QMap<QString, QHostAddress> specialHostAddresses {
{ QStringLiteral("QHostAddress::Null"), QHostAddress::Null },
{ QStringLiteral("QHostAddress::Broadcast"), QHostAddress::Broadcast },
{ QStringLiteral("QHostAddress::LocalHost"), QHostAddress::LocalHost },
{ QStringLiteral("QHostAddress::LocalHostIPv6"), QHostAddress::LocalHostIPv6 },
{ QStringLiteral("QHostAddress::Any"), QHostAddress::Any },
{ QStringLiteral("QHostAddress::AnyIPv6"), QHostAddress::AnyIPv6 },
{ QStringLiteral("QHostAddress::AnyIPv4"), QHostAddress::AnyIPv4 }
};
const auto iter = specialHostAddresses.find(hostAddress);
if(iter != specialHostAddresses.constEnd())
return *iter;
return QHostAddress(hostAddress);
}
template<>
QJsonDocument getJson<QJsonDocument>(const QJsonDocument &document)
{
return document;
}
template<>
QJsonObject getJson<QJsonObject>(const QJsonDocument &document)
{
if(!document.isObject())
throw std::runtime_error("JSON document does not contain an object!");
return document.object();
}
template<>
QJsonArray getJson<QJsonArray>(const QJsonDocument &document)
{
if(!document.isArray())
throw std::runtime_error("JSON document does not contain an object!");
return document.array();
}

60
webserverlib/utils.h Normal file
View File

@@ -0,0 +1,60 @@
#pragma once
#include <QHostAddress>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QFile>
#include <stdexcept>
QHostAddress parseHostAddress(const QString &hostAddress);
template<typename T>
T getJson(const QJsonDocument &document);
template<>
QJsonDocument getJson<QJsonDocument>(const QJsonDocument &document);
template<>
QJsonObject getJson<QJsonObject>(const QJsonDocument &document);
template<>
QJsonArray getJson<QJsonArray>(const QJsonDocument &document);
template<typename T>
T getJson(const QByteArray &byteArray);
template<typename T>
T getJson(QIODevice &device);
template<typename T>
T getJson(const QString &filename);
template<typename T>
T getJson(const QByteArray &byteArray)
{
QJsonParseError error;
auto document = QJsonDocument::fromJson(byteArray, &error);
if(error.error != QJsonParseError::NoError)
throw std::runtime_error(QString("Could not parse json: %0").arg(error.errorString()).toStdString());
return getJson<T>(document);
}
template<typename T>
T getJson(QIODevice &device)
{
return getJson<T>(device.readAll());
}
template<typename T>
T getJson(const QString &filename)
{
QFile file(filename);
if(!file.open(QIODevice::ReadOnly|QIODevice::Text))
throw std::runtime_error(QString("Could not open json file %0: %1").arg(filename, file.errorString()).toStdString());
return getJson<T>(file);
}

View File

@@ -0,0 +1,7 @@
#include "webapplication.h"
WebApplication::WebApplication(QObject *parent) :
QObject(parent)
{
}

View File

@@ -0,0 +1,14 @@
#pragma once
#include <QObject>
#include "webserverlib_global.h"
class WEBSERVERLIB_EXPORT WebApplication : public QObject
{
Q_OBJECT
public:
WebApplication(QObject *parent = Q_NULLPTR);
virtual void start() = 0;
};

View File

@@ -0,0 +1,93 @@
#include "weblistener.h"
#include <QJsonObject>
#include <QHash>
#include <QTcpServer>
#include <QDebug>
#include <stdexcept>
#include "utils.h"
#include "httpclientconnection.h"
WebListener::WebListener(const QJsonObject &config, const QHash<QString, WebApplication*> &applications, QObject *parent) :
QObject(parent)
{
if(!config.contains(QStringLiteral("hostAddress")))
throw std::runtime_error("listener does not contain hostAddress");
const auto hostAddressVal = config.value(QStringLiteral("hostAddress"));
if(!hostAddressVal.isString())
throw std::runtime_error("listener hostAddress is not a string");
m_address = parseHostAddress(hostAddressVal.toString());
if(!config.contains(QStringLiteral("port")))
throw std::runtime_error("listener does not contain port");
const auto portVal = config.value(QStringLiteral("port"));
if(!portVal.isDouble())
throw std::runtime_error("listener port is not a number");
m_port = portVal.toInt();
m_tcpServer = new QTcpServer(this);
if(!config.contains(QStringLiteral("vhosts")))
throw std::runtime_error("listener does not contain vhosts");
const auto vhostsVal = config.value(QStringLiteral("vhosts"));
if(!vhostsVal.isObject())
throw std::runtime_error("listener vhosts is not an object");
const auto vhosts = vhostsVal.toObject();
for(auto iter = vhosts.constBegin(); iter != vhosts.constEnd(); iter++)
{
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());
const auto applicationName = applicationNameVal.toString();
const auto applicationsIter = applications.find(applicationName);
if(applicationsIter == 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());
qDebug() << iter.key() << applicationName;
}
}
void WebListener::start()
{
qDebug() << "starting listening" << m_address << m_port;
if(!m_tcpServer->listen(m_address, 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());
connect(m_tcpServer, &QTcpServer::acceptError, this, &WebListener::acceptError);
connect(m_tcpServer, &QTcpServer::newConnection, this, &WebListener::newConnection);
}
void WebListener::handleRequest(HttpClientConnection *connection, const HttpRequest &request)
{
HttpResponse response;
response.protocol = request.protocol;
response.statusCode = HttpResponse::StatusCode::OK;
connection->sendResponse(response, request.path);
}
void WebListener::acceptError(QAbstractSocket::SocketError socketError)
{
qCritical() << socketError;
}
void WebListener::newConnection()
{
auto connection = m_tcpServer->nextPendingConnection();
if(!connection)
return;
new HttpClientConnection(*connection, *this);
}

View File

@@ -0,0 +1,35 @@
#pragma once
#include <QObject>
#include "webserverlib_global.h"
#include <QAbstractSocket>
#include <QHostAddress>
class QJsonObject;
template <class Key, class T> class QHash;
class QTcpServer;
class WebApplication;
class HttpClientConnection;
class HttpRequest;
class WEBSERVERLIB_EXPORT WebListener : public QObject
{
Q_OBJECT
public:
WebListener(const QJsonObject &config, const QHash<QString, WebApplication*> &applications, QObject *parent = Q_NULLPTR);
void start();
void handleRequest(HttpClientConnection *connection, const HttpRequest &request);
private Q_SLOTS:
void acceptError(QAbstractSocket::SocketError socketError);
void newConnection();
private:
QTcpServer *m_tcpServer;
QHostAddress m_address;
quint16 m_port;
};

View File

@@ -0,0 +1,6 @@
#include "webplugin.h"
WebPlugin::WebPlugin(QObject *parent) :
QObject(parent)
{
}

21
webserverlib/webplugin.h Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#include <QObject>
#include "webserverlib_global.h"
class QJsonObject;
class WebApplication;
class WEBSERVERLIB_EXPORT WebPlugin : public QObject
{
Q_OBJECT
public:
WebPlugin(QObject *parent = Q_NULLPTR);
virtual QString pluginName() const = 0;
virtual WebApplication *createApplication(const QJsonObject &config) const = 0;
};
Q_DECLARE_INTERFACE(WebPlugin, "dbsoftware.webserver.plugin/1.0")

View File

@@ -0,0 +1,30 @@
QT += core network
QT -= gui widgets
PROJECT_ROOT = ../..
DEFINES += WEBSERVERLIB_LIBRARY
SOURCES += \
weblistener.cpp \
webapplication.cpp \
webplugin.cpp \
utils.cpp \
httpclientconnection.cpp \
httpcontainers.cpp
HEADERS += webserverlib_global.h \
weblistener.h \
webapplication.h \
webplugin.h \
utils.h \
httpclientconnection.h \
httpcontainers.h
FORMS +=
RESOURCES +=
TRANSLATIONS +=
include($${PROJECT_ROOT}/lib.pri)

View File

@@ -0,0 +1,9 @@
#pragma once
#include <QtGlobal>
#if defined(WEBSERVERLIB_LIBRARY)
# define WEBSERVERLIB_EXPORT Q_DECL_EXPORT
#else
# define WEBSERVERLIB_EXPORT Q_DECL_IMPORT
#endif