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:
parent
9856170359
commit
f53818c8ef
@ -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)
|
||||
|
392
src/httpserver/qhttpserverresponder.cpp
Normal file
392
src/httpserver/qhttpserverresponder.cpp
Normal file
@ -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
|
179
src/httpserver/qhttpserverresponder.h
Normal file
179
src/httpserver/qhttpserverresponder.h
Normal file
@ -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
|
114
src/httpserver/qhttpserverresponder_p.h
Normal file
114
src/httpserver/qhttpserverresponder_p.h
Normal file
@ -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
|
||||
|
5
tests/auto/qhttpserverresponder/qhttpserverresponder.pro
Normal file
5
tests/auto/qhttpserverresponder/qhttpserverresponder.pro
Normal file
@ -0,0 +1,5 @@
|
||||
CONFIG += testcase
|
||||
TARGET = tst_qhttpserverresponder
|
||||
SOURCES += tst_qhttpserverresponder.cpp
|
||||
|
||||
QT = httpserver testlib
|
159
tests/auto/qhttpserverresponder/tst_qhttpserverresponder.cpp
Normal file
159
tests/auto/qhttpserverresponder/tst_qhttpserverresponder.cpp
Normal file
@ -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"
|
Loading…
x
Reference in New Issue
Block a user