LCOV - code coverage report
Current view: top level - include/boost/http/server - route_handler.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 92.9 % 28 26 2
Test Date: 2026-06-13 19:44:58 Functions: 91.7 % 12 11 1

           TLA  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 HIT          50 :     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             132 :     route_result(what_t<W>)
     134             132 :     {
     135                 :         static_assert(W != route_what::error);
     136             132 :         set(W);
     137             132 :     }
     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               1 :     bool failed() const noexcept
     182                 :     {
     183               1 :         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              11 : inline route_result route_error(system::error_code ec) noexcept
     280                 : {
     281              11 :     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               2 : auto route_error(E e) noexcept ->
     293                 :     std::enable_if_t<
     294                 :         system::is_error_code_enum<E>::value,
     295                 :         route_result>
     296                 : {
     297               2 :     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 MIS           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 HIT         136 :     void adjust_path(
     464                 :         route_params& p,
     465                 :         std::size_t n)
     466                 :     {
     467             136 :         n_ = n;
     468             136 :         if(n_ == 0)
     469              50 :             return;
     470              86 :         p.base_path = {
     471                 :             p.base_path.data(),
     472              86 :             p.base_path.size() + n_ };
     473              86 :         if(n_ < p.path.size())
     474                 :         {
     475              28 :             p.path.remove_prefix(n_);
     476                 :         }
     477                 :         else
     478                 :         {
     479                 :             // append a soft slash
     480              58 :             p.path = { p.priv_.decoded_path_.data() +
     481              58 :                 p.priv_.decoded_path_.size() - 1, 1};
     482              58 :             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             360 :     route_params_base_privates& operator*() const noexcept
     523                 :     {
     524             360 :         return rp.priv_;
     525                 :     }
     526                 : 
     527              62 :     route_params_base_privates* operator->() const noexcept
     528                 :     {
     529              62 :         return &rp.priv_;
     530                 :     }
     531                 : };
     532                 : 
     533                 : } // detail
     534                 : 
     535                 : } // http
     536                 : } // boost
     537                 : 
     538                 : #endif
        

Generated by: LCOV version 2.3