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

Generated by: LCOV version 2.3