4
0
mirror of https://github.com/QuasarApp/qthttpserver.git synced 2025-05-10 00:19:46 +00:00

Introduce QHttpServerResponder

It encapsulates the socket and gives an API to answer the received
requests.

Change-Id: Ic95db2c50224a650a02b206faca9a0ff8d1cc62b
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
Reviewed-by: Ryan Chu <ryan.chu@qt.io>
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
Jesus Fernandez 2018-03-13 15:20:47 +01:00 committed by Jesus Fernandez
parent 9856170359
commit f53818c8ef
9 changed files with 865 additions and 2 deletions

@ -9,11 +9,14 @@ HEADERS += \
qthttpserverglobal.h \
qabstracthttpserver.h \
qabstracthttpserver_p.h \
qhttpserverresponder.h \
qhttpserverresponder_p.h \
qhttpserverrequest.h \
qhttpserverrequest_p.h
SOURCES += \
qabstracthttpserver.cpp \
qhttpserverresponder.cpp \
qhttpserverrequest.cpp
include(../3rdparty/http-parser.pri)

@ -38,6 +38,7 @@
#include <QtHttpServer/qabstracthttpserver.h>
#include <QtHttpServer/qhttpserverrequest.h>
#include <QtHttpServer/qhttpserverresponder.h>
#include <private/qabstracthttpserver_p.h>
#include <private/qhttpserverrequest_p.h>
@ -89,6 +90,7 @@ void QAbstractHttpServerPrivate::handleReadyRead()
{
Q_Q(QAbstractHttpServer);
auto socket = qobject_cast<QTcpSocket *>(currentSender->sender);
Q_ASSERT(socket);
#if !defined(QT_NO_USERDATA)
auto request = static_cast<QHttpServerRequest *>(socket->userData(uint(userDataId)));
#else
@ -271,4 +273,10 @@ QWebSocket *QAbstractHttpServer::nextPendingWebSocketConnection()
}
#endif
QHttpServerResponder QAbstractHttpServer::makeResponder(const QHttpServerRequest &request,
QTcpSocket *socket)
{
return QHttpServerResponder(request, socket);
}
QT_END_NAMESPACE

@ -49,6 +49,7 @@
QT_BEGIN_NAMESPACE
class QHttpServerRequest;
class QHttpServerResponder;
class QTcpServer;
class QTcpSocket;
class QWebSocket;
@ -81,6 +82,8 @@ protected:
QAbstractHttpServer(QAbstractHttpServerPrivate &dd, QObject *parent = nullptr);
virtual bool handleRequest(const QHttpServerRequest &request, QTcpSocket *socket) = 0;
static QHttpServerResponder makeResponder(const QHttpServerRequest &request,
QTcpSocket *socket);
private:
Q_DECLARE_PRIVATE(QAbstractHttpServer)

