diff options
author | Mikhail Svetkin <mikhail.svetkin@gmail.com> | 2020-04-29 22:06:00 +0200 |
---|---|---|
committer | Mikhail Svetkin <mikhail.svetkin@gmail.com> | 2020-05-14 20:36:31 +0200 |
commit | e0fa37441136aa1ca826b396aeb97726b2c04ade (patch) | |
tree | 69e3f4d8214b35fd97f8a3ea0b7d71fae4bc431d | |
parent | f28694ea2c6ef25c2803df0916891dd0f33a3e4e (diff) |
Add QHttpServer::afterRequest
This function allows to register a function to be run after each request.
Task-number: QTBUG-77090
Change-Id: I40dd4c1e9a447fbe034149ffc1923c7c814cf0e9
Reviewed-by: Mikhail Svetkin <mikhail.svetkin@gmail.com>
-rw-r--r-- | examples/httpserver/CMakeLists.txt | 1 | ||||
-rw-r--r-- | examples/httpserver/afterrequest/CMakeLists.txt | 27 | ||||
-rw-r--r-- | examples/httpserver/afterrequest/afterrequest.pro | 13 | ||||
-rw-r--r-- | examples/httpserver/afterrequest/main.cpp | 80 | ||||
-rw-r--r-- | examples/httpserver/httpserver.pro | 1 | ||||
-rw-r--r-- | src/httpserver/CMakeLists.txt | 9 | ||||
-rw-r--r-- | src/httpserver/httpserver.pro | 4 | ||||
-rw-r--r-- | src/httpserver/qhttpserver.cpp | 48 | ||||
-rw-r--r-- | src/httpserver/qhttpserver.h | 63 | ||||
-rw-r--r-- | src/httpserver/qhttpserver_p.h | 3 | ||||
-rw-r--r-- | src/httpserver/qhttpserverresponse.cpp | 11 | ||||
-rw-r--r-- | src/httpserver/qhttpserverresponse.h | 4 | ||||
-rw-r--r-- | src/httpserver/qhttpserverrouterviewtraits.h | 255 | ||||
-rw-r--r-- | src/httpserver/qhttpserverviewtraits.h | 109 | ||||
-rw-r--r-- | src/httpserver/qhttpserverviewtraits_impl.h | 194 | ||||
-rw-r--r-- | tests/auto/qhttpserver/tst_qhttpserver.cpp | 35 | ||||
-rw-r--r-- | tests/auto/qhttpserverrouter/tst_qhttpserverrouter.cpp | 36 |
17 files changed, 643 insertions, 250 deletions
diff --git a/examples/httpserver/CMakeLists.txt b/examples/httpserver/CMakeLists.txt index 693183c..5598230 100644 --- a/examples/httpserver/CMakeLists.txt +++ b/examples/httpserver/CMakeLists.txt @@ -1,3 +1,4 @@ # Generated from httpserver.pro. +add_subdirectory(afterrequest) add_subdirectory(simple) diff --git a/examples/httpserver/afterrequest/CMakeLists.txt b/examples/httpserver/afterrequest/CMakeLists.txt new file mode 100644 index 0000000..4f38035 --- /dev/null +++ b/examples/httpserver/afterrequest/CMakeLists.txt @@ -0,0 +1,27 @@ +# Generated from afterrequest.pro. + +cmake_minimum_required(VERSION 3.14) +project(afterrequest LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +set(INSTALL_EXAMPLEDIR "examples/httpserver/afterrequest") + +find_package(Qt6 COMPONENTS HttpServer) + +add_executable(afterrequest + main.cpp +) +target_link_libraries(afterrequest PUBLIC + Qt::HttpServer +) + +install(TARGETS afterrequest + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/httpserver/afterrequest/afterrequest.pro b/examples/httpserver/afterrequest/afterrequest.pro new file mode 100644 index 0000000..0602f10 --- /dev/null +++ b/examples/httpserver/afterrequest/afterrequest.pro @@ -0,0 +1,13 @@ +requires(qtHaveModule(httpserver)) + +TEMPLATE = app + +QT = httpserver + +SOURCES += \ + main.cpp + +target.path = $$[QT_INSTALL_EXAMPLES]/httpserver/afterrequest +INSTALLS += target + +CONFIG += cmdline diff --git a/examples/httpserver/afterrequest/main.cpp b/examples/httpserver/afterrequest/main.cpp new file mode 100644 index 0000000..c31f0a1 --- /dev/null +++ b/examples/httpserver/afterrequest/main.cpp @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Mikhail Svetkin <mikhail.svetkin@gmail.com> +** 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:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore> +#include <QtHttpServer> + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + QHttpServer httpServer; + httpServer.route("/", []() { + return "Hello world"; + }); + + httpServer.afterRequest([](QHttpServerResponse &&resp) { + resp.setHeader("Server", "Super server!"); + return std::move(resp); + }); + + const auto port = httpServer.listen(QHostAddress::Any); + if (!port) { + qDebug() << QCoreApplication::translate( + "QHttpServerExample", "Server failed to listen on a port."); + return 0; + } + + qDebug() << QCoreApplication::translate( + "QHttpServerExample", "Running on http://127.0.0.1:%1/ (Press CTRL+C to quit)").arg(port); + + return app.exec(); +} diff --git a/examples/httpserver/httpserver.pro b/examples/httpserver/httpserver.pro index b9fe1d4..7bd69d0 100644 --- a/examples/httpserver/httpserver.pro +++ b/examples/httpserver/httpserver.pro @@ -1,5 +1,6 @@ TEMPLATE = subdirs SUBDIRS = \ + afterrequest \ simple diff --git a/src/httpserver/CMakeLists.txt b/src/httpserver/CMakeLists.txt index b3586eb..efcce0d 100644 --- a/src/httpserver/CMakeLists.txt +++ b/src/httpserver/CMakeLists.txt @@ -16,6 +16,8 @@ qt_add_module(HttpServer qhttpserverrouter.cpp qhttpserverrouter.h qhttpserverrouter_p.h qhttpserverrouterrule.cpp qhttpserverrouterrule.h qhttpserverrouterrule_p.h qhttpserverrouterviewtraits.h + qhttpserverviewtraits.h + qhttpserverviewtraits_impl.h qthttpserverglobal.h INCLUDE_DIRECTORIES . @@ -45,10 +47,3 @@ 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 20f567f..fd1a231 100644 --- a/src/httpserver/httpserver.pro +++ b/src/httpserver/httpserver.pro @@ -23,7 +23,9 @@ HEADERS += \ qhttpserverrouter_p.h \ qhttpserverrouterrule.h \ qhttpserverrouterrule_p.h \ - qhttpserverrouterviewtraits.h + qhttpserverrouterviewtraits.h \ + qhttpserverviewtraits.h \ + qhttpserverviewtraits_impl.h SOURCES += \ qabstracthttpserver.cpp \ diff --git a/src/httpserver/qhttpserver.cpp b/src/httpserver/qhttpserver.cpp index 413e3c3..fb0b67b 100644 --- a/src/httpserver/qhttpserver.cpp +++ b/src/httpserver/qhttpserver.cpp @@ -66,8 +66,7 @@ QHttpServer::QHttpServer(QObject *parent) connect(this, &QAbstractHttpServer::missingHandler, this, [=] (const QHttpServerRequest &request, QTcpSocket *socket) { qCDebug(lcHS) << tr("missing handler:") << request.url().path(); - sendResponse( - QHttpServerResponse(QHttpServerResponder::StatusCode::NotFound), request, socket); + sendResponse(QHttpServerResponder::StatusCode::NotFound, request, socket); }); } @@ -103,6 +102,40 @@ QHttpServer::QHttpServer(QObject *parent) \sa QHttpServerRouter::addRule */ +/*! \fn template<typename ViewHandler> void afterRequest(ViewHandler &&viewHandler) + Register a function to be run after each request. + + \c ViewHandler can only be a lambda. The lambda definition can take two + arguments: \c {QHttpServerResponse &&} and \c {const QHttpServerRequest&} (optional). + + Examples: + + \code + + QHttpServer server; + + // Valid: + server.afterRequest([] (QHttpServerResponse &&resp, const QHttpServerRequest &request) { + return std::move(resp); + } + server.afterRequest([] (const QHttpServerRequest &request, QHttpServerResponse &&resp) { + return std::move(resp); + } + server.afterRequest([] (QHttpServerResponse &&resp) { return std::move(resp); } + + // Invalid (compile time error): + // resp must be passed by universal reference + server.afterRequest([] (QHttpServerResponse &resp, const QHttpServerRequest &request) { + return std::move(resp); + } + // request must be passed by const reference + server.afterRequest([] (QHttpServerResponse &&resp, QHttpServerRequest &request) { + return std::move(resp); + } + + \endcode +*/ + /*! Destroys a QHttpServer. */ @@ -119,13 +152,22 @@ QHttpServerRouter *QHttpServer::router() return &d->router; } +void QHttpServer::afterRequestImpl(AfterRequestHandler &&afterRequestHandler) +{ + Q_D(QHttpServer); + d->afterRequestHandlers.push_back(std::move(afterRequestHandler)); +} + /*! \internal */ -void QHttpServer::sendResponse(const QHttpServerResponse &response, +void QHttpServer::sendResponse(QHttpServerResponse &&response, const QHttpServerRequest &request, QTcpSocket *socket) { + Q_D(QHttpServer); + for (auto afterRequestHandler : d->afterRequestHandlers) + response = std::move(afterRequestHandler(std::move(response), request)); response.write(makeResponder(request, socket)); } diff --git a/src/httpserver/qhttpserver.h b/src/httpserver/qhttpserver.h index 6d754b1..97c5bc5 100644 --- a/src/httpserver/qhttpserver.h +++ b/src/httpserver/qhttpserver.h @@ -1,5 +1,6 @@ /**************************************************************************** ** +** Copyright (C) 2020 Mikhail Svetkin <mikhail.svetkin@gmail.com> ** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** @@ -35,6 +36,7 @@ #include <QtHttpServer/qhttpserverrouterrule.h> #include <QtHttpServer/qhttpserverresponse.h> #include <QtHttpServer/qhttpserverrouterviewtraits.h> +#include <QtHttpServer/qhttpserverviewtraits.h> #include <tuple> @@ -75,6 +77,57 @@ public: std::forward<Args>(args)...); } + template<typename ViewHandler> + void afterRequest(ViewHandler &&viewHandler) + { + using ViewTraits = QHttpServerAfterRequestViewTraits<ViewHandler>; + static_assert(ViewTraits::Arguments::StaticAssert, + "ViewHandler arguments are in the wrong order or not supported"); + afterRequestHelper<ViewTraits, ViewHandler>(std::move(viewHandler)); + } + + using AfterRequestHandler = + std::function<QHttpServerResponse(QHttpServerResponse &&response, + const QHttpServerRequest &request)>; +private: + template<typename ViewTraits, typename ViewHandler> + typename std::enable_if<ViewTraits::Arguments::Last::IsRequest::Value && + ViewTraits::Arguments::Count == 2, void>::type + afterRequestHelper(ViewHandler &&viewHandler) { + auto handler = [viewHandler](QHttpServerResponse &&resp, + const QHttpServerRequest &request) { + return std::move(viewHandler(std::move(resp), request)); + }; + + afterRequestImpl(std::move(handler)); + } + + template<typename ViewTraits, typename ViewHandler> + typename std::enable_if<ViewTraits::Arguments::Last::IsResponse::Value && + ViewTraits::Arguments::Count == 1, void>::type + afterRequestHelper(ViewHandler &&viewHandler) { + auto handler = [viewHandler](QHttpServerResponse &&resp, + const QHttpServerRequest &) { + return std::move(viewHandler(std::move(resp))); + }; + + afterRequestImpl(std::move(handler)); + } + + template<typename ViewTraits, typename ViewHandler> + typename std::enable_if<ViewTraits::Arguments::Last::IsResponse::Value && + ViewTraits::Arguments::Count == 2, void>::type + afterRequestHelper(ViewHandler &&viewHandler) { + auto handler = [viewHandler](QHttpServerResponse &&resp, + const QHttpServerRequest &request) { + return std::move(viewHandler(request, std::move(resp))); + }; + + afterRequestImpl(std::move(handler)); + } + + void afterRequestImpl(AfterRequestHandler &&afterRequestHandler); + private: template<typename Rule, typename ViewHandler, typename ViewTraits, int ... I, typename ... Args> bool routeHelper(QtPrivate::IndexesList<I...>, Args &&... args) @@ -107,8 +160,8 @@ private: const QHttpServerRequest &request, QTcpSocket *socket) { - const QHttpServerResponse response(boundViewHandler()); - sendResponse(response, request, socket); + QHttpServerResponse response(boundViewHandler()); + sendResponse(std::move(response), request, socket); } template<typename ViewTraits, typename T> @@ -124,8 +177,8 @@ private: ViewTraits::Arguments::PlaceholdersCount == 1, void>::type responseImpl(T &boundViewHandler, const QHttpServerRequest &request, QTcpSocket *socket) { - const QHttpServerResponse response(boundViewHandler(request)); - sendResponse(response, request, socket); + QHttpServerResponse response(boundViewHandler(request)); + sendResponse(std::move(response), request, socket); } template<typename ViewTraits, typename T> @@ -150,7 +203,7 @@ private: bool handleRequest(const QHttpServerRequest &request, QTcpSocket *socket) override final; - void sendResponse(const QHttpServerResponse &response, + void sendResponse(QHttpServerResponse &&response, const QHttpServerRequest &request, QTcpSocket *socket); }; diff --git a/src/httpserver/qhttpserver_p.h b/src/httpserver/qhttpserver_p.h index 649c41f..eb39a9e 100644 --- a/src/httpserver/qhttpserver_p.h +++ b/src/httpserver/qhttpserver_p.h @@ -49,6 +49,8 @@ #include <QtCore/qglobal.h> +#include <list> + QT_BEGIN_NAMESPACE class QHttpServerPrivate: public QAbstractHttpServerPrivate @@ -59,6 +61,7 @@ public: QHttpServerPrivate() = default; QHttpServerRouter router; + std::list<QHttpServer::AfterRequestHandler> afterRequestHandlers; }; QT_END_NAMESPACE diff --git a/src/httpserver/qhttpserverresponse.cpp b/src/httpserver/qhttpserverresponse.cpp index 9cedeb9..5ed230e 100644 --- a/src/httpserver/qhttpserverresponse.cpp +++ b/src/httpserver/qhttpserverresponse.cpp @@ -40,11 +40,20 @@ QT_BEGIN_NAMESPACE -QHttpServerResponse::QHttpServerResponse(QHttpServerResponse &&other) +QHttpServerResponse::QHttpServerResponse(QHttpServerResponse &&other) noexcept : d_ptr(other.d_ptr.take()) { } +QHttpServerResponse& QHttpServerResponse::operator=(QHttpServerResponse &&other) noexcept +{ + if (this == &other) + return *this; + + qSwap(d_ptr, other.d_ptr); + return *this; +} + QHttpServerResponse::QHttpServerResponse( const QHttpServerResponse::StatusCode statusCode) : QHttpServerResponse(QHttpServerLiterals::contentTypeXEmpty(), diff --git a/src/httpserver/qhttpserverresponse.h b/src/httpserver/qhttpserverresponse.h index 3fef112..df748cb 100644 --- a/src/httpserver/qhttpserverresponse.h +++ b/src/httpserver/qhttpserverresponse.h @@ -50,8 +50,8 @@ public: QHttpServerResponse(const QHttpServerResponse &other) = delete; QHttpServerResponse& operator=(const QHttpServerResponse &other) = delete; - QHttpServerResponse(QHttpServerResponse &&other); - QHttpServerResponse& operator=(QHttpServerResponse &&other) = delete; + QHttpServerResponse(QHttpServerResponse &&other) noexcept; + QHttpServerResponse& operator=(QHttpServerResponse &&other) noexcept; QHttpServerResponse(const StatusCode statusCode); diff --git a/src/httpserver/qhttpserverrouterviewtraits.h b/src/httpserver/qhttpserverrouterviewtraits.h index 94fd5cc..2e812a5 100644 --- a/src/httpserver/qhttpserverrouterviewtraits.h +++ b/src/httpserver/qhttpserverrouterviewtraits.h @@ -1,5 +1,6 @@ /**************************************************************************** ** +** Copyright (C) 2020 Mikhail Svetkin <mikhail.svetkin@gmail.com> ** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** @@ -30,14 +31,7 @@ #ifndef QHTTPSERVERROUTERVIEWTRAITS_H #define QHTTPSERVERROUTERVIEWTRAITS_H -#include <QtCore/qglobal.h> -#include <QtCore/qmetatype.h> -#include <QtCore/qnamespace.h> -#include <QtCore/qobjectdefs.h> - -#include <functional> -#include <tuple> -#include <type_traits> +#include <QtHttpServer/qhttpserverviewtraits_impl.h> QT_BEGIN_NAMESPACE @@ -46,220 +40,53 @@ class QHttpServerResponder; namespace QtPrivate { -template<typename T> -struct RemoveCVRef -{ - using Type = typename std::remove_cv<typename std::remove_reference<T>::type>::type; -}; - - -template<bool classMember, typename ReturnT, typename ... Args> -struct FunctionTraitsHelper -{ - static constexpr const int ArgumentCount = sizeof ... (Args); - static constexpr const int ArgumentIndexMax = ArgumentCount - 1; - static constexpr const bool IsClassMember = classMember; - using ReturnType = ReturnT; - - template <int I> - struct Arg { - using Type = typename std::tuple_element<I, std::tuple<Args...>>::type; - - using CleanType = typename QtPrivate::RemoveCVRef<Type>::Type; - - static constexpr bool Defined = QMetaTypeId2<CleanType>::Defined; - }; -}; - -template<bool classMember, typename ReturnT> -struct FunctionTraitsHelper<classMember, ReturnT> -{ - static constexpr const int ArgumentCount = 0; - static constexpr const int ArgumentIndexMax = -1; - static constexpr const bool IsClassMember = classMember; - using ReturnType = ReturnT; - - template <int I> - struct Arg { - using Type = std::false_type; - using CleanType = Type; - static constexpr bool Defined = QMetaTypeId2<CleanType>::Defined; - }; -}; - -template<typename T> -struct FunctionTraits; - -template<typename T> -struct FunctionTraits : public FunctionTraits<decltype(&T::operator())>{}; - -template<typename ReturnT, typename ... Args> -struct FunctionTraits<ReturnT (*)(Args...)> - : public FunctionTraitsHelper<false, ReturnT, Args...> -{ -}; - -template<class ReturnT, class ClassT, class ...Args> -struct FunctionTraits<ReturnT (ClassT::*)(Args...) const> - : public FunctionTraitsHelper<true, ReturnT, Args...> -{ - using classType = ClassT; -}; - template<typename ViewHandler, bool DisableStaticAssert> -struct ViewTraitsHelper { - using FunctionTraits = typename QtPrivate::FunctionTraits<ViewHandler>; - using ArgumentIndexes = typename QtPrivate::Indexes<FunctionTraits::ArgumentCount>::Value; - - struct StaticMath { - template <template<typename> class Predicate, bool defaultValue> - struct Loop { - static constexpr bool eval() noexcept { - return defaultValue; - } +struct RouterViewTraitsHelper : ViewTraits<ViewHandler, DisableStaticAssert> { + using VTraits = ViewTraits<ViewHandler, DisableStaticAssert>; + using FunctionTraits = typename VTraits::FTraits; + + template<int I> + struct ArgumentChecker : FunctionTraits::template Arg<I> { + using IsRequest = typename VTraits::template Special<I, const QHttpServerRequest &>; + static_assert(IsRequest::AssertCondition, + "ViewHandler arguments error: " + "QHttpServerRequest can only be passed as a const reference"); + + using IsResponder = typename VTraits::template Special<I, QHttpServerResponder &&>; + static_assert(IsResponder::AssertCondition, + "ViewHandler arguments error: " + "QHttpServerResponder can only be passed as a universal reference"); + + using IsSpecial = CheckAny<IsRequest, IsResponder>; + + struct IsSimple { + static constexpr bool Value = !IsSpecial::Value && + I < FunctionTraits::ArgumentCount && + FunctionTraits::ArgumentIndexMax != -1; + static constexpr bool Valid = FunctionTraits::template Arg<I>::Defined; - template<typename T, typename ... N> - static constexpr T eval(const T it, N ...n) noexcept { - return Predicate<T>::eval(it, eval(n...)); - } - }; + static constexpr bool StaticAssert = + DisableStaticAssert || !Value || Valid; - template<typename T> - struct SumPredicate { - static constexpr T eval(const T rs, const T ls) noexcept - { - return rs + ls; - } - }; - template<typename T> - struct AndPredicate { - static constexpr T eval(const T rs, const T ls) noexcept - { - return rs && ls; - } + static_assert(StaticAssert, + "ViewHandler arguments error: " + "Type is not registered, please use the Q_DECLARE_METATYPE macro " + "to make it known to Qt's meta-object system"); }; - using Sum = Loop<SumPredicate, false>; - using And = Loop<AndPredicate, true>; - using Or = Sum; - }; - - struct Arguments { - template<int I> - struct StaticCheck { - using Arg = typename FunctionTraits::template Arg<I>; - using CleanType = typename Arg::CleanType; + using CheckOk = CheckAny<IsSimple, IsSpecial>; - template<typename T, bool Clean = false> - static constexpr bool isType() noexcept - { - using SelectedType = - typename std::conditional< - Clean, - CleanType, - typename Arg::Type - >::type; - return std::is_same<SelectedType, T>::value; - } + static constexpr bool Valid = CheckOk::Valid; + static constexpr bool StaticAssert = CheckOk::StaticAssert; + }; - template<typename T> - struct SpecialHelper { - using CleanTypeT = typename QtPrivate::RemoveCVRef<T>::Type; - - static constexpr bool TypeMatched = isType<CleanTypeT, true>(); - static constexpr bool TypeCVRefMatched = isType<T>(); - - static constexpr bool ValidPosition = - (I == FunctionTraits::ArgumentIndexMax || - I == FunctionTraits::ArgumentIndexMax - 1); - static constexpr bool ValidAll = TypeCVRefMatched && ValidPosition; - - static constexpr bool assertCondition = - DisableStaticAssert || !TypeMatched || TypeCVRefMatched; - - static constexpr bool assertConditionOrder = - DisableStaticAssert || !TypeMatched || ValidPosition; - - static constexpr bool staticAssert() noexcept - { - static_assert(assertConditionOrder, - "ViewHandler arguments error: " - "QHttpServerRequest or QHttpServerResponder" - " can only be the last argument"); - return true; - } - }; - - template<typename ... T> - struct CheckAny { - static constexpr bool Value = StaticMath::Or::eval(T::Value...); - static constexpr bool Valid = StaticMath::Or::eval(T::Valid...); - static constexpr bool staticAssert() noexcept - { - return StaticMath::Or::eval(T::staticAssert()...); - } - }; - - struct IsRequest { - using Helper = SpecialHelper<const QHttpServerRequest &>; - static constexpr bool Value = Helper::TypeMatched; - static constexpr bool Valid = Helper::ValidAll; - - static constexpr bool staticAssert() noexcept - { - static_assert(Helper::assertCondition, - "ViewHandler arguments error: " - "QHttpServerRequest can only be passed as a const reference"); - return Helper::staticAssert(); - } - }; - - struct IsResponder { - using Helper = SpecialHelper<QHttpServerResponder &&>; - static constexpr bool Value = Helper::TypeMatched; - static constexpr bool Valid = Helper::ValidAll; - - static constexpr bool staticAssert() noexcept - { - static_assert(Helper::assertCondition, - "ViewHandler arguments error: " - "QHttpServerResponder can only be passed as a universal reference"); - return Helper::staticAssert(); - } - }; - - using IsSpecial = CheckAny<IsRequest, IsResponder>; - - struct IsSimple { - static constexpr bool Value = !IsSpecial::Value && - I < FunctionTraits::ArgumentCount && - FunctionTraits::ArgumentIndexMax != -1; - static constexpr bool Valid = Arg::Defined; - - static constexpr bool assertCondition = - DisableStaticAssert || !Value || Valid; - - static constexpr bool staticAssert() noexcept - { - static_assert(assertCondition, - "ViewHandler arguments error: " - "Type is not registered, please use the Q_DECLARE_METATYPE macro " - "to make it known to Qt's meta-object system"); - return true; - } - }; - - using CheckOk = CheckAny<IsSimple, IsSpecial>; - - static constexpr bool Valid = CheckOk::Valid; - static constexpr bool StaticAssert = CheckOk::staticAssert(); - }; + struct Arguments { template<int ... I> struct ArgumentsReturn { template<int Idx> - using Arg = StaticCheck<Idx>; + using Arg = ArgumentChecker<Idx>; template<int Idx> static constexpr int metaTypeId() noexcept @@ -279,9 +106,9 @@ struct ViewTraitsHelper { static_cast<std::size_t>(FunctionTraits::template Arg<I>::Defined)...); static constexpr std::size_t PlaceholdersCount = Count - CapturableCount; - static constexpr bool Valid = StaticMath::And::eval(StaticCheck<I>::Valid...); + static constexpr bool Valid = StaticMath::And::eval(Arg<I>::Valid...); static constexpr bool StaticAssert = - StaticMath::And::eval(StaticCheck<I>::StaticAssert...); + StaticMath::And::eval(Arg<I>::StaticAssert...); using Indexes = typename QtPrivate::IndexesList<I...>; @@ -317,18 +144,20 @@ struct ViewTraitsHelper { }; }; + } // namespace QtPrivate template <typename ViewHandler, bool DisableStaticAssert = false> struct QHttpServerRouterViewTraits { - using Helpers = typename QtPrivate::ViewTraitsHelper<ViewHandler, DisableStaticAssert>; + using Helpers = typename QtPrivate::RouterViewTraitsHelper<ViewHandler, DisableStaticAssert>; using Arguments = decltype(Helpers::Arguments::eval(typename Helpers::ArgumentIndexes{})); using BindableType = decltype( Helpers::template BindType<Arguments::CapturableCount>::eval( typename Arguments::PlaceholdersIndexes{})); }; + QT_END_NAMESPACE #endif // QHTTPSERVERROUTERVIEWTRAITS_H diff --git a/src/httpserver/qhttpserverviewtraits.h b/src/httpserver/qhttpserverviewtraits.h new file mode 100644 index 0000000..0469ffb --- /dev/null +++ b/src/httpserver/qhttpserverviewtraits.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Mikhail Svetkin <mikhail.svetkin@gmail.com> +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtHttpServer module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QHTTPSERVERVIEWTRAITS_H +#define QHTTPSERVERVIEWTRAITS_H + +#include <QtHttpServer/qhttpserverviewtraits_impl.h> + +QT_BEGIN_NAMESPACE + +class QHttpServerRequest; +class QHttpServerResponse; + +namespace QtPrivate { + +template <typename ViewHandler, bool DisableStaticAssert> +struct AfterRequestViewTraitsHelper : ViewTraits<ViewHandler, DisableStaticAssert> { + using VTraits = ViewTraits<ViewHandler, DisableStaticAssert>; + using FunctionTraits = typename VTraits::FTraits; + + static_assert(DisableStaticAssert || + FunctionTraits::ArgumentCount == 2 || + FunctionTraits::ArgumentCount == 1, + "ViewHandler arguments error: " + "afterRequest can only accept QHttpServerResponse and QHttpServerRequest"); + + static_assert(DisableStaticAssert || + std::is_same<typename FunctionTraits::ReturnType, + QHttpServerResponse>::value, + "ViewHandler return type error: " + "Return type can only be QHttpServerResponse"); + + template<int I> + struct ArgumentChecker { + using IsRequest = typename VTraits::template Special<I, const QHttpServerRequest &>; + static_assert(IsRequest::AssertCondition, + "ViewHandler arguments error: " + "QHttpServerRequest can only be passed as a const reference"); + + using IsResponse = typename VTraits::template Special<I, QHttpServerResponse &&>; + static_assert(IsResponse::AssertCondition, + "ViewHandler arguments error: " + "QHttpServerResponse can only be passed as a universal reference"); + + using IsSpecial = CheckAny<IsRequest, IsResponse>; + + static constexpr bool Valid = IsSpecial::Valid; + static constexpr bool StaticAssert = IsSpecial::StaticAssert; + }; + + struct Arguments { + template<int ... I> + struct ArgumentsReturn { + template<int Idx> + using Arg = ArgumentChecker<Idx>; + static constexpr bool Valid = QtPrivate::StaticMath::And::eval(Arg<I>::Valid...); + static constexpr bool StaticAssert = QtPrivate::StaticMath::And::eval( + Arg<I>::StaticAssert...); + using Last = Arg<FunctionTraits::ArgumentIndexMax>; + static constexpr std::size_t Count = FunctionTraits::ArgumentCount; + }; + + template<int ... I> + static constexpr ArgumentsReturn<I...> eval(QtPrivate::IndexesList<I...>) noexcept + { + return ArgumentsReturn<I...>{}; + } + }; +}; + +} // namespace QtPrivate + +template <typename ViewHandler, bool DisableStaticAssert = false> +struct QHttpServerAfterRequestViewTraits +{ + using Helpers = typename QtPrivate::AfterRequestViewTraitsHelper<ViewHandler, DisableStaticAssert>; + using Arguments = decltype(Helpers::Arguments::eval(typename Helpers::ArgumentIndexes{})); +}; + +QT_END_NAMESPACE + +#endif // QHTTPSERVERVIEWTRAITS_H diff --git a/src/httpserver/qhttpserverviewtraits_impl.h b/src/httpserver/qhttpserverviewtraits_impl.h new file mode 100644 index 0000000..45c46d7 --- /dev/null +++ b/src/httpserver/qhttpserverviewtraits_impl.h @@ -0,0 +1,194 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Mikhail Svetkin <mikhail.svetkin@gmail.com> +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtHttpServer module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QHTTPSERVERVIEWTRAITS_IMPL_H +#define QHTTPSERVERVIEWTRAITS_IMPL_H + +#include <QtCore/qglobal.h> +#include <QtCore/qmetatype.h> +#include <QtCore/qnamespace.h> +#include <QtCore/qobjectdefs.h> + +#include <functional> +#include <tuple> +#include <type_traits> + +QT_BEGIN_NAMESPACE + +namespace QtPrivate { + +template<typename T> +struct RemoveCVRef +{ + using Type = typename std::remove_cv<typename std::remove_reference<T>::type>::type; +}; + + +template<bool classMember, typename ReturnT, typename ... Args> +struct FunctionTraitsHelper +{ + static constexpr const int ArgumentCount = sizeof ... (Args); + static constexpr const int ArgumentIndexMax = ArgumentCount - 1; + static constexpr const bool IsClassMember = classMember; + using ReturnType = ReturnT; + + template <int I> + struct Arg { + using Type = typename std::tuple_element<I, std::tuple<Args...>>::type; + + using CleanType = typename QtPrivate::RemoveCVRef<Type>::Type; + + static constexpr bool Defined = QMetaTypeId2<CleanType>::Defined; + }; +}; + +template<bool classMember, typename ReturnT> +struct FunctionTraitsHelper<classMember, ReturnT> +{ + static constexpr const int ArgumentCount = 0; + static constexpr const int ArgumentIndexMax = -1; + static constexpr const bool IsClassMember = classMember; + using ReturnType = ReturnT; + + template <int I> + struct Arg { + using Type = std::false_type; + using CleanType = Type; + static constexpr bool Defined = QMetaTypeId2<CleanType>::Defined; + }; +}; + +template<typename T> +struct FunctionTraits; + +template<typename T> +struct FunctionTraits : public FunctionTraits<decltype(&T::operator())>{}; + +template<typename ReturnT, typename ... Args> +struct FunctionTraits<ReturnT (*)(Args...)> + : public FunctionTraitsHelper<false, ReturnT, Args...> +{ +}; + +template<class ReturnT, class ClassT, class ...Args> +struct FunctionTraits<ReturnT (ClassT::*)(Args...) const> + : public FunctionTraitsHelper<true, ReturnT, Args...> +{ + using classType = ClassT; +}; + +struct StaticMath { + template <template<typename> class Predicate, bool defaultValue> + struct Loop { + static constexpr bool eval() noexcept { + return defaultValue; + } + + template<typename T, typename ... N> + static constexpr T eval(const T it, N ...n) noexcept { + return Predicate<T>::eval(it, eval(n...)); + } + }; + + template<typename T> + struct SumPredicate { + static constexpr T eval(const T rs, const T ls) noexcept + { + return rs + ls; + } + }; + + template<typename T> + struct AndPredicate { + static constexpr T eval(const T rs, const T ls) noexcept + { + return rs && ls; + } + }; + + using Sum = Loop<SumPredicate, false>; + using And = Loop<AndPredicate, true>; + using Or = Sum; +}; + +template<typename ... T> +struct CheckAny { + static constexpr bool Value = StaticMath::Or::eval(T::Value...); + static constexpr bool Valid = StaticMath::Or::eval(T::Valid...); + static constexpr bool StaticAssert = StaticMath::Or::eval(T::StaticAssert...); +}; + +template<typename ViewHandler, bool DisableStaticAssert> +struct ViewTraits { + using FTraits = FunctionTraits<ViewHandler>; + using ArgumentIndexes = typename Indexes<FTraits::ArgumentCount>::Value; + + template<int I, typename Special> + struct SpecialHelper { + using Arg = typename FTraits::template Arg<I>; + using CleanSpecialT = typename RemoveCVRef<Special>::Type; + + static constexpr bool TypeMatched = std::is_same<typename Arg::CleanType, CleanSpecialT>::value; + static constexpr bool TypeCVRefMatched = std::is_same<typename Arg::Type, Special>::value; + + static constexpr bool ValidPosition = + (I == FTraits::ArgumentIndexMax || + I == FTraits::ArgumentIndexMax - 1); + static constexpr bool ValidAll = TypeCVRefMatched && ValidPosition; + + static constexpr bool AssertCondition = + DisableStaticAssert || !TypeMatched || TypeCVRefMatched; + + static constexpr bool AssertConditionOrder = + DisableStaticAssert || !TypeMatched || ValidPosition; + + static constexpr bool StaticAssert = AssertCondition && AssertConditionOrder; + + static_assert(AssertConditionOrder, + "ViewHandler arguments error: " + "QHttpServerRequest or QHttpServerResponder" + " can only be the last argument"); + }; + + template<int I, typename T> + struct Special { + using Helper = SpecialHelper<I, T>; + static constexpr bool Value = Helper::TypeMatched; + static constexpr bool Valid = Helper::ValidAll; + static constexpr bool StaticAssert = Helper::StaticAssert; + static constexpr bool AssertCondition = Helper::AssertCondition; + }; +}; + +} // namespace QtPrivate + +QT_END_NAMESPACE + +#endif // QHTTPSERVERVIEWTRAITS_IMPL_H diff --git a/tests/auto/qhttpserver/tst_qhttpserver.cpp b/tests/auto/qhttpserver/tst_qhttpserver.cpp index 4c50463..a5feaf9 100644 --- a/tests/auto/qhttpserver/tst_qhttpserver.cpp +++ b/tests/auto/qhttpserver/tst_qhttpserver.cpp @@ -129,6 +129,7 @@ private slots: void routeExtraHeaders(); void invalidRouterArguments(); void checkRouteLambdaCapture(); + void afterRequest(); private: void checkReply(QNetworkReply *reply, const QString &response); @@ -289,6 +290,10 @@ void tst_QHttpServer::initTestCase() return resp; }); + httpserver.afterRequest([] (QHttpServerResponse &&resp) { + return std::move(resp); + }); + quint16 port = httpserver.listen(); if (!port) qCritical() << "Http server listen failed"; @@ -870,6 +875,36 @@ void tst_QHttpServer::checkRouteLambdaCapture() return; } +void tst_QHttpServer::afterRequest() +{ + httpserver.afterRequest([] (QHttpServerResponse &&resp, + const QHttpServerRequest &request) { + if (request.url().path() == "/test-after-request") + resp.setHeader("Arguments-Order-1", "resp, request"); + + return std::move(resp); + }); + + httpserver.afterRequest([] (const QHttpServerRequest &request, + QHttpServerResponse &&resp) { + if (request.url().path() == "/test-after-request") + resp.setHeader("Arguments-Order-2", "request, resp"); + + return std::move(resp); + }); + + const QUrl requestUrl(urlBase.arg("/test-after-request")); + auto reply = networkAccessManager.get(QNetworkRequest(requestUrl)); + + QTRY_VERIFY(reply->isFinished()); + + QCOMPARE(reply->header(QNetworkRequest::ContentTypeHeader), "application/x-empty"); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 404); + QCOMPARE(reply->rawHeader("Arguments-Order-1"), "resp, request"); + QCOMPARE(reply->rawHeader("Arguments-Order-2"), "request, resp"); + reply->deleteLater(); +} + void tst_QHttpServer::checkReply(QNetworkReply *reply, const QString &response) { QTRY_VERIFY(reply->isFinished()); diff --git a/tests/auto/qhttpserverrouter/tst_qhttpserverrouter.cpp b/tests/auto/qhttpserverrouter/tst_qhttpserverrouter.cpp index fb0c409..d68221c 100644 --- a/tests/auto/qhttpserverrouter/tst_qhttpserverrouter.cpp +++ b/tests/auto/qhttpserverrouter/tst_qhttpserverrouter.cpp @@ -252,8 +252,8 @@ void tst_QHttpServerRouter::viewHandlerNoArg() "viewOneArg: Args::Last::Valid"); static_assert(Args::Last::StaticAssert, "viewOneArg: Args::Last::StaticAssert"); - static_assert(Args::Last::isType<std::false_type>(), - "viewNonArg: Args::Last::isType<std::false_type>()"); + static_assert(std::is_same<Args::Last::Type, std::false_type>::value, + "viewNonArg: std::is_same<Args::Last::Type, std::false_type>"); using Arg1 = typename Args::template Arg<1>; static_assert(Arg1::IsRequest::Value == 0, @@ -272,8 +272,8 @@ void tst_QHttpServerRouter::viewHandlerNoArg() "viewOneArg: Args::Arg<1>::Valid"); static_assert(Arg1::StaticAssert, "viewOneArg: Args::Arg<1>::Valid::StaticAssert"); - static_assert(Arg1::isType<std::false_type>(), - "viewNonArg: Args::Arg<1>::isType<std::false_type>()"); + static_assert(std::is_same<Arg1::Type, std::false_type>::value, + "viewNonArg: std::is_same<Args::Arg<1>::Type, std::false_type>"); static_assert(Args::Valid, "viewNonArg: Args::Valid"); static_assert(Args::StaticAssert, "viewNonArg: Args::StaticAssert"); @@ -313,8 +313,8 @@ void tst_QHttpServerRouter::viewHandlerOneArg() "viewOneArg: Args::Last::Valid"); static_assert(Args::Last::StaticAssert, "viewOneArg: Args::Last::StaticAssert"); - static_assert(Args::Last::isType<const quint64 &>(), - "viewNonArg: Args::Last::isType<const quint64 &>()"); + static_assert(std::is_same<Args::Last::Type, const quint64 &>::value, + "viewNonArg: std::is_same<Args::Last::Type, const quint64 &>"); static_assert(Args::Valid, "viewOneArg: Args::Valid"); static_assert(Args::StaticAssert, "viewOneArg: Args::StaticAssert"); } @@ -355,8 +355,8 @@ void tst_QHttpServerRouter::viewHandlerTwoArgs() "viewTwoArgs: Args::Arg0::Valid"); static_assert(Arg0::StaticAssert, "viewTwoArgs: Args::Arg0::StaticAssert"); - static_assert(Arg0::isType<const quint64 &>(), - "viewNonArg: Args::Arg0::isType<const quint64>()"); + static_assert(std::is_same<Arg0::Type, const quint64 &>::value, + "viewNonArg: std::is_same<Args::Arg0::Type, const quint64>"); using Arg1 = typename Args::template Arg<1>; static_assert(Arg1::IsRequest::Value == 0, @@ -380,8 +380,8 @@ void tst_QHttpServerRouter::viewHandlerTwoArgs() // StaticAssert is disabled in tests static_assert(Arg1::StaticAssert, "viewOneArg: Args::Arg1::StaticAssert"); - static_assert(Arg1::isType<const QHttpServerResponder &>(), - "viewTwoArgs: Args::Arg1::isType<const QHttpServerResponder &>>()"); + static_assert(std::is_same<Arg1::Type, const QHttpServerResponder &>::value, + "viewTwoArgs: std::is_same<Args::Arg1::Type, const QHttpServerResponder &>)"); static_assert(Args::Valid == 0, "viewTwoArgs: Args::Valid == 0"); // StaticAssert is disabled in tests @@ -422,8 +422,8 @@ void tst_QHttpServerRouter::viewHandlerResponder() "viewResponder: Args::Last::Valid"); static_assert(Args::Last::StaticAssert, "viewResponder: Args::Last::StaticAssert"); - static_assert(Args::Last::isType<QHttpServerResponder &&>(), - "viewNonArg: Args::Last::isType<QHttpServerResponder &&>()"); + static_assert(std::is_same<Args::Last::Type, QHttpServerResponder &&>::value, + "viewNonArg: std::is_same<Args::Last::Type, QHttpServerResponder &&>"); static_assert(Args::Valid, "viewResponder: Args::Valid"); static_assert(Args::StaticAssert, "viewResponder: Args::StaticAssert"); } @@ -462,8 +462,8 @@ void tst_QHttpServerRouter::viewHandlerRequest() "viewResponder: Args::Last::Valid"); static_assert(Args::Last::StaticAssert, "viewResponder: Args::Last::StaticAssert"); - static_assert(Args::Last::isType<const QHttpServerRequest &>(), - "viewNonArg: Args::Last::isType<const QHttpServerRequest &>()"); + static_assert(std::is_same<Args::Last::Type, const QHttpServerRequest &>::value, + "viewNonArg: std::is_same<Args::Last::Type, const QHttpServerRequest &>"); static_assert(Args::Valid, "viewResponder: Args::Valid"); static_assert(Args::StaticAssert, "viewResponder: Args::StaticAssert"); } @@ -505,8 +505,8 @@ void tst_QHttpServerRouter::viewHandlerLastTwoSpecials() // StaticAssert is disabled in tests static_assert(Arg0::StaticAssert, "viewTwoSpecialArgs: Args::Arg0::StaticAssert"); - static_assert(Arg0::isType<const QHttpServerRequest &>(), - "viewNonArg: Args::Arg0::isType<const QHttpServerRequest &>()"); + static_assert(std::is_same<Arg0::Type, const QHttpServerRequest &>::value, + "viewNonArg: std::is_same<Args::Arg0::Type, const QHttpServerRequest &>"); using Arg1 = typename Args::template Arg<1>; static_assert(Arg1::IsRequest::Value == 0, @@ -529,8 +529,8 @@ void tst_QHttpServerRouter::viewHandlerLastTwoSpecials() "viewTwoSpecialArgs: Args::Arg1::Valid"); static_assert(Arg1::StaticAssert, "viewTwoSpecialArgs: Args::Arg1::StaticAssert"); - static_assert(Arg1::isType<QHttpServerResponder &&>(), - "viewTwoSpecialArgs: Args::Arg1::isType<QHttpServerResponder &&>()"); + static_assert(std::is_same<Arg1::Type, QHttpServerResponder &&>::value, + "viewTwoSpecialArgs: std::is_same<Args::Arg1::Type, QHttpServerResponder &&>"); static_assert(Args::Valid, "viewTwoSpecialArgs: Args::Valid"); // StaticAssert is disabled in tests |