src/server/any_router.cpp
91.6% Lines (218/238)
94.1% List of functions (16/17)
Functions (17)
Function
Calls
Lines
Blocks
boost::http::detail::router_base::impl::build_allow_header(unsigned long, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&)
:31
187x
93.3%
82.0%
boost::http::detail::router_base::impl::compute_effective_opts(unsigned int, unsigned int)
:94
495x
100.0%
100.0%
boost::http::detail::router_base::impl::restore_path(boost::http::route_params&, unsigned long)
:117
34x
100.0%
100.0%
boost::http::detail::router_base::impl::update_allow_for_entry(boost::http::detail::router_base::matcher&, boost::http::detail::router_base::entry const&)
:134
68x
94.1%
95.0%
boost::http::detail::router_base::impl::rebuild_global_allow_header()
:164
117x
100.0%
79.0%
boost::http::detail::router_base::impl::finalize_pending()
:176
341x
87.5%
90.0%
boost::http::detail::router_base::impl::dispatch_loop(boost::http::route_params&, bool) const
:201
112x
100.0%
44.0%
boost::http::detail::router_base::router_base(unsigned int)
:382
168x
100.0%
100.0%
boost::http::detail::router_base::add_middleware(std::basic_string_view<char, std::char_traits<char> >, boost::http::detail::router_base::handlers)
:390
65x
94.4%
91.0%
boost::http::detail::router_base::inline_router(std::basic_string_view<char, std::char_traits<char> >, boost::http::detail::router_base&&)
:420
50x
89.6%
79.0%
boost::http::detail::router_base::new_route(std::basic_string_view<char, std::char_traits<char> >)
:505
77x
93.3%
86.0%
boost::http::detail::router_base::add_to_route(unsigned long, boost::http::method, boost::http::detail::router_base::handlers)
:529
58x
90.0%
90.0%
boost::http::detail::router_base::add_to_route(unsigned long, std::basic_string_view<char, std::char_traits<char> >, boost::http::detail::router_base::handlers)
:549
10x
100.0%
100.0%
boost::http::detail::router_base::finalize_pending()
:581
0
0.0%
0.0%
boost::http::detail::router_base::set_options_handler_impl(std::unique_ptr<boost::http::detail::router_base::options_handler, std::default_delete<boost::http::detail::router_base::options_handler> >)
:589
4x
100.0%
100.0%
boost::http::detail::router_base::dispatch(boost::http::method, boost::urls::url_view const&, boost::http::route_params&) const
:603
109x
100.0%
100.0%
boost::http::detail::router_base::dispatch(std::basic_string_view<char, std::char_traits<char> >, boost::urls::url_view const&, boost::http::route_params&) const
:651
6x
82.1%
69.0%
| Line | TLA | Hits | 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 | #include "src/server/detail/any_router.hpp" | ||
| 11 | #include <boost/http/server/detail/router_base.hpp> | ||
| 12 | #include <boost/http/detail/except.hpp> | ||
| 13 | #include <boost/http/error.hpp> | ||
| 14 | #include <boost/url/grammar/ci_string.hpp> | ||
| 15 | #include <boost/url/grammar/hexdig_chars.hpp> | ||
| 16 | #include "src/server/detail/pct_decode.hpp" | ||
| 17 | |||
| 18 | #include <algorithm> | ||
| 19 | |||
| 20 | namespace boost { | ||
| 21 | namespace http { | ||
| 22 | namespace detail { | ||
| 23 | |||
| 24 | //------------------------------------------------ | ||
| 25 | // | ||
| 26 | // impl helpers | ||
| 27 | // | ||
| 28 | //------------------------------------------------ | ||
| 29 | |||
| 30 | std::string | ||
| 31 | 187x | router_base::impl:: | |
| 32 | build_allow_header( | ||
| 33 | std::uint64_t methods, | ||
| 34 | std::vector<std::string> const& custom) | ||
| 35 | { | ||
| 36 | 187x | if(methods == ~0ULL) | |
| 37 | 34x | return "DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT"; | |
| 38 | |||
| 39 | 170x | std::string result; | |
| 40 | static constexpr std::pair<http::method, char const*> known[] = { | ||
| 41 | {http::method::acl, "ACL"}, | ||
| 42 | {http::method::bind, "BIND"}, | ||
| 43 | {http::method::checkout, "CHECKOUT"}, | ||
| 44 | {http::method::connect, "CONNECT"}, | ||
| 45 | {http::method::copy, "COPY"}, | ||
| 46 | {http::method::delete_, "DELETE"}, | ||
| 47 | {http::method::get, "GET"}, | ||
| 48 | {http::method::head, "HEAD"}, | ||
| 49 | {http::method::link, "LINK"}, | ||
| 50 | {http::method::lock, "LOCK"}, | ||
| 51 | {http::method::merge, "MERGE"}, | ||
| 52 | {http::method::mkactivity, "MKACTIVITY"}, | ||
| 53 | {http::method::mkcalendar, "MKCALENDAR"}, | ||
| 54 | {http::method::mkcol, "MKCOL"}, | ||
| 55 | {http::method::move, "MOVE"}, | ||
| 56 | {http::method::msearch, "M-SEARCH"}, | ||
| 57 | {http::method::notify, "NOTIFY"}, | ||
| 58 | {http::method::options, "OPTIONS"}, | ||
| 59 | {http::method::patch, "PATCH"}, | ||
| 60 | {http::method::post, "POST"}, | ||
| 61 | {http::method::propfind, "PROPFIND"}, | ||
| 62 | {http::method::proppatch, "PROPPATCH"}, | ||
| 63 | {http::method::purge, "PURGE"}, | ||
| 64 | {http::method::put, "PUT"}, | ||
| 65 | {http::method::rebind, "REBIND"}, | ||
| 66 | {http::method::report, "REPORT"}, | ||
| 67 | {http::method::search, "SEARCH"}, | ||
| 68 | {http::method::subscribe, "SUBSCRIBE"}, | ||
| 69 | {http::method::trace, "TRACE"}, | ||
| 70 | {http::method::unbind, "UNBIND"}, | ||
| 71 | {http::method::unlink, "UNLINK"}, | ||
| 72 | {http::method::unlock, "UNLOCK"}, | ||
| 73 | {http::method::unsubscribe, "UNSUBSCRIBE"}, | ||
| 74 | }; | ||
| 75 | 5780x | for(auto const& [m, name] : known) | |
| 76 | { | ||
| 77 | 5610x | if(methods & (1ULL << static_cast<unsigned>(m))) | |
| 78 | { | ||
| 79 | 128x | if(!result.empty()) | |
| 80 | 8x | result += ", "; | |
| 81 | 128x | result += name; | |
| 82 | } | ||
| 83 | } | ||
| 84 | 174x | for(auto const& v : custom) | |
| 85 | { | ||
| 86 | 4x | if(!result.empty()) | |
| 87 | ✗ | result += ", "; | |
| 88 | 4x | result += v; | |
| 89 | } | ||
| 90 | 170x | return result; | |
| 91 | 170x | } | |
| 92 | |||
| 93 | router_base::opt_flags | ||
| 94 | 495x | router_base::impl:: | |
| 95 | compute_effective_opts( | ||
| 96 | opt_flags parent, | ||
| 97 | opt_flags child) | ||
| 98 | { | ||
| 99 | 495x | opt_flags result = parent; | |
| 100 | |||
| 101 | // case_sensitive: bits 1-2 (2=true, 4=false) | ||
| 102 | 495x | if(child & 2) | |
| 103 | 4x | result = (result & ~6) | 2; | |
| 104 | 491x | else if(child & 4) | |
| 105 | 4x | result = (result & ~6) | 4; | |
| 106 | |||
| 107 | // strict: bits 3-4 (8=true, 16=false) | ||
| 108 | 495x | if(child & 8) | |
| 109 | 1x | result = (result & ~24) | 8; | |
| 110 | 494x | else if(child & 16) | |
| 111 | 1x | result = (result & ~24) | 16; | |
| 112 | |||
| 113 | 495x | return result; | |
| 114 | } | ||
| 115 | |||
| 116 | void | ||
| 117 | 34x | router_base::impl:: | |
| 118 | restore_path( | ||
| 119 | route_params& p, | ||
| 120 | std::size_t base_len) | ||
| 121 | { | ||
| 122 | 34x | auto& pv = *route_params_access{p}; | |
| 123 | 34x | p.base_path = { pv.decoded_path_.data(), base_len }; | |
| 124 | 34x | auto const path_len = pv.decoded_path_.size() - (pv.addedSlash_ ? 1 : 0); | |
| 125 | 34x | if(base_len < path_len) | |
| 126 | 33x | p.path = { pv.decoded_path_.data() + base_len, | |
| 127 | path_len - base_len }; | ||
| 128 | else | ||
| 129 | 2x | p.path = { pv.decoded_path_.data() + | |
| 130 | 1x | pv.decoded_path_.size() - 1, 1 }; // soft slash | |
| 131 | 34x | } | |
| 132 | |||
| 133 | void | ||
| 134 | 68x | router_base::impl:: | |
| 135 | update_allow_for_entry( | ||
| 136 | matcher& m, | ||
| 137 | entry const& e) | ||
| 138 | { | ||
| 139 | 68x | if(!m.end_) | |
| 140 | ✗ | return; | |
| 141 | |||
| 142 | // Per-matcher collection | ||
| 143 | 68x | if(e.all) | |
| 144 | 8x | m.allowed_methods_ = ~0ULL; | |
| 145 | 60x | else if(e.verb != http::method::unknown) | |
| 146 | 58x | m.allowed_methods_ |= (1ULL << static_cast<unsigned>(e.verb)); | |
| 147 | 2x | else if(!e.verb_str.empty()) | |
| 148 | 2x | m.custom_verbs_.push_back(e.verb_str); | |
| 149 | |||
| 150 | // Rebuild per-matcher Allow header eagerly | ||
| 151 | 68x | m.allow_header_ = build_allow_header( | |
| 152 | 68x | m.allowed_methods_, m.custom_verbs_); | |
| 153 | |||
| 154 | // Global collection (for OPTIONS *) | ||
| 155 | 68x | if(e.all) | |
| 156 | 8x | global_methods_ = ~0ULL; | |
| 157 | 60x | else if(e.verb != http::method::unknown) | |
| 158 | 58x | global_methods_ |= (1ULL << static_cast<unsigned>(e.verb)); | |
| 159 | 2x | else if(!e.verb_str.empty()) | |
| 160 | 2x | global_custom_verbs_.push_back(e.verb_str); | |
| 161 | } | ||
| 162 | |||
| 163 | void | ||
| 164 | 117x | router_base::impl:: | |
| 165 | rebuild_global_allow_header() | ||
| 166 | { | ||
| 167 | 117x | std::sort(global_custom_verbs_.begin(), global_custom_verbs_.end()); | |
| 168 | 234x | global_custom_verbs_.erase( | |
| 169 | 117x | std::unique(global_custom_verbs_.begin(), global_custom_verbs_.end()), | |
| 170 | 117x | global_custom_verbs_.end()); | |
| 171 | 117x | global_allow_header_ = build_allow_header( | |
| 172 | 117x | global_methods_, global_custom_verbs_); | |
| 173 | 117x | } | |
| 174 | |||
| 175 | void | ||
| 176 | 341x | router_base::impl:: | |
| 177 | finalize_pending() | ||
| 178 | { | ||
| 179 | 341x | if(pending_route_ == SIZE_MAX) | |
| 180 | 277x | return; | |
| 181 | 64x | auto& m = matchers[pending_route_]; | |
| 182 | 64x | if(entries.size() == m.first_entry_) | |
| 183 | { | ||
| 184 | // empty route, remove it | ||
| 185 | ✗ | matchers.pop_back(); | |
| 186 | } | ||
| 187 | else | ||
| 188 | { | ||
| 189 | 64x | m.skip_ = entries.size(); | |
| 190 | } | ||
| 191 | 64x | pending_route_ = SIZE_MAX; | |
| 192 | } | ||
| 193 | |||
| 194 | //------------------------------------------------ | ||
| 195 | // | ||
| 196 | // dispatch | ||
| 197 | // | ||
| 198 | //------------------------------------------------ | ||
| 199 | |||
| 200 | route_task | ||
| 201 | 112x | router_base::impl:: | |
| 202 | dispatch_loop(route_params& p, bool is_options) const | ||
| 203 | { | ||
| 204 | auto& pv = *route_params_access{p}; | ||
| 205 | |||
| 206 | std::size_t last_matched = SIZE_MAX; | ||
| 207 | std::uint32_t current_depth = 0; | ||
| 208 | |||
| 209 | std::uint64_t options_methods = 0; | ||
| 210 | std::vector<std::string> options_custom_verbs; | ||
| 211 | |||
| 212 | std::size_t path_stack[router_base::max_path_depth]; | ||
| 213 | path_stack[0] = 0; | ||
| 214 | |||
| 215 | std::size_t matched_at_depth[router_base::max_path_depth]; | ||
| 216 | for(std::size_t d = 0; d < router_base::max_path_depth; ++d) | ||
| 217 | matched_at_depth[d] = SIZE_MAX; | ||
| 218 | |||
| 219 | for(std::size_t i = 0; i < entries.size(); ) | ||
| 220 | { | ||
| 221 | auto const& e = entries[i]; | ||
| 222 | auto const& m = matchers[e.matcher_idx]; | ||
| 223 | auto const target_depth = m.depth_; | ||
| 224 | |||
| 225 | bool ancestors_ok = true; | ||
| 226 | |||
| 227 | std::size_t start_idx = (last_matched == SIZE_MAX) ? 0 : last_matched + 1; | ||
| 228 | |||
| 229 | for(std::size_t check_idx = start_idx; | ||
| 230 | check_idx <= e.matcher_idx && ancestors_ok; | ||
| 231 | ++check_idx) | ||
| 232 | { | ||
| 233 | auto const& cm = matchers[check_idx]; | ||
| 234 | |||
| 235 | bool is_needed_ancestor = (cm.depth_ < target_depth) && | ||
| 236 | (matched_at_depth[cm.depth_] == SIZE_MAX); | ||
| 237 | bool is_self = (check_idx == e.matcher_idx); | ||
| 238 | |||
| 239 | if(!is_needed_ancestor && !is_self) | ||
| 240 | continue; | ||
| 241 | |||
| 242 | if(cm.depth_ <= current_depth && current_depth > 0) | ||
| 243 | { | ||
| 244 | restore_path(p, path_stack[cm.depth_]); | ||
| 245 | } | ||
| 246 | |||
| 247 | if(cm.end_ && pv.kind_ != router_base::is_plain) | ||
| 248 | { | ||
| 249 | i = cm.skip_; | ||
| 250 | ancestors_ok = false; | ||
| 251 | break; | ||
| 252 | } | ||
| 253 | |||
| 254 | pv.case_sensitive = (cm.effective_opts_ & 2) != 0; | ||
| 255 | pv.strict = (cm.effective_opts_ & 8) != 0; | ||
| 256 | |||
| 257 | if(cm.depth_ < router_base::max_path_depth) | ||
| 258 | path_stack[cm.depth_] = p.base_path.size(); | ||
| 259 | |||
| 260 | match_result mr; | ||
| 261 | if(!cm(p, mr)) | ||
| 262 | { | ||
| 263 | for(std::size_t d = cm.depth_; d < router_base::max_path_depth; ++d) | ||
| 264 | matched_at_depth[d] = SIZE_MAX; | ||
| 265 | i = cm.skip_; | ||
| 266 | ancestors_ok = false; | ||
| 267 | break; | ||
| 268 | } | ||
| 269 | |||
| 270 | if(!mr.params_.empty()) | ||
| 271 | { | ||
| 272 | for(auto& param : mr.params_) | ||
| 273 | p.params.push_back(std::move(param)); | ||
| 274 | } | ||
| 275 | |||
| 276 | if(cm.depth_ < router_base::max_path_depth) | ||
| 277 | matched_at_depth[cm.depth_] = check_idx; | ||
| 278 | |||
| 279 | last_matched = check_idx; | ||
| 280 | current_depth = cm.depth_ + 1; | ||
| 281 | |||
| 282 | if(current_depth < router_base::max_path_depth) | ||
| 283 | path_stack[current_depth] = p.base_path.size(); | ||
| 284 | } | ||
| 285 | |||
| 286 | if(!ancestors_ok) | ||
| 287 | continue; | ||
| 288 | |||
| 289 | // Collect methods from matching end-route matchers for OPTIONS | ||
| 290 | if(is_options && m.end_) | ||
| 291 | { | ||
| 292 | options_methods |= m.allowed_methods_; | ||
| 293 | for(auto const& v : m.custom_verbs_) | ||
| 294 | options_custom_verbs.push_back(v); | ||
| 295 | } | ||
| 296 | |||
| 297 | if(m.end_ && !e.match_method( | ||
| 298 | const_cast<route_params&>(p))) | ||
| 299 | { | ||
| 300 | ++i; | ||
| 301 | continue; | ||
| 302 | } | ||
| 303 | |||
| 304 | if(e.h->kind != pv.kind_) | ||
| 305 | { | ||
| 306 | ++i; | ||
| 307 | continue; | ||
| 308 | } | ||
| 309 | |||
| 310 | //-------------------------------------------------- | ||
| 311 | // Invoke handler | ||
| 312 | //-------------------------------------------------- | ||
| 313 | |||
| 314 | route_result rv; | ||
| 315 | try | ||
| 316 | { | ||
| 317 | rv = co_await e.h->invoke( | ||
| 318 | const_cast<route_params&>(p)); | ||
| 319 | } | ||
| 320 | catch(...) | ||
| 321 | { | ||
| 322 | pv.ep_ = std::current_exception(); | ||
| 323 | pv.kind_ = router_base::is_exception; | ||
| 324 | ++i; | ||
| 325 | continue; | ||
| 326 | } | ||
| 327 | |||
| 328 | if(rv.what() == route_what::next) | ||
| 329 | { | ||
| 330 | ++i; | ||
| 331 | continue; | ||
| 332 | } | ||
| 333 | |||
| 334 | if(rv.what() == route_what::next_route) | ||
| 335 | { | ||
| 336 | if(!m.end_) | ||
| 337 | co_return route_error(error::invalid_route_result); | ||
| 338 | i = m.skip_; | ||
| 339 | continue; | ||
| 340 | } | ||
| 341 | |||
| 342 | if(rv.what() == route_what::done || | ||
| 343 | rv.what() == route_what::close) | ||
| 344 | { | ||
| 345 | co_return rv; | ||
| 346 | } | ||
| 347 | |||
| 348 | // Error - transition to error mode | ||
| 349 | pv.ec_ = rv.error(); | ||
| 350 | pv.kind_ = router_base::is_error; | ||
| 351 | |||
| 352 | if(m.end_) | ||
| 353 | { | ||
| 354 | i = m.skip_; | ||
| 355 | continue; | ||
| 356 | } | ||
| 357 | |||
| 358 | ++i; | ||
| 359 | } | ||
| 360 | |||
| 361 | if(pv.kind_ == router_base::is_exception) | ||
| 362 | co_return route_error(error::unhandled_exception); | ||
| 363 | if(pv.kind_ == router_base::is_error) | ||
| 364 | co_return route_error(pv.ec_); | ||
| 365 | |||
| 366 | // OPTIONS fallback | ||
| 367 | if(is_options && options_methods != 0 && options_handler_) | ||
| 368 | { | ||
| 369 | std::string allow = build_allow_header(options_methods, options_custom_verbs); | ||
| 370 | co_return co_await options_handler_->invoke(p, allow); | ||
| 371 | } | ||
| 372 | |||
| 373 | co_return route_next; | ||
| 374 | 224x | } | |
| 375 | |||
| 376 | //------------------------------------------------ | ||
| 377 | // | ||
| 378 | // router_base | ||
| 379 | // | ||
| 380 | //------------------------------------------------ | ||
| 381 | |||
| 382 | 168x | router_base:: | |
| 383 | router_base( | ||
| 384 | 168x | opt_flags opt) | |
| 385 | 168x | : impl_(std::make_shared<impl>(opt)) | |
| 386 | { | ||
| 387 | 168x | } | |
| 388 | |||
| 389 | void | ||
| 390 | 65x | router_base:: | |
| 391 | add_middleware( | ||
| 392 | std::string_view pattern, | ||
| 393 | handlers hn) | ||
| 394 | { | ||
| 395 | 65x | impl_->finalize_pending(); | |
| 396 | |||
| 397 | 65x | if(pattern.empty()) | |
| 398 | 34x | pattern = "/"; | |
| 399 | |||
| 400 | 65x | auto const matcher_idx = impl_->matchers.size(); | |
| 401 | 65x | impl_->matchers.emplace_back(pattern, false); | |
| 402 | 65x | auto& m = impl_->matchers.back(); | |
| 403 | 65x | if(m.error()) | |
| 404 | ✗ | throw_invalid_argument(); | |
| 405 | 65x | m.first_entry_ = impl_->entries.size(); | |
| 406 | 65x | m.effective_opts_ = impl::compute_effective_opts(0, impl_->opt_); | |
| 407 | 65x | m.own_opts_ = impl_->opt_; | |
| 408 | 65x | m.depth_ = 0; | |
| 409 | |||
| 410 | 139x | for(std::size_t i = 0; i < hn.n; ++i) | |
| 411 | { | ||
| 412 | 74x | impl_->entries.emplace_back(std::move(hn.p[i])); | |
| 413 | 74x | impl_->entries.back().matcher_idx = matcher_idx; | |
| 414 | } | ||
| 415 | |||
| 416 | 65x | m.skip_ = impl_->entries.size(); | |
| 417 | 65x | } | |
| 418 | |||
| 419 | void | ||
| 420 | 50x | router_base:: | |
| 421 | inline_router( | ||
| 422 | std::string_view pattern, | ||
| 423 | router_base&& sub) | ||
| 424 | { | ||
| 425 | 50x | impl_->finalize_pending(); | |
| 426 | |||
| 427 | 50x | if(!sub.impl_) | |
| 428 | ✗ | return; | |
| 429 | |||
| 430 | 50x | sub.impl_->finalize_pending(); | |
| 431 | |||
| 432 | 50x | if(pattern.empty()) | |
| 433 | ✗ | pattern = "/"; | |
| 434 | |||
| 435 | // Create parent matcher for the mount point | ||
| 436 | 50x | auto const parent_matcher_idx = impl_->matchers.size(); | |
| 437 | 50x | impl_->matchers.emplace_back(pattern, false); | |
| 438 | 50x | auto& parent_m = impl_->matchers.back(); | |
| 439 | 50x | if(parent_m.error()) | |
| 440 | ✗ | throw_invalid_argument(); | |
| 441 | 50x | parent_m.first_entry_ = impl_->entries.size(); | |
| 442 | |||
| 443 | 50x | auto parent_eff = impl::compute_effective_opts(0, impl_->opt_); | |
| 444 | 50x | parent_m.effective_opts_ = parent_eff; | |
| 445 | 50x | parent_m.own_opts_ = impl_->opt_; | |
| 446 | 50x | parent_m.depth_ = 0; | |
| 447 | |||
| 448 | // Check nesting depth | ||
| 449 | 50x | std::size_t max_sub_depth = 0; | |
| 450 | 332x | for(auto const& sm : sub.impl_->matchers) | |
| 451 | 564x | max_sub_depth = (std::max)(max_sub_depth, | |
| 452 | 282x | static_cast<std::size_t>(sm.depth_)); | |
| 453 | 50x | if(max_sub_depth + 1 >= max_path_depth) | |
| 454 | 1x | throw_length_error( | |
| 455 | "router nesting depth exceeds max_path_depth"); | ||
| 456 | |||
| 457 | // Compute offsets for re-indexing | ||
| 458 | 49x | auto const matcher_offset = impl_->matchers.size(); | |
| 459 | 49x | auto const entry_offset = impl_->entries.size(); | |
| 460 | |||
| 461 | // Recompute effective_opts for inlined matchers using depth stack | ||
| 462 | 49x | auto sub_root_eff = impl::compute_effective_opts( | |
| 463 | 49x | parent_eff, sub.impl_->opt_); | |
| 464 | opt_flags eff_stack[max_path_depth]; | ||
| 465 | 49x | eff_stack[0] = sub_root_eff; | |
| 466 | |||
| 467 | // Inline sub's matchers | ||
| 468 | 315x | for(auto& sm : sub.impl_->matchers) | |
| 469 | { | ||
| 470 | 266x | auto d = sm.depth_; | |
| 471 | 266x | opt_flags parent = (d > 0) ? eff_stack[d - 1] : parent_eff; | |
| 472 | 266x | eff_stack[d] = impl::compute_effective_opts(parent, sm.own_opts_); | |
| 473 | 266x | sm.effective_opts_ = eff_stack[d]; | |
| 474 | 266x | sm.depth_ += 1; // increase by 1 (parent is at depth 0) | |
| 475 | 266x | sm.first_entry_ += entry_offset; | |
| 476 | 266x | sm.skip_ += entry_offset; | |
| 477 | 266x | impl_->matchers.push_back(std::move(sm)); | |
| 478 | } | ||
| 479 | |||
| 480 | // Inline sub's entries | ||
| 481 | 98x | for(auto& se : sub.impl_->entries) | |
| 482 | { | ||
| 483 | 49x | se.matcher_idx += matcher_offset; | |
| 484 | 49x | impl_->entries.push_back(std::move(se)); | |
| 485 | } | ||
| 486 | |||
| 487 | // Set parent matcher's skip | ||
| 488 | // Need to re-fetch since vector may have reallocated | ||
| 489 | 49x | impl_->matchers[parent_matcher_idx].skip_ = impl_->entries.size(); | |
| 490 | |||
| 491 | // Merge global methods | ||
| 492 | 49x | impl_->global_methods_ |= sub.impl_->global_methods_; | |
| 493 | 49x | for(auto& v : sub.impl_->global_custom_verbs_) | |
| 494 | ✗ | impl_->global_custom_verbs_.push_back(std::move(v)); | |
| 495 | 49x | impl_->rebuild_global_allow_header(); | |
| 496 | |||
| 497 | // Move options handler if sub has one and parent doesn't | ||
| 498 | 49x | if(sub.impl_->options_handler_ && !impl_->options_handler_) | |
| 499 | ✗ | impl_->options_handler_ = std::move(sub.impl_->options_handler_); | |
| 500 | |||
| 501 | 49x | sub.impl_.reset(); | |
| 502 | } | ||
| 503 | |||
| 504 | std::size_t | ||
| 505 | 77x | router_base:: | |
| 506 | new_route( | ||
| 507 | std::string_view pattern) | ||
| 508 | { | ||
| 509 | 77x | impl_->finalize_pending(); | |
| 510 | |||
| 511 | 77x | if(pattern.empty()) | |
| 512 | ✗ | throw_invalid_argument(); | |
| 513 | |||
| 514 | 77x | auto const idx = impl_->matchers.size(); | |
| 515 | 77x | impl_->matchers.emplace_back(pattern, true); | |
| 516 | 77x | auto& m = impl_->matchers.back(); | |
| 517 | 77x | if(m.error()) | |
| 518 | 12x | throw_invalid_argument(); | |
| 519 | 65x | m.first_entry_ = impl_->entries.size(); | |
| 520 | 65x | m.effective_opts_ = impl::compute_effective_opts(0, impl_->opt_); | |
| 521 | 65x | m.own_opts_ = impl_->opt_; | |
| 522 | 65x | m.depth_ = 0; | |
| 523 | |||
| 524 | 65x | impl_->pending_route_ = idx; | |
| 525 | 65x | return idx; | |
| 526 | } | ||
| 527 | |||
| 528 | void | ||
| 529 | 58x | router_base:: | |
| 530 | add_to_route( | ||
| 531 | std::size_t idx, | ||
| 532 | http::method verb, | ||
| 533 | handlers hn) | ||
| 534 | { | ||
| 535 | 58x | if(verb == http::method::unknown) | |
| 536 | ✗ | throw_invalid_argument(); | |
| 537 | |||
| 538 | 58x | auto& m = impl_->matchers[idx]; | |
| 539 | 116x | for(std::size_t i = 0; i < hn.n; ++i) | |
| 540 | { | ||
| 541 | 58x | impl_->entries.emplace_back(verb, std::move(hn.p[i])); | |
| 542 | 58x | impl_->entries.back().matcher_idx = idx; | |
| 543 | 58x | impl_->update_allow_for_entry(m, impl_->entries.back()); | |
| 544 | } | ||
| 545 | 58x | impl_->rebuild_global_allow_header(); | |
| 546 | 58x | } | |
| 547 | |||
| 548 | void | ||
| 549 | 10x | router_base:: | |
| 550 | add_to_route( | ||
| 551 | std::size_t idx, | ||
| 552 | std::string_view verb, | ||
| 553 | handlers hn) | ||
| 554 | { | ||
| 555 | 10x | auto& m = impl_->matchers[idx]; | |
| 556 | |||
| 557 | 10x | if(verb.empty()) | |
| 558 | { | ||
| 559 | // all methods | ||
| 560 | 16x | for(std::size_t i = 0; i < hn.n; ++i) | |
| 561 | { | ||
| 562 | 8x | impl_->entries.emplace_back(std::move(hn.p[i])); | |
| 563 | 8x | impl_->entries.back().matcher_idx = idx; | |
| 564 | 8x | impl_->update_allow_for_entry(m, impl_->entries.back()); | |
| 565 | } | ||
| 566 | } | ||
| 567 | else | ||
| 568 | { | ||
| 569 | // specific method string | ||
| 570 | 4x | for(std::size_t i = 0; i < hn.n; ++i) | |
| 571 | { | ||
| 572 | 2x | impl_->entries.emplace_back(verb, std::move(hn.p[i])); | |
| 573 | 2x | impl_->entries.back().matcher_idx = idx; | |
| 574 | 2x | impl_->update_allow_for_entry(m, impl_->entries.back()); | |
| 575 | } | ||
| 576 | } | ||
| 577 | 10x | impl_->rebuild_global_allow_header(); | |
| 578 | 10x | } | |
| 579 | |||
| 580 | void | ||
| 581 | ✗ | router_base:: | |
| 582 | finalize_pending() | ||
| 583 | { | ||
| 584 | ✗ | if(impl_) | |
| 585 | ✗ | impl_->finalize_pending(); | |
| 586 | ✗ | } | |
| 587 | |||
| 588 | void | ||
| 589 | 4x | router_base:: | |
| 590 | set_options_handler_impl( | ||
| 591 | options_handler_ptr p) | ||
| 592 | { | ||
| 593 | 4x | impl_->options_handler_ = std::move(p); | |
| 594 | 4x | } | |
| 595 | |||
| 596 | //------------------------------------------------ | ||
| 597 | // | ||
| 598 | // dispatch | ||
| 599 | // | ||
| 600 | //------------------------------------------------ | ||
| 601 | |||
| 602 | route_task | ||
| 603 | 109x | router_base:: | |
| 604 | dispatch( | ||
| 605 | http::method verb, | ||
| 606 | urls::url_view const& url, | ||
| 607 | route_params& p) const | ||
| 608 | { | ||
| 609 | 109x | if(verb == http::method::unknown) | |
| 610 | 1x | throw_invalid_argument(); | |
| 611 | |||
| 612 | 108x | impl_->ensure_finalized(); | |
| 613 | |||
| 614 | // Handle OPTIONS * before normal dispatch | ||
| 615 | 113x | if(verb == http::method::options && | |
| 616 | 113x | url.encoded_path() == "*") | |
| 617 | { | ||
| 618 | 1x | if(impl_->options_handler_) | |
| 619 | { | ||
| 620 | 1x | return impl_->options_handler_->invoke( | |
| 621 | 1x | p, impl_->global_allow_header_); | |
| 622 | } | ||
| 623 | } | ||
| 624 | |||
| 625 | // Initialize params | ||
| 626 | 107x | auto& pv = *route_params_access{p}; | |
| 627 | 107x | pv.kind_ = is_plain; | |
| 628 | 107x | pv.verb_ = verb; | |
| 629 | 107x | pv.verb_str_.clear(); | |
| 630 | 107x | pv.ec_.clear(); | |
| 631 | 107x | pv.ep_ = nullptr; | |
| 632 | 107x | p.params.clear(); | |
| 633 | 107x | pv.decoded_path_ = pct_decode_path(url.encoded_path()); | |
| 634 | 107x | if(pv.decoded_path_.empty() || pv.decoded_path_.back() != '/') | |
| 635 | { | ||
| 636 | 70x | pv.decoded_path_.push_back('/'); | |
| 637 | 70x | pv.addedSlash_ = true; | |
| 638 | } | ||
| 639 | else | ||
| 640 | { | ||
| 641 | 37x | pv.addedSlash_ = false; | |
| 642 | } | ||
| 643 | 107x | p.base_path = { pv.decoded_path_.data(), 0 }; | |
| 644 | 107x | auto const subtract = (pv.addedSlash_ && pv.decoded_path_.size() > 1) ? 1 : 0; | |
| 645 | 107x | p.path = { pv.decoded_path_.data(), pv.decoded_path_.size() - subtract }; | |
| 646 | |||
| 647 | 107x | return impl_->dispatch_loop(p, verb == http::method::options); | |
| 648 | } | ||
| 649 | |||
| 650 | route_task | ||
| 651 | 6x | router_base:: | |
| 652 | dispatch( | ||
| 653 | std::string_view verb, | ||
| 654 | urls::url_view const& url, | ||
| 655 | route_params& p) const | ||
| 656 | { | ||
| 657 | 6x | if(verb.empty()) | |
| 658 | 1x | throw_invalid_argument(); | |
| 659 | |||
| 660 | 5x | impl_->ensure_finalized(); | |
| 661 | |||
| 662 | 5x | auto const method = http::string_to_method(verb); | |
| 663 | 5x | bool const is_options = (method == http::method::options); | |
| 664 | |||
| 665 | // Handle OPTIONS * before normal dispatch | ||
| 666 | 5x | if(is_options && url.encoded_path() == "*") | |
| 667 | { | ||
| 668 | ✗ | if(impl_->options_handler_) | |
| 669 | { | ||
| 670 | ✗ | return impl_->options_handler_->invoke( | |
| 671 | ✗ | p, impl_->global_allow_header_); | |
| 672 | } | ||
| 673 | } | ||
| 674 | |||
| 675 | // Initialize params | ||
| 676 | 5x | auto& pv = *route_params_access{p}; | |
| 677 | 5x | pv.kind_ = is_plain; | |
| 678 | 5x | pv.verb_ = method; | |
| 679 | 5x | if(pv.verb_ == http::method::unknown) | |
| 680 | 4x | pv.verb_str_ = verb; | |
| 681 | else | ||
| 682 | 1x | pv.verb_str_.clear(); | |
| 683 | 5x | pv.ec_.clear(); | |
| 684 | 5x | pv.ep_ = nullptr; | |
| 685 | 5x | p.params.clear(); | |
| 686 | 5x | pv.decoded_path_ = pct_decode_path(url.encoded_path()); | |
| 687 | 5x | if(pv.decoded_path_.empty() || pv.decoded_path_.back() != '/') | |
| 688 | { | ||
| 689 | ✗ | pv.decoded_path_.push_back('/'); | |
| 690 | ✗ | pv.addedSlash_ = true; | |
| 691 | } | ||
| 692 | else | ||
| 693 | { | ||
| 694 | 5x | pv.addedSlash_ = false; | |
| 695 | } | ||
| 696 | 5x | p.base_path = { pv.decoded_path_.data(), 0 }; | |
| 697 | 5x | auto const subtract = (pv.addedSlash_ && pv.decoded_path_.size() > 1) ? 1 : 0; | |
| 698 | 5x | p.path = { pv.decoded_path_.data(), pv.decoded_path_.size() - subtract }; | |
| 699 | |||
| 700 | 5x | return impl_->dispatch_loop(p, is_options); | |
| 701 | } | ||
| 702 | |||
| 703 | } // detail | ||
| 704 | } // http | ||
| 705 | } // boost | ||
| 706 |