summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMikhail Svetkin <mikhail.svetkin@gmail.com>2020-03-05 22:54:21 +0100
committerMikhail Svetkin <mikhail.svetkin@gmail.com>2020-06-11 21:20:57 +0200
commite49b9a111aeb50ccb3c6fc6c9c5f0bcc1781b03d (patch)
treeb7959227c6523414e375f9af31d54e5c65d74d9a
parent2a67efadf1f02671e74ada7fc84bea9009182bc7 (diff)
Introduce QHttpServerFutureResponse
Provide simple API for asynchronous resoponses Change-Id: Ic0c92cce95751dc8f9d6b0dfa96e39019f5f5e9e Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io>
-rw-r--r--src/httpserver/CMakeLists.txt7
-rw-r--r--src/httpserver/httpserver.pro6
-rw-r--r--src/httpserver/qhttpserver.h21
-rw-r--r--src/httpserver/qhttpserverfutureresponse.cpp139
-rw-r--r--src/httpserver/qhttpserverfutureresponse.h184
-rw-r--r--src/httpserver/qhttpserverresponse.cpp41
-rw-r--r--src/httpserver/qhttpserverresponse.h8
-rw-r--r--src/httpserver/qhttpserverresponse_p.h8
-rw-r--r--src/httpserver/qhttpserverrouterviewtraits.h1
-rw-r--r--tests/auto/qhttpserver/tst_qhttpserver.cpp32
10 files changed, 413 insertions, 34 deletions
diff --git a/src/httpserver/CMakeLists.txt b/src/httpserver/CMakeLists.txt
index efcce0d..a552ca2 100644
--- a/src/httpserver/CMakeLists.txt
+++ b/src/httpserver/CMakeLists.txt
@@ -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
+)
diff --git a/src/httpserver/httpserver.pro b/src/httpserver/httpserver.pro
index fd1a231..9e9d17a 100644
--- a/src/httpserver/httpserver.pro
+++ b/src/httpserver/httpserver.pro
@@ -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)
diff --git a/src/httpserver/qhttpserver.h b/src/httpserver/qhttpserver.h
index 97c5bc5..5274d0f 100644
--- a/src/httpserver/qhttpserver.h
+++ b/src/httpserver/qhttpserver.h
@@ -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,25 +169,25 @@ 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 == 2, void>::type
+ ViewTraits::Arguments::PlaceholdersCount == 1, void>::type
responseImpl(T &boundViewHandler, const QHttpServerRequest &request, QTcpSocket *socket)
{
- boundViewHandler(makeResponder(request, socket), request);
+ ResponseType<typename ViewTraits::ReturnType> response(boundViewHandler(request));
+ 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
+ ViewTraits::Arguments::PlaceholdersCount == 2, void>::type
responseImpl(T &boundViewHandler, const QHttpServerRequest &request, QTcpSocket *socket)
{
- QHttpServerResponse response(boundViewHandler(request));
- sendResponse(std::move(response), request, socket);
+ boundViewHandler(makeResponder(request, socket), request);
}
template<typename ViewTraits, typename T>
diff --git a/src/httpserver/qhttpserverfutureresponse.cpp b/src/httpserver/qhttpserverfutureresponse.cpp
new file mode 100644
index 0000000..3d2784b
--- /dev/null
+++ b/src/httpserver/qhttpserverfutureresponse.cpp
@@ -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"
diff --git a/src/httpserver/qhttpserverfutureresponse.h b/src/httpserver/qhttpserverfutureresponse.h
new file mode 100644
index 0000000..45dc376
--- /dev/null
+++ b/src/httpserver/qhttpserverfutureresponse.h
@@ -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
diff --git a/src/httpserver/qhttpserverresponse.cpp b/src/httpserver/qhttpserverresponse.cpp
index 5bd510b..47c3b49 100644
--- a/src/httpserver/qhttpserverresponse.cpp
+++ b/src/httpserver/qhttpserverresponse.cpp
@@ -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)
- : d_ptr(d)
-{
- setHeader(QHttpServerLiterals::contentTypeHeader(), mimeType);
-}
-
-QHttpServerResponse::QHttpServerResponse(QByteArray &&mimeType,
- QHttpServerResponsePrivate *d)
+QHttpServerResponse::QHttpServerResponse(QHttpServerResponsePrivate *d)
: d_ptr(d)
{
- setHeader(QHttpServerLiterals::contentTypeHeader(),
- std::move(mimeType));
+ d->derived = true;
}
/*!
diff --git a/src/httpserver/qhttpserverresponse.h b/src/httpserver/qhttpserverresponse.h
index df748cb..6d2bc1d 100644
--- a/src/httpserver/qhttpserverresponse.h
+++ b/src/httpserver/qhttpserverresponse.h
@@ -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;
};
diff --git a/src/httpserver/qhttpserverresponse_p.h b/src/httpserver/qhttpserverresponse_p.h
index 8b81ad0..5011552 100644
--- a/src/httpserver/qhttpserverresponse_p.h
+++ b/src/httpserver/qhttpserverresponse_p.h
@@ -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
diff --git a/src/httpserver/qhttpserverrouterviewtraits.h b/src/httpserver/qhttpserverrouterviewtraits.h
index 2e812a5..b572675 100644
--- a/src/httpserver/qhttpserverrouterviewtraits.h
+++ b/src/httpserver/qhttpserverrouterviewtraits.h
@@ -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(
diff --git a/tests/auto/qhttpserver/tst_qhttpserver.cpp b/tests/auto/qhttpserver/tst_qhttpserver.cpp
index c7176ff..cf5c43d 100644
--- a/tests/auto/qhttpserver/tst_qhttpserver.cpp
+++ b/tests/auto/qhttpserver/tst_qhttpserver.cpp
@@ -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")