qthttpserver/src/httpserver/qhttpserverrequest.cpp
Mikhail Svetkin 5147076e5e Fix support for keep-alive connection
QAbstractHttpServer did not clear a request's internal properties (headers,
url, body).
If a request has a keep-alive header, QAbstractHttpServer should clear the
request's internal properties.

Change-Id: I2dfd0565369bd3291cd8d9900045c5a7f9d43ca3
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
2019-04-08 13:49:38 +00:00

315 lines
10 KiB
C++

/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtHttpServer module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qhttpserverrequest_p.h"
#include <QtHttpServer/qhttpserverrequest.h>
#include <QtCore/qdebug.h>
#include <QtCore/qloggingcategory.h>
#include <QtNetwork/qtcpsocket.h>
#if QT_CONFIG(ssl)
#include <QtNetwork/qsslsocket.h>
#endif
Q_LOGGING_CATEGORY(lc, "qt.httpserver.request")
QT_BEGIN_NAMESPACE
#if !defined(QT_NO_DEBUG_STREAM)
Q_HTTPSERVER_EXPORT QDebug operator<<(QDebug debug, const QHttpServerRequest &request)
{
const auto oldSetting = debug.autoInsertSpaces();
debug.nospace() << "QHttpServerRequest(";
debug << "(Url: " << request.url() << ")";
debug << "(Headers: " << request.headers() << ")";
debug << ')';
debug.setAutoInsertSpaces(oldSetting);
return debug.maybeSpace();
}
QDebug operator<<(QDebug debug, const http_parser *const httpParser)
{
const auto oldSetting = debug.autoInsertSpaces();
debug.nospace() << "http_parser(" << static_cast<const void *>(httpParser) << ": ";
debug << "HTTP " << httpParser->http_major << "." << httpParser->http_minor << " "
<< http_method_str(http_method(httpParser->method)) << ')';
debug.setAutoInsertSpaces(oldSetting);
return debug.maybeSpace();
}
#endif
http_parser_settings QHttpServerRequestPrivate::httpParserSettings {
&QHttpServerRequestPrivate::onMessageBegin,
&QHttpServerRequestPrivate::onUrl,
&QHttpServerRequestPrivate::onStatus,
&QHttpServerRequestPrivate::onHeaderField,
&QHttpServerRequestPrivate::onHeaderValue,
&QHttpServerRequestPrivate::onHeadersComplete,
&QHttpServerRequestPrivate::onBody,
&QHttpServerRequestPrivate::onMessageComplete,
&QHttpServerRequestPrivate::onChunkHeader,
&QHttpServerRequestPrivate::onChunkComplete
};
QHttpServerRequestPrivate::QHttpServerRequestPrivate()
{
httpParser.data = this;
}
QString QHttpServerRequestPrivate::header(const QString &key) const
{
return headers.value(headerHash(key)).second;
}
bool QHttpServerRequestPrivate::parse(QIODevice *socket)
{
const auto fragment = socket->readAll();
if (fragment.size()) {
#if QT_CONFIG(ssl)
auto sslSocket = qobject_cast<QSslSocket *>(socket);
url.setScheme(sslSocket && sslSocket->isEncrypted() ? QStringLiteral("https")
: QStringLiteral("http"));
#else
url.setScheme(QStringLiteral("http"));
#endif
const auto parsed = http_parser_execute(&httpParser,
&httpParserSettings,
fragment.constData(),
size_t(fragment.size()));
if (int(parsed) < fragment.size()) {
qCDebug(lc, "Parse error: %d", httpParser.http_errno);
return false;
}
}
return true;
}
uint QHttpServerRequestPrivate::headerHash(const QString &key) const
{
return qHash(key.toLower(), headersSeed);
}
void QHttpServerRequestPrivate::clear()
{
url.clear();
lastHeader.clear();
headers.clear();
body.clear();
}
bool QHttpServerRequestPrivate::parseUrl(const char *at, size_t length, bool connect, QUrl *url)
{
static const std::map<std::size_t, std::function<void(const QString &, QUrl *)>> functions {
{ UF_SCHEMA, [](const QString &string, QUrl *url) { url->setScheme(string); } },
{ UF_HOST, [](const QString &string, QUrl *url) { url->setHost(string); } },
{ UF_PORT, [](const QString &string, QUrl *url) { url->setPort(string.toInt()); } },
{ UF_PATH,
[](const QString &string, QUrl *url) { url->setPath(string, QUrl::TolerantMode); } },
{ UF_QUERY, [](const QString &string, QUrl *url) { url->setQuery(string); } },
{ UF_FRAGMENT, [](const QString &string, QUrl *url) { url->setFragment(string); } },
{ UF_USERINFO, [](const QString &string, QUrl *url) { url->setUserInfo(string); } },
};
struct http_parser_url u;
if (http_parser_parse_url(at, length, connect ? 1 : 0, &u) == 0) {
for (auto i = 0u; i < UF_MAX; i++) {
if (u.field_set & (1 << i)) {
functions.find(i)->second(QString::fromUtf8(at + u.field_data[i].off,
u.field_data[i].len),
url);
}
}
return true;
}
return false;
}
QHttpServerRequestPrivate *QHttpServerRequestPrivate::instance(http_parser *httpParser)
{
return static_cast<QHttpServerRequestPrivate *>(httpParser->data);
}
int QHttpServerRequestPrivate::onMessageBegin(http_parser *httpParser)
{
qCDebug(lc) << static_cast<void *>(httpParser);
instance(httpParser)->state = State::OnMessageBegin;
return 0;
}
int QHttpServerRequestPrivate::onUrl(http_parser *httpParser, const char *at, size_t length)
{
qCDebug(lc) << httpParser << QString::fromUtf8(at, int(length));
auto instance = static_cast<QHttpServerRequestPrivate *>(httpParser->data);
instance->state = State::OnUrl;
parseUrl(at, length, false, &instance->url);
return 0;
}
int QHttpServerRequestPrivate::onStatus(http_parser *httpParser, const char *at, size_t length)
{
qCDebug(lc) << httpParser << QString::fromUtf8(at, int(length));
instance(httpParser)->state = State::OnStatus;
return 0;
}
int QHttpServerRequestPrivate::onHeaderField(http_parser *httpParser, const char *at, size_t length)
{
qCDebug(lc) << httpParser << QString::fromUtf8(at, int(length));
auto i = instance(httpParser);
i->state = State::OnHeaders;
const auto key = QString::fromUtf8(at, int(length));
i->headers.insert(i->headerHash(key), qMakePair(key, QString()));
i->lastHeader = key;
return 0;
}
int QHttpServerRequestPrivate::onHeaderValue(http_parser *httpParser, const char *at, size_t length)
{
qCDebug(lc) << httpParser << QString::fromUtf8(at, int(length));
auto i = instance(httpParser);
i->state = State::OnHeaders;
Q_ASSERT(!i->lastHeader.isEmpty());
const auto value = QString::fromUtf8(at, int(length));
i->headers[i->headerHash(i->lastHeader)] = qMakePair(i->lastHeader, value);
if (i->lastHeader.compare(QStringLiteral("host"), Qt::CaseInsensitive) == 0)
parseUrl(at, length, true, &i->url);
#if defined(QT_DEBUG)
i->lastHeader.clear();
#endif
return 0;
}
int QHttpServerRequestPrivate::onHeadersComplete(http_parser *httpParser)
{
qCDebug(lc) << httpParser;
instance(httpParser)->state = State::OnHeadersComplete;
return 0;
}
int QHttpServerRequestPrivate::onBody(http_parser *httpParser, const char *at, size_t length)
{
qCDebug(lc) << httpParser << QString::fromUtf8(at, int(length));
auto i = instance(httpParser);
i->state = State::OnBody;
if (i->body.isEmpty()) {
i->body.reserve(
static_cast<int>(httpParser->content_length) +
static_cast<int>(length));
}
i->body.append(at, int(length));
return 0;
}
int QHttpServerRequestPrivate::onMessageComplete(http_parser *httpParser)
{
qCDebug(lc) << httpParser;
instance(httpParser)->state = State::OnMessageComplete;
return 0;
}
int QHttpServerRequestPrivate::onChunkHeader(http_parser *httpParser)
{
qCDebug(lc) << httpParser;
instance(httpParser)->state = State::OnChunkHeader;
return 0;
}
int QHttpServerRequestPrivate::onChunkComplete(http_parser *httpParser)
{
qCDebug(lc) << httpParser;
instance(httpParser)->state = State::OnChunkComplete;
return 0;
}
QHttpServerRequest::QHttpServerRequest() :
QObjectUserData(),
d(new QHttpServerRequestPrivate)
{}
QHttpServerRequest::QHttpServerRequest(const QHttpServerRequest &other) :
QObjectUserData(),
d(other.d)
{}
QHttpServerRequest::~QHttpServerRequest()
{}
QString QHttpServerRequest::value(const QString &key) const
{
return d->headers.value(d->headerHash(key)).second;
}
QUrl QHttpServerRequest::url() const
{
return d->url;
}
QUrlQuery QHttpServerRequest::query() const
{
return QUrlQuery(d->url.query());
}
QHttpServerRequest::Method QHttpServerRequest::method() const
{
switch (d->httpParser.method) {
case HTTP_GET:
return QHttpServerRequest::Method::Get;
case HTTP_PUT:
return QHttpServerRequest::Method::Put;
case HTTP_DELETE:
return QHttpServerRequest::Method::Delete;
case HTTP_POST:
return QHttpServerRequest::Method::Post;
case HTTP_HEAD:
return QHttpServerRequest::Method::Head;
case HTTP_OPTIONS:
return QHttpServerRequest::Method::Options;
case HTTP_PATCH:
return QHttpServerRequest::Method::Patch;
default:
return QHttpServerRequest::Method::Unknown;
}
}
QVariantMap QHttpServerRequest::headers() const
{
QVariantMap ret;
for (auto it = d->headers.cbegin(), end = d->headers.cend(); it != end; ++it)
ret.insert(it.value().first, it.value().second);
return ret;
}
QByteArray QHttpServerRequest::body() const
{
return d->body;
}
QT_END_NAMESPACE