Introduce QHttpServerFutureResponse

Provide simple API for asynchronous resoponses

Change-Id: Ic0c92cce95751dc8f9d6b0dfa96e39019f5f5e9e
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Mikhail Svetkin 2020-03-05 22:54:21 +01:00
parent 2a67efadf1
commit e49b9a111a
10 changed files with 417 additions and 38 deletions

View File

@ -47,3 +47,10 @@ qt_extend_target(HttpServer CONDITION QT_FEATURE_ssl
PUBLIC_LIBRARIES
Qt::SslServer
)
qt_extend_target(HttpServer CONDITION TARGET Qt::Concurrent
SOURCES
qhttpserverfutureresponse.cpp qhttpserverfutureresponse.h
PUBLIC_LIBRARIES
Qt::Concurrent
)

View File

@ -37,6 +37,12 @@ SOURCES += \
qhttpserverrouter.cpp \
qhttpserverrouterrule.cpp
qtHaveModule(concurrent) {
QT += concurrent
HEADERS += qhttpserverfutureresponse.h
SOURCES += qhttpserverfutureresponse.cpp
}
include(../3rdparty/http-parser.pri)
load(qt_module)

View File

@ -59,6 +59,15 @@ class Q_HTTPSERVER_EXPORT QHttpServer final : public QAbstractHttpServer
using Type = typename VariadicTypeAt<sizeof ... (Ts) - 1, Ts...>::Type;
};
template<typename T>
using ResponseType =
typename std::conditional<
std::is_base_of<QHttpServerResponse, T>::value,
T,
QHttpServerResponse
>::type;
public:
explicit QHttpServer(QObject *parent = nullptr);
~QHttpServer();
@ -160,7 +169,16 @@ private:
const QHttpServerRequest &request,
QTcpSocket *socket)
{
QHttpServerResponse response(boundViewHandler());
ResponseType<typename ViewTraits::ReturnType> response(boundViewHandler());
sendResponse(std::move(response), request, socket);
}
template<typename ViewTraits, typename T>
typename std::enable_if<ViewTraits::Arguments::Last::IsRequest::Value &&
ViewTraits::Arguments::PlaceholdersCount == 1, void>::type
responseImpl(T &boundViewHandler, const QHttpServerRequest &request, QTcpSocket *socket)
{
ResponseType<typename ViewTraits::ReturnType> response(boundViewHandler(request));
sendResponse(std::move(response), request, socket);
}
@ -172,15 +190,6 @@ private:
boundViewHandler(makeResponder(request, socket), request);
}
template<typename ViewTraits, typename T>
typename std::enable_if<ViewTraits::Arguments::Last::IsRequest::Value &&
ViewTraits::Arguments::PlaceholdersCount == 1, void>::type
responseImpl(T &boundViewHandler, const QHttpServerRequest &request, QTcpSocket *socket)
{
QHttpServerResponse response(boundViewHandler(request));
sendResponse(std::move(response), request, socket);
}
template<typename ViewTraits, typename T>
typename std::enable_if<ViewTraits::Arguments::Last::IsResponder::Value &&
ViewTraits::Arguments::PlaceholdersCount == 2, void>::type

View File