@ -0,0 +1,392 @@
/****************************************************************************
**
** Copyright (C) 2018 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:LGPL$
** 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 Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or 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.GPL2 and 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-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtHttpServer/qhttpserverresponder.h>
#include <QtHttpServer/qhttpserverrequest.h>
#include <private/qhttpserverresponder_p.h>
#include <private/qhttpserverrequest_p.h>
#include <QtCore/qjsondocument.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qtimer.h>
#include <QtNetwork/qtcpsocket.h>
#if defined(QT_WEBSOCKETS_LIB)
#include <QtWebSockets/qwebsocket.h>
#include <private/qwebsocket_p.h>
#include <private/qwebsockethandshakeresponse_p.h>
#include <private/qwebsockethandshakerequest_p.h>
#endif
#include <map>
#include <memory>
QT_BEGIN_NAMESPACE
static const QLoggingCategory &lc()
{
static const QLoggingCategory category("qt.httpserver.response");
return category;
}
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
static const std::map<QHttpServerResponder::StatusCode, QByteArray> statusString {
#define STATUS_CODE(CODE, TEXT) { QHttpServerResponder::StatusCode::CODE, QByteArrayLiteral(TEXT) }
STATUS_CODE(Continue, "Continue"),
STATUS_CODE(SwitchingProtocols, "Switching Protocols"),
STATUS_CODE(Processing, "Processing"),
STATUS_CODE(Ok, "OK"),
STATUS_CODE(Created, "Created"),
STATUS_CODE(Accepted, "Accepted"),
STATUS_CODE(NonAuthoritativeInformation, "Non-Authoritative Information"),
STATUS_CODE(NoContent, "No Content"),
STATUS_CODE(ResetContent, "Reset Content"),
STATUS_CODE(PartialContent, "Partial Content"),
STATUS_CODE(MultiStatus, "Multi Status"),
STATUS_CODE(AlreadyReported, "Already Reported"),
STATUS_CODE(IMUsed, "IM Used"),
STATUS_CODE(MultipleChoices, "Multiple Choices"),
STATUS_CODE(MovedPermanently, "Moved Permanently"),
STATUS_CODE(Found, "Found"),
STATUS_CODE(SeeOther, "See Other"),
STATUS_CODE(NotModified, "Not Modified"),
STATUS_CODE(UseProxy, "Use Proxy"),
STATUS_CODE(TemporaryRedirect, "Temporary Redirect"),
STATUS_CODE(PermanentRedirect, "Permanent Redirect"),
STATUS_CODE(BadRequest, "Bad Request"),
STATUS_CODE(Unauthorized, "Unauthorized"),
STATUS_CODE(PaymentRequired, "Payment Required"),
STATUS_CODE(Forbidden, "Forbidden"),
STATUS_CODE(NotFound, "Not Found"),
STATUS_CODE(MethodNotAllowed, "Method Not Allowed"),
STATUS_CODE(NotAcceptable, "Not Acceptable"),
STATUS_CODE(ProxyAuthenticationRequired, "Proxy Authentication Required"),
STATUS_CODE(RequestTimeout, "Request Time-out"),
STATUS_CODE(Conflict, "Conflict"),
STATUS_CODE(Gone, "Gone"),
STATUS_CODE(LengthRequired, "Length Required"),
STATUS_CODE(PreconditionFailed, "Precondition Failed"),
STATUS_CODE(PayloadTooLarge, "Payload Too Large"),
STATUS_CODE(UriTooLong, "URI Too Long"),
STATUS_CODE(UnsupportedMediaType, "Unsupported Media Type"),
STATUS_CODE(RequestRangeNotSatisfiable, "Request Range Not Satisfiable"),
STATUS_CODE(ExpectationFailed, "Expectation Failed"),
STATUS_CODE(ImATeapot, "I'm A Teapot"),
STATUS_CODE(MisdirectedRequest, "Misdirected Request"),
STATUS_CODE(UnprocessableEntity, "Unprocessable Entity"),
STATUS_CODE(Locked, "Locked"),
STATUS_CODE(FailedDependency, "Failed Dependency"),
STATUS_CODE(UpgradeRequired, "Upgrade Required"),
STATUS_CODE(PreconditionRequired, "Precondition Required"),
STATUS_CODE(TooManyRequests, "Too Many Requests"),
STATUS_CODE(RequestHeaderFieldsTooLarge, "Request Header Fields Too Large"),
STATUS_CODE(UnavailableForLegalReasons, "Unavailable For Legal Reasons"),
STATUS_CODE(InternalServerError, "Internal Server Error"),
STATUS_CODE(NotImplemented, "Not Implemented"),
STATUS_CODE(BadGateway, "Bad Gateway"),
STATUS_CODE(ServiceUnavailable, "Service Unavailable"),
STATUS_CODE(GatewayTimeout, "Gateway Time-out"),
STATUS_CODE(HttpVersionNotSupported, "HTTP Version not supported"),
STATUS_CODE(VariantAlsoNegotiates, "Variant Also Negotiates"),
STATUS_CODE(InsufficientStorage, "Insufficient Storage"),
STATUS_CODE(LoopDetected, "Loop Detected"),
STATUS_CODE(NotExtended, "Not Extended"),
STATUS_CODE(NetworkAuthenticationRequired, "Network Authentication Required"),
STATUS_CODE(NetworkConnectTimeoutError, "Network Connect Timeout Error"),
#undef STATUS_CODE
};
static const QByteArray contentTypeString(QByteArrayLiteral("Content-Type"));
static const QByteArray contentLengthString(QByteArrayLiteral("Content-Length"));
template <qint64 BUFFERSIZE = 512>
struct IOChunkedTransfer
{
// TODO This is not the fastest implementation, as it does read & write
// in a sequential fashion, but these operation could potentially overlap.
// TODO Can we implement it without the buffer? Direct write to the target buffer
// would be great.
const qint64 bufferSize = BUFFERSIZE;
char buffer[BUFFERSIZE];
qint64 beginIndex = -1;
qint64 endIndex = -1;
QScopedPointer<QIODevice, QScopedPointerDeleteLater> source;
const QPointer<QIODevice> sink;
const QMetaObject::Connection bytesWrittenConnection;
const QMetaObject::Connection readyReadConnection;
IOChunkedTransfer(QIODevice *input, QIODevice *output) :
source(input),
sink(output),
bytesWrittenConnection(QObject::connect(sink, &QIODevice::bytesWritten, [this] () {
writeToOutput();
})),
readyReadConnection(QObject::connect(source.get(), &QIODevice::readyRead, [this] () {
readFromInput();
}))
{
Q_ASSERT(!source->atEnd()); // TODO error out
readFromInput();
}
~IOChunkedTransfer()
{
QObject::disconnect(bytesWrittenConnection);
QObject::disconnect(readyReadConnection);
}
inline bool isBufferEmpty()
{
Q_ASSERT(beginIndex <= endIndex);
return beginIndex == endIndex;
}
void readFromInput()
{
if (!isBufferEmpty()) // We haven't consumed all the data yet.
return;
beginIndex = 0;
endIndex = source->read(buffer, bufferSize);
if (endIndex < 0) {
endIndex = beginIndex; // Mark the buffer as empty
qCWarning(lc, "Error reading chunk: %s", qPrintable(source->errorString()));
return;
} else if (endIndex) {
memset(buffer + endIndex, 0, sizeof(buffer) - std::size_t(endIndex));
writeToOutput();
}
}
void writeToOutput()
{
if (isBufferEmpty())
return;
const auto writtenBytes = sink->write(buffer + beginIndex, endIndex);
if (writtenBytes < 0) {
qCWarning(lc, "Error writing chunk: %s", qPrintable(sink->errorString()));
return;
}
beginIndex += writtenBytes;
if (isBufferEmpty()) {
if (source->bytesAvailable())
QTimer::singleShot(0, source.get(), [this]() { readFromInput(); });
else if (source->atEnd()) // Finishing
source.reset();
}
}
};
/*!
Constructs a QHttpServerResponder using the request \a request
and the socket \a socket.
*/
QHttpServerResponder::QHttpServerResponder(const QHttpServerRequest &request,
QTcpSocket *socket) :
d_ptr(new QHttpServerResponderPrivate(request, socket))
{
Q_ASSERT(socket);
}
/*!
Move-constructs a QHttpServerResponder instance, making it point
at the same object that \a other was pointing to.
*/
QHttpServerResponder::QHttpServerResponder(QHttpServerResponder &&other) :
d_ptr(other.d_ptr.take())
{}
/*!
Destroys a QHttpServerResponder.
*/
QHttpServerResponder::~QHttpServerResponder()
{}
/*!
Answers a request with an HTTP status code \a status and a
MIME type \a mimeType. The I/O device \a data provides the body
of the response. If \a data is sequential, the body of the
message is sent in chunks: otherwise, the function assumes all
the content is available and sends it all at once but the read
is done in chunks.
\note This function takes the ownership of \a data.
*/
void QHttpServerResponder::write(QIODevice *data,
const QByteArray &mimeType,
StatusCode status)
{
Q_D(QHttpServerResponder);
Q_ASSERT(d->socket);
QScopedPointer<QIODevice, QScopedPointerDeleteLater> input(data);
auto socket = d->socket;
QObject::connect(input.get(), &QIODevice::aboutToClose, [&input](){ input.reset(); });
// TODO protect keep alive sockets
QObject::connect(input.get(), &QObject::destroyed, socket, &QObject::deleteLater);
QObject::connect(socket, &QObject::destroyed, [&input](){ input.reset(); });
input->setParent(nullptr);
auto openMode = input->openMode();
if (!(openMode & QIODevice::ReadOnly)) {
if (openMode == QIODevice::NotOpen) {
if (!input->open(QIODevice::ReadOnly)) {
// TODO Add developer error handling
// TODO Send 500
qCDebug(lc, "500: Could not open device %s", qPrintable(input->errorString()));
return;
}
} else {
// TODO Handle that and send 500, the device is opened but not for reading.
// That doesn't make sense
qCDebug(lc) << "500: Device is opened in a wrong mode" << openMode
<< qPrintable(input->errorString());
return;
}
}
if (!socket->isOpen()) {
qCWarning(lc, "Cannot write to socket. It's disconnected");
delete socket;
return;
}
d->writeStatusLine(status);
if (!input->isSequential()) // Non-sequential QIODevice should know its data size
d->addHeader(contentLengthString, QByteArray::number(input->size()));
d->addHeader(contentTypeString, mimeType);
d->writeHeaders();
socket->write("\r\n");
if (input->atEnd()) {
qCDebug(lc, "No more data available.");
return;
}
auto transfer = new IOChunkedTransfer<>(input.take(), socket);
QObject::connect(transfer->source.get(), &QObject::destroyed, [transfer]() {
delete transfer;
});
}
/*!
Answers a request with an HTTP status code \a status, a
MIME type \a mimeType and a body \a data.
*/
void QHttpServerResponder::write(const QByteArray &data,
const QByteArray &mimeType,
StatusCode status)
{
Q_D(QHttpServerResponder);
d->writeStatusLine(status);
addHeaders(contentTypeString, mimeType,
contentLengthString, QByteArray::number(data.size()));
d->writeHeaders();
d->writeBody(data);
}
/*!
Answers a request with an HTTP status code \a status, and JSON
document \a document.
*/
void QHttpServerResponder::write(const QJsonDocument &document, StatusCode status)
{
write(document.toJson(), QByteArrayLiteral("text/json"), status);
}
/*!
Answers a request with an HTTP status code \a status.
*/
void QHttpServerResponder::write(StatusCode status)
{
write(QByteArray(), QByteArrayLiteral("application/x-empty"), status);
}
/*!
Returns the socket used.
*/
QTcpSocket *QHttpServerResponder::socket() const
{
Q_D(const QHttpServerResponder);
return d->socket;
}
bool QHttpServerResponder::addHeader(const QByteArray &key, const QByteArray &value)
{
Q_D(QHttpServerResponder);
return d->addHeader(key, value);
}
void QHttpServerResponderPrivate::writeStatusLine(StatusCode status,
const QPair<quint8, quint8> &version) const
{
Q_ASSERT(socket->isOpen());
socket->write("HTTP/");
socket->write(QByteArray::number(version.first));
socket->write(".");
socket->write(QByteArray::number(version.second));
socket->write(" ");
socket->write(QByteArray::number(quint32(status)));
socket->write(" ");
socket->write(statusString.at(status));
socket->write("\r\n");
}
void QHttpServerResponderPrivate::writeHeader(const QByteArray &header,
const QByteArray &value) const
{
socket->write(header);
socket->write(": ");
socket->write(value);
socket->write("\r\n");
}
void QHttpServerResponderPrivate::writeHeaders() const
{
for (const auto &pair : qAsConst(headers()))
writeHeader(pair.first, pair.second);
}
void QHttpServerResponderPrivate::writeBody(const QByteArray &body) const
{
Q_ASSERT(socket->isOpen());
socket->write("\r\n");
socket->write(body);
}
QT_END_NAMESPACE

