416 lines
10 KiB
C++
Executable File
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();
|
|
}
|
|
}
|