@ -0,0 +1,139 @@
/****************************************************************************
**
** Copyright (C) 2020 Mikhail Svetkin <mikhail.svetkin@gmail.com>
** 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 "qhttpserverfutureresponse.h"
#include <QtCore/qfuture.h>
#include <QtCore/qfuturewatcher.h>
#include <QtNetwork/qtcpsocket.h>
#include <QtHttpServer/qhttpserverresponder.h>
#include <private/qhttpserverresponse_p.h>
QT_BEGIN_NAMESPACE
/*!
\class QHttpServerFutureResponse
\brief QHttpServerFutureResponse is a simplified API for asynchronous responses.
\code
QHttpServer server;
server.route("/feature/", [] (int id) -> QHttpServerFutureResponse {
auto future = QtConcurrent::run([] () {
return QHttpServerResponse("the future is coming");
});
return future;
});
server.listen();
\endcode
*/
struct QResponseWatcher : public QFutureWatcher<QHttpServerResponse>
{
Q_OBJECT
public:
QResponseWatcher(QHttpServerResponder &&_responder)
: QFutureWatcher<QHttpServerResponse>(),
responder(std::move(_responder)) {
}
QHttpServerResponder responder;
};
class QHttpServerFutureResponsePrivate : public QHttpServerResponsePrivate
{
public:
QHttpServerFutureResponsePrivate(const QFuture<QHttpServerResponse> &futureResponse)
: QHttpServerResponsePrivate(),
futureResp(futureResponse)
{
}
QFuture<QHttpServerResponse> futureResp;
};
/*!
Constructs a new QHttpServerFutureResponse with the \a future response.
*/
QHttpServerFutureResponse::QHttpServerFutureResponse(const QFuture<QHttpServerResponse> &futureResp)
: QHttpServerFutureResponse(new QHttpServerFutureResponsePrivate{futureResp})
{
}
/*!
\internal
*/
QHttpServerFutureResponse::QHttpServerFutureResponse(QHttpServerFutureResponsePrivate *d)
: QHttpServerResponse(d)
{
}
/*!
\reimp
*/
void QHttpServerFutureResponse::write(QHttpServerResponder &&responder) const
{
if (!d_ptr->derived) {
QHttpServerResponse::write(std::move(responder));
return;
}
Q_D(const QHttpServerFutureResponse);
auto socket = responder.socket();
auto futureWatcher = new QResponseWatcher(std::move(responder));
QObject::connect(socket, &QObject::destroyed,
futureWatcher, &QObject::deleteLater);
QObject::connect(futureWatcher, &QFutureWatcherBase::finished,
socket,
[futureWatcher] () mutable {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
auto resp = futureWatcher->future().d.takeResult();
#else
auto resp = futureWatcher->future().takeResult();
#endif
resp.write(std::move(futureWatcher->responder));
futureWatcher->deleteLater();
});
futureWatcher->setFuture(d->futureResp);
}
QT_END_NAMESPACE
#include "qhttpserverfutureresponse.moc"

View File

@ -0,0 +1,184 @@
/****************************************************************************
**
** Copyright (C) 2020 Mikhail Svetkin <mikhail.svetkin@gmail.com>
** 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$
**
****************************************************************************/
#ifndef QHTTPSERVERFUTURERESPONSE_H
#define QHTTPSERVERFUTURERESPONSE_H
#include <QtHttpServer/qhttpserverresponse.h>
#include <QtCore/qbytearray.h>
#include <QtCore/qfuture.h>
#include <QtConcurrent>
#include <mutex>
QT_BEGIN_NAMESPACE
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
template <>
class QFutureInterface<QHttpServerResponse> : public QFutureInterfaceBase
{
public:
QFutureInterface(State initialState = NoState)
: QFutureInterfaceBase(initialState)
{
refT();
}
QFutureInterface(const QFutureInterface &other)
: QFutureInterfaceBase(other)
{
refT();
}
~QFutureInterface()
{
if (!derefT())
resultStoreBase().template clear<QHttpServerResponse>();
}
static QFutureInterface canceledResult()
{ return QFutureInterface(State(Started | Finished | Canceled)); }
QFutureInterface &operator=(const QFutureInterface &other)
{
other.refT();
if (!derefT())
resultStoreBase().template clear<QHttpServerResponse>();
QFutureInterfaceBase::operator=(other);
return *this;
}
inline QFuture<QHttpServerResponse> future()
{
return QFuture<QHttpServerResponse>(this);
}
void reportAndMoveResult(QHttpServerResponse &&result, int index = -1)
{
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
std::lock_guard<QMutex> locker{*mutex()};
#else
std::lock_guard<QMutex> locker{mutex(0)};
#endif
if (queryState(Canceled) || queryState(Finished))
return;
QtPrivate::ResultStoreBase &store = resultStoreBase();
const int oldResultCount = store.count();
const int insertIndex = store.addResult(
index, static_cast<void *>(new QHttpServerResponse(std::move_if_noexcept(result))));
if (!store.filterMode() || oldResultCount < store.count()) // Let's make sure it's not in pending results.
reportResultsReady(insertIndex, store.count());
}
void reportFinished()
{
QFutureInterfaceBase::reportFinished();
}
QHttpServerResponse takeResult()
{
if (isCanceled()) {
exceptionStore().throwPossibleException();
return QHttpServerResponse::StatusCode::NotFound;
}
// Note: we wait for all, this is intentional,
// not to mess with other unready results.
waitForResult(-1);
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
std::lock_guard<QMutex> locker{*mutex()};
#else
std::lock_guard<QMutex> locker{mutex(0)};
#endif
QtPrivate::ResultIteratorBase position = resultStoreBase().resultAt(0);
auto ret = std::move_if_noexcept(
*const_cast<QHttpServerResponse *>(position.pointer<QHttpServerResponse>()));
resultStoreBase().template clear<QHttpServerResponse>();
return ret;
}
};
#endif // QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
namespace QtConcurrent {
template <>
class RunFunctionTask<QHttpServerResponse> : public RunFunctionTaskBase<QHttpServerResponse>
{
public:
void run() override
{
if (this->isCanceled()) {
this->reportFinished();
return;
}
#ifndef QT_NO_EXCEPTIONS
try {
#endif
this->runFunctor();
#ifndef QT_NO_EXCEPTIONS
} catch (QException &e) {
QFutureInterface<QHttpServerResponse>::reportException(e);
} catch (...) {
QFutureInterface<QHttpServerResponse>::reportException(QUnhandledException());
}
#endif
this->reportAndMoveResult(std::move_if_noexcept(result));
this->reportFinished();
}
QHttpServerResponse result{QHttpServerResponse::StatusCode::NotFound};
};
}
class QHttpServerFutureResponsePrivate;
class Q_HTTPSERVER_EXPORT QHttpServerFutureResponse : public QHttpServerResponse
{
Q_DECLARE_PRIVATE(QHttpServerFutureResponse)
public:
using QHttpServerResponse::QHttpServerResponse;
QHttpServerFutureResponse(const QFuture<QHttpServerResponse> &futureResponse);
virtual void write(QHttpServerResponder &&responder) const override;
protected:
QHttpServerFutureResponse(QHttpServerFutureResponsePrivate *d);
};
QT_END_NAMESPACE
#endif // QHTTPSERVERFUTURERESPONSE_H