@ -0,0 +1,179 @@
/****************************************************************************
**
** Copyright (C) 2018 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:LGPL$
** 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 Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or 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.GPL2 and 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-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QHTTPSERVERRESPONDER_H
#define QHTTPSERVERRESPONDER_H
#include <QtHttpServer/qthttpserverglobal.h>
#include <QtCore/qdebug.h>
#include <QtCore/qglobal.h>
#include <QtCore/qstring.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qmimetype.h>
QT_BEGIN_NAMESPACE
class QTcpSocket;
class QHttpServerRequest;
class QWebSocket;
class QHttpServerResponderPrivate;
class Q_HTTPSERVER_EXPORT QHttpServerResponder final
{
Q_DECLARE_PRIVATE(QHttpServerResponder)
friend class QAbstractHttpServer;
public:
enum class StatusCode {
// 1xx: Informational
Continue = 100,
SwitchingProtocols,
Processing,
// 2xx: Success
Ok = 200,
Created,
Accepted,
NonAuthoritativeInformation,
NoContent,
ResetContent,
PartialContent,
MultiStatus,
AlreadyReported,
IMUsed = 226,
// 3xx: Redirection
MultipleChoices = 300,
MovedPermanently,
Found,
SeeOther,
NotModified,
UseProxy,
// 306: not used, was proposed as "Switch Proxy" but never standardized
TemporaryRedirect = 307,
PermanentRedirect,
// 4xx: Client Error
BadRequest = 400,
Unauthorized,
PaymentRequired,
Forbidden,
NotFound,
MethodNotAllowed,
NotAcceptable,
ProxyAuthenticationRequired,
RequestTimeout,
Conflict,
Gone,
LengthRequired,
PreconditionFailed,
PayloadTooLarge,
UriTooLong,
UnsupportedMediaType,
RequestRangeNotSatisfiable,
ExpectationFailed,
ImATeapot,
MisdirectedRequest = 421,
UnprocessableEntity,
Locked,
FailedDependency,
UpgradeRequired = 426,
PreconditionRequired = 428,
TooManyRequests,
RequestHeaderFieldsTooLarge = 431,
UnavailableForLegalReasons = 451,
// 5xx: Server Error
InternalServerError = 500,
NotImplemented,
BadGateway,
ServiceUnavailable,
GatewayTimeout,
HttpVersionNotSupported,
VariantAlsoNegotiates,
InsufficientStorage,
LoopDetected,
NotExtended = 510,
NetworkAuthenticationRequired,
NetworkConnectTimeoutError = 599,
};
QHttpServerResponder(QHttpServerResponder &&other);
~QHttpServerResponder();
void write(QIODevice *data, const QByteArray &mimeType, StatusCode status = StatusCode::Ok);
void write(const QByteArray &data,
const QByteArray &mimeType,
StatusCode status = StatusCode::Ok);
void write(const QJsonDocument &document, StatusCode status = StatusCode::Ok);
void write(StatusCode status = StatusCode::Ok);
QTcpSocket *socket() const;
bool addHeader(const QByteArray &key, const QByteArray &value);
template <typename... Args>
inline void addHeaders(const QPair<QByteArray, QByteArray> &first, Args &&... others)
{
addHeader(first.first, first.second);
addHeaders(std::forward<Args>(others)...);
}
template <typename... Args>
inline void addHeaders(const QByteArray &key, const QByteArray &value, Args &&... others)
{
addHeader(key, value);
addHeaders(std::forward<Args>(others)...);
}
private:
QHttpServerResponder(const QHttpServerRequest &request, QTcpSocket *socket);
inline void addHeaders() {}
QScopedPointer<QHttpServerResponderPrivate> d_ptr;
};
Q_DECLARE_METATYPE(QHttpServerResponder::StatusCode)
QT_END_NAMESPACE
#endif // QHTTPSERVERRESPONDER_H

@ -0,0 +1,114 @@
/****************************************************************************
**
** Copyright (C) 2018 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:LGPL$
** 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 Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or 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.GPL2 and 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-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QHTTPSERVERRESPONDER_P_H
#define QHTTPSERVERRESPONDER_P_H
#include <QtHttpServer/qthttpserverglobal.h>
#include <QtHttpServer/qhttpserverrequest.h>
#include <QtHttpServer/qhttpserverresponder.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qpair.h>
#include <QtCore/qpointer.h>
#include <QtCore/qsysinfo.h>
#include <type_traits>
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists for the convenience
// of QHttpServer. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
QT_BEGIN_NAMESPACE
class QHttpServerResponderPrivate
{
using StatusCode = QHttpServerResponder::StatusCode;
public:
QHttpServerResponderPrivate(const QHttpServerRequest &request, QTcpSocket *const socket) :
request(request),
socket(socket)
{
const auto server = QStringLiteral("%1/%2(%3)")
.arg(QCoreApplication::instance()->applicationName())
.arg(QCoreApplication::instance()->applicationVersion())
.arg(QSysInfo::prettyProductName());
addHeader(QByteArrayLiteral("Server"), server.toUtf8());
}
inline bool addHeader(const QByteArray &key, const QByteArray &value)
{
const auto hash = qHash(key.toLower());
if (m_headers.contains(hash))
return false;
m_headers.insert(hash, qMakePair(key, value));
return true;
}
void writeStatusLine(StatusCode status = StatusCode::Ok,
const QPair<quint8, quint8> &version = qMakePair(1u, 1u)) const;
void writeHeaders() const;
void writeBody(const QByteArray &body) const;
const QHttpServerRequest &request;
#if defined(QT_DEBUG)
const QPointer<QTcpSocket> socket;
#else
QTcpSocket *const socket;
#endif
QMap<uint, QPair<QByteArray, QByteArray>> m_headers;
private:
void writeHeader(const QByteArray &header, const QByteArray &value) const;
public:
const decltype(m_headers) &headers() const { return m_headers; }
};
QT_END_NAMESPACE
#endif // QHTTPSERVERRESPONDER_P_H

@ -2,5 +2,5 @@ TEMPLATE = subdirs
SUBDIRS = \
cmake \
qabstracthttpserver
qabstracthttpserver \
qhttpserverresponder

@ -0,0 +1,5 @@
CONFIG += testcase
TARGET = tst_qhttpserverresponder
SOURCES += tst_qhttpserverresponder.cpp
QT = httpserver testlib

@ -0,0 +1,159 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** $QT_BEGIN_LICENSE:LGPL$
** 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 Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or 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.GPL2 and 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-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtHttpServer/qhttpserverresponder.h>
#include <QtHttpServer/qabstracthttpserver.h>
#include <QtCore/qjsondocument.h>
#include <QtTest/qsignalspy.h>
#include <QtTest/qtest.h>
#include <QtNetwork/qnetworkaccessmanager.h>
#include <functional>
QT_BEGIN_NAMESPACE
class tst_QHttpServerResponder : public QObject
{
Q_OBJECT
std::unique_ptr<QNetworkAccessManager> networkAccessManager;
private slots:
void init() { networkAccessManager.reset(new QNetworkAccessManager); }
void cleanup() { networkAccessManager.reset(); }
void defaultStatusCodeNoParameters();
void defaultStatusCodeByteArray();
void defaultStatusCodeJson();
void writeStatusCode_data();
void writeStatusCode();
void writeJson();
};
#define qWaitForFinished(REPLY) QVERIFY(QSignalSpy(REPLY, &QNetworkReply::finished).wait())
struct HttpServer : QAbstractHttpServer {
std::function<void(QHttpServerResponder responder)> handleRequestFunction;
QUrl url { QStringLiteral("http://localhost:%1").arg(listen()) };
HttpServer(decltype(handleRequestFunction) function) : handleRequestFunction(function) {}
bool handleRequest(const QHttpServerRequest &request, QTcpSocket *socket) override;
};
bool HttpServer::handleRequest(const QHttpServerRequest &request, QTcpSocket *socket)
{
handleRequestFunction(makeResponder(request, socket));
return true;
}
void tst_QHttpServerResponder::defaultStatusCodeNoParameters()
{
HttpServer server([](QHttpServerResponder responder) { responder.write(); });
auto reply = networkAccessManager->get(QNetworkRequest(server.url));
qWaitForFinished(reply);
QCOMPARE(reply->error(), QNetworkReply::NoError);
}
void tst_QHttpServerResponder::defaultStatusCodeByteArray()
{
HttpServer server([](QHttpServerResponder responder) {
responder.write(QByteArray(), QByteArrayLiteral("application/x-empty"));
});
auto reply = networkAccessManager->get(QNetworkRequest(server.url));
qWaitForFinished(reply);
QCOMPARE(reply->error(), QNetworkReply::NoError);
}
void tst_QHttpServerResponder::defaultStatusCodeJson()
{
const auto json = QJsonDocument::fromJson(QByteArrayLiteral("{}"));
HttpServer server([json](QHttpServerResponder responder) { responder.write(json); });
auto reply = networkAccessManager->get(QNetworkRequest(server.url));
qWaitForFinished(reply);
QCOMPARE(reply->error(), QNetworkReply::NoError);
}
void tst_QHttpServerResponder::writeStatusCode_data()
{
using StatusCode = QHttpServerResponder::StatusCode;
QTest::addColumn<QHttpServerResponder::StatusCode>("statusCode");
QTest::addColumn<QNetworkReply::NetworkError>("networkError");
QTest::addRow("OK") << StatusCode::Ok << QNetworkReply::NoError;
QTest::addRow("Content Access Denied") << StatusCode::Forbidden
<< QNetworkReply::ContentAccessDenied;
QTest::addRow("Connection Refused") << StatusCode::NotFound
<< QNetworkReply::ContentNotFoundError;
}
void tst_QHttpServerResponder::writeStatusCode()
{
QFETCH(QHttpServerResponder::StatusCode, statusCode);
QFETCH(QNetworkReply::NetworkError, networkError);
HttpServer server([statusCode](QHttpServerResponder responder) {
responder.write(statusCode);
});
auto reply = networkAccessManager->get(QNetworkRequest(server.url));
qWaitForFinished(reply);
QCOMPARE(reply->bytesAvailable(), 0);
QCOMPARE(reply->error(), networkError);
QCOMPARE(reply->header(QNetworkRequest::ContentTypeHeader),
QByteArrayLiteral("application/x-empty"));
QCOMPARE(reply->header(QNetworkRequest::ServerHeader), QStringLiteral("%1/%2(%3)")
.arg(QCoreApplication::instance()->applicationName())
.arg(QCoreApplication::instance()->applicationVersion())
.arg(QSysInfo::prettyProductName()).toUtf8());
}
void tst_QHttpServerResponder::writeJson()
{
const auto json = QJsonDocument::fromJson(QByteArrayLiteral(R"JSON({ "key" : "value" })JSON"));
HttpServer server([json](QHttpServerResponder responder) { responder.write(json); });
auto reply = networkAccessManager->get(QNetworkRequest(server.url));
qWaitForFinished(reply);
QCOMPARE(reply->error(), QNetworkReply::NoError);
QCOMPARE(reply->header(QNetworkRequest::ContentTypeHeader), QByteArrayLiteral("text/json"));
QCOMPARE(QJsonDocument::fromJson(reply->readAll()), json);
}
QT_END_NAMESPACE
QTEST_MAIN(tst_QHttpServerResponder)
#include "tst_qhttpserverresponder.moc"