LCOV - code coverage report
Current view: top level - libs/http/src/server - flat_router.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 95.6 % 90 86
Test Date: 2026-01-20 00:11:34 Functions: 100.0 % 10 10

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2026 Vinnie Falco (vinnie dot falco at gmail dot 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              : #include <boost/http/server/flat_router.hpp>
      11              : #include <boost/http/server/router.hpp>
      12              : #include <boost/http/detail/except.hpp>
      13              : 
      14              : #include "src/server/detail/router_base.hpp"
      15              : #include "src/server/detail/pct_decode.hpp"
      16              : #include "src/server/detail/route_match.hpp"
      17              : 
      18              : #include <system_error>
      19              : 
      20              : namespace boost {
      21              : namespace http {
      22              : 
      23              : //------------------------------------------------
      24              : 
      25              : struct flat_router::impl
      26              : {
      27              :     using entry = detail::router_base::entry;
      28              :     using layer = detail::router_base::layer;
      29              :     using handler = detail::router_base::handler;
      30              :     using matcher = detail::router_base::matcher;
      31              :     using opt_flags = detail::router_base::opt_flags;
      32              :     using handler_ptr = detail::router_base::handler_ptr;
      33              :     using match_result = route_params_base::match_result;
      34              : 
      35              :     std::vector<entry> entries;
      36              :     std::vector<matcher> matchers;
      37              : 
      38              :     // RAII scope tracker sets matcher's skip_ when scope ends
      39              :     struct scope_tracker
      40              :     {
      41              :         std::vector<matcher>& matchers_;
      42              :         std::vector<entry>& entries_;
      43              :         std::size_t matcher_idx_;
      44              : 
      45           99 :         scope_tracker(
      46              :             std::vector<matcher>& m,
      47              :             std::vector<entry>& e,
      48              :             std::size_t idx)
      49           99 :             : matchers_(m)
      50           99 :             , entries_(e)
      51           99 :             , matcher_idx_(idx)
      52              :         {
      53           99 :         }
      54              : 
      55           99 :         ~scope_tracker()
      56              :         {
      57           99 :             matchers_[matcher_idx_].skip_ = entries_.size();
      58           99 :         }
      59              :     };
      60              : 
      61              :     static opt_flags
      62           84 :     compute_effective_opts(
      63              :         opt_flags parent,
      64              :         opt_flags child)
      65              :     {
      66           84 :         opt_flags result = parent;
      67              : 
      68              :         // case_sensitive: bits 1-2 (2=true, 4=false)
      69           84 :         if(child & 2)
      70            3 :             result = (result & ~6) | 2;
      71           81 :         else if(child & 4)
      72            1 :             result = (result & ~6) | 4;
      73              : 
      74              :         // strict: bits 3-4 (8=true, 16=false)
      75           84 :         if(child & 8)
      76            0 :             result = (result & ~24) | 8;
      77           84 :         else if(child & 16)
      78            0 :             result = (result & ~24) | 16;
      79              : 
      80           84 :         return result;
      81              :     }
      82              : 
      83              :     void
      84           60 :     flatten(detail::router_base::impl& src)
      85              :     {
      86           60 :         flatten_recursive(src, opt_flags{}, 0);
      87           60 :     }
      88              : 
      89              :     void
      90           84 :     flatten_recursive(
      91              :         detail::router_base::impl& src,
      92              :         opt_flags parent_opts,
      93              :         std::uint32_t depth)
      94              :     {
      95           84 :         opt_flags eff = compute_effective_opts(parent_opts, src.opt);
      96              : 
      97          183 :         for(auto& layer : src.layers)
      98              :         {
      99              :             // Move matcher, set flat router fields
     100           99 :             std::size_t matcher_idx = matchers.size();
     101           99 :             matchers.emplace_back(std::move(layer.match));
     102           99 :             auto& m = matchers.back();
     103           99 :             m.first_entry_ = entries.size();
     104           99 :             m.effective_opts_ = eff;
     105           99 :             m.depth_ = depth;
     106              :             // m.skip_ set by scope_tracker dtor
     107              : 
     108           99 :             scope_tracker scope(matchers, entries, matcher_idx);
     109              : 
     110          211 :             for(auto& e : layer.entries)
     111              :             {
     112          112 :                 if(e.h->kind == detail::router_base::is_router)
     113              :                 {
     114              :                     // Recurse into nested router
     115           24 :                     auto* nested = e.h->get_router();
     116           24 :                     if(nested && nested->impl_)
     117           24 :                         flatten_recursive(*nested->impl_, eff, depth + 1);
     118              :                 }
     119              :                 else
     120              :                 {
     121              :                     // Set matcher_idx, then move entire entry
     122           88 :                     e.matcher_idx = matcher_idx;
     123           88 :                     entries.emplace_back(std::move(e));
     124              :                 }
     125              :             }
     126              :             // ~scope_tracker sets matchers[matcher_idx].skip_
     127           99 :         }
     128           84 :     }
     129              : 
     130              :     // Restore path to a given base_path length
     131              :     static void
     132           20 :     restore_path(
     133              :         route_params_base& p,
     134              :         std::size_t base_len)
     135              :     {
     136           20 :         p.base_path = { p.decoded_path_.data(), base_len };
     137              :         // Account for the addedSlash_ when computing path length
     138           20 :         auto const path_len = p.decoded_path_.size() - (p.addedSlash_ ? 1 : 0);
     139           20 :         if(base_len < path_len)
     140           19 :             p.path = { p.decoded_path_.data() + base_len,
     141              :                 path_len - base_len };
     142              :         else
     143            2 :             p.path = { p.decoded_path_.data() +
     144            1 :                 p.decoded_path_.size() - 1, 1 };  // soft slash
     145           20 :     }
     146              : 
     147              :     capy::task<route_result>
     148           58 :     dispatch_loop(route_params_base& p) const
     149              :     {
     150              :         // All checks happen BEFORE co_await to minimize coroutine launches.
     151              :         // Avoid touching p.ep_ (expensive atomic on Windows) - use p.kind_ for mode checks.
     152              : 
     153              :         std::size_t last_matched = SIZE_MAX;
     154              :         std::uint32_t current_depth = 0;
     155              : 
     156              :         // Stack of base_path lengths at each depth level.
     157              :         // path_stack[d] = base_path.size() before any matcher at depth d was tried.
     158              :         std::size_t path_stack[detail::router_base::max_path_depth];
     159              :         path_stack[0] = 0;
     160              : 
     161              :         // Track which matcher index is matched at each depth level.
     162              :         // matched_at_depth[d] = matcher index that successfully matched at depth d.
     163              :         std::size_t matched_at_depth[detail::router_base::max_path_depth];
     164              :         for(std::size_t d = 0; d < detail::router_base::max_path_depth; ++d)
     165              :             matched_at_depth[d] = SIZE_MAX;
     166              : 
     167              :         for(std::size_t i = 0; i < entries.size(); )
     168              :         {
     169              :             auto const& e = entries[i];
     170              :             auto const& m = matchers[e.matcher_idx];
     171              :             auto const target_depth = m.depth_;
     172              : 
     173              :             //--------------------------------------------------
     174              :             // Pre-invoke checks (no coroutine yet)
     175              :             //--------------------------------------------------
     176              : 
     177              :             // For nested routes: verify ancestors at depths 0..target_depth-1 are matched.
     178              :             // For siblings: if moving to same depth with different matcher, restore path.
     179              :             bool ancestors_ok = true;
     180              :             
     181              :             // Check if we need to match new ancestor matchers for this entry.
     182              :             // We iterate through matchers from last_matched+1 to e.matcher_idx,
     183              :             // but ONLY process those that are at depths we need (ancestors or self).
     184              :             std::size_t start_idx = (last_matched == SIZE_MAX) ? 0 : last_matched + 1;
     185              :             
     186              :             for(std::size_t check_idx = start_idx;
     187              :                 check_idx <= e.matcher_idx && ancestors_ok;
     188              :                 ++check_idx)
     189              :             {
     190              :                 auto const& cm = matchers[check_idx];
     191              :                 
     192              :                 // Only check matchers that are:
     193              :                 // 1. Ancestors (depth < target_depth) that we haven't matched yet, or
     194              :                 // 2. The entry's own matcher
     195              :                 bool is_needed_ancestor = (cm.depth_ < target_depth) && 
     196              :                     (matched_at_depth[cm.depth_] == SIZE_MAX);
     197              :                 bool is_self = (check_idx == e.matcher_idx);
     198              :                 
     199              :                 if(!is_needed_ancestor && !is_self)
     200              :                     continue;
     201              : 
     202              :                 // Restore path if moving to same or shallower depth
     203              :                 if(cm.depth_ <= current_depth && current_depth > 0)
     204              :                 {
     205              :                     restore_path(p, path_stack[cm.depth_]);
     206              :                 }
     207              : 
     208              :                 // In error/exception mode, skip end routes
     209              :                 if(cm.end_ && p.kind_ != detail::router_base::is_plain)
     210              :                 {
     211              :                     i = cm.skip_;
     212              :                     ancestors_ok = false;
     213              :                     break;
     214              :                 }
     215              : 
     216              :                 // Apply effective_opts for this matcher
     217              :                 p.case_sensitive = (cm.effective_opts_ & 2) != 0;
     218              :                 p.strict = (cm.effective_opts_ & 8) != 0;
     219              : 
     220              :                 // Save path state before trying this matcher
     221              :                 if(cm.depth_ < detail::router_base::max_path_depth)
     222              :                     path_stack[cm.depth_] = p.base_path.size();
     223              : 
     224              :                 match_result mr;
     225              :                 if(!cm(p, mr))
     226              :                 {
     227              :                     // Clear matched_at_depth for this depth and deeper
     228              :                     for(std::size_t d = cm.depth_; d < detail::router_base::max_path_depth; ++d)
     229              :                         matched_at_depth[d] = SIZE_MAX;
     230              :                     i = cm.skip_;
     231              :                     ancestors_ok = false;
     232              :                     break;
     233              :                 }
     234              : 
     235              :                 // Mark this depth as matched
     236              :                 if(cm.depth_ < detail::router_base::max_path_depth)
     237              :                     matched_at_depth[cm.depth_] = check_idx;
     238              :                 
     239              :                 last_matched = check_idx;
     240              :                 current_depth = cm.depth_ + 1;
     241              : 
     242              :                 // Save state for next depth level
     243              :                 if(current_depth < detail::router_base::max_path_depth)
     244              :                     path_stack[current_depth] = p.base_path.size();
     245              :             }
     246              : 
     247              :             if(!ancestors_ok)
     248              :                 continue;
     249              : 
     250              :             // Check method match (only for end routes)
     251              :             if(m.end_ && !e.match_method(
     252              :                 const_cast<route_params_base&>(p)))
     253              :             {
     254              :                 ++i;
     255              :                 continue;
     256              :             }
     257              : 
     258              :             // Check kind match (cheap char comparison)
     259              :             if(e.h->kind != p.kind_)
     260              :             {
     261              :                 ++i;
     262              :                 continue;
     263              :             }
     264              : 
     265              :             //--------------------------------------------------
     266              :             // Invoke handler (coroutine starts here)
     267              :             //--------------------------------------------------
     268              : 
     269              :             route_result rv;
     270              :             try
     271              :             {
     272              :                 rv = co_await e.h->invoke(
     273              :                     const_cast<route_params_base&>(p));
     274              :             }
     275              :             catch(...)
     276              :             {
     277              :                 // Only touch ep_ when actually catching
     278              :                 p.ep_ = std::current_exception();
     279              :                 p.kind_ = detail::router_base::is_exception;
     280              :                 ++i;
     281              :                 continue;
     282              :             }
     283              : 
     284              :             //--------------------------------------------------
     285              :             // Handle result
     286              :             //
     287              :             // Coroutines invert control - handler does the send.
     288              :             // Success = !rv.failed() (handler completed request)
     289              :             // route::next = continue to next handler
     290              :             // route::next_route = skip to next route
     291              :             // Failing error_code = enter error mode
     292              :             //--------------------------------------------------
     293              : 
     294              :             if(rv == route::next)
     295              :             {
     296              :                 ++i;
     297              :                 continue;
     298              :             }
     299              : 
     300              :             if(rv == route::next_route)
     301              :             {
     302              :                 // next_route only valid for end routes, not middleware
     303              :                 if(!m.end_)
     304              :                     co_return make_error_code(std::errc::invalid_argument);
     305              :                 i = m.skip_;
     306              :                 continue;
     307              :             }
     308              : 
     309              :             if(!rv.failed())
     310              :             {
     311              :                 // Success - handler completed the request
     312              :                 co_return rv;
     313              :             }
     314              : 
     315              :             // Failing error_code - transition to error mode
     316              :             p.ec_ = rv;
     317              :             p.kind_ = detail::router_base::is_error;
     318              : 
     319              :             if(m.end_)
     320              :             {
     321              :                 // End routes don't have error handlers
     322              :                 i = m.skip_;
     323              :                 continue;
     324              :             }
     325              : 
     326              :             ++i;
     327              :         }
     328              : 
     329              :         // Final state
     330              :         if(p.kind_ == detail::router_base::is_exception)
     331              :             co_return error::unhandled_exception;
     332              :         if(p.kind_ == detail::router_base::is_error)
     333              :             co_return p.ec_;
     334              : 
     335              :         co_return route::next;  // no handler matched
     336          116 :     }
     337              : };
     338              : 
     339              : //------------------------------------------------
     340              : 
     341           60 : flat_router::
     342              : flat_router(
     343           60 :     detail::router_base&& src)
     344           60 :     : impl_(std::make_shared<impl>())
     345              : {
     346           60 :     impl_->flatten(*src.impl_);
     347           60 : }
     348              : 
     349              : capy::task<route_result>
     350           54 : flat_router::
     351              : dispatch(
     352              :     http::method verb,
     353              :     urls::url_view const& url,
     354              :     route_params_base& p) const
     355              : {
     356           54 :     if(verb == http::method::unknown)
     357            1 :         detail::throw_invalid_argument();
     358              : 
     359              :     // Initialize params
     360           53 :     p.kind_ = detail::router_base::is_plain;
     361           53 :     p.verb_ = verb;
     362           53 :     p.verb_str_.clear();
     363           53 :     p.ec_.clear();
     364           53 :     p.ep_ = nullptr;
     365           53 :     p.decoded_path_ = detail::pct_decode_path(url.encoded_path());
     366           53 :     p.base_path = { p.decoded_path_.data(), 0 };
     367           53 :     p.path = p.decoded_path_;
     368           53 :     if(p.decoded_path_.back() != '/')
     369              :     {
     370           25 :         p.decoded_path_.push_back('/');
     371           25 :         p.addedSlash_ = true;
     372              :     }
     373              :     else
     374              :     {
     375           28 :         p.addedSlash_ = false;
     376              :     }
     377              : 
     378           53 :     return impl_->dispatch_loop(p);
     379              : }
     380              : 
     381              : capy::task<route_result>
     382            6 : flat_router::
     383              : dispatch(
     384              :     std::string_view verb,
     385              :     urls::url_view const& url,
     386              :     route_params_base& p) const
     387              : {
     388            6 :     if(verb.empty())
     389            1 :         detail::throw_invalid_argument();
     390              : 
     391              :     // Initialize params
     392            5 :     p.kind_ = detail::router_base::is_plain;
     393            5 :     p.verb_ = http::string_to_method(verb);
     394            5 :     if(p.verb_ == http::method::unknown)
     395            4 :         p.verb_str_ = verb;
     396              :     else
     397            1 :         p.verb_str_.clear();
     398            5 :     p.ec_.clear();
     399            5 :     p.ep_ = nullptr;
     400            5 :     p.decoded_path_ = detail::pct_decode_path(url.encoded_path());
     401            5 :     p.base_path = { p.decoded_path_.data(), 0 };
     402            5 :     p.path = p.decoded_path_;
     403            5 :     if(p.decoded_path_.back() != '/')
     404              :     {
     405            0 :         p.decoded_path_.push_back('/');
     406            0 :         p.addedSlash_ = true;
     407              :     }
     408              :     else
     409              :     {
     410            5 :         p.addedSlash_ = false;
     411              :     }
     412              : 
     413            5 :     return impl_->dispatch_loop(p);
     414              : }
     415              : 
     416              : } // http
     417              : } // boost
     418              : 
        

Generated by: LCOV version 2.3