View File

@ -41,6 +41,16 @@
QT_BEGIN_NAMESPACE
QHttpServerResponsePrivate::QHttpServerResponsePrivate(
QByteArray &&d, const QHttpServerResponse::StatusCode sc)
: data(std::move(d)),
statusCode(sc)
{ }
QHttpServerResponsePrivate::QHttpServerResponsePrivate(const QHttpServerResponse::StatusCode sc)
: statusCode(sc)
{ }
QHttpServerResponse::QHttpServerResponse(QHttpServerResponse &&other) noexcept
: d_ptr(other.d_ptr.take())
{
@ -100,35 +110,33 @@ QHttpServerResponse::QHttpServerResponse(const QJsonArray &data)
QHttpServerResponse::QHttpServerResponse(const QByteArray &mimeType,
const QByteArray &data,
const StatusCode status)
: QHttpServerResponse(mimeType,
new QHttpServerResponsePrivate{data, status, {}})
: d_ptr(new QHttpServerResponsePrivate(QByteArray(data), status))
{
setHeader(QHttpServerLiterals::contentTypeHeader(), mimeType);
}
QHttpServerResponse::QHttpServerResponse(QByteArray &&mimeType,
const QByteArray &data,
const StatusCode status)
: QHttpServerResponse(std::move(mimeType),
new QHttpServerResponsePrivate{data, status, {}})
: d_ptr(new QHttpServerResponsePrivate(QByteArray(data), status))
{
setHeader(QHttpServerLiterals::contentTypeHeader(), std::move(mimeType));
}
QHttpServerResponse::QHttpServerResponse(const QByteArray &mimeType,
QByteArray &&data,
const StatusCode status)
: QHttpServerResponse(
mimeType,
new QHttpServerResponsePrivate{std::move(data), status, {}})
: d_ptr(new QHttpServerResponsePrivate(std::move(data), status))
{
setHeader(QHttpServerLiterals::contentTypeHeader(), mimeType);
}
QHttpServerResponse::QHttpServerResponse(QByteArray &&mimeType,
QByteArray &&data,
const StatusCode status)
: QHttpServerResponse(
std::move(mimeType),
new QHttpServerResponsePrivate{std::move(data), status, {}})
: d_ptr(new QHttpServerResponsePrivate(std::move(data), status))
{
setHeader(QHttpServerLiterals::contentTypeHeader(), std::move(mimeType));
}
QHttpServerResponse::~QHttpServerResponse()
@ -146,19 +154,10 @@ QHttpServerResponse QHttpServerResponse::fromFile(const QString &fileName)
return QHttpServerResponse(mimeType, data);
}
QHttpServerResponse::QHttpServerResponse(const QByteArray &mimeType,
QHttpServerResponsePrivate *d)
QHttpServerResponse::QHttpServerResponse(QHttpServerResponsePrivate *d)
: d_ptr(d)
{
setHeader(QHttpServerLiterals::contentTypeHeader(), mimeType);
}
QHttpServerResponse::QHttpServerResponse(QByteArray &&mimeType,
QHttpServerResponsePrivate *d)
: d_ptr(d)
{
setHeader(QHttpServerLiterals::contentTypeHeader(),
std::move(mimeType));
d->derived = true;
}
/*!

View File

@ -118,12 +118,8 @@ public:
virtual void write(QHttpServerResponder &&responder) const;
private:
QHttpServerResponse(const QByteArray &mimeType,
QHttpServerResponsePrivate *d);
QHttpServerResponse(QByteArray &&mimeType,
QHttpServerResponsePrivate *d);
protected:
QHttpServerResponse(QHttpServerResponsePrivate *d);
QScopedPointer<QHttpServerResponsePrivate> d_ptr;
};

View File

@ -59,10 +59,16 @@ class QHttpServerResponsePrivate
};
public:
explicit QHttpServerResponsePrivate() = default;
virtual ~QHttpServerResponsePrivate() = default;
QHttpServerResponsePrivate(QByteArray &&d, const QHttpServerResponse::StatusCode sc);
QHttpServerResponsePrivate(const QHttpServerResponse::StatusCode sc);
QByteArray data;
QHttpServerResponse::StatusCode statusCode;
std::unordered_multimap<QByteArray, QByteArray, HashHelper> headers;
bool derived{false};
};
QT_END_NAMESPACE

View File

@ -151,6 +151,7 @@ template <typename ViewHandler, bool DisableStaticAssert = false>
struct QHttpServerRouterViewTraits
{
using Helpers = typename QtPrivate::RouterViewTraitsHelper<ViewHandler, DisableStaticAssert>;
using ReturnType = typename Helpers::FunctionTraits::ReturnType;
using Arguments = decltype(Helpers::Arguments::eval(typename Helpers::ArgumentIndexes{}));
using BindableType = decltype(
Helpers::template BindType<Arguments::CapturableCount>::eval(

View File

@ -31,6 +31,10 @@
#include <QtHttpServer/qhttpserverrequest.h>
#include <QtHttpServer/qhttpserverrouterrule.h>
#if QT_CONFIG(concurrent)
# include <QtHttpServer/qhttpserverfutureresponse.h>
#endif
#include <private/qhttpserverrouterrule_p.h>
#include <private/qhttpserverliterals_p.h>
@ -296,6 +300,20 @@ void tst_QHttpServer::initTestCase()
return std::move(resp);
});
#if QT_CONFIG(concurrent)
httpserver.route("/future/", [] (int id) -> QHttpServerFutureResponse {
if (id == 0)
return QHttpServerResponse::StatusCode::NotFound;
auto future = QtConcurrent::run([] () {
QTest::qSleep(500);
return QHttpServerResponse("future is coming");
});
return future;
});
#endif
quint16 port = httpserver.listen();
if (!port)
qCritical() << "Http server listen failed";
@ -526,6 +544,20 @@ void tst_QHttpServer::routeGet_data()
<< "text/plain"
<< "part 1 of the message, part 2 of the message";
#if QT_CONFIG(concurrent)
QTest::addRow("future")
<< urlBase.arg("/future/1")
<< 200
<< "text/plain"
<< "future is coming";
QTest::addRow("future-not-found")
<< urlBase.arg("/future/0")
<< 404
<< "application/x-empty"
<< "";
#endif
#if QT_CONFIG(ssl)
QTest::addRow("hello world, ssl")