Files
qt6-migration-pipeline/Lts/Common/httq/HttpRequest.cpp
T

416 lines
10 KiB
C++
Executable File

#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();
}
}