diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 19f44165..a73ea944 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1018,50 +1018,50 @@ jobs: run: | echo "LD_LIBRARY_PATH=$GITHUB_WORKSPACE/.local/lib:$LD_LIBRARY_PATH" >> "$GITHUB_ENV" - - name: Find Package Integration Workflow - uses: alandefreitas/cpp-actions/cmake-workflow@v1.9.0 - if: ${{ matrix.build-cmake || matrix.is-earliest }} - with: - source-dir: boost-root/libs/${{ steps.patch.outputs.module }}/test/cmake_test - build-dir: __build_cmake_install_test__ - generator: ${{ matrix.generator }} - generator-toolset: ${{ matrix.generator-toolset }} - build-type: ${{ matrix.build-type }} - cxxstd: ${{ matrix.latest-cxxstd }} - cc: ${{ steps.setup-cpp.outputs.cc || matrix.cc }} - ccflags: ${{ matrix.ccflags }} - cxx: ${{ steps.setup-cpp.outputs.cxx || matrix.cxx }} - cxxflags: ${{ matrix.cxxflags }} - shared: ${{ matrix.shared }} - install: false - cmake-version: '>=3.20' - extra-args: | - -D BOOST_CI_INSTALL_TEST=ON - -D CMAKE_PREFIX_PATH=${{ steps.patch.outputs.workspace_root }}/.local - ref-source-dir: boost-root/libs/http_proto - trace-commands: true - toolchain: ${{ (startsWith(matrix.runs-on, 'windows') && steps.patch-user-config.outputs.toolchain) || '' }} - - - name: Subdirectory Integration Workflow - uses: alandefreitas/cpp-actions/cmake-workflow@v1.9.0 - if: ${{ matrix.build-cmake || matrix.is-earliest }} - with: - source-dir: boost-root/libs/${{ steps.patch.outputs.module }}/test/cmake_test - build-dir: __build_cmake_subdir_test__ - generator: ${{ matrix.generator }} - generator-toolset: ${{ matrix.generator-toolset }} - build-type: ${{ matrix.build-type }} - cxxstd: ${{ matrix.latest-cxxstd }} - cc: ${{ steps.setup-cpp.outputs.cc || matrix.cc }} - ccflags: ${{ matrix.ccflags }} - cxx: ${{ steps.setup-cpp.outputs.cxx || matrix.cxx }} - cxxflags: ${{ matrix.cxxflags }} - shared: ${{ matrix.shared }} - install: false - cmake-version: '>=3.20' - extra-args: -D BOOST_CI_INSTALL_TEST=OFF - ref-source-dir: boost-root/libs/http_proto/test/cmake_test - toolchain: ${{ (startsWith(matrix.runs-on, 'windows') && steps.patch-user-config.outputs.toolchain) || '' }} +# - name: Find Package Integration Workflow +# uses: alandefreitas/cpp-actions/cmake-workflow@v1.9.0 +# if: ${{ matrix.build-cmake || matrix.is-earliest }} +# with: +# source-dir: boost-root/libs/${{ steps.patch.outputs.module }}/test/cmake_test +# build-dir: __build_cmake_install_test__ +# generator: ${{ matrix.generator }} +# generator-toolset: ${{ matrix.generator-toolset }} +# build-type: ${{ matrix.build-type }} +# cxxstd: ${{ matrix.latest-cxxstd }} +# cc: ${{ steps.setup-cpp.outputs.cc || matrix.cc }} +# ccflags: ${{ matrix.ccflags }} +# cxx: ${{ steps.setup-cpp.outputs.cxx || matrix.cxx }} +# cxxflags: ${{ matrix.cxxflags }} +# shared: ${{ matrix.shared }} +# install: false +# cmake-version: '>=3.20' +# extra-args: | +# -D BOOST_CI_INSTALL_TEST=ON +# -D CMAKE_PREFIX_PATH=${{ steps.patch.outputs.workspace_root }}/.local +# ref-source-dir: boost-root/libs/http_proto +# trace-commands: true +# toolchain: ${{ (startsWith(matrix.runs-on, 'windows') && steps.patch-user-config.outputs.toolchain) || '' }} + +# - name: Subdirectory Integration Workflow +# uses: alandefreitas/cpp-actions/cmake-workflow@v1.9.0 +# if: ${{ matrix.build-cmake || matrix.is-earliest }} +# with: +# source-dir: boost-root/libs/${{ steps.patch.outputs.module }}/test/cmake_test +# build-dir: __build_cmake_subdir_test__ +# generator: ${{ matrix.generator }} +# generator-toolset: ${{ matrix.generator-toolset }} +# build-type: ${{ matrix.build-type }} +# cxxstd: ${{ matrix.latest-cxxstd }} +# cc: ${{ steps.setup-cpp.outputs.cc || matrix.cc }} +# ccflags: ${{ matrix.ccflags }} +# cxx: ${{ steps.setup-cpp.outputs.cxx || matrix.cxx }} +# cxxflags: ${{ matrix.cxxflags }} +# shared: ${{ matrix.shared }} +# install: false +# cmake-version: '>=3.20' +# extra-args: -D BOOST_CI_INSTALL_TEST=OFF +# ref-source-dir: boost-root/libs/http_proto/test/cmake_test +# toolchain: ${{ (startsWith(matrix.runs-on, 'windows') && steps.patch-user-config.outputs.toolchain) || '' }} - name: Root Project CMake Workflow uses: alandefreitas/cpp-actions/cmake-workflow@v1.9.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index ff212d84..ae6a2365 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,7 @@ set(BOOST_HTTP_PROTO_DEPENDENCIES Boost::capy Boost::config Boost::core + Boost::mp11 Boost::static_assert Boost::system Boost::throw_exception diff --git a/include/boost/http_proto.hpp b/include/boost/http_proto.hpp index 38dca128..8944aba9 100644 --- a/include/boost/http_proto.hpp +++ b/include/boost/http_proto.hpp @@ -20,11 +20,14 @@ #include #include #include +#include #include #include #include +#include #include #include +#include #include #include #include @@ -37,10 +40,16 @@ #include #include +//#include #include #include #include #include #include +#include +#include +#include +#include + #endif diff --git a/include/boost/http_proto/detail/type_traits.hpp b/include/boost/http_proto/detail/type_traits.hpp index 590b2974..457e5ea2 100644 --- a/include/boost/http_proto/detail/type_traits.hpp +++ b/include/boost/http_proto/detail/type_traits.hpp @@ -80,11 +80,6 @@ using derived_from = std::integral_constant::value>; -template struct bool_pack {}; -template -struct all_true : std::is_same, bool_pack> {}; - } // detail } // http_proto } // boost diff --git a/include/boost/http_proto/rfc/media_type.hpp b/include/boost/http_proto/rfc/media_type.hpp index 356faba7..b3747118 100644 --- a/include/boost/http_proto/rfc/media_type.hpp +++ b/include/boost/http_proto/rfc/media_type.hpp @@ -23,11 +23,11 @@ struct mime_type { /** The type */ - string_view type; + core::string_view type; /** The subtype */ - string_view subtype; + core::string_view subtype; }; //------------------------------------------------ @@ -58,7 +58,7 @@ struct media_type_rule_t parse( char const*& it, char const* end) const noexcept -> - result; + system::result; }; } // implementation_defined diff --git a/include/boost/http_proto/server/basic_router.hpp b/include/boost/http_proto/server/basic_router.hpp index f76f0bfa..90535107 100644 --- a/include/boost/http_proto/server/basic_router.hpp +++ b/include/boost/http_proto/server/basic_router.hpp @@ -13,26 +13,26 @@ #include #include #include -#include #include -#include -#include -#include +#include // VFALCO fix #include +#include #include +#include +#include #include namespace boost { namespace http_proto { -template +template class basic_router; -/** Configuration options for routers. +/** Configuration options for HTTP routers. */ struct router_options { - /** Constructor + /** Constructor. Routers constructed with default options inherit the values of @ref case_sensitive and @ref strict from the parent router. @@ -50,13 +50,14 @@ struct router_options @par Example @code - router r(router_options() - .merge_params(true) - .case_sensitive(true) - .strict(false)); + router r( router_options() + .merge_params( true ) + .case_sensitive( true ) + .strict( false ) ); @endcode @param value `true` to merge parameters from parent routers. + @return A reference to `*this` for chaining. */ router_options& @@ -74,12 +75,13 @@ struct router_options @par Example @code - router r(router_options() - .case_sensitive(true) - .strict(true)); + router r( router_options() + .case_sensitive( true ) + .strict( true ) ); @endcode @param value `true` to perform case-sensitive path matching. + @return A reference to `*this` for chaining. */ router_options& @@ -104,12 +106,13 @@ struct router_options @par Example @code - router r(router_options() - .strict(true) - .case_sensitive(false)); + router r( router_options() + .strict( true ) + .case_sensitive( false ) ); @endcode @param value `true` to enable strict path matching. + @return A reference to `*this` for chaining. */ router_options& @@ -124,7 +127,7 @@ struct router_options } private: - template friend class basic_router; + template friend class basic_router; unsigned int v_ = 0; }; @@ -140,7 +143,7 @@ class any_router; class any_router { private: - template + template friend class http_proto::basic_router; using opt_flags = unsigned int; @@ -149,7 +152,7 @@ class any_router virtual ~any_handler() = default; virtual std::size_t count() const noexcept = 0; virtual route_result invoke( - basic_request&, basic_response&) const = 0; + route_params_base&) const = 0; }; using handler_ptr = std::unique_ptr; @@ -160,7 +163,7 @@ class any_router handler_ptr* p; }; - using match_result = basic_request::match_result; + using match_result = route_params_base::match_result; struct matcher; struct layer; struct impl; @@ -179,13 +182,13 @@ class any_router BOOST_HTTP_PROTO_DECL void add_impl(layer&, core::string_view, handler_list const&); BOOST_HTTP_PROTO_DECL route_result resume_impl( - basic_request&, basic_response&, route_result ec) const; + route_params_base&, route_result ec) const; BOOST_HTTP_PROTO_DECL route_result dispatch_impl(http_proto::method, core::string_view, urls::url_view const&, - basic_request&, basic_response&) const; + route_params_base&) const; BOOST_HTTP_PROTO_DECL route_result dispatch_impl( - basic_request&, basic_response&) const; - route_result do_dispatch(basic_request&, basic_response&) const; + route_params_base&) const; + route_result do_dispatch(route_params_base&) const; impl* impl_ = nullptr; }; @@ -198,8 +201,8 @@ class any_router `basic_router` objects store and dispatch route handlers based on the HTTP method and path of an incoming request. Routes are added with a - path pattern and an associated handler, and the router is then used to - dispatch the appropriate handler. + path pattern, method, and an associated handler, and the router is then + used to dispatch the appropriate handler. Patterns used to create route definitions have percent-decoding applied when handlers are mounted. A literal "%2F" in the pattern string is @@ -208,15 +211,15 @@ class any_router @par Example @code - using router_type = basic_router; + using router_type = basic_router; router_type router; - router.get("/hello", - [](Req& req, Res& res) + router.get( "/hello", + []( route_params& p ) { - res.status(http_proto::status::ok); - res.set_body("Hello, world!"); + p.res.status( status::ok ); + p.res.set_body( "Hello, world!" ); return route::send; - }); + } ); @endcode Router objects are lightweight, shared references to their contents. @@ -225,10 +228,11 @@ class any_router underlying data. @par Handlers + Regular handlers are invoked for matching routes and have this equivalent signature: @code - route_result handler( Req& req, Res& res ) + route_result handler( Params& p ) @endcode The return value is a @ref route_result used to indicate the desired @@ -244,41 +248,39 @@ class any_router Error handlers have this equivalent signature: @code - route_result error_handler( Req& req, Res& res, system::error_code ec ) + route_result error_handler( Params& p, system::error_code ec ) @endcode Each error handler may return any failing @ref system::error_code, which is equivalent to calling: @code - res.next(ec); // with ec.failed() == true + p.next( ec ); // with ec.failed() == true @endcode + Returning @ref route::next indicates that control should proceed to the next matching error handler. Returning a different failing code replaces the current error and continues dispatch in error mode using that new code. Error handlers are invoked until one returns a result other than @ref route::next. - @par Handler requirements - Regular handlers must be callable with: + Exception handlers have this equivalent signature: @code - route_result( Req&, Res& ) + route_result exception_handler( Params& p, E ex ) @endcode - Error handlers must be callable with: - @code - route_result( Req&, Res&, system::error_code ) - @endcode - Error handlers are invoked only when the response has a failing - error code, and execute in sequence until one returns a result - other than @ref route::next. + Where `E` is the type of exception caught. Handlers installed for an + exception of type `E` will also be called when the exception type is + a derived class of `E`. Exception handlers are invoked in the order + of registration whenever an exception is present in the request. The prefix match is not strict: middleware attached to `"/api"` will also match `"/api/users"` and `"/api/data"`. When registered before route handlers for the same prefix, middleware runs before - those routes. This is analogous to `app.use(path, ...)` in + those routes. This is analogous to `app.use( path, ... )` in Express.js. @par Thread Safety + Member functions marked `const` such as @ref dispatch and @ref resume may be called concurrently on routers that refer to the same data. Modification of routers through calls to non-`const` member functions @@ -286,27 +288,22 @@ class any_router other member function. @par Constraints - `Req` must be publicly derived from @ref basic_request. - `Res` must be publicly derived from @ref basic_response. - @tparam Req The type of request object. - @tparam Res The type of response object. + `Params` must be publicly derived from @ref route_params_base. + + @tparam Params The type of the parameters object passed to handlers. */ -template +template class basic_router : public /*detail::*/any_router { - // Req must be publicly derived from basic_request - BOOST_CORE_STATIC_ASSERT( - detail::derived_from::value); - - // Res must be publicly derived from basic_response + // Params must be publicly derived from route_params_base BOOST_CORE_STATIC_ASSERT( - detail::derived_from::value); + detail::derived_from::value); // 0 = unrecognized - // 1 = normal handler (Req&, Res&) - // 2 = error handler (Req&, Res&, error_code) - // 4 = basic_router + // 1 = normal handler (Params&) + // 2 = error handler (Params&, error_code) + // 4 = basic_router template struct handler_type @@ -314,35 +311,33 @@ class basic_router : public /*detail::*/any_router { }; - // route_result( Req&, Res& ) const + // route_result( Params& ) const template struct handler_type()( - std::declval(), - std::declval())), + std::declval())), route_result>::value >::type> : std::integral_constant {}; - // route_result( Req&, Res&, system::error_code const& ) const + // route_result( Params&, system::error_code const& ) const template struct handler_type()( - std::declval(), - std::declval(), + std::declval(), std::declval())), route_result>::value >::type> : std::integral_constant {}; - // basic_router + // basic_router template struct handler_type::value && std::is_convertible::value && - std::is_constructible>::value + std::is_constructible>::value >::type> : std::integral_constant {}; template @@ -356,45 +351,33 @@ class basic_router : public /*detail::*/any_router std::false_type >::type {}; + // exception handler (Params&, E) + template struct except_type : std::false_type {}; template struct except_type{} && ( - capy::detail::type_list_size::arg_types>{} == 3) && - std::is_convertible::arg_types>::type>::value && - std::is_convertible::arg_types>::type>{} + mp11::mp_size::arg_types>{} == 2) && + std::is_convertible::arg_types>>::value >::type> : std::true_type { - using type = typename std::decay::arg_types>::type>::type; + // type of exception + using type = typename std::decay::arg_types>>::type; }; - template - struct except_types; - - template - struct except_types : std::true_type {}; - - template - struct except_types : std::integral_constant{} && except_types{}> - {}; + template using except_types = + mp11::mp_all< except_type... >; public: - /** The type of request object used in handlers + /** The type of params used in handlers. */ - using request_type = Req; - - /** The type of response object used in handlers - */ - using response_type = Res; + using params_type = Params; /** A fluent interface for defining handlers on a specific route. @@ -404,17 +387,17 @@ class basic_router : public /*detail::*/any_router Typical usage registers one or more handlers for a route: @code - router.route("/users/:id") - .get(show_user) - .put(update_user) - .all(log_access); + router.route( "/users/:id" ) + .get( show_user ) + .put( update_user ) + .all( log_access ); @endcode Each call appends handlers in registration order. */ class fluent_route; - /** Constructor + /** Constructor. Creates an empty router with the specified configuration. Routers constructed with default options inherit the values @@ -427,7 +410,8 @@ class basic_router : public /*detail::*/any_router @param options The configuration options to use. */ explicit - basic_router(router_options options = {}) + basic_router( + router_options options = {}) : any_router(options.v_) { } @@ -435,8 +419,7 @@ class basic_router : public /*detail::*/any_router /** Construct a router from another router with compatible types. This constructs a router that shares the same underlying routing - state as another router whose request and response types are base - classes of `Req` and `Res`, respectively. + state as another router whose params type is a base class of `Params`. The resulting router participates in shared ownership of the implementation; copying the router does not duplicate routes or @@ -444,26 +427,25 @@ class basic_router : public /*detail::*/any_router through all routers that share the same underlying state. @par Constraints - `Req` must be derived from `OtherReq`, and `Res` must be - derived from `OtherRes`. - @tparam OtherReq The request type of the source router. - @tparam OtherRes The response type of the source router. - @param other The router to copy. + `Params` must be derived from `OtherParams`. + + @param other The router to construct from. + + @tparam OtherParams The params type of the source router. */ template< - class OtherReq, class OtherRes, + class OtherParams, class = typename std::enable_if< - detail::derived_from::value && - detail::derived_from::value>::type + detail::derived_from::value>::type > basic_router( - basic_router const& other) + basic_router const& other) noexcept : any_router(other) { } - /** Add one or more middleware handlers for a path prefix. + /** Add middleware handlers for a path prefix. Each handler registered with this function participates in the routing and error-dispatch process for requests whose path begins @@ -474,32 +456,35 @@ class basic_router : public /*detail::*/any_router @par Example @code - router.use("/api", - [](Request& req, Response& res) + router.use( "/api", + []( route_params& p ) { - if (!authenticate(req)) + if( ! authenticate( p ) ) { - res.status(401); - res.set_body("Unauthorized"); + p.res.status( 401 ); + p.res.set_body( "Unauthorized" ); return route::send; } return route::next; }, - [](Request&, Response& res) + []( route_params& p ) { - res.set_header("X-Powered-By", "MyServer"); + p.res.set_header( "X-Powered-By", "MyServer" ); return route::next; - }); + } ); @endcode @par Preconditions - @p `pattern` must be a valid path prefix; it may be empty to - indicate the root scope. + + @p pattern must be a valid path prefix; it may be empty to + indicate the root scope. @param pattern The pattern to match. + @param h1 The first handler to add. + @param hn Additional handlers to add, invoked after @p h1 in - registration order. + registration order. */ template void use( @@ -514,8 +499,8 @@ class basic_router : public /*detail::*/any_router std::forward

(h1), std::forward(hn)...)); } - /** Add one or more global middleware handlers. - + /** Add global middleware handlers. + Each handler registered with this function participates in the routing and error-dispatch process as described in the @ref basic_router class documentation. Handlers execute in the @@ -530,19 +515,21 @@ class basic_router : public /*detail::*/any_router @par Example @code router.use( - [](Request&, Response& res) + []( Params& p ) { - res.message.erase("X-Powered-By"); + p.res.erase( "X-Powered-By" ); return route::next; - }); + } ); @endcode @par Constraints - @li `h1` must not be convertible to @ref core::string_view. + + @p h1 must not be convertible to @ref core::string_view. @param h1 The first handler to add. + @param hn Additional handlers to add, invoked after @p h1 in - registration order. + registration order. */ template(h1), std::forward(hn)...); } - /** Add a global exception handler. + /** Add exception handlers for a route pattern. + + Registers one or more exception handlers that will be invoked + when an exception is thrown during request processing for routes + matching the specified pattern. + + Handlers are invoked in the order provided until one handles + the exception. + + @par Example + @code + app.except( "/api*", + []( route_params& p, std::exception const& ex ) + { + p.res.set_status( 500 ); + return route::send; + } ); + @endcode + + @param pattern The route pattern to match, or empty to match + all routes. + + @param h1 The first exception handler. + + @param hn Additional exception handlers. */ template void except( @@ -566,11 +577,33 @@ class basic_router : public /*detail::*/any_router { // If you get a compile error on this line it means that one or // more of the provided types is not a valid exception handler - BOOST_CORE_STATIC_ASSERT(except_types<0, H1, HN...>::value); + BOOST_CORE_STATIC_ASSERT(except_types::value); add_impl(pattern, make_except_list( std::forward

(h1), std::forward(hn)...)); } + /** Add global exception handlers. + + Registers one or more exception handlers that will be invoked + when an exception is thrown during request processing for any + route. + + Equivalent to calling `except( "", h1, hn... )`. + + @par Example + @code + app.except( + []( route_params& p, std::exception const& ex ) + { + p.res.set_status( 500 ); + return route::send; + } ); + @endcode + + @param h1 The first exception handler. + + @param hn Additional exception handlers. + */ template::value>::type> @@ -578,13 +611,13 @@ class basic_router : public /*detail::*/any_router { // If you get a compile error on this line it means that one or // more of the provided types is not a valid exception handler - BOOST_CORE_STATIC_ASSERT(except_types<0, H1, HN...>::value); + BOOST_CORE_STATIC_ASSERT(except_types::value); except(core::string_view(), std::forward

(h1), std::forward(hn)...); } /** Add handlers for all HTTP methods matching a path pattern. - + This registers regular handlers for the specified path pattern, participating in dispatch as described in the @ref basic_router class documentation. Handlers run when the route matches, @@ -592,20 +625,24 @@ class basic_router : public /*detail::*/any_router Error handlers and routers cannot be passed here. A new route object is created even if the pattern already exists. + @par Example @code - router.route("/status") - .head(check_headers) - .get(send_status) - .all(log_access); + router.route( "/status" ) + .add( method::head, check_headers ) + .add( method::get, send_status ) + .all( log_access ); @endcode @par Preconditions - @p `pattern` must be a valid path pattern; it must not be empty. + + @p pattern must be a valid path pattern; it must not be empty. @param pattern The path pattern to match. + @param h1 The first handler to add. + @param hn Additional handlers to add, invoked after @p h1 in - registration order. + registration order. */ template void all( @@ -620,18 +657,21 @@ class basic_router : public /*detail::*/any_router std::forward

(h1), std::forward(hn)...); } - /** Add one or more route handlers for a method and pattern. - + /** Add route handlers for a method and pattern. + This registers regular handlers for the specified HTTP verb and path pattern, participating in dispatch as described in the @ref basic_router class documentation. Error handlers and routers cannot be passed here. @param verb The known HTTP method to match. + @param pattern The path pattern to match. + @param h1 The first handler to add. + @param hn Additional handlers to add, invoked after @p h1 in - registration order. + registration order. */ template void add( @@ -647,18 +687,21 @@ class basic_router : public /*detail::*/any_router std::forward

(h1), std::forward(hn)...); } - /** Add one or more route handlers for a method and pattern. - + /** Add route handlers for a method string and pattern. + This registers regular handlers for the specified HTTP verb and path pattern, participating in dispatch as described in the @ref basic_router class documentation. Error handlers and routers cannot be passed here. @param verb The HTTP method string to match. + @param pattern The path pattern to match. + @param h1 The first handler to add. + @param hn Additional handlers to add, invoked after @p h1 in - registration order. + registration order. */ template void add( @@ -686,7 +729,9 @@ class basic_router : public /*detail::*/any_router @param pattern The path expression to match against request targets. This may include parameters or wildcards following the router's pattern syntax. May not be empty. - @return A fluent route interface for chaining handler registrations. + + @return A fluent route interface for chaining handler + registrations. */ auto route( @@ -698,62 +743,71 @@ class basic_router : public /*detail::*/any_router //-------------------------------------------- /** Dispatch a request to the appropriate handler. - + This runs the routing and error-dispatch logic for the given HTTP method and target URL, as described in the @ref basic_router class documentation. @par Thread Safety + This function may be called concurrently on the same object along with other `const` member functions. Each concurrent invocation - must use distinct request and response objects. + must use distinct params objects. @param verb The HTTP method to match. This must not be - @ref http_proto::method::unknown. + @ref http_proto::method::unknown. + @param url The full request target used for route matching. - @param req The request to pass to handlers. - @param res The response to pass to handlers. + + @param p The params to pass to handlers. + @return The @ref route_result describing how routing completed. + @throws std::invalid_argument If @p verb is - @ref http_proto::method::unknown. + @ref http_proto::method::unknown. */ auto dispatch( http_proto::method verb, urls::url_view const& url, - Req& req, Res& res) const -> + Params& p) const -> route_result { if(verb == http_proto::method::unknown) detail::throw_invalid_argument(); return dispatch_impl(verb, - core::string_view(), url, req, res); + core::string_view(), url, p); } - /** Dispatch a request to the appropriate handler using a method string. - + /** Dispatch a request using a method string. + This runs the routing and error-dispatch logic for the given HTTP method string and target URL, as described in the @ref basic_router class documentation. This overload is intended for method tokens that are not represented by @ref http_proto::method. @par Thread Safety + This function may be called concurrently on the same object along with other `const` member functions. Each concurrent invocation - must use distinct request and response objects. + must use distinct params objects. + + @param verb The HTTP method string to match. This must not + be empty. - @param verb The HTTP method string to match. This must not be empty. @param url The full request target used for route matching. - @param req The request to pass to handlers. - @param res The response to pass to handlers. + + @param p The params to pass to handlers. + @return The @ref route_result describing how routing completed. + @throws std::invalid_argument If @p verb is empty. */ auto dispatch( core::string_view verb, urls::url_view const& url, - Req& req, Res& res) -> + Params& p) -> route_result { // verb cannot be empty @@ -761,56 +815,61 @@ class basic_router : public /*detail::*/any_router detail::throw_invalid_argument(); return dispatch_impl( http_proto::method::unknown, - verb, url, req, res); + verb, url, p); } /** Resume dispatch after a detached handler. - + This continues routing after a previous call to @ref dispatch returned @ref route::detach. It recreates the routing state and resumes as if the handler that detached had instead returned - the specified @p ec from its body. The regular routing and + the specified @p rv from its body. The regular routing and error-dispatch logic then proceeds as described in the - @ref basic_router class documentation. For example, if @p ec is + @ref basic_router class documentation. For example, if @p rv is @ref route::next, the next matching handlers are invoked. @par Thread Safety + This function may be called concurrently on the same object along with other `const` member functions. Each concurrent invocation - must use distinct request and response objects. + must use distinct params objects. + + @param p The params to pass to handlers. - @param req The request to pass to handlers. - @param res The response to pass to handlers. @param rv The @ref route_result to resume with, as if returned - by the detached handler. + by the detached handler. + @return The @ref route_result describing how routing completed. */ auto resume( - Req& req, Res& res, + Params& p, route_result const& rv) const -> route_result { - return resume_impl(req, res, rv); + return resume_impl(p, rv); } private: - // used to avoid a race when modifying res.resume_ + // used to avoid a race when modifying p.resume_ struct set_resume { std::size_t& resume; bool cancel_ = true; + ~set_resume() { if(cancel_) resume = 0; } + set_resume( - basic_response& res) noexcept - : resume(res.resume_) + route_params_base& p) noexcept + : resume(p.resume_) { - resume = res.pos_; + resume = p.pos_; } + void apply() noexcept { cancel_ = false; @@ -840,12 +899,9 @@ class basic_router : public /*detail::*/any_router route_result invoke( - basic_request& req, - basic_response& res) const override + route_params_base& p) const override { - return invoke( - static_cast(req), - static_cast(res), Ty{}); + return invoke(static_cast(p), Ty{}); } private: @@ -870,19 +926,19 @@ class basic_router : public /*detail::*/any_router return 1 + h.count(); } - route_result invoke(Req&, Res&, + route_result invoke(Params&, std::integral_constant) const = delete; - // (Req, Res) - route_result invoke(Req& req, Res& res, + // ( Params& ) + route_result invoke(Params& p, std::integral_constant) const { - basic_response& res_(res); - if( res_.ec_.failed() || - res_.ep_) + route_params_base& p_(p); + if( p_.ec_.failed() || + p_.ep_) return http_proto::route::next; - set_resume u(res_); - auto rv = h(req, res); + set_resume u(p_); + auto rv = h(p); if(rv == http_proto::route::detach) { u.apply(); @@ -891,16 +947,16 @@ class basic_router : public /*detail::*/any_router return rv; } - // (Req&, Res&, error_code) + // ( Params&, error_code ) route_result - invoke(Req& req, Res& res, + invoke(Params& p, std::integral_constant) const { - basic_response& res_(res); - if(! res_.ec_.failed()) + route_params_base& p_(p); + if(! p_.ec_.failed()) return http_proto::route::next; - set_resume u(res_); - auto rv = h(req, res, res_.ec_); + set_resume u(p_); + auto rv = h(p, p_.ec_); if(rv == http_proto::route::detach) { u.apply(); @@ -910,14 +966,14 @@ class basic_router : public /*detail::*/any_router } // any_router - route_result invoke(Req& req, Res& res, + route_result invoke(Params& p, std::integral_constant) const { - basic_response& res_(res); - if( res_.resume_ > 0 || - ( ! res_.ec_.failed() && - ! res_.ep_)) - return h.dispatch_impl(req, res); + route_params_base& p_(p); + if( p_.resume_ > 0 || + ( ! p_.ec_.failed() && + ! p_.ep_)) + return h.dispatch_impl(p); return http_proto::route::next; } }; @@ -941,21 +997,21 @@ class basic_router : public /*detail::*/any_router } route_result - invoke(Req& req, Res& res) const override + invoke(Params& p) const override { #ifndef BOOST_NO_EXCEPTIONS - basic_response& res_(res); - if(! res_.ep_) + route_params_base& p_(p); + if(! p_.ep_) return http_proto::route::next; try { - std::rethrow_exception(res_.ep_); + std::rethrow_exception(p_.ep_); } catch(E const& ex) { - set_resume u(res_); + set_resume u(p_); // VFALCO What if h throws? - auto rv = h(req, res, ex); + auto rv = h(p, ex); if(rv == http_proto::route::detach) { u.apply(); @@ -965,12 +1021,11 @@ class basic_router : public /*detail::*/any_router } catch(...) { - BOOST_ASSERT(res_.ep_); + BOOST_ASSERT(p_.ep_); return http_proto::route::next; } #else - (void)req; - (void)res; + (void)p; return http_proto::route::next; #endif } @@ -1050,15 +1105,15 @@ class basic_router : public /*detail::*/any_router //----------------------------------------------- -template -class basic_router:: +template +class basic_router:: fluent_route { public: fluent_route(fluent_route const&) = default; /** Add handlers that apply to all HTTP methods. - + This registers regular handlers that run for any request matching the route's pattern, regardless of HTTP method. Handlers are appended to the route's handler sequence and are invoked in @@ -1068,16 +1123,18 @@ class basic_router:: This function returns a @ref fluent_route, allowing additional method registrations to be chained. For example: @code - router.route("/resource") - .all(log_request) - .get(show_resource) - .post(update_resource); + router.route( "/resource" ) + .all( log_request ) + .add( method::get, show_resource ) + .add( method::post, update_resource ); @endcode @param h1 The first handler to add. + @param hn Additional handlers to add, invoked after @p h1 in - registration order. - @return The @ref fluent_route for further chained registrations. + registration order. + + @return A reference to `*this` for chained registrations. */ template auto all( @@ -1094,7 +1151,7 @@ class basic_router:: } /** Add handlers for a specific HTTP method. - + This registers regular handlers for the given method on the current route, participating in dispatch as described in the @ref basic_router class documentation. Handlers are appended @@ -1103,10 +1160,13 @@ class basic_router:: Error handlers and routers cannot be passed here. @param verb The HTTP method to match. + @param h1 The first handler to add. + @param hn Additional handlers to add, invoked after @p h1 in - registration order. - @return The @ref fluent_route for further chained registrations. + registration order. + + @return A reference to `*this` for chained registrations. */ template auto add( @@ -1123,25 +1183,24 @@ class basic_router:: return *this; } - /** Add handlers for a method name. - + /** Add handlers for a method string. + This registers regular handlers for the given HTTP method string on the current route, participating in dispatch as described in the @ref basic_router class documentation. This overload is intended for methods not represented by @ref http_proto::method. Handlers are appended to the route's handler sequence and invoked in registration order whenever a preceding handler returns - @ref route::next. - - @par Constraints - @li Each handler must be a regular handler; error handlers and - routers cannot be passed. + @ref route::next. Error handlers and routers cannot be passed here. @param verb The HTTP method string to match. + @param h1 The first handler to add. + @param hn Additional handlers to add, invoked after @p h1 in - registration order. - @return The @ref fluent_route for further chained registrations. + registration order. + + @return A reference to `*this` for chained registrations. */ template auto add( @@ -1175,4 +1234,4 @@ class basic_router:: } // http_proto } // boost -#endif +#endif \ No newline at end of file diff --git a/include/boost/http_proto/server/cors.hpp b/include/boost/http_proto/server/cors.hpp index eb6dd288..fdfc0e1b 100644 --- a/include/boost/http_proto/server/cors.hpp +++ b/include/boost/http_proto/server/cors.hpp @@ -39,9 +39,7 @@ class cors BOOST_HTTP_PROTO_DECL route_result - operator()( - Request& req, - Response& res) const; + operator()(route_params& p) const; private: cors_options options_; diff --git a/include/boost/http_proto/server/route_handler.hpp b/include/boost/http_proto/server/route_handler.hpp index 1c7eaff4..399310d5 100644 --- a/include/boost/http_proto/server/route_handler.hpp +++ b/include/boost/http_proto/server/route_handler.hpp @@ -32,9 +32,10 @@ struct acceptor_config //----------------------------------------------- -/** Request object for HTTP route handlers +/** Parameters object for HTTP route handlers */ -struct Request : basic_request +struct BOOST_SYMBOL_VISIBLE + route_params : route_params_base { /** The complete request target @@ -46,33 +47,31 @@ struct Request : basic_request /** The HTTP request message */ - http_proto::request message; + http_proto::request req; + + /** The HTTP response message + */ + http_proto::response res; /** The HTTP request parser This can be used to take over reading the body. */ http_proto::request_parser parser; + /** The HTTP response serializer + */ + http_proto::serializer serializer; + /** A container for storing arbitrary data associated with the request. This starts out empty for each new request. */ - capy::datastore data; -}; - -//----------------------------------------------- + capy::datastore route_data; -/** Response object for HTTP route handlers -*/ -struct BOOST_SYMBOL_VISIBLE - Response : basic_response -{ - /** The HTTP response message - */ - http_proto::response message; + /** A container for storing arbitrary data associated with the session. - /** The HTTP response serializer + This starts out empty for each new session. */ - http_proto::serializer serializer; + capy::datastore session_data; /** The detacher for this session. This can be used to detach from the @@ -81,21 +80,10 @@ struct BOOST_SYMBOL_VISIBLE */ detacher detach; - /** A container for storing arbitrary data associated with the session. - - This starts out empty for each new session. - */ - capy::datastore data; - - /** Constructor. - */ - BOOST_HTTP_PROTO_DECL - Response(); - - /** Destructor. + /** Destructor */ BOOST_HTTP_PROTO_DECL - virtual ~Response(); + ~route_params(); /** Reset the object for a new request. This clears any state associated with @@ -108,17 +96,17 @@ struct BOOST_SYMBOL_VISIBLE /** Set the status code of the response. @par Example @code - res.status(http_proto::status::not_found); + res.status( http_proto::status::not_found ); @endcode @param code The status code to set. @return A reference to this response. */ BOOST_HTTP_PROTO_DECL - Response& + route_params& status(http_proto::status code); BOOST_HTTP_PROTO_DECL - Response& + route_params& set_body(std::string s); // VFALCO this doc isn't quite right because it doesn't explain @@ -185,7 +173,7 @@ struct BOOST_SYMBOL_VISIBLE template auto -Response:: +route_params:: post(F&& f) -> route_result { // task already posted @@ -210,9 +198,9 @@ post(F&& f) -> route_result , public detacher::owner { public: - model(Response& res, + model(route_params& p, F&& f, resumer resume) - : res_(res) + : p_(p) , f_(std::forward(f)) , resume_(resume) { @@ -231,12 +219,12 @@ post(F&& f) -> route_result { resumed_ = true; resumer resume(resume_); - res_.task_.reset(); // destroys *this + p_.task_.reset(); // destroys *this resume(rv); } private: - Response& res_; + route_params& p_; typename std::decay::type f_; resumer resume_; bool resumed_; diff --git a/include/boost/http_proto/server/router_types.hpp b/include/boost/http_proto/server/router_types.hpp index f833aa94..39f2306f 100644 --- a/include/boost/http_proto/server/router_types.hpp +++ b/include/boost/http_proto/server/router_types.hpp @@ -288,7 +288,7 @@ operator()(F&& f) -> namespace detail { class any_router; } // detail -template +template class basic_router; /** Base class for request objects @@ -296,7 +296,7 @@ class basic_router; This is a required public base for any `Request` type used with @ref basic_router. */ -class basic_request +class route_params_base { public: /** The mount path of the current router @@ -314,36 +314,25 @@ class basic_request private: friend class /*detail::*/any_router; + template + friend class basic_router; struct match_result; - http_proto::method verb_ = - http_proto::method::unknown; + route_params_base& operator=( + route_params_base const&) = delete; + std::string verb_str_; std::string decoded_path_; + system::error_code ec_; + std::exception_ptr ep_; + std::size_t pos_ = 0; + std::size_t resume_ = 0; + http_proto::method verb_ = + http_proto::method::unknown; bool addedSlash_ = false; bool case_sensitive = false; bool strict = false; }; -//----------------------------------------------- - -/** Base class for response objects - - This is a required public base for any `Response` - type used with @ref basic_router. -*/ -class basic_response -{ -private: - friend class /*detail::*/any_router; - template - friend class basic_router; - - std::size_t pos_ = 0; - std::size_t resume_ = 0; - system::error_code ec_; - std::exception_ptr ep_; -}; - } // http_proto } // boost diff --git a/src/server/basic_router.cpp b/src/server/basic_router.cpp index ef3956a5..a87c98c3 100644 --- a/src/server/basic_router.cpp +++ b/src/server/basic_router.cpp @@ -199,50 +199,50 @@ pct_decode_path( //} // detail -struct basic_request:: +struct route_params_base:: match_result { void adjust_path( - basic_request& req, + route_params_base& p, std::size_t n) { n_ = n; if(n_ == 0) return; - req.base_path = { - req.base_path.data(), - req.base_path.size() + n_ }; - if(n_ < req.path.size()) + p.base_path = { + p.base_path.data(), + p.base_path.size() + n_ }; + if(n_ < p.path.size()) { - req.path.remove_prefix(n_); + p.path.remove_prefix(n_); } else { // append a soft slash - req.path = { req.decoded_path_.data() + - req.decoded_path_.size() - 1, 1}; - BOOST_ASSERT(req.path == "/"); + p.path = { p.decoded_path_.data() + + p.decoded_path_.size() - 1, 1}; + BOOST_ASSERT(p.path == "/"); } } void restore_path( - basic_request& req) + route_params_base& p) { if( n_ > 0 && - req.addedSlash_ && - req.path.data() == - req.decoded_path_.data() + - req.decoded_path_.size() - 1) + p.addedSlash_ && + p.path.data() == + p.decoded_path_.data() + + p.decoded_path_.size() - 1) { // remove soft slash - req.path = { - req.base_path.data() + - req.base_path.size(), 0 }; + p.path = { + p.base_path.data() + + p.base_path.size(), 0 }; } - req.base_path.remove_suffix(n_); - req.path = { - req.path.data() - n_, - req.path.size() + n_ }; + p.base_path.remove_suffix(n_); + p.path = { + p.path.data() - n_, + p.path.size() + n_ }; } private: @@ -278,30 +278,30 @@ struct any_router::matcher decoded_pat_, path_rule).value(); } - /** Return true if req.path is a match + /** Return true if p.path is a match */ bool operator()( - basic_request& req, + route_params_base& p, match_result& mr) const { - BOOST_ASSERT(! req.path.empty()); + BOOST_ASSERT(! p.path.empty()); if( slash_ && ( ! end || - req.path == "/")) + p.path == "/")) { // params = {}; - mr.adjust_path(req, 0); + mr.adjust_path(p, 0); return true; } - auto it = req.path.data(); + auto it = p.path.data(); auto pit = pv_.segs.begin(); - auto const end_ = it + req.path.size(); + auto const end_ = it + p.path.size(); auto const pend = pv_.segs.end(); while(it != end_ && pit != pend) { // prefix has to match auto s = core::string_view(it, end_); - if(! req.case_sensitive) + if(! p.case_sensitive) { if(pit->prefix.size() > s.size()) return false; @@ -330,8 +330,8 @@ struct any_router::matcher return false; } // number of matching characters - auto const n = it - req.path.data(); - mr.adjust_path(req, n); + auto const n = it - p.path.data(); + mr.adjust_path(p, n); return true; } @@ -386,15 +386,15 @@ struct any_router::layer } bool match_method( - basic_request const& req) const noexcept + route_params_base const& p) const noexcept { if(all) return true; if(verb != http_proto::method::unknown) - return req.verb_ == verb; - if(req.verb_ != http_proto::method::unknown) + return p.verb_ == verb; + if(p.verb_ != http_proto::method::unknown) return false; - return req.verb_str_ == verb_str; + return p.verb_str_ == verb_str; } }; @@ -588,11 +588,11 @@ add_impl( auto any_router:: resume_impl( - basic_request& req, basic_response& res, + route_params_base& p, route_result ec) const -> route_result { - BOOST_ASSERT(res.resume_ > 0); + BOOST_ASSERT(p.resume_ > 0); if( ec == route::send || ec == route::close || ec == route::complete) @@ -605,16 +605,16 @@ resume_impl( } // restore base_path and path - req.base_path = { req.decoded_path_.data(), 0 }; - req.path = req.decoded_path_; - if(req.addedSlash_) - req.path.remove_suffix(1); + p.base_path = { p.decoded_path_.data(), 0 }; + p.path = p.decoded_path_; + if(p.addedSlash_) + p.path.remove_suffix(1); // resume_ was set in the handler's wrapper - BOOST_ASSERT(res.resume_ == res.pos_); - res.pos_ = 0; - res.ec_ = ec; - return do_dispatch(req, res); + BOOST_ASSERT(p.resume_ == p.pos_); + p.pos_ = 0; + p.ec_ = ec; + return do_dispatch(p); } // top-level dispatch that gets called first @@ -624,53 +624,56 @@ dispatch_impl( http_proto::method verb, core::string_view verb_str, urls::url_view const& url, - basic_request& req, - basic_response& res) const + route_params_base& p) const { - // VFALCO we could reuse the string storage by not clearing them - // set req.case_sensitive, req.strict to default of false - req = {}; + p.verb_str_.clear(); + p.decoded_path_.clear(); + p.ec_.clear(); + p.ep_ = nullptr; + p.pos_ = 0; + p.resume_ = 0; + p.addedSlash_ = false; + p.case_sensitive = false; + p.strict = false; + if(verb == http_proto::method::unknown) { BOOST_ASSERT(! verb_str.empty()); verb = http_proto::string_to_method(verb_str); if(verb == http_proto::method::unknown) - req.verb_str_ = verb_str; + p.verb_str_ = verb_str; } else { BOOST_ASSERT(verb_str.empty()); } - req.verb_ = verb; + p.verb_ = verb; + // VFALCO use reusing-StringToken - req.decoded_path_ = + p.decoded_path_ = pct_decode_path(url.encoded_path()); - BOOST_ASSERT(! req.decoded_path_.empty()); - req.base_path = { req.decoded_path_.data(), 0 }; - req.path = req.decoded_path_; - if(req.decoded_path_.back() != '/') + BOOST_ASSERT(! p.decoded_path_.empty()); + p.base_path = { p.decoded_path_.data(), 0 }; + p.path = p.decoded_path_; + if(p.decoded_path_.back() != '/') { - req.decoded_path_.push_back('/'); - req.addedSlash_ = true; + p.decoded_path_.push_back('/'); + p.addedSlash_ = true; } - BOOST_ASSERT(req.case_sensitive == false); - BOOST_ASSERT(req.strict == false); - - res = {}; // we cannot do anything after do_dispatch returns, // other than return the route_result, or else we // could race with the detached operation trying to resume. - auto rv = do_dispatch(req, res); + auto rv = do_dispatch(p); if(rv == route::detach) return rv; - if(res.ep_) + if(p.ep_) { - res.ep_ = nullptr; + p.ep_ = nullptr; return error::unhandled_exception; } - if( res.ec_.failed()) - res.ec_ = {}; + if( p.ec_.failed()) + p.ec_ = {}; return rv; } @@ -678,81 +681,80 @@ dispatch_impl( route_result any_router:: dispatch_impl( - basic_request& req, - basic_response& res) const + route_params_base& p) const { // options are recursive and need to be restored on // exception or when returning to a calling router. struct option_saver { option_saver( - basic_request& req) noexcept - : req_(&req) - , case_sensitive_(req.case_sensitive) - , strict_(req.strict) + route_params_base& p) noexcept + : p_(&p) + , case_sensitive_(p.case_sensitive) + , strict_(p.strict) { } ~option_saver() { - if(! req_) + if(! p_) return; - req_->case_sensitive = case_sensitive_; - req_->strict = strict_; + p_->case_sensitive = case_sensitive_; + p_->strict = strict_; }; void cancel() noexcept { - req_ = nullptr; + p_ = nullptr; } private: - basic_request* req_; + route_params_base* p_; bool case_sensitive_; bool strict_; }; - option_saver restore_options(req); + option_saver restore_options(p); // inherit or apply options if((impl_->opt & 2) != 0) - req.case_sensitive = true; + p.case_sensitive = true; else if((impl_->opt & 4) != 0) - req.case_sensitive = false; + p.case_sensitive = false; if((impl_->opt & 8) != 0) - req.strict = true; + p.strict = true; else if((impl_->opt & 16) != 0) - req.strict = false; + p.strict = false; match_result mr; for(auto const& i : impl_->layers) { - if(res.resume_ > 0) + if(p.resume_ > 0) { auto const n = i.count(); // handlers in layer - if(res.pos_ + n < res.resume_) + if(p.pos_ + n < p.resume_) { - res.pos_ += n; // skip layer + p.pos_ += n; // skip layer continue; } // repeat match to recreate the stack - bool is_match = i.match(req, mr); + bool is_match = i.match(p, mr); BOOST_ASSERT(is_match); (void)is_match; } else { - if(i.match.end && res.ec_.failed()) + if(i.match.end && p.ec_.failed()) { // routes can't have error handlers - res.pos_ += i.count(); // skip layer + p.pos_ += i.count(); // skip layer continue; } - if(! i.match(req, mr)) + if(! i.match(p, mr)) { // not a match - res.pos_ += i.count(); // skip layer + p.pos_ += i.count(); // skip layer continue; } } @@ -760,51 +762,51 @@ dispatch_impl( it != i.entries.end(); ++it) { auto const& e(*it); - if(res.resume_) + if(p.resume_) { auto const n = e.handler->count(); - if(res.pos_ + n < res.resume_) + if(p.pos_ + n < p.resume_) { - res.pos_ += n; // skip entry + p.pos_ += n; // skip entry continue; } - BOOST_ASSERT(e.match_method(req)); + BOOST_ASSERT(e.match_method(p)); } else if(i.match.end) { // check verb for match - if(! e.match_method(req)) + if(! e.match_method(p)) { - res.pos_ += e.handler->count(); // skip entry + p.pos_ += e.handler->count(); // skip entry continue; } } route_result rv; // increment before invoke - ++res.pos_; - if(res.pos_ != res.resume_) + ++p.pos_; + if(p.pos_ != p.resume_) { // call the handler #ifdef BOOST_NO_EXCEPTIONS - rv = e.handler->invoke(req, res); + rv = e.handler->invoke(p); #else try { - rv = e.handler->invoke(req, res); - if(res.ec_.failed()) - res.ep_ = {}; // transition to error mode + rv = e.handler->invoke(p); + if(p.ec_.failed()) + p.ep_ = {}; // transition to error mode } catch(...) { - if(res.ec_.failed()) - res.ec_ = {}; // transition to except mode - res.ep_ = std::current_exception(); + if(p.ec_.failed()) + p.ec_ = {}; // transition to except mode + p.ep_ = std::current_exception(); rv = route::next; } #endif - // res.pos_ can be incremented further + // p.pos_ can be incremented further // inside the above call to invoke. if(rv == route::detach) { @@ -821,21 +823,21 @@ dispatch_impl( // a subrouter never detaches on its own BOOST_ASSERT(e.handler->count() == 1); // can't detach on resume - if(res.ec_ == route::detach) + if(p.ec_ == route::detach) detail::throw_invalid_argument(); // do resume - res.resume_ = 0; - rv = res.ec_; - res.ec_ = {}; + p.resume_ = 0; + rv = p.ec_; + p.ec_ = {}; } if( rv == route::send || rv == route::complete || rv == route::close) { - if( res.ec_.failed()) - res.ec_ = {}; - if( res.ep_) - res.ep_ = nullptr; + if( p.ec_.failed()) + p.ec_ = {}; + if( p.ep_) + p.ep_ = nullptr; return rv; } if(rv == route::next) @@ -846,7 +848,7 @@ dispatch_impl( if(! i.match.end) detail::throw_invalid_argument(); while(++it != i.entries.end()) - res.pos_ += it->handler->count(); + p.pos_ += it->handler->count(); break; // skip remaining entries } // we must handle all route enums @@ -857,16 +859,16 @@ dispatch_impl( detail::throw_invalid_argument(); } // error handling mode - res.ec_ = rv; + p.ec_ = rv; if(! i.match.end) continue; // next entry // routes don't have error handlers while(++it != i.entries.end()) - res.pos_ += it->handler->count(); + p.pos_ += it->handler->count(); break; // skip remaining entries } - mr.restore_path(req); + mr.restore_path(p); } return route::next; @@ -875,10 +877,9 @@ dispatch_impl( route_result any_router:: do_dispatch( - basic_request& req, - basic_response& res) const + route_params_base& p) const { - auto rv = dispatch_impl(req, res); + auto rv = dispatch_impl(p); BOOST_ASSERT(is_route_result(rv)); BOOST_ASSERT(rv != route::next_route); if(rv != route::next) @@ -887,13 +888,13 @@ do_dispatch( // without attempting to perform any additional operations. return rv; } - if(! res.ec_.failed()) + if(! p.ec_.failed()) { // unhandled route return route::next; } // error condition - return res.ec_; + return p.ec_; } //} // detail diff --git a/src/server/cors.cpp b/src/server/cors.cpp index 4293f6a0..25f5b5a6 100644 --- a/src/server/cors.cpp +++ b/src/server/cors.cpp @@ -25,34 +25,34 @@ namespace { struct Vary { - Vary(Response& res) - : res_(res) + Vary(route_params& p) + : p_(p) { } void set(field f, core::string_view s) { - res_.message.set(f, s); + p_.res.set(f, s); } void append(field f, core::string_view v) { - auto it = res_.message.find(f); - if (it != res_.message.end()) + auto it = p_.res.find(f); + if (it != p_.res.end()) { std::string s = it->value; s += ", "; s += v; - res_.message.set(it, s); + p_.res.set(it, s); } else { - res_.message.set(f, v); + p_.res.set(f, v); } } private: - Response& res_; + route_params& p_; std::string v_; }; @@ -61,7 +61,7 @@ struct Vary // Access-Control-Allow-Origin static void setOrigin( Vary& v, - Request const&, + route_params const&, cors_options const& options) { if( options.origin.empty() || @@ -109,7 +109,7 @@ static void setCredentials( // Access-Control-Allowed-Headers static void setAllowedHeaders( Vary& v, - Request const& req, + route_params const& p, cors_options const& options) { if(! options.allowedHeaders.empty()) @@ -119,13 +119,11 @@ static void setAllowedHeaders( options.allowedHeaders); return; } - auto s = req.message.value_or( + auto s = p.res.value_or( field::access_control_request_headers, ""); if(! s.empty()) { - v.set( - field::access_control_allow_headers, - s); + v.set(field::access_control_allow_headers, s); v.append(field::vary, s); } } @@ -158,31 +156,30 @@ static void setMaxAge( route_result cors:: operator()( - Request& req, - Response& res) const + route_params& p) const { - Vary v(res); - if(req.message.method() == + Vary v(p); + if(p.req.method() == method::options) { // preflight - setOrigin(v, req, options_); + setOrigin(v, p, options_); setMethods(v, options_); setCredentials(v, options_); - setAllowedHeaders(v, req, options_); + setAllowedHeaders(v, p, options_); setMaxAge(v, options_); setExposeHeaders(v, options_); if(options_.preFligthContinue) return route::next; // Safari and others need this for 204 or may hang - res.message.set_status(options_.result); - res.message.set_content_length(0); - res.serializer.start(res.message); + p.res.set_status(options_.result); + p.res.set_content_length(0); + p.serializer.start(p.res); return route::send; } // actual response - setOrigin(v, req, options_); + setOrigin(v, p, options_); setCredentials(v, options_); setExposeHeaders(v, options_); return route::next; diff --git a/src/server/route_handler.cpp b/src/server/route_handler.cpp index 4ad459a7..5aad08e1 100644 --- a/src/server/route_handler.cpp +++ b/src/server/route_handler.cpp @@ -15,49 +15,44 @@ namespace boost { namespace http_proto { -Response:: -Response() +route_params:: +~route_params() { } -Response:: -~Response() -{ -} - -Response& -Response:: +route_params& +route_params:: status( http_proto::status code) { - message.set_start_line(code, message.version()); + res.set_start_line(code, res.version()); return *this; } -Response& -Response:: +route_params& +route_params:: set_body(std::string s) { - if(! message.exists(http_proto::field::content_type)) + if(! res.exists(http_proto::field::content_type)) { // VFALCO TODO detect Content-Type - message.set(http_proto::field::content_type, + res.set(http_proto::field::content_type, "text/plain; charset=UTF-8"); } - if(!message.exists(http_proto::field::content_length)) + if(! res.exists(field::content_length)) { - message.set_payload_size(s.size()); + res.set_payload_size(s.size()); } - serializer.start(message, + serializer.start(res, http_proto::string_body(std::string(s))); return *this; } void -Response:: +route_params:: do_post() { BOOST_ASSERT(task_); diff --git a/test/unit/server/basic_router.cpp b/test/unit/server/basic_router.cpp index a476ea1c..07254845 100644 --- a/test/unit/server/basic_router.cpp +++ b/test/unit/server/basic_router.cpp @@ -23,61 +23,25 @@ struct basic_router_test { void compileTimeTests() { - struct Req : basic_request {}; - struct Res : basic_response {}; - struct OtherReq : basic_request {}; + struct params : route_params_base {}; - BOOST_CORE_STATIC_ASSERT(std::is_copy_assignable>::value); + BOOST_CORE_STATIC_ASSERT(std::is_copy_assignable>::value); struct h0 { void operator()(); }; struct h1 { system::error_code operator()(); }; struct h2 { system::error_code operator()(int); }; - struct h3 { system::error_code operator()(Req&, Res&) const; }; - struct h4 { system::error_code operator()(Req&, Res&, system::error_code) const; }; - struct h5 { void operator()(Req&, Res&) {} }; - struct h6 { void operator()(Req&, Res&, system::error_code) {} }; - struct h7 { system::error_code operator()(Req&, Res&, int); }; - struct h8 { system::error_code operator()(Req, Res&, int); }; - struct h9 { system::error_code operator()(Req, Res&, system::error_code const&) const; }; - -#if 0 - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 1); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 1); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 1); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value == 1); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 1); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 1); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 1); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 1); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 1); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 1); - - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 2); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 2); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 2); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 2); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value == 2); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 2); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 2); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 2); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 2); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value == 2); - - BOOST_CORE_STATIC_ASSERT(detail::handler_type< - basic_router, Req, Res>::value == 4); - BOOST_CORE_STATIC_ASSERT(detail::handler_type< - basic_router, Req, Res>::value == 4); - BOOST_CORE_STATIC_ASSERT(detail::handler_type< - basic_router, Req, Res>::value == 4); - BOOST_CORE_STATIC_ASSERT(detail::handler_type< - basic_router, Req, Res>::value == 0); -#endif + struct h3 { system::error_code operator()(params&) const; }; + struct h4 { system::error_code operator()(params&, system::error_code) const; }; + struct h5 { void operator()(params&) {} }; + struct h6 { void operator()(params&, system::error_code) {} }; + struct h7 { system::error_code operator()(params&, int); }; + struct h8 { system::error_code operator()(params, int); }; + struct h9 { system::error_code operator()(params, system::error_code const&) const; }; } - //-------------------------------------------- + using params = route_params_base; - using Req = basic_request; - using Res = basic_response; + //-------------------------------------------- /** A handler for testing */ @@ -107,7 +71,7 @@ struct basic_router_test ec_ = other.ec_; } - route_result operator()(Req&, Res&) const + route_result operator()(params&) const { called_ = true; switch(want_) @@ -153,7 +117,7 @@ struct basic_router_test other.alive_ = false; } - route_result operator()(Req&, Res&) const + route_result operator()(params&) const { called_ = true; throw E("ex"); @@ -193,7 +157,7 @@ struct basic_router_test } route_result operator()( - Req&, Res&, system::error_code ec) const + params&, system::error_code ec) const { called_ = true; switch(want_) @@ -252,7 +216,7 @@ struct basic_router_test } route_result operator()( - Req&, Res&, E const&) const + params&, E const&) const { called_ = true; switch(want_) @@ -302,7 +266,7 @@ struct basic_router_test , path_(path) { } - route_result operator()(Req& req, Res&) const + route_result operator()(params& req) const { called_ = true; BOOST_TEST_EQ(req.base_path, base_path_); @@ -395,18 +359,17 @@ struct basic_router_test return ex_handler(2); } - using test_router = basic_router; + using test_router = basic_router; void check( test_router& r, core::string_view url, route_result rv0 = route::send) { - Req req; - Res res; + params req; auto rv = r.dispatch( http_proto::method::get, - urls::url_view(url), req, res); + urls::url_view(url), req); if(BOOST_TEST_EQ(rv.message(), rv0.message())) BOOST_TEST(rv == rv0); } @@ -417,10 +380,9 @@ struct basic_router_test core::string_view url, route_result rv0 = route::send) { - Req req; - Res res; + params req; auto rv = r.dispatch(verb, - urls::url_view(url), req, res); + urls::url_view(url), req); if(BOOST_TEST_EQ(rv.message(), rv0.message())) BOOST_TEST(rv == rv0); } @@ -431,10 +393,9 @@ struct basic_router_test core::string_view url, route_result rv0 = route::send) { - Req req; - Res res; + params req; auto rv = r.dispatch(verb, - urls::url_view(url), req, res); + urls::url_view(url), req); if(BOOST_TEST_EQ(rv.message(), rv0.message())) BOOST_TEST(rv == rv0); } @@ -1445,17 +1406,14 @@ struct basic_router_test { test_router r; r.use( pat, - [&](Req& req, Res&) + [&](params& req) { BOOST_TEST_EQ(req.path, good); return route::send; }); - Req req; - Res res; - r.dispatch( - http_proto::method::get, - urls::url_view(target), - req, res); + params req; + r.dispatch(http_proto::method::get, + urls::url_view(target), req); }; path("/", "/", "/"); @@ -1519,9 +1477,8 @@ struct basic_router_test test_router r; r.use(next()); r.use(fail(route::detach)); - Req req; - Res res; - auto rv1 = r.dispatch(GET, urls::url_view("/"), req, res); + params req; + auto rv1 = r.dispatch(GET, urls::url_view("/"), req); BOOST_TEST(rv1 == route::detach); } { @@ -1530,11 +1487,10 @@ struct basic_router_test r.use(fail(route::detach)); r.use(next()); r.use(fail(route::send)); - Req req; - Res res; - auto rv1 = r.dispatch(GET, urls::url_view("/"), req, res); + params req; + auto rv1 = r.dispatch(GET, urls::url_view("/"), req); BOOST_TEST(rv1 == route::detach); - auto rv2 = r.resume(req, res, route::next); + auto rv2 = r.resume(req, route::next); BOOST_TEST(rv2 == route::send); } { @@ -1549,11 +1505,10 @@ struct basic_router_test next()); return r; }()); r.use(send()); - Req req; - Res res; - auto rv1 = r.dispatch(GET, urls::url_view("/"), req, res); + params req; + auto rv1 = r.dispatch(GET, urls::url_view("/"), req); BOOST_TEST(rv1 == route::detach); - auto rv2 = r.resume(req, res, route::next); + auto rv2 = r.resume(req, route::next); BOOST_TEST(rv2 == route::send); } @@ -1561,30 +1516,29 @@ struct basic_router_test { test_router r; r.use(fail(route::detach)); - Req req; - Res res; + params req; { - auto rv1 = r.dispatch(GET, urls::url_view("/"), req, res); + auto rv1 = r.dispatch(GET, urls::url_view("/"), req); BOOST_TEST(rv1 == route::detach); - auto rv2 = r.resume(req, res, route::send); + auto rv2 = r.resume(req, route::send); BOOST_TEST(rv2 == route::send); } { - auto rv1 = r.dispatch(GET, urls::url_view("/"), req, res); + auto rv1 = r.dispatch(GET, urls::url_view("/"), req); BOOST_TEST(rv1 == route::detach); - auto rv2 = r.resume(req, res, route::close); + auto rv2 = r.resume(req, route::close); BOOST_TEST(rv2 == route::close); } { - auto rv1 = r.dispatch(GET, urls::url_view("/"), req, res); + auto rv1 = r.dispatch(GET, urls::url_view("/"), req); BOOST_TEST(rv1 == route::detach); - auto rv2 = r.resume(req, res, route::complete); + auto rv2 = r.resume(req, route::complete); BOOST_TEST(rv2 == route::complete); } { - auto rv1 = r.dispatch(GET, urls::url_view("/"), req, res); + auto rv1 = r.dispatch(GET, urls::url_view("/"), req); BOOST_TEST(rv1 == route::detach); - BOOST_TEST_THROWS(r.resume(req, res, system::error_code()), + BOOST_TEST_THROWS(r.resume(req, system::error_code()), std::invalid_argument); } } @@ -1602,11 +1556,10 @@ struct basic_router_test next()); return r; }()); r.use("/api", send()); - Req req; - Res res; - auto rv1 = r.dispatch(GET, urls::url_view("/api"), req, res); + params req; + auto rv1 = r.dispatch(GET, urls::url_view("/api"), req); BOOST_TEST(rv1 == route::detach); - auto rv2 = r.resume(req, res, route::next); + auto rv2 = r.resume(req, route::next); BOOST_TEST(rv2 == route::send); } @@ -1614,11 +1567,10 @@ struct basic_router_test { test_router r; r.use(fail(route::detach)); - Req req; - Res res; - auto rv1 = r.dispatch(GET, urls::url_view("/"), req, res); + params req; + auto rv1 = r.dispatch(GET, urls::url_view("/"), req); BOOST_TEST(rv1 == route::detach); - BOOST_TEST_THROWS(r.resume(req, res, route::detach), + BOOST_TEST_THROWS(r.resume(req, route::detach), std::invalid_argument); } @@ -1626,17 +1578,16 @@ struct basic_router_test { test_router r; r.use(fail(route::detach)); - Req req; - Res res; - auto rv1 = r.dispatch(GET, urls::url_view("/"), req, res); + params req; + auto rv1 = r.dispatch(GET, urls::url_view("/"), req); BOOST_TEST(rv1 == route::detach); - BOOST_TEST_THROWS(r.resume(req, res, system::error_code()), + BOOST_TEST_THROWS(r.resume(req, system::error_code()), std::invalid_argument); } } static route_result func( - Req&, Res&) + params&) { return route::send; } diff --git a/test/unit/server/route_handler.cpp b/test/unit/server/route_handler.cpp index 24673040..264a0b4f 100644 --- a/test/unit/server/route_handler.cpp +++ b/test/unit/server/route_handler.cpp @@ -21,7 +21,7 @@ namespace http_proto { struct route_handler_test { using test_router = - basic_router; + basic_router; void check( test_router& r, @@ -29,10 +29,9 @@ struct route_handler_test core::string_view url, route_result rv0 = route::send) { - Request req; - Response res; + route_params p; auto rv = r.dispatch( - verb, urls::url_view(url), req, res); + verb, urls::url_view(url), p); if(BOOST_TEST_EQ(rv.message(), rv0.message())) BOOST_TEST(rv == rv0); } @@ -53,37 +52,37 @@ struct route_handler_test test_router r; r.use( - [](Request&, Response& res) + [](route_params& p) { // create session_token - auto& st = res.data.try_emplace(); + auto& st = p.session_data.try_emplace(); BOOST_TEST_EQ(st.valid, false); return route::next; }); r.use("/user", - [](Request&, Response& res) + [](route_params& p) { // make session token valid - auto* st = res.data.find(); + auto* st = p.session_data.find(); if(BOOST_TEST_NE(st, nullptr)) st->valid = true; return route::next; }); r.route("/user/auth") .add(POST, - [](Request& req, Response& res) + [](route_params& p) { - auto& st = res.data.get(); + auto& st = p.session_data.get(); BOOST_TEST_EQ(st.valid, true); // create auth_token each time - auto& at = req.data.emplace(); + auto& at = p.route_data.emplace(); at.valid = true; return route::next; }, - [](Request& req, Response& res) + [](route_params& p) { - auto& at = req.data.get(); - auto& st = res.data.get(); + auto& at = p.route_data.get(); + auto& st = p.session_data.get(); BOOST_TEST_EQ(at.valid, true); BOOST_TEST_EQ(st.valid, true); return route::send;