LCOV - code coverage report
Current view: top level - include/boost/http/server - route_handler.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 92.9 % 28 26
Test Date: 2026-02-09 01:37:05 Functions: 91.7 % 12 11

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
       3              : //
       4              : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       5              : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       6              : //
       7              : // Official repository: https://github.com/cppalliance/http
       8              : //
       9              : 
      10              : #ifndef BOOST_HTTP_SERVER_ROUTE_HANDLER_HPP
      11              : #define BOOST_HTTP_SERVER_ROUTE_HANDLER_HPP
      12              : 
      13              : #include <boost/http/detail/config.hpp>
      14              : #include <boost/http/method.hpp>
      15              : #include <boost/http/detail/except.hpp>
      16              : #include <boost/http/datastore.hpp>
      17              : #include <boost/http/request.hpp>
      18              : #include <boost/http/response.hpp>
      19              : #include <boost/core/detail/string_view.hpp>
      20              : #include <boost/capy/buffers.hpp>
      21              : #include <boost/capy/buffers/make_buffer.hpp>
      22              : #include <boost/capy/io_result.hpp>
      23              : #include <boost/capy/io_task.hpp>
      24              : #include <boost/capy/task.hpp>
      25              : #include <boost/capy/write.hpp>
      26              : #include <boost/capy/io/any_buffer_source.hpp>
      27              : #include <boost/capy/io/any_buffer_sink.hpp>
      28              : #include <boost/url/url_view.hpp>
      29              : #include <boost/system/error_category.hpp>
      30              : #include <boost/system/error_code.hpp>
      31              : #include <concepts>
      32              : #include <exception>
      33              : #include <memory>
      34              : #include <span>
      35              : #include <string>
      36              : #include <type_traits>
      37              : #include <utility>
      38              : #include <vector>
      39              : 
      40              : namespace boost {
      41              : namespace http {
      42              : 
      43              : /** Directive values for route handler results.
      44              : 
      45              :     These values indicate how the router should proceed
      46              :     after a handler completes. Handlers return one of
      47              :     the predefined constants (@ref route_done, @ref route_next,
      48              :     @ref route_next_route, @ref route_close) or an error code.
      49              : 
      50              :     @see route_result, route_task
      51              : */
      52              : enum class route_what
      53              : {
      54              :     /// Handler completed successfully, response was sent
      55              :     done,
      56              : 
      57              :     /// Handler declined, try next handler in the route
      58              :     next,
      59              : 
      60              :     /// Handler declined, skip to next matching route
      61              :     next_route,
      62              : 
      63              :     /// Handler requests connection closure
      64              :     close,
      65              : 
      66              :     /// Handler encountered an error
      67              :     error
      68              : };
      69              : 
      70              : //------------------------------------------------
      71              : 
      72              : /** The result type returned by route handlers.
      73              : 
      74              :     This class represents the outcome of a route handler.
      75              :     Handlers return this type to indicate how the router
      76              :     should proceed. Construct from a directive constant
      77              :     or an error code:
      78              : 
      79              :     @code
      80              :     route_task my_handler(route_params& p)
      81              :     {
      82              :         if(! authorized(p))
      83              :             co_return route_next;        // try next handler
      84              : 
      85              :         if(auto ec = process(p); ec)
      86              :             co_return ec;                // return error
      87              : 
      88              :         co_return route_done;            // success
      89              :     }
      90              :     @endcode
      91              : 
      92              :     @par Checking Results
      93              : 
      94              :     Use @ref what() to determine the directive, and
      95              :     @ref error() to retrieve any error code:
      96              : 
      97              :     @code
      98              :     route_result rv = co_await handler(p);
      99              :     if(rv.what() == route_what::error)
     100              :         handle_error(rv.error());
     101              :     @endcode
     102              : 
     103              :     @see route_task, route_what, route_done, route_next
     104              : */
     105              : class BOOST_HTTP_DECL
     106              :     route_result
     107              : {
     108              :     system::error_code ec_;
     109              : 
     110              :     template<route_what T>
     111              :     struct what_t {};
     112              : 
     113              :     route_result(system::error_code ec);
     114              :     void set(route_what w);
     115              : 
     116              : public:
     117           59 :     route_result() = default;
     118              : 
     119              :     /** Construct from a directive constant.
     120              : 
     121              :         This constructor allows implicit conversion from
     122              :         the predefined constants (@ref route_done, @ref route_next,
     123              :         @ref route_next_route, @ref route_close).
     124              : 
     125              :         @code
     126              :         route_task handler(route_params& p)
     127              :         {
     128              :             co_return route_done;  // implicitly converts
     129              :         }
     130              :         @endcode
     131              :     */
     132              :     template<route_what W>
     133          244 :     route_result(what_t<W>)
     134          244 :     {
     135              :         static_assert(W != route_what::error);
     136          244 :         set(W);
     137          244 :     }
     138              : 
     139              :     /** Return the directive for this result.
     140              : 
     141              :         Call this to determine how the router should proceed:
     142              : 
     143              :         @code
     144              :         route_result rv = co_await handler(p);
     145              :         switch(rv.what())
     146              :         {
     147              :         case route_what::done:
     148              :             // response sent, done with request
     149              :             break;
     150              :         case route_what::next:
     151              :             // try next handler
     152              :             break;
     153              :         case route_what::error:
     154              :             log_error(rv.error());
     155              :             break;
     156              :         }
     157              :         @endcode
     158              : 
     159              :         @return The directive value.
     160              :     */
     161              :     auto
     162              :     what() const noexcept ->
     163              :         route_what;
     164              : 
     165              :     /** Return the error code, if any.
     166              : 
     167              :         If @ref what() returns `route_what::error`, this
     168              :         returns the underlying error code. Otherwise returns
     169              :         a default-constructed (non-failing) error code.
     170              : 
     171              :         @return The error code, or a non-failing code.
     172              :     */
     173              :     auto
     174              :     error() const noexcept ->
     175              :         system::error_code;
     176              : 
     177              :     /** Return true if the result indicates an error.
     178              : 
     179              :         @return `true` if @ref what() equals `route_what::error`.
     180              :     */
     181           66 :     bool failed() const noexcept
     182              :     {
     183           66 :         return what() == route_what::error;
     184              :     }
     185              : 
     186              :     static constexpr route_result::what_t<route_what::done> route_done{};
     187              :     static constexpr route_result::what_t<route_what::next> route_next{};
     188              :     static constexpr route_result::what_t<route_what::next_route> route_next_route{};
     189              :     static constexpr route_result::what_t<route_what::close> route_close{};
     190              :     friend route_result route_error(system::error_code ec) noexcept;
     191              : 
     192              :     template<class E>
     193              :     friend auto route_error(E e) noexcept ->
     194              :         std::enable_if_t<
     195              :             system::is_error_code_enum<E>::value,
     196              :             route_result>;
     197              : };
     198              : 
     199              : //------------------------------------------------
     200              : 
     201              : /** Handler completed successfully.
     202              : 
     203              :     Return this from a handler to indicate the response
     204              :     was sent and the request is complete:
     205              : 
     206              :     @code
     207              :     route_task handler(route_params& p)
     208              :     {
     209              :         p.res.set(field::content_type, "text/plain");
     210              :         co_await p.send("Hello, World!");
     211              :         co_return route_done;
     212              :     }
     213              :     @endcode
     214              : */
     215              : inline constexpr decltype(auto) route_done = route_result::route_done;
     216              : 
     217              : /** Handler declined, try next handler.
     218              : 
     219              :     Return this from a handler to decline processing
     220              :     and allow the next handler in the route to try:
     221              : 
     222              :     @code
     223              :     route_task auth_handler(route_params& p)
     224              :     {
     225              :         if(! p.req.exists(field::authorization))
     226              :             co_return route_next;  // let another handler try
     227              : 
     228              :         // process authenticated request...
     229              :         co_return route_done;
     230              :     }
     231              :     @endcode
     232              : */
     233              : inline constexpr decltype(auto) route_next = route_result::route_next;
     234              : 
     235              : /** Handler declined, skip to next route.
     236              : 
     237              :     Return this from a handler to skip all remaining
     238              :     handlers in the current route and proceed to the
     239              :     next matching route:
     240              : 
     241              :     @code
     242              :     route_task version_check(route_params& p)
     243              :     {
     244              :         if(p.req.version() < 11)
     245              :             co_return route_next_route;  // skip this route
     246              : 
     247              :         co_return route_next;  // continue with this route
     248              :     }
     249              :     @endcode
     250              : */
     251              : inline constexpr decltype(auto) route_next_route = route_result::route_next_route;
     252              : 
     253              : /** Handler requests connection closure.
     254              : 
     255              :     Return this from a handler to immediately close
     256              :     the connection without sending a response:
     257              : 
     258              :     @code
     259              :     route_task ban_check(route_params& p)
     260              :     {
     261              :         if(is_banned(p.req.remote_address()))
     262              :             co_return route_close;  // drop connection
     263              : 
     264              :         co_return route_next;
     265              :     }
     266              :     @endcode
     267              : */
     268              : inline constexpr decltype(auto) route_close = route_result::route_close;
     269              : 
     270              : /** Construct from an error code.
     271              : 
     272              :     Use this constructor to return an error from a handler.
     273              :     The error code must represent a failure condition.
     274              : 
     275              :     @param ec The error code to return.
     276              : 
     277              :     @throw std::invalid_argument if `!ec` (non-failing code).
     278              : */
     279           33 : inline route_result route_error(system::error_code ec) noexcept
     280              : {
     281           33 :     return route_result(ec);
     282              : }
     283              : 
     284              : /** Construct from an error enum.
     285              : 
     286              :     Use this overload to return an error from a handler
     287              :     using any type satisfying `is_error_code_enum`.
     288              : 
     289              :     @param e The error enum value to return.
     290              : */
     291              : template<class E>
     292           12 : auto route_error(E e) noexcept ->
     293              :     std::enable_if_t<
     294              :         system::is_error_code_enum<E>::value,
     295              :         route_result>
     296              : {
     297           12 :     return route_result(make_error_code(e));
     298              : }
     299              : 
     300              : //------------------------------------------------
     301              : 
     302              : /** Convenience alias for route handler return type.
     303              : 
     304              :     Route handlers are coroutines that return a @ref route_result
     305              :     indicating how the router should proceed. This alias simplifies
     306              :     handler declarations:
     307              : 
     308              :     @code
     309              :     route_task my_handler(route_params& p)
     310              :     {
     311              :         // process request...
     312              :         co_return route_done;
     313              :     }
     314              : 
     315              :     route_task auth_middleware(route_params& p)
     316              :     {
     317              :         if(! check_token(p))
     318              :         {
     319              :             p.res.set_status(status::unauthorized);
     320              :             co_await p.send();
     321              :             co_return route_done;
     322              :         }
     323              :         co_return route_next;  // continue to next handler
     324              :     }
     325              :     @endcode
     326              : 
     327              :     @see route_result, route_params
     328              : */
     329              : using route_task = capy::task<route_result>;
     330              : 
     331              : //------------------------------------------------
     332              : 
     333              : template<class, class> class router;
     334              : 
     335              : namespace detail {
     336              : 
     337              : struct route_params_access;
     338              : class router_base;
     339              : 
     340              : struct route_params_base_privates
     341              : {
     342              :     std::string verb_str_;
     343              :     std::string decoded_path_;
     344              :     system::error_code ec_;
     345              :     std::exception_ptr ep_;
     346              :     std::size_t pos_ = 0;
     347              :     std::size_t resume_ = 0;
     348              :     http::method verb_ =
     349              :         http::method::unknown;
     350              :     bool addedSlash_ = false;
     351              :     bool case_sensitive = false;
     352              :     bool strict = false;
     353              :     char kind_ = 0;
     354              : };
     355              : 
     356              : } // detail
     357              : 
     358              : //------------------------------------------------
     359              : 
     360              : /** Parameters object for HTTP route handlers.
     361              : 
     362              :     This structure holds all the context needed for a route
     363              :     handler to process an HTTP request and generate a response.
     364              : 
     365              :     @par Example
     366              :     @code
     367              :     route_task my_handler(route_params& p)
     368              :     {
     369              :         p.res.set(field::content_type, "text/plain");
     370              :         co_await p.send("Hello, World!");
     371              :         co_return route_done;
     372              :     }
     373              :     @endcode
     374              : 
     375              :     @see route_task, route_result
     376              : */
     377              : class BOOST_HTTP_SYMBOL_VISIBLE
     378              :     route_params
     379              : {
     380              :     detail::route_params_base_privates priv_;
     381              : 
     382              : public:
     383              :     struct match_result;
     384              : 
     385              :     /** Return true if the request method matches `m`
     386              :     */
     387            0 :     bool is_method(
     388              :         http::method m) const noexcept
     389              :     {
     390            0 :         return priv_.verb_ == m;
     391              :     }
     392              : 
     393              :     /** Return true if the request method matches `s`
     394              :     */
     395              :     BOOST_HTTP_DECL
     396              :     bool is_method(
     397              :         core::string_view s) const noexcept;
     398              : 
     399              :     /** The mount path of the current router
     400              : 
     401              :         This is the portion of the request path
     402              :         which was matched to select the handler.
     403              :         The remaining portion is available in
     404              :         @ref path.
     405              :     */
     406              :     core::string_view base_path;
     407              : 
     408              :     /** The current pathname, relative to the base path
     409              :     */
     410              :     core::string_view path;
     411              : 
     412              :     /** Captured route parameters
     413              : 
     414              :         Contains name-value pairs extracted from the path
     415              :         by matching :param and *wildcard tokens.
     416              :     */
     417              :     std::vector<std::pair<std::string, std::string>> params;
     418              : 
     419              :     /// The complete request target
     420              :     urls::url_view url;
     421              : 
     422              :     /// The HTTP request
     423              :     http::request req;
     424              : 
     425              :     /// The HTTP response
     426              :     http::response res;
     427              : 
     428              :     /// Provides access to the request body
     429              :     capy::any_buffer_source req_body;
     430              : 
     431              :     /// Provides access to the response body
     432              :     capy::any_buffer_sink res_body;
     433              : 
     434              :     /// Arbitrary per-route data
     435              :     http::datastore route_data;
     436              : 
     437              :     /// Arbitrary per-session data
     438              :     http::datastore session_data;
     439              : 
     440              :     BOOST_HTTP_DECL ~route_params();
     441              :     BOOST_HTTP_DECL void reset();
     442              :     BOOST_HTTP_DECL route_params& status(http::status code);
     443              : 
     444              :     /** Send the response with an optional body.
     445              :     */
     446              :     BOOST_HTTP_DECL capy::io_task<> send(std::string_view body = {});
     447              : 
     448              : private:
     449              :     template<class, class>
     450              :     friend class router;
     451              :     friend class detail::router_base;
     452              :     friend struct detail::route_params_access;
     453              : 
     454              :     route_params& operator=(
     455              :         route_params const&) = delete;
     456              : };
     457              : 
     458              : struct route_params::
     459              :     match_result
     460              : {
     461              :     std::vector<std::pair<std::string, std::string>> params_;
     462              : 
     463          297 :     void adjust_path(
     464              :         route_params& p,
     465              :         std::size_t n)
     466              :     {
     467          297 :         n_ = n;
     468          297 :         if(n_ == 0)
     469          133 :             return;
     470          164 :         p.base_path = {
     471              :             p.base_path.data(),
     472          164 :             p.base_path.size() + n_ };
     473          164 :         if(n_ < p.path.size())
     474              :         {
     475           49 :             p.path.remove_prefix(n_);
     476              :         }
     477              :         else
     478              :         {
     479              :             // append a soft slash
     480          115 :             p.path = { p.priv_.decoded_path_.data() +
     481          115 :                 p.priv_.decoded_path_.size() - 1, 1};
     482          115 :             BOOST_ASSERT(p.path == "/");
     483              :         }
     484              :     }
     485              : 
     486              :     void restore_path(
     487              :         route_params& p)
     488              :     {
     489              :         if( n_ > 0 &&
     490              :             p.priv_.addedSlash_ &&
     491              :             p.path.data() ==
     492              :                 p.priv_.decoded_path_.data() +
     493              :                 p.priv_.decoded_path_.size() - 1)
     494              :         {
     495              :             // remove soft slash
     496              :             p.path = {
     497              :                 p.base_path.data() +
     498              :                 p.base_path.size(), 0 };
     499              :         }
     500              :         p.base_path.remove_suffix(n_);
     501              :         p.path = {
     502              :             p.path.data() - n_,
     503              :             p.path.size() + n_ };
     504              :     }
     505              : 
     506              : private:
     507              :     std::size_t n_ = 0; // chars moved from path to base_path
     508              : };
     509              : 
     510              : //------------------------------------------------
     511              : 
     512              : namespace detail {
     513              : 
     514              : template<class H, class... Args>
     515              : concept returns_route_task = std::same_as<
     516              :     std::invoke_result_t<H, Args...>, route_task>;
     517              : 
     518              : struct route_params_access
     519              : {
     520              :     route_params& rp;
     521              : 
     522          739 :     route_params_base_privates& operator*() const noexcept
     523              :     {
     524          739 :         return rp.priv_;
     525              :     }
     526              : 
     527          122 :     route_params_base_privates* operator->() const noexcept
     528              :     {
     529          122 :         return &rp.priv_;
     530              :     }
     531              : };
     532              : 
     533              : } // detail
     534              : 
     535              : } // http
     536              : } // boost
     537              : 
     538              : #endif
        

Generated by: LCOV version 2.3