Continues migration with DDS, Common
This commit is contained in:
1 parent
9b15cee841
commit
fa48db111e
668 files changed
+111841
-2
No files matched your search
Executable
+91
@@ -0,0 +1,91 @@
|
||||
#include <httq/AbstractServer.h>
|
||||
#include <httq/HttpRequest.h>
|
||||
#include <httq/Logger.h>
|
||||
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
#include <QWebSocketServer>
|
||||
|
||||
|
||||
namespace httq
|
||||
{
|
||||
AbstractServer::AbstractServer(QObject *parent)
|
||||
: QObject(parent)
|
||||
, mSvr(new QTcpServer(this))
|
||||
, mLoggerFactory(nullptr)
|
||||
, mLogger(nullptr)
|
||||
{}
|
||||
|
||||
|
||||
LoggerFactory *AbstractServer::createLoggerFactory()
|
||||
{
|
||||
return new LoggerFactory(this);
|
||||
}
|
||||
|
||||
|
||||
LoggerFactory *AbstractServer::getLoggerFactory()
|
||||
{
|
||||
if (mLoggerFactory == nullptr)
|
||||
mLoggerFactory = createLoggerFactory();
|
||||
return mLoggerFactory;
|
||||
}
|
||||
|
||||
|
||||
void AbstractServer::slotNewConnection()
|
||||
{
|
||||
mLogger->debug(QStringLiteral("newConnection"));
|
||||
|
||||
while (QTcpSocket *cli = mSvr->nextPendingConnection())
|
||||
{
|
||||
mLogger->debug(QStringLiteral("nextPendingConnection"));
|
||||
|
||||
if (cli == nullptr)
|
||||
{
|
||||
mLogger->debug(QStringLiteral("invalid tcp client"));
|
||||
return;
|
||||
}
|
||||
|
||||
HttpRequest *req = new HttpRequest(cli, this);
|
||||
|
||||
connect(req, &HttpRequest::signalUpgrade,
|
||||
this, [this, req](QTcpSocket *sock)
|
||||
{
|
||||
mLogger->debug(QStringLiteral("signalUpgrade"));
|
||||
|
||||
QWebSocketServer *wsSvr = webSocketServer();
|
||||
|
||||
if (wsSvr == nullptr)
|
||||
{
|
||||
mLogger->debug(QStringLiteral("no websocket server available for websocket upgrade"));
|
||||
sock->close();
|
||||
sock->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
wsSvr->handleConnection(sock);
|
||||
emit sock->readyRead();
|
||||
});
|
||||
|
||||
connect(req, &HttpRequest::signalReady,
|
||||
this, [this, req]()
|
||||
{
|
||||
mLogger->debug(QStringLiteral("signalReady"));
|
||||
newHttpConnection(req);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool AbstractServer::listen(qint16 port, const QHostAddress &host)
|
||||
{
|
||||
mLoggerFactory = createLoggerFactory();
|
||||
mLogger = mLoggerFactory->createLogger(this);
|
||||
|
||||
mLogger->debug(QStringLiteral("listen"));
|
||||
|
||||
connect(mSvr, &QTcpServer::newConnection,
|
||||
this, &AbstractServer::slotNewConnection);
|
||||
|
||||
return mSvr->listen(host, port);
|
||||
}
|
||||
}
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
# Modified CMakeLists.txt from github.com/wille-io/httq
|
||||
|
||||
set(src
|
||||
include/http_parser.h
|
||||
include/http_parser.c # TODO: move out of include dir
|
||||
|
||||
include/httq/AbstractServer.h
|
||||
include/httq/DataStream.h
|
||||
include/httq/HttpRequest.h
|
||||
include/httq/Logger.h
|
||||
|
||||
AbstractServer.cpp
|
||||
DataStream.cpp
|
||||
HttpRequest.cpp
|
||||
Logger.cpp
|
||||
)
|
||||
|
||||
NGL_ADD_LIBRARY2(SV WS httq SHARED ${src})
|
||||
|
||||
NGL_TARGET_INCLUDE_DIRECTORIES(httq
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_BINARY_DIR}/../ # export file
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/
|
||||
)
|
||||
|
||||
NGL_TARGET_LINK_LIBRARIES(httq
|
||||
Qt5::Core
|
||||
Qt5::Network
|
||||
Qt5::WebSockets
|
||||
)
|
||||
|
||||
NGL_INSTALL_TARGET(httq)
|
||||
Executable
+262
@@ -0,0 +1,262 @@
|
||||
#include <httq/DataStream.h>
|
||||
#include <httq/HttpRequest.h>
|
||||
|
||||
#include <QIODevice>
|
||||
#include <QTcpSocket>
|
||||
#include <QTimer>
|
||||
#include <QDataStream>
|
||||
#include <QTemporaryFile>
|
||||
#include <QFile>
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace httq
|
||||
{
|
||||
DataStream::DataStream(QIODevice *from, QIODevice *to, const QByteArray &bodyPartial,
|
||||
qint64 fileLength, size_t bufferSize, LoggerFactory *loggerFactory,
|
||||
QObject *parent)
|
||||
: QObject(parent)
|
||||
, mBodyPartial(bodyPartial)
|
||||
, mFrom(from)
|
||||
, mTo(to)
|
||||
, mBufferSize(bufferSize)
|
||||
, mFileLength(fileLength)
|
||||
, mLogger(loggerFactory->createLogger(this))
|
||||
{
|
||||
|
||||
#ifdef SAVE_PARTIAL_BODY
|
||||
{
|
||||
QTemporaryFile f;
|
||||
f.open();
|
||||
f.write(mBodyPartial);
|
||||
f.setAutoRemove(false);
|
||||
qWarning() << "partial:" << f.fileName();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
if (mFileLength == 0)
|
||||
{
|
||||
mLogger->warning(QStringLiteral("file size = 0 - done"));
|
||||
QTimer::singleShot(1, this, [this]() { emit signalDone(); deleteLater(); } );
|
||||
return;
|
||||
}
|
||||
|
||||
mLogger->debug(QStringLiteral("DataStream c_tor"));
|
||||
|
||||
|
||||
|
||||
mBuffer.reserve(bufferSize);
|
||||
|
||||
|
||||
|
||||
mFromSupportsSignals = (qobject_cast<QFileDevice *>(mFrom) == nullptr);
|
||||
mToSupportsSignals = (qobject_cast<QFileDevice *>(mTo) == nullptr);
|
||||
mLogger->debug(QStringLiteral("signal support - from: %1 - to: %2").arg(mFromSupportsSignals).arg(mToSupportsSignals));
|
||||
|
||||
|
||||
|
||||
|
||||
auto sock = qobject_cast<QTcpSocket *>(mFrom);
|
||||
qWarning() << "######################### SOCK" << sock;
|
||||
connect(sock, &QTcpSocket::readyRead,
|
||||
this, [this]()
|
||||
{
|
||||
mLogger->debug(QStringLiteral(">> DataStream readyRead"));
|
||||
|
||||
|
||||
QTimer::singleShot(1, this, [this]() { readNext(); } );
|
||||
});
|
||||
|
||||
|
||||
if (mToSupportsSignals)
|
||||
connect(mTo, &QIODevice::bytesWritten,
|
||||
this, [this]()
|
||||
{
|
||||
mLogger->debug(QStringLiteral(">> DataStream write"));
|
||||
|
||||
mReadyWrite = true;
|
||||
QTimer::singleShot(1, this, [this]() { readNext(); } );
|
||||
});
|
||||
|
||||
|
||||
QTimer::singleShot(1, this, [this]() { readNext(); } );
|
||||
}
|
||||
|
||||
|
||||
DataStream::~DataStream()
|
||||
{
|
||||
mLogger->debug(QStringLiteral("~DataStream"));
|
||||
}
|
||||
|
||||
|
||||
void DataStream::readNext()
|
||||
{
|
||||
#if defined(DEBUG_SLOW) && defined(Q_OS_UNIX)
|
||||
mLogger->debug(QStringLiteral("read: slowing down....."));
|
||||
usleep(2 * 1000 * 1000);
|
||||
#endif
|
||||
|
||||
mLogger->debug(QStringLiteral("read: readNext: %1").arg(mBuffer.size()));
|
||||
|
||||
|
||||
if (mBuffer.size() > 0)
|
||||
{
|
||||
mLogger->debug(QStringLiteral("read: ignoring read until buffer was written"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (mToSupportsSignals && !mReadyWrite)
|
||||
{
|
||||
mLogger->debug(QStringLiteral("read: won't read until write is ready!"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!mBodyPartial.isEmpty())
|
||||
{
|
||||
mBuffer = mBodyPartial;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
alreadyRead += mBuffer.size();
|
||||
mLogger->debug(QStringLiteral("read: prebody size: %1").arg(mBuffer.size()));
|
||||
|
||||
mBodyPartial.clear();
|
||||
|
||||
|
||||
mLogger->debug(QStringLiteral("read: queueing writeNext after prebody read"));
|
||||
QTimer::singleShot(1, this, [this]() { writeNext(); } );
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if ( (mFrom->bytesAvailable() < 1 ))
|
||||
{
|
||||
|
||||
|
||||
mLogger->debug(QStringLiteral("read: no bytes available and not ready read"));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
QTimer::singleShot(1000, this, [this]() { readNext(); } );
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
mBuffer = mFrom->read(mBufferSize);
|
||||
mLogger->debug(QStringLiteral("read: mPos is now %1").arg(mBuffer.size()));
|
||||
|
||||
|
||||
|
||||
|
||||
alreadyRead += mBuffer.size();
|
||||
mLogger->debug(QStringLiteral("read: alreadyRead = %1").arg(alreadyRead));
|
||||
|
||||
if (mBuffer.size() < 1)
|
||||
{
|
||||
mLogger->debug(QStringLiteral("read: cannot read anything"));
|
||||
deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
mLogger->debug(QStringLiteral("read: queueing writeNext"));
|
||||
QTimer::singleShot(1, this, [this]() { writeNext(); } );
|
||||
}
|
||||
|
||||
|
||||
void DataStream::writeNext()
|
||||
{
|
||||
mLogger->debug(QStringLiteral("write: writeNext: %1").arg(mBuffer.size()));
|
||||
|
||||
if (mBuffer.size() < 1)
|
||||
{
|
||||
mLogger->debug(QStringLiteral("write: nothing to write"));
|
||||
QTimer::singleShot(1, this, [this]() { readNext(); } );
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!mTo->isOpen() || !mTo->isWritable())
|
||||
{
|
||||
mLogger->debug(QStringLiteral("write: file error: open? %1 - writable? %2").arg(mTo->isOpen()).arg(mTo->isWritable()));
|
||||
deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mToSupportsSignals && !mReadyWrite )
|
||||
{
|
||||
mLogger->debug(QStringLiteral("write: !mReadyWrite"));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (mToSupportsSignals)
|
||||
mReadyWrite = false;
|
||||
|
||||
const qint64 size = mTo->write(mBuffer);
|
||||
mLogger->debug(QStringLiteral("write: wrote to buffer %1 bytes").arg(size));
|
||||
|
||||
mLogger->debug(QStringLiteral("write: %1 / %2 bytes done (%3 bytes left until DataStream done)").arg(alreadyRead).arg(mFileLength).arg(mFileLength - alreadyRead));
|
||||
|
||||
if (size < mBuffer.size())
|
||||
{
|
||||
mLogger->warning(QStringLiteral("write: cannot write all bytes: %1 / %2 bytes").arg(mBuffer.size()).arg(size));
|
||||
deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
mLogger->debug(QStringLiteral("write: mPos is now %1 and was set to zero now!!").arg(mBuffer.size()));
|
||||
mBuffer.clear();
|
||||
|
||||
if (alreadyRead == mFileLength)
|
||||
{
|
||||
mLogger->debug(QStringLiteral("write: DONE!"));
|
||||
emit signalDone();
|
||||
deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
if (alreadyRead > mFileLength)
|
||||
{
|
||||
mLogger->debug(QStringLiteral("write: oops... we read too much!! data may be corrupt!! already read %1 bytes, but content length is only %2 (%1 / %2)").arg(alreadyRead).arg(mFileLength));
|
||||
emit signalDone();
|
||||
deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (mToSupportsSignals)
|
||||
mLogger->debug(QStringLiteral("write: waiting for bytes written to be triggered..."));
|
||||
|
||||
mLogger->debug(QStringLiteral("write: queueing writeNext again"));
|
||||
QTimer::singleShot(1, this, [this]() { writeNext(); } );
|
||||
}
|
||||
}
|
||||
Executable
+415
@@ -0,0 +1,415 @@
|
||||
#include <httq/AbstractServer.h>
|
||||
#include <httq/HttpRequest.h>
|
||||
#include <httq/DataStream.h>
|
||||
|
||||
#include <http_parser.h>
|
||||
#include <httq/Logger.h>
|
||||
|
||||
#include <QTcpSocket>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
#include <QJsonValue>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
|
||||
|
||||
namespace httq
|
||||
{
|
||||
HttpRequest::HttpRequest(QTcpSocket *cli, AbstractServer *parent)
|
||||
: QObject(parent)
|
||||
, mCli(cli)
|
||||
, mLogger(parent->getLoggerFactory()->createLogger(this))
|
||||
{
|
||||
mLogger->debug(QStringLiteral("HttpRequest c_tor"));
|
||||
|
||||
|
||||
|
||||
|
||||
http_parser_init(&mParser, HTTP_REQUEST);
|
||||
mData.mHttpRquest = this;
|
||||
mParser.data = static_cast<void *>(&mData);
|
||||
|
||||
connect(cli, &QTcpSocket::readyRead,
|
||||
this, &HttpRequest::slotReadyRead);
|
||||
connect(cli, &QTcpSocket::disconnected,
|
||||
this, [this]()
|
||||
{
|
||||
qWarning() << "Client disconnected himself!!";
|
||||
});
|
||||
connect(cli, &QTcpSocket::disconnected,
|
||||
this, &HttpRequest::deleteLater);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
mParserSettings.on_message_begin = &HttpRequest::on_message_begin;
|
||||
mParserSettings.on_url = &HttpRequest::on_url;
|
||||
mParserSettings.on_status = &HttpRequest::on_status;
|
||||
mParserSettings.on_header_field = &HttpRequest::on_header_field;
|
||||
mParserSettings.on_header_value = &HttpRequest::on_header_value;
|
||||
mParserSettings.on_headers_complete = &HttpRequest::on_headers_complete;
|
||||
mParserSettings.on_body = &HttpRequest::on_body;
|
||||
mParserSettings.on_message_complete = &HttpRequest::on_message_complete;
|
||||
mParserSettings.on_chunk_header = &HttpRequest::on_chunk_header;
|
||||
mParserSettings.on_chunk_complete = &HttpRequest::on_chunk_complete;
|
||||
}
|
||||
|
||||
|
||||
HttpRequest::~HttpRequest()
|
||||
{
|
||||
mLogger->debug(QStringLiteral("~HttpRequest"));
|
||||
|
||||
if (mCli == nullptr)
|
||||
return;
|
||||
|
||||
mLogger->debug(QStringLiteral("mCli flush & close"));
|
||||
mCli->flush();
|
||||
|
||||
}
|
||||
|
||||
|
||||
QString HttpRequest::toString() const
|
||||
{
|
||||
return QStringLiteral("HttpRequest(url = '%1', method = %2)").arg(url().toString()).arg(methodString());
|
||||
}
|
||||
|
||||
|
||||
int HttpRequest::on_message_begin(http_parser *parser)
|
||||
{
|
||||
dataLogger(parser)->debug(QStringLiteral("on_message_begin"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int HttpRequest::on_url(http_parser *parser, const char *at, size_t length)
|
||||
{
|
||||
dataLogger(parser)->debug(QStringLiteral("on_url '%1'").arg(QString::fromUtf8(at, length)));
|
||||
|
||||
|
||||
|
||||
|
||||
http_parser_url parserUrl;
|
||||
if (http_parser_parse_url(at, length, false, &parserUrl) != 0)
|
||||
{
|
||||
dataLogger(parser)->debug(QStringLiteral("could not determine url! %1").arg(QString::fromUtf8(at, length)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
QUrl url;
|
||||
|
||||
#define isUrlFieldSet(field) parserUrl.field_set & (1 << field)
|
||||
if (isUrlFieldSet(UF_SCHEMA))
|
||||
url.setScheme(QString::fromUtf8(at + parserUrl.field_data[UF_SCHEMA].off, parserUrl.field_data[UF_SCHEMA].len));
|
||||
|
||||
if (parserUrl.field_set & (1 << UF_HOST))
|
||||
url.setHost(QString::fromUtf8(at + parserUrl.field_data[UF_HOST].off, parserUrl.field_data[UF_HOST].len));
|
||||
|
||||
if (parserUrl.field_set & (1 << UF_PORT))
|
||||
{
|
||||
bool k = false;
|
||||
QString portString(QString::fromUtf8(at + parserUrl.field_data[UF_PORT].off, parserUrl.field_data[UF_PORT].len));
|
||||
qint16 port = portString.toShort(&k);
|
||||
|
||||
if (!k)
|
||||
{
|
||||
dataLogger(parser)->warning(QStringLiteral("could not determine port! %1").arg(portString));
|
||||
return 1;
|
||||
}
|
||||
|
||||
url.setPort(static_cast<int>(port));
|
||||
}
|
||||
|
||||
if (parserUrl.field_set & (1 << UF_PATH))
|
||||
url.setPath(QString::fromUtf8(at + parserUrl.field_data[UF_PATH].off, parserUrl.field_data[UF_PATH].len));
|
||||
|
||||
HttpRequestData *reqData = data(parser);
|
||||
if (isUrlFieldSet(UF_QUERY))
|
||||
{
|
||||
|
||||
QUrlQuery q(QString::fromUtf8(at + parserUrl.field_data[UF_QUERY].off, parserUrl.field_data[UF_QUERY].len));
|
||||
url.setQuery(q);
|
||||
reqData->mQuery = q;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (isUrlFieldSet(UF_USERINFO))
|
||||
url.setUserInfo(QString::fromUtf8(at + parserUrl.field_data[UF_USERINFO].off, parserUrl.field_data[UF_USERINFO].len));
|
||||
#undef isUrlFieldSet
|
||||
|
||||
|
||||
dataLogger(parser)->debug(QStringLiteral("final url = %1").arg(url.toString()));
|
||||
reqData->mUrl = url;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int HttpRequest::on_status(http_parser *parser, const char *, size_t )
|
||||
{
|
||||
dataLogger(parser)->debug(QStringLiteral("on_status"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int HttpRequest::on_header_field(http_parser *parser, const char *at, size_t length)
|
||||
{
|
||||
dataLogger(parser)->debug(QStringLiteral("on_header_field"));
|
||||
|
||||
data(parser)->mCurrentHeaderField = QString::fromUtf8(at, length);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int HttpRequest::on_header_value(http_parser *parser, const char *at, size_t length)
|
||||
{
|
||||
dataLogger(parser)->debug(QStringLiteral("on_header_value"));
|
||||
|
||||
QString value(QString::fromUtf8(at, length));
|
||||
HttpRequestData *reqdata = data(parser);
|
||||
|
||||
reqdata->mHeaders.insert(reqdata->mCurrentHeaderField, value);
|
||||
|
||||
dataLogger(parser)->debug(QStringLiteral("added header: %1 = %2").arg(reqdata->mCurrentHeaderField).arg(value));
|
||||
|
||||
reqdata->mCurrentHeaderField.clear();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int HttpRequest::on_headers_complete(http_parser *parser)
|
||||
{
|
||||
dataLogger(parser)->debug(QStringLiteral("on_headers_complete"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int HttpRequest::on_body(http_parser *parser, const char *at, size_t length)
|
||||
{
|
||||
dataLogger(parser)->debug(QStringLiteral("on_body"));
|
||||
data(parser)->mBodyPartial = QByteArray(at, length);
|
||||
|
||||
|
||||
|
||||
data(parser)->mDone = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int HttpRequest::on_message_complete(http_parser *parser)
|
||||
{
|
||||
dataLogger(parser)->debug(QStringLiteral("on_message_complete"));
|
||||
|
||||
data(parser)->mDone = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int HttpRequest::on_chunk_header(http_parser *parser)
|
||||
{
|
||||
dataLogger(parser)->debug(QStringLiteral("on_chunk_header"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int HttpRequest::on_chunk_complete(http_parser *parser)
|
||||
{
|
||||
dataLogger(parser)->debug(QStringLiteral("on_chunk_complete"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
qint64 HttpRequest::availableBytes() const
|
||||
{
|
||||
return (mContentLength - alreadyRead);
|
||||
}
|
||||
|
||||
|
||||
DataStream *HttpRequest::createDataStreamToClient(int status, const QString &contentType, QIODevice *from, qint64 fileSize, qint64 bufferSize)
|
||||
{
|
||||
mLogger->debug(QStringLiteral("createDataStreamToClient"));
|
||||
|
||||
writeStatusHeader(status);
|
||||
addHeader(QStringLiteral("Content-Type"), contentType);
|
||||
addHeader(QStringLiteral("Content-Length"), QString::number(fileSize));
|
||||
writeHeaders();
|
||||
mCli->write("\r\n");
|
||||
|
||||
return new DataStream(from, mCli, {}, fileSize, bufferSize, mLogger->getLoggerFactory(), this);
|
||||
}
|
||||
|
||||
|
||||
DataStream *HttpRequest::createDataStreamFromClient(QIODevice *to, qint64 fileSize, qint64 bufferSize)
|
||||
{
|
||||
mLogger->debug(QStringLiteral("createDataStreamFromClient"));
|
||||
|
||||
return new DataStream(mCli, to, mData.mBodyPartial, fileSize, bufferSize, mLogger->getLoggerFactory(), this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void HttpRequest::write(int status, const QJsonValue &jsonValue)
|
||||
{
|
||||
QJsonDocument doc;
|
||||
|
||||
if (jsonValue.isArray())
|
||||
doc = QJsonDocument(jsonValue.toArray());
|
||||
else
|
||||
doc = QJsonDocument(jsonValue.toObject());
|
||||
|
||||
write(status, doc.toJson(), QStringLiteral("application/json"));
|
||||
}
|
||||
|
||||
|
||||
void HttpRequest::write(int status, const QByteArray &data, const QString &contentType)
|
||||
{
|
||||
mLogger->debug(QStringLiteral("write to client: status = %1, data (size) = %2, contentType = %3").arg(status).arg(data.size()).arg(contentType));
|
||||
writeStatusHeader(status);
|
||||
|
||||
addHeader(QStringLiteral("Content-Type"), contentType);
|
||||
addHeader(QStringLiteral("Content-Length"), QString::number(data.size()));
|
||||
|
||||
writeHeaders();
|
||||
mCli->write("\r\n");
|
||||
|
||||
mCli->write(data);
|
||||
}
|
||||
|
||||
|
||||
void HttpRequest::writeStatusHeader(int status)
|
||||
{
|
||||
QString header(QStringLiteral("HTTP/1.1 %1 %2\r\n").arg(status).arg(QStringLiteral("OK") ));
|
||||
mLogger->debug(QStringLiteral("write to client: header = %1").arg(header));
|
||||
mCli->write(header.toUtf8());
|
||||
}
|
||||
|
||||
|
||||
void HttpRequest::addHeader(const QString &key, const QString &value)
|
||||
{
|
||||
mHeaders.insert(key, value);
|
||||
}
|
||||
|
||||
|
||||
void HttpRequest::writeHeaders()
|
||||
{
|
||||
QString headers;
|
||||
|
||||
QMapIterator<QString, QString> it(mHeaders);
|
||||
while (it.hasNext())
|
||||
{
|
||||
it.next();
|
||||
headers += QStringLiteral("%1: %2\r\n").arg(it.key(), it.value());
|
||||
}
|
||||
|
||||
mLogger->debug(QStringLiteral("write to client: headers = %1").arg(headers));
|
||||
mCli->write(headers.toUtf8());
|
||||
}
|
||||
|
||||
|
||||
void HttpRequest::slotReadyRead()
|
||||
{
|
||||
|
||||
mLogger->debug(QStringLiteral("slotReadyRead"));
|
||||
|
||||
if (mDone)
|
||||
{
|
||||
mLogger->debug(QStringLiteral("slotReadyRead: already marked as done"));
|
||||
return;
|
||||
}
|
||||
|
||||
mCli->startTransaction();
|
||||
|
||||
|
||||
while (mCli->bytesAvailable() && !mData.mDone)
|
||||
{
|
||||
mLogger->debug(QStringLiteral("slotReadyRead: bytes available"));
|
||||
const QByteArray data(mCli->read(10000));
|
||||
|
||||
|
||||
int dataSize = data.size();
|
||||
|
||||
size_t size = http_parser_execute(&mParser, &mParserSettings, data.constData(), static_cast<size_t>(dataSize));
|
||||
|
||||
if (size < static_cast<size_t>(dataSize))
|
||||
{
|
||||
mLogger->error(QStringLiteral("http parser error: %1").arg(QString::fromUtf8(http_errno_name(HTTP_PARSER_ERRNO(&mParser)))));
|
||||
mCli->commitTransaction();
|
||||
deleteLater();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (mData.mDone)
|
||||
{
|
||||
mLogger->debug(QStringLiteral("slotReadyRead: now marked as done"));
|
||||
|
||||
mDone = true;
|
||||
|
||||
|
||||
mLogger->debug(QStringLiteral("slotReadyRead: method = %1").arg(methodString()));
|
||||
|
||||
|
||||
|
||||
auto it = mData.mHeaders.constFind(QStringLiteral("Upgrade"));
|
||||
|
||||
if (it != mData.mHeaders.constEnd() && (*it).toLower() == QStringLiteral("websocket"))
|
||||
{
|
||||
mLogger->debug(QStringLiteral("slotReadyRead: Upgrade: %1").arg(*it));
|
||||
disconnect(mCli, &QTcpSocket::readyRead, nullptr, nullptr);
|
||||
|
||||
mCli->rollbackTransaction();
|
||||
mCli->setParent(nullptr);
|
||||
auto cli = mCli;
|
||||
mCli = nullptr;
|
||||
emit signalUpgrade(cli);
|
||||
|
||||
deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
it = mData.mHeaders.constFind(QStringLiteral("Content-Length"));
|
||||
|
||||
if (it != mData.mHeaders.constEnd())
|
||||
{
|
||||
bool k = false;
|
||||
mContentLength = (*it).toLongLong(&k);
|
||||
|
||||
if (!k)
|
||||
{
|
||||
mLogger->error(QStringLiteral("slotReadyRead: invalid content length: ").arg(*it));
|
||||
deleteLater();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
alreadyRead = mData.mBodyPartial.size();
|
||||
|
||||
mLogger->debug(QStringLiteral("slotReadyRead: already read %1 / %2 bytes").arg(alreadyRead).arg(contentLength()));
|
||||
|
||||
mCli->commitTransaction();
|
||||
emit signalReady();
|
||||
return;
|
||||
}
|
||||
|
||||
mLogger->debug(QStringLiteral("slotReadyRead: commit transaction"));
|
||||
mCli->commitTransaction();
|
||||
}
|
||||
}
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
Copyright Joyent, Inc. and other Node contributors.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Mike Wille <mike@wille.io>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Executable
+71
@@ -0,0 +1,71 @@
|
||||
#include <httq/Logger.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
namespace httq
|
||||
{
|
||||
|
||||
Logger *LoggerFactory::createLogger(QObject *parent)
|
||||
{
|
||||
return new Logger(this, parent);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Logger::Logger(LoggerFactory *loggerFactory, QObject *parent)
|
||||
: QObject(parent)
|
||||
, mLoggerFactory(loggerFactory)
|
||||
{
|
||||
if (parent == nullptr)
|
||||
{
|
||||
|
||||
mPath = QStringLiteral("Global");
|
||||
}
|
||||
else
|
||||
{
|
||||
mPath = QString::fromUtf8(parent->metaObject()->className());
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Logger::debug(const QString &message)
|
||||
{
|
||||
|
||||
qtLogger(QStringLiteral("DEBUG"), qWarning()) << message;
|
||||
}
|
||||
|
||||
|
||||
void Logger::warning(const QString &message)
|
||||
{
|
||||
qtLogger(QStringLiteral("WARNING"), qWarning()) << message;
|
||||
}
|
||||
|
||||
|
||||
void Logger::error(const QString &message)
|
||||
{
|
||||
qtLogger(QStringLiteral("ERROR"), qCritical()) << message;
|
||||
}
|
||||
|
||||
|
||||
QDebug Logger::qtLogger(const QString &type, QDebug logger)
|
||||
{
|
||||
QDebug d(logger.noquote());
|
||||
d << type << mPath << ":";
|
||||
return d;
|
||||
}
|
||||
}
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
# httq
|
||||
|
||||
Proof of Concept. Do not use in production.
|
||||
HTTP Server for Qt. Doesn't clog the memory like The Other HTTP Server™.
|
||||
Based on NodeJS's wonderful http-parser.
|
||||
Can download a message's body in chunks, instead of reading it all into memory, allowing an easy DoS, like The Other HTTP Server™.
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
nodejs/http-parser/v2.9.4
|
||||
Executable
+2480
File diff suppressed because it is too large.
Load diff
Executable
+380
@@ -0,0 +1,380 @@
|
||||
|
||||
|
||||
#ifndef http_parser_h
|
||||
#define http_parser_h
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#define HTTP_PARSER_VERSION_MAJOR 2
|
||||
#define HTTP_PARSER_VERSION_MINOR 9
|
||||
#define HTTP_PARSER_VERSION_PATCH 4
|
||||
|
||||
#include <stddef.h>
|
||||
#if defined(_WIN32) && !defined(__MINGW32__) && \
|
||||
(!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__)
|
||||
#include <BaseTsd.h>
|
||||
typedef __int8 int8_t;
|
||||
typedef unsigned __int8 uint8_t;
|
||||
typedef __int16 int16_t;
|
||||
typedef unsigned __int16 uint16_t;
|
||||
typedef __int32 int32_t;
|
||||
typedef unsigned __int32 uint32_t;
|
||||
typedef __int64 int64_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#ifndef HTTP_PARSER_STRICT
|
||||
# define HTTP_PARSER_STRICT 1
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#ifndef HTTP_MAX_HEADER_SIZE
|
||||
# define HTTP_MAX_HEADER_SIZE (80*1024)
|
||||
#endif
|
||||
|
||||
typedef struct http_parser http_parser;
|
||||
typedef struct http_parser_settings http_parser_settings;
|
||||
|
||||
|
||||
|
||||
|
||||
typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
|
||||
typedef int (*http_cb) (http_parser*);
|
||||
|
||||
|
||||
|
||||
#define HTTP_STATUS_MAP(XX) \
|
||||
XX(100, CONTINUE, Continue) \
|
||||
XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \
|
||||
XX(102, PROCESSING, Processing) \
|
||||
XX(200, OK, OK) \
|
||||
XX(201, CREATED, Created) \
|
||||
XX(202, ACCEPTED, Accepted) \
|
||||
XX(203, NON_AUTHORITATIVE_INFORMATION, Non-Authoritative Information) \
|
||||
XX(204, NO_CONTENT, No Content) \
|
||||
XX(205, RESET_CONTENT, Reset Content) \
|
||||
XX(206, PARTIAL_CONTENT, Partial Content) \
|
||||
XX(207, MULTI_STATUS, Multi-Status) \
|
||||
XX(208, ALREADY_REPORTED, Already Reported) \
|
||||
XX(226, IM_USED, IM Used) \
|
||||
XX(300, MULTIPLE_CHOICES, Multiple Choices) \
|
||||
XX(301, MOVED_PERMANENTLY, Moved Permanently) \
|
||||
XX(302, FOUND, Found) \
|
||||
XX(303, SEE_OTHER, See Other) \
|
||||
XX(304, NOT_MODIFIED, Not Modified) \
|
||||
XX(305, USE_PROXY, Use Proxy) \
|
||||
XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \
|
||||
XX(308, PERMANENT_REDIRECT, Permanent Redirect) \
|
||||
XX(400, BAD_REQUEST, Bad Request) \
|
||||
XX(401, UNAUTHORIZED, Unauthorized) \
|
||||
XX(402, PAYMENT_REQUIRED, Payment Required) \
|
||||
XX(403, FORBIDDEN, Forbidden) \
|
||||
XX(404, NOT_FOUND, Not Found) \
|
||||
XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \
|
||||
XX(406, NOT_ACCEPTABLE, Not Acceptable) \
|
||||
XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \
|
||||
XX(408, REQUEST_TIMEOUT, Request Timeout) \
|
||||
XX(409, CONFLICT, Conflict) \
|
||||
XX(410, GONE, Gone) \
|
||||
XX(411, LENGTH_REQUIRED, Length Required) \
|
||||
XX(412, PRECONDITION_FAILED, Precondition Failed) \
|
||||
XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \
|
||||
XX(414, URI_TOO_LONG, URI Too Long) \
|
||||
XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \
|
||||
XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \
|
||||
XX(417, EXPECTATION_FAILED, Expectation Failed) \
|
||||
XX(421, MISDIRECTED_REQUEST, Misdirected Request) \
|
||||
XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \
|
||||
XX(423, LOCKED, Locked) \
|
||||
XX(424, FAILED_DEPENDENCY, Failed Dependency) \
|
||||
XX(426, UPGRADE_REQUIRED, Upgrade Required) \
|
||||
XX(428, PRECONDITION_REQUIRED, Precondition Required) \
|
||||
XX(429, TOO_MANY_REQUESTS, Too Many Requests) \
|
||||
XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \
|
||||
XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \
|
||||
XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \
|
||||
XX(501, NOT_IMPLEMENTED, Not Implemented) \
|
||||
XX(502, BAD_GATEWAY, Bad Gateway) \
|
||||
XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \
|
||||
XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \
|
||||
XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \
|
||||
XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \
|
||||
XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \
|
||||
XX(508, LOOP_DETECTED, Loop Detected) \
|
||||
XX(510, NOT_EXTENDED, Not Extended) \
|
||||
XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \
|
||||
|
||||
enum http_status
|
||||
{
|
||||
#define XX(num, name, string) HTTP_STATUS_##name = num,
|
||||
HTTP_STATUS_MAP(XX)
|
||||
#undef XX
|
||||
};
|
||||
|
||||
|
||||
|
||||
#define HTTP_METHOD_MAP(XX) \
|
||||
XX(0, DELETE, DELETE) \
|
||||
XX(1, GET, GET) \
|
||||
XX(2, HEAD, HEAD) \
|
||||
XX(3, POST, POST) \
|
||||
XX(4, PUT, PUT) \
|
||||
\
|
||||
XX(5, CONNECT, CONNECT) \
|
||||
XX(6, OPTIONS, OPTIONS) \
|
||||
XX(7, TRACE, TRACE) \
|
||||
\
|
||||
XX(8, COPY, COPY) \
|
||||
XX(9, LOCK, LOCK) \
|
||||
XX(10, MKCOL, MKCOL) \
|
||||
XX(11, MOVE, MOVE) \
|
||||
XX(12, PROPFIND, PROPFIND) \
|
||||
XX(13, PROPPATCH, PROPPATCH) \
|
||||
XX(14, SEARCH, SEARCH) \
|
||||
XX(15, UNLOCK, UNLOCK) \
|
||||
XX(16, BIND, BIND) \
|
||||
XX(17, REBIND, REBIND) \
|
||||
XX(18, UNBIND, UNBIND) \
|
||||
XX(19, ACL, ACL) \
|
||||
\
|
||||
XX(20, REPORT, REPORT) \
|
||||
XX(21, MKACTIVITY, MKACTIVITY) \
|
||||
XX(22, CHECKOUT, CHECKOUT) \
|
||||
XX(23, MERGE, MERGE) \
|
||||
\
|
||||
XX(24, MSEARCH, M-SEARCH) \
|
||||
XX(25, NOTIFY, NOTIFY) \
|
||||
XX(26, SUBSCRIBE, SUBSCRIBE) \
|
||||
XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
|
||||
\
|
||||
XX(28, PATCH, PATCH) \
|
||||
XX(29, PURGE, PURGE) \
|
||||
\
|
||||
XX(30, MKCALENDAR, MKCALENDAR) \
|
||||
\
|
||||
XX(31, LINK, LINK) \
|
||||
XX(32, UNLINK, UNLINK) \
|
||||
\
|
||||
XX(33, SOURCE, SOURCE) \
|
||||
|
||||
enum http_method
|
||||
{
|
||||
#define XX(num, name, string) HTTP_##name = num,
|
||||
HTTP_METHOD_MAP(XX)
|
||||
#undef XX
|
||||
};
|
||||
|
||||
|
||||
enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
|
||||
|
||||
|
||||
|
||||
enum flags
|
||||
{ F_CHUNKED = 1 << 0
|
||||
, F_CONNECTION_KEEP_ALIVE = 1 << 1
|
||||
, F_CONNECTION_CLOSE = 1 << 2
|
||||
, F_CONNECTION_UPGRADE = 1 << 3
|
||||
, F_TRAILING = 1 << 4
|
||||
, F_UPGRADE = 1 << 5
|
||||
, F_SKIPBODY = 1 << 6
|
||||
, F_CONTENTLENGTH = 1 << 7
|
||||
, F_TRANSFER_ENCODING = 1 << 8
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
#define HTTP_ERRNO_MAP(XX) \
|
||||
\
|
||||
XX(OK, "success") \
|
||||
\
|
||||
\
|
||||
XX(CB_message_begin, "the on_message_begin callback failed") \
|
||||
XX(CB_url, "the on_url callback failed") \
|
||||
XX(CB_header_field, "the on_header_field callback failed") \
|
||||
XX(CB_header_value, "the on_header_value callback failed") \
|
||||
XX(CB_headers_complete, "the on_headers_complete callback failed") \
|
||||
XX(CB_body, "the on_body callback failed") \
|
||||
XX(CB_message_complete, "the on_message_complete callback failed") \
|
||||
XX(CB_status, "the on_status callback failed") \
|
||||
XX(CB_chunk_header, "the on_chunk_header callback failed") \
|
||||
XX(CB_chunk_complete, "the on_chunk_complete callback failed") \
|
||||
\
|
||||
\
|
||||
XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
|
||||
XX(HEADER_OVERFLOW, \
|
||||
"too many header bytes seen; overflow detected") \
|
||||
XX(CLOSED_CONNECTION, \
|
||||
"data received after completed connection: close message") \
|
||||
XX(INVALID_VERSION, "invalid HTTP version") \
|
||||
XX(INVALID_STATUS, "invalid HTTP status code") \
|
||||
XX(INVALID_METHOD, "invalid HTTP method") \
|
||||
XX(INVALID_URL, "invalid URL") \
|
||||
XX(INVALID_HOST, "invalid host") \
|
||||
XX(INVALID_PORT, "invalid port") \
|
||||
XX(INVALID_PATH, "invalid path") \
|
||||
XX(INVALID_QUERY_STRING, "invalid query string") \
|
||||
XX(INVALID_FRAGMENT, "invalid fragment") \
|
||||
XX(LF_EXPECTED, "LF character expected") \
|
||||
XX(INVALID_HEADER_TOKEN, "invalid character in header") \
|
||||
XX(INVALID_CONTENT_LENGTH, \
|
||||
"invalid character in content-length header") \
|
||||
XX(UNEXPECTED_CONTENT_LENGTH, \
|
||||
"unexpected content-length header") \
|
||||
XX(INVALID_CHUNK_SIZE, \
|
||||
"invalid character in chunk size header") \
|
||||
XX(INVALID_CONSTANT, "invalid constant string") \
|
||||
XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\
|
||||
XX(STRICT, "strict mode assertion failed") \
|
||||
XX(PAUSED, "parser is paused") \
|
||||
XX(UNKNOWN, "an unknown error occurred") \
|
||||
XX(INVALID_TRANSFER_ENCODING, \
|
||||
"request has invalid transfer-encoding") \
|
||||
|
||||
|
||||
|
||||
#define HTTP_ERRNO_GEN(n, s) HPE_##n,
|
||||
enum http_errno {
|
||||
HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
|
||||
};
|
||||
#undef HTTP_ERRNO_GEN
|
||||
|
||||
|
||||
|
||||
#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno)
|
||||
|
||||
|
||||
struct http_parser {
|
||||
|
||||
unsigned int type : 2;
|
||||
unsigned int flags : 8;
|
||||
unsigned int state : 7;
|
||||
unsigned int header_state : 7;
|
||||
unsigned int index : 5;
|
||||
unsigned int extra_flags : 2;
|
||||
unsigned int lenient_http_headers : 1;
|
||||
|
||||
uint32_t nread;
|
||||
uint64_t content_length;
|
||||
|
||||
|
||||
unsigned short http_major;
|
||||
unsigned short http_minor;
|
||||
unsigned int status_code : 16;
|
||||
unsigned int method : 8;
|
||||
unsigned int http_errno : 7;
|
||||
|
||||
|
||||
|
||||
unsigned int upgrade : 1;
|
||||
|
||||
|
||||
void *data;
|
||||
};
|
||||
|
||||
|
||||
struct http_parser_settings {
|
||||
http_cb on_message_begin;
|
||||
http_data_cb on_url;
|
||||
http_data_cb on_status;
|
||||
http_data_cb on_header_field;
|
||||
http_data_cb on_header_value;
|
||||
http_cb on_headers_complete;
|
||||
http_data_cb on_body;
|
||||
http_cb on_message_complete;
|
||||
|
||||
|
||||
http_cb on_chunk_header;
|
||||
http_cb on_chunk_complete;
|
||||
};
|
||||
|
||||
|
||||
enum http_parser_url_fields
|
||||
{ UF_SCHEMA = 0
|
||||
, UF_HOST = 1
|
||||
, UF_PORT = 2
|
||||
, UF_PATH = 3
|
||||
, UF_QUERY = 4
|
||||
, UF_FRAGMENT = 5
|
||||
, UF_USERINFO = 6
|
||||
, UF_MAX = 7
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
struct http_parser_url {
|
||||
uint16_t field_set;
|
||||
uint16_t port;
|
||||
|
||||
struct {
|
||||
uint16_t off;
|
||||
uint16_t len;
|
||||
} field_data[UF_MAX];
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
unsigned long http_parser_version(void);
|
||||
|
||||
void http_parser_init(http_parser *parser, enum http_parser_type type);
|
||||
|
||||
|
||||
|
||||
|
||||
void http_parser_settings_init(http_parser_settings *settings);
|
||||
|
||||
|
||||
|
||||
|
||||
size_t http_parser_execute(http_parser *parser,
|
||||
const http_parser_settings *settings,
|
||||
const char *data,
|
||||
size_t len);
|
||||
|
||||
|
||||
|
||||
|
||||
int http_should_keep_alive(const http_parser *parser);
|
||||
|
||||
|
||||
const char *http_method_str(enum http_method m);
|
||||
|
||||
|
||||
const char *http_status_str(enum http_status s);
|
||||
|
||||
|
||||
const char *http_errno_name(enum http_errno err);
|
||||
|
||||
|
||||
const char *http_errno_description(enum http_errno err);
|
||||
|
||||
|
||||
void http_parser_url_init(struct http_parser_url *u);
|
||||
|
||||
|
||||
int http_parser_parse_url(const char *buf, size_t buflen,
|
||||
int is_connect,
|
||||
struct http_parser_url *u);
|
||||
|
||||
|
||||
void http_parser_pause(http_parser *parser, int paused);
|
||||
|
||||
|
||||
int http_body_is_final(const http_parser *parser);
|
||||
|
||||
|
||||
void http_parser_set_max_header_size(uint32_t size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <httq/httq_export.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QHostAddress>
|
||||
|
||||
|
||||
class QTcpServer;
|
||||
class QWebSocketServer;
|
||||
|
||||
|
||||
namespace httq
|
||||
{
|
||||
class HttpRequest;
|
||||
class LoggerFactory;
|
||||
class Logger;
|
||||
|
||||
|
||||
class HTTQ_EXPORT AbstractServer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AbstractServer(QObject *parent = nullptr);
|
||||
|
||||
virtual bool newHttpConnection(HttpRequest *request) = 0;
|
||||
virtual QWebSocketServer *webSocketServer() const { return nullptr; }
|
||||
virtual LoggerFactory *createLoggerFactory();
|
||||
LoggerFactory *getLoggerFactory();
|
||||
bool listen(qint16 port, const QHostAddress &host = QHostAddress::Any);
|
||||
|
||||
private slots:
|
||||
void slotNewConnection();
|
||||
|
||||
private:
|
||||
QTcpServer *mSvr;
|
||||
LoggerFactory *mLoggerFactory { nullptr };
|
||||
Logger *mLogger;
|
||||
};
|
||||
}
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <httq/httq_export.h>
|
||||
#include <httq/Logger.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QScopedPointer>
|
||||
|
||||
|
||||
class QIODevice;
|
||||
|
||||
|
||||
namespace httq
|
||||
{
|
||||
class HttpRequest;
|
||||
|
||||
class HTTQ_EXPORT DataStream : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
explicit DataStream(QIODevice *from, QIODevice *to, const QByteArray &bodyPartial,
|
||||
qint64 fileLength, size_t bufferSize, LoggerFactory *loggerFactory,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
public:
|
||||
~DataStream();
|
||||
|
||||
private:
|
||||
friend class HttpRequest;
|
||||
|
||||
void readNext();
|
||||
void writeNext();
|
||||
|
||||
bool mReadyWrite { true };
|
||||
|
||||
QByteArray mBuffer;
|
||||
QByteArray mBodyPartial;
|
||||
QIODevice *mFrom;
|
||||
QIODevice *mTo;
|
||||
|
||||
qint64 mBufferSize;
|
||||
qint64 alreadyRead { 0 };
|
||||
qint64 mFileLength;
|
||||
bool mFromSupportsSignals { false };
|
||||
bool mToSupportsSignals { false };
|
||||
QScopedPointer<Logger> mLogger;
|
||||
|
||||
signals:
|
||||
void signalDone();
|
||||
|
||||
};
|
||||
}
|
||||
Executable
+112
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
|
||||
#include <httq/httq_export.h>
|
||||
#include <http_parser.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QUrl>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QUrlQuery>
|
||||
|
||||
|
||||
class QTcpSocket;
|
||||
class QIODevice;
|
||||
|
||||
|
||||
namespace httq
|
||||
{
|
||||
class AbstractServer;
|
||||
class Logger;
|
||||
class DataStream;
|
||||
class HttpRequest;
|
||||
|
||||
|
||||
struct HttpRequestData
|
||||
{
|
||||
QByteArray mData;
|
||||
QUrl mUrl;
|
||||
QString mCurrentHeaderField;
|
||||
QMap<QString, QString> mHeaders;
|
||||
bool mDone { false };
|
||||
QByteArray mBodyPartial;
|
||||
QUrlQuery mQuery;
|
||||
HttpRequest *mHttpRquest;
|
||||
};
|
||||
|
||||
|
||||
class HTTQ_EXPORT HttpRequest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
explicit HttpRequest(QTcpSocket *cli, AbstractServer *parent = nullptr);
|
||||
public:
|
||||
~HttpRequest();
|
||||
|
||||
qint64 availableBytes() const;
|
||||
http_method method() const { return (enum http_method)mParser.method; }
|
||||
QString methodString() const { return QString::fromUtf8(http_method_str(method())); }
|
||||
const QUrl &url() const { return mData.mUrl; }
|
||||
const QUrlQuery &query() const { return mData.mQuery; }
|
||||
qint64 contentLength() const { return mContentLength; }
|
||||
const QMap<QString, QString> &requestHeaders() const { return mData.mHeaders; }
|
||||
|
||||
|
||||
Logger *logger() { return mLogger; }
|
||||
QString toString() const;
|
||||
|
||||
|
||||
void addHeader(const QString &key, const QString &value);
|
||||
void write(int status, const QByteArray &data, const QString &contentType);
|
||||
void write(int status, const QJsonValue &jsonValue);
|
||||
DataStream *createDataStreamFromClient(QIODevice *to, qint64 fileSize, qint64 bufferSize = 1024 * 1024);
|
||||
DataStream *createDataStreamToClient(int status, const QString &contentType, QIODevice *from, qint64 fileSize, qint64 bufferSize = 1024 * 1024);
|
||||
|
||||
friend class DataStream;
|
||||
|
||||
|
||||
private:
|
||||
friend class AbstractServer;
|
||||
|
||||
|
||||
void writeStatusHeader(int status);
|
||||
void writeHeaders();
|
||||
|
||||
static int on_message_begin(http_parser *parser);
|
||||
static int on_url(http_parser *parser, const char *at, size_t length);
|
||||
static int on_status(http_parser *parser, const char *at, size_t length);
|
||||
static int on_header_field(http_parser *parser, const char *at, size_t length);
|
||||
static int on_header_value(http_parser *parser, const char *at, size_t length);
|
||||
static int on_headers_complete(http_parser *parser);
|
||||
static int on_body(http_parser *parser, const char *at, size_t length);
|
||||
static int on_message_complete(http_parser *parser);
|
||||
static int on_chunk_header(http_parser *parser);
|
||||
static int on_chunk_complete(http_parser *parser);
|
||||
|
||||
static HttpRequestData *data(http_parser *parser) { return static_cast<HttpRequestData *>(parser->data); }
|
||||
static Logger *dataLogger(http_parser *parser) { return static_cast<HttpRequestData *>(parser->data)->mHttpRquest->logger(); }
|
||||
|
||||
|
||||
QTcpSocket *mCli;
|
||||
http_parser mParser;
|
||||
http_parser_settings mParserSettings;
|
||||
HttpRequestData mData;
|
||||
bool mDone { false };
|
||||
qint64 mContentLength { 0 };
|
||||
qint64 alreadyRead { 0 };
|
||||
QMap<QString, QString> mHeaders;
|
||||
Logger *mLogger;
|
||||
|
||||
|
||||
private slots:
|
||||
void slotReadyRead();
|
||||
|
||||
|
||||
signals:
|
||||
void signalUpgrade(QTcpSocket *sock);
|
||||
void signalReady();
|
||||
|
||||
};
|
||||
}
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <httq/httq_export.h>
|
||||
|
||||
#include <QScopedPointer>
|
||||
#include <QPointer>
|
||||
|
||||
|
||||
namespace httq
|
||||
{
|
||||
class Logger;
|
||||
|
||||
|
||||
class HTTQ_EXPORT LoggerFactory : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
LoggerFactory(QObject *parent)
|
||||
: QObject(parent)
|
||||
{}
|
||||
|
||||
virtual Logger *createLogger(QObject *parent);
|
||||
};
|
||||
|
||||
|
||||
class HTTQ_EXPORT Logger : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Logger(LoggerFactory *loggerFactory, QObject *parent);
|
||||
|
||||
virtual void debug(const QString &message);
|
||||
virtual void warning(const QString &message);
|
||||
virtual void error(const QString &message);
|
||||
|
||||
virtual LoggerFactory *getLoggerFactory() const
|
||||
{
|
||||
Q_ASSERT(mLoggerFactory);
|
||||
return mLoggerFactory;
|
||||
}
|
||||
|
||||
protected:
|
||||
QPointer<LoggerFactory> mLoggerFactory;
|
||||
|
||||
private:
|
||||
QDebug qtLogger(const QString &type, QDebug debug);
|
||||
|
||||
QString mPath;
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user