Imported existing sources
This commit is contained in:
1 parent
5634a187a0
commit
3426433a22
26 files changed
+1011
No files matched your search
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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("I’m 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");
|
||||
}
|
||||
}
|
||||
@@ -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, // I’m 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;
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#include "webapplication.h"
|
||||
|
||||
WebApplication::WebApplication(QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
#include "webplugin.h"
|
||||
|
||||
WebPlugin::WebPlugin(QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
}
|
||||
@@ -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")
|
||||
@@ -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)
|
||||
@@ -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
|
||||
Reference in new issue
Block a user