src/server/any_router.cpp
91.6% Lines (218/238)
94.1% Functions (16/17)
| Line | TLA | Hits | Source Code |
|---|---|---|---|
| 1 | // | ||
| 2 | // Copyright (c) 2025 Vinnie Falco ([email protected]) | ||
| 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 | 187 | router_base::impl:: | |
| 32 | build_allow_header( | ||
| 33 | std::uint64_t methods, | ||
| 34 | std::vector<std::string> const& custom) | ||
| 35 | { | ||
| 36 | 187 | if(methods == ~0ULL) | |
| 37 | 34 | return "DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT"; | |
| 38 | |||
| 39 | 170 | 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 | 5780 | for(auto const& [m, name] : known) | |
| 76 | { | ||
| 77 | 5610 | if(methods & (1ULL << static_cast<unsigned>(m))) | |
| 78 | { | ||
| 79 | 128 | if(!result.empty()) | |
| 80 | 8 | result += ", "; | |
| 81 | 128 | result += name; | |
| 82 | } | ||
| 83 | } | ||
| 84 | 174 | for(auto const& v : custom) | |
| 85 | { | ||
| 86 | 4 | if(!result.empty()) | |
| 87 | ✗ | result += ", "; | |
| 88 | 4 | result += v; | |
| 89 | } | ||
| 90 | 170 | return result; | |
| 91 | 170 | } | |
| 92 | |||
| 93 | router_base::opt_flags | ||
| 94 | 495 | router_base::impl:: | |
| 95 | compute_effective_opts( | ||
| 96 | opt_flags parent, | ||
| 97 | opt_flags child) | ||
| 98 | { | ||
| 99 | 495 | opt_flags result = parent; | |
| 100 | |||
| 101 | // case_sensitive: bits 1-2 (2=true, 4=false) | ||
| 102 | 495 | if(child & 2) | |
| 103 | 4 | result = (result & ~6) | 2; | |
| 104 | 491 | else if(child & 4) | |
| 105 | 4 | result = (result & ~6) | 4; | |
| 106 | |||
| 107 | // strict: bits 3-4 (8=true, 16=false) | ||
| 108 | 495 | if(child & 8) | |
| 109 | 1 | result = (result & ~24) | 8; | |
| 110 | 494 | else if(child & 16) | |
| 111 | 1 | result = (result & ~24) | 16; | |
| 112 | |||
| 113 | 495 | return result; | |
| 114 | } | ||
| 115 | |||
| 116 | void | ||
| 117 | 34 | router_base::impl:: | |
| 118 | restore_path( | ||
| 119 | route_params& p, | ||
| 120 | std::size_t base_len) | ||
| 121 | { | ||
| 122 | 34 | auto& pv = *route_params_access{p}; | |
| 123 | 34 | p.base_path = { pv.decoded_path_.data(), base_len }; | |
| 124 | 34 | auto const path_len = pv.decoded_path_.size() - (pv.addedSlash_ ? 1 : 0); | |
| 125 | 34 | if(base_len < path_len) | |
| 126 | 33 | p.path = { pv.decoded_path_.data() + base_len, | |
| 127 | path_len - base_len }; | ||
| 128 | else | ||
| 129 | 2 | p.path = { pv.decoded_path_.data() + | |
| 130 | 1 | pv.decoded_path_.size() - 1, 1 }; // soft slash | |
| 131 | 34 | } | |
| 132 | |||
| 133 | void | ||
| 134 | 68 | router_base::impl:: | |
| 135 | update_allow_for_entry( | ||
| 136 | matcher& m, | ||
| 137 | entry const& e) | ||
| 138 | { | ||
| 139 | 68 | if(!m.end_) | |
| 140 | ✗ | return; | |
| 141 | |||
| 142 | // Per-matcher collection | ||
| 143 | 68 | if(e.all) | |
| 144 | 8 | m.allowed_methods_ = ~0ULL; | |
| 145 | 60 | else if(e.verb != http::method::unknown) | |
| 146 | 58 | m.allowed_methods_ |= (1ULL << static_cast<unsigned>(e.verb)); | |
| 147 | 2 | else if(!e.verb_str.empty()) | |
| 148 | 2 | m.custom_verbs_.push_back(e.verb_str); | |
| 149 | |||
| 150 | // Rebuild per-matcher Allow header eagerly | ||
| 151 | 68 | m.allow_header_ = build_allow_header( | |
| 152 | 68 | m.allowed_methods_, m.custom_verbs_); | |
| 153 | |||
| 154 | // Global collection (for OPTIONS *) | ||
| 155 | 68 | if(e.all) | |
| 156 | 8 | global_methods_ = ~0ULL; | |
| 157 | 60 | else if(e.verb != http::method::unknown) | |
| 158 | 58 | global_methods_ |= (1ULL << static_cast<unsigned>(e.verb)); | |
| 159 | 2 | else if(!e.verb_str.empty()) | |
| 160 | 2 | global_custom_verbs_.push_back(e.verb_str); | |
| 161 | } | ||
| 162 | |||
| 163 | void | ||
| 164 | 117 | router_base::impl:: | |
| 165 | rebuild_global_allow_header() | ||
| 166 | { | ||
| 167 | 117 | std::sort(global_custom_verbs_.begin(), global_custom_verbs_.end()); | |
| 168 | 234 | global_custom_verbs_.erase( | |
| 169 | 117 | std::unique(global_custom_verbs_.begin(), global_custom_verbs_.end()), | |
| 170 | 117 | global_custom_verbs_.end()); | |
| 171 | 117 | global_allow_header_ = build_allow_header( | |
| 172 | 117 | global_methods_, global_custom_verbs_); | |
| 173 | 117 | } | |
| 174 | |||
| 175 | void | ||
| 176 | 341 | router_base::impl:: | |
| 177 | finalize_pending() | ||
| 178 | { | ||
| 179 | 341 | if(pending_route_ == SIZE_MAX) | |
| 180 | 277 | return; | |
| 181 | 64 | auto& m = matchers[pending_route_]; | |
| 182 | 64 | if(entries.size() == m.first_entry_) | |
| 183 | { | ||
| 184 | // empty route, remove it | ||
| 185 | ✗ | matchers.pop_back(); | |
| 186 | } | ||
| 187 | else | ||
| 188 | { | ||
| 189 | 64 | m.skip_ = entries.size(); | |
| 190 | } | ||
| 191 | 64 | pending_route_ = SIZE_MAX; | |
| 192 | } | ||
| 193 | |||
| 194 | //------------------------------------------------ | ||
| 195 | // | ||
| 196 | // dispatch | ||
| 197 | // | ||
| 198 | //------------------------------------------------ | ||
| 199 | |||
| 200 | route_task | ||
| 201 | 112 | 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 | 224 | } | |
| 375 | |||
| 376 | //------------------------------------------------ | ||
| 377 | // | ||
| 378 | // router_base | ||
| 379 | // | ||
| 380 | //------------------------------------------------ | ||
| 381 | |||
| 382 | 168 | router_base:: | |
| 383 | router_base( | ||
| 384 | 168 | opt_flags opt) | |
| 385 | 168 | : impl_(std::make_shared<impl>(opt)) | |
| 386 | { | ||
| 387 | 168 | } | |
| 388 | |||
| 389 | void | ||
| 390 | 65 | router_base:: | |
| 391 | add_middleware( | ||
| 392 | std::string_view pattern, | ||
| 393 | handlers hn) | ||
| 394 | { | ||
| 395 | 65 | impl_->finalize_pending(); | |
| 396 | |||
| 397 | 65 | if(pattern.empty()) | |
| 398 | 34 | pattern = "/"; | |
| 399 | |||
| 400 | 65 | auto const matcher_idx = impl_->matchers.size(); | |
| 401 | 65 | impl_->matchers.emplace_back(pattern, false); | |
| 402 | 65 | auto& m = impl_->matchers.back(); | |
| 403 | 65 | if(m.error()) | |
| 404 | ✗ | throw_invalid_argument(); | |
| 405 | 65 | m.first_entry_ = impl_->entries.size(); | |
| 406 | 65 | m.effective_opts_ = impl::compute_effective_opts(0, impl_->opt_); | |
| 407 | 65 | m.own_opts_ = impl_->opt_; | |
| 408 | 65 | m.depth_ = 0; | |
| 409 | |||
| 410 | 139 | for(std::size_t i = 0; i < hn.n; ++i) | |
| 411 | { | ||
| 412 | 74 | impl_->entries.emplace_back(std::move(hn.p[i])); | |
| 413 | 74 | impl_->entries.back().matcher_idx = matcher_idx; | |
| 414 | } | ||
| 415 | |||
| 416 | 65 | m.skip_ = impl_->entries.size(); | |
| 417 | 65 | } | |
| 418 | |||
| 419 | void | ||
| 420 | 50 | router_base:: | |
| 421 | inline_router( | ||
| 422 | std::string_view pattern, | ||
| 423 | router_base&& sub) | ||
| 424 | { | ||
| 425 | 50 | impl_->finalize_pending(); | |
| 426 | |||
| 427 | 50 | if(!sub.impl_) | |
| 428 | ✗ | return; | |
| 429 | |||
| 430 | 50 | sub.impl_->finalize_pending(); | |
| 431 | |||
| 432 | 50 | if(pattern.empty()) | |
| 433 | ✗ | pattern = "/"; | |
| 434 | |||
| 435 | // Create parent matcher for the mount point | ||
| 436 | 50 | auto const parent_matcher_idx = impl_->matchers.size(); | |
| 437 | 50 | impl_->matchers.emplace_back(pattern, false); | |
| 438 | 50 | auto& parent_m = impl_->matchers.back(); | |
| 439 | 50 | if(parent_m.error()) | |
| 440 | ✗ | throw_invalid_argument(); | |
| 441 | 50 | parent_m.first_entry_ = impl_->entries.size(); | |
| 442 | |||
| 443 | 50 | auto parent_eff = impl::compute_effective_opts(0, impl_->opt_); | |
| 444 | 50 | parent_m.effective_opts_ = parent_eff; | |
| 445 | 50 | parent_m.own_opts_ = impl_->opt_; | |
| 446 | 50 | parent_m.depth_ = 0; | |
| 447 | |||
| 448 | // Check nesting depth | ||
| 449 | 50 | std::size_t max_sub_depth = 0; | |
| 450 | 332 | for(auto const& sm : sub.impl_->matchers) | |
| 451 | 564 | max_sub_depth = (std::max)(max_sub_depth, | |
| 452 | 282 | static_cast<std::size_t>(sm.depth_)); | |
| 453 | 50 | if(max_sub_depth + 1 >= max_path_depth) | |
| 454 | 1 | throw_length_error( | |
| 455 | "router nesting depth exceeds max_path_depth"); | ||
| 456 | |||
| 457 | // Compute offsets for re-indexing | ||
| 458 | 49 | auto const matcher_offset = impl_->matchers.size(); | |
| 459 | 49 | auto const entry_offset = impl_->entries.size(); | |
| 460 | |||
| 461 | // Recompute effective_opts for inlined matchers using depth stack | ||
| 462 | 49 | auto sub_root_eff = impl::compute_effective_opts( | |
| 463 | 49 | parent_eff, sub.impl_->opt_); | |
| 464 | opt_flags eff_stack[max_path_depth]; | ||
| 465 | 49 | eff_stack[0] = sub_root_eff; | |
| 466 | |||
| 467 | // Inline sub's matchers | ||
| 468 | 315 | for(auto& sm : sub.impl_->matchers) | |
| 469 | { | ||
| 470 | 266 | auto d = sm.depth_; | |
| 471 | 266 | opt_flags parent = (d > 0) ? eff_stack[d - 1] : parent_eff; | |
| 472 | 266 | eff_stack[d] = impl::compute_effective_opts(parent, sm.own_opts_); | |
| 473 | 266 | sm.effective_opts_ = eff_stack[d]; | |
| 474 | 266 | sm.depth_ += 1; // increase by 1 (parent is at depth 0) | |
| 475 | 266 | sm.first_entry_ += entry_offset; | |
| 476 | 266 | sm.skip_ += entry_offset; | |
| 477 | 266 | impl_->matchers.push_back(std::move(sm)); | |
| 478 | } | ||
| 479 | |||
| 480 | // Inline sub's entries | ||
| 481 | 98 | for(auto& se : sub.impl_->entries) | |
| 482 | { | ||
| 483 | 49 | se.matcher_idx += matcher_offset; | |
| 484 | 49 | 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 | 49 | impl_->matchers[parent_matcher_idx].skip_ = impl_->entries.size(); | |
| 490 | |||
| 491 | // Merge global methods | ||
| 492 | 49 | impl_->global_methods_ |= sub.impl_->global_methods_; | |
| 493 | 49 | for(auto& v : sub.impl_->global_custom_verbs_) | |
| 494 | ✗ | impl_->global_custom_verbs_.push_back(std::move(v)); | |
| 495 | 49 | impl_->rebuild_global_allow_header(); | |
| 496 | |||
| 497 | // Move options handler if sub has one and parent doesn't | ||
| 498 | 49 | if(sub.impl_->options_handler_ && !impl_->options_handler_) | |
| 499 | ✗ | impl_->options_handler_ = std::move(sub.impl_->options_handler_); | |
| 500 | |||
| 501 | 49 | sub.impl_.reset(); | |
| 502 | } | ||
| 503 | |||
| 504 | std::size_t | ||
| 505 | 77 | router_base:: | |
| 506 | new_route( | ||
| 507 | std::string_view pattern) | ||
| 508 | { | ||
| 509 | 77 | impl_->finalize_pending(); | |
| 510 | |||
| 511 | 77 | if(pattern.empty()) | |
| 512 | ✗ | throw_invalid_argument(); | |
| 513 | |||
| 514 | 77 | auto const idx = impl_->matchers.size(); | |
| 515 | 77 | impl_->matchers.emplace_back(pattern, true); | |
| 516 | 77 | auto& m = impl_->matchers.back(); | |
| 517 | 77 | if(m.error()) | |
| 518 | 12 | throw_invalid_argument(); | |
| 519 | 65 | m.first_entry_ = impl_->entries.size(); | |
| 520 | 65 | m.effective_opts_ = impl::compute_effective_opts(0, impl_->opt_); | |
| 521 | 65 | m.own_opts_ = impl_->opt_; | |
| 522 | 65 | m.depth_ = 0; | |
| 523 | |||
| 524 | 65 | impl_->pending_route_ = idx; | |
| 525 | 65 | return idx; | |
| 526 | } | ||
| 527 | |||
| 528 | void | ||
| 529 | 58 | router_base:: | |
| 530 | add_to_route( | ||
| 531 | std::size_t idx, | ||
| 532 | http::method verb, | ||
| 533 | handlers hn) | ||
| 534 | { | ||
| 535 | 58 | if(verb == http::method::unknown) | |
| 536 | ✗ | throw_invalid_argument(); | |
| 537 | |||
| 538 | 58 | auto& m = impl_->matchers[idx]; | |
| 539 | 116 | for(std::size_t i = 0; i < hn.n; ++i) | |
| 540 | { | ||
| 541 | 58 | impl_->entries.emplace_back(verb, std::move(hn.p[i])); | |
| 542 | 58 | impl_->entries.back().matcher_idx = idx; | |
| 543 | 58 | impl_->update_allow_for_entry(m, impl_->entries.back()); | |
| 544 | } | ||
| 545 | 58 | impl_->rebuild_global_allow_header(); | |
| 546 | 58 | } | |
| 547 | |||
| 548 | void | ||
| 549 | 10 | router_base:: | |
| 550 | add_to_route( | ||
| 551 | std::size_t idx, | ||
| 552 | std::string_view verb, | ||
| 553 | handlers hn) | ||
| 554 | { | ||
| 555 | 10 | auto& m = impl_->matchers[idx]; | |
| 556 | |||
| 557 | 10 | if(verb.empty()) | |
| 558 | { | ||
| 559 | // all methods | ||
| 560 | 16 | for(std::size_t i = 0; i < hn.n; ++i) | |
| 561 | { | ||
| 562 | 8 | impl_->entries.emplace_back(std::move(hn.p[i])); | |
| 563 | 8 | impl_->entries.back().matcher_idx = idx; | |
| 564 | 8 | impl_->update_allow_for_entry(m, impl_->entries.back()); | |
| 565 | } | ||
| 566 | } | ||
| 567 | else | ||
| 568 | { | ||
| 569 | // specific method string | ||
| 570 | 4 | for(std::size_t i = 0; i < hn.n; ++i) | |
| 571 | { | ||
| 572 | 2 | impl_->entries.emplace_back(verb, std::move(hn.p[i])); | |
| 573 | 2 | impl_->entries.back().matcher_idx = idx; | |
| 574 | 2 | impl_->update_allow_for_entry(m, impl_->entries.back()); | |
| 575 | } | ||
| 576 | } | ||
| 577 | 10 | impl_->rebuild_global_allow_header(); | |
| 578 | 10 | } | |
| 579 | |||
| 580 | void | ||
| 581 | ✗ | router_base:: | |
| 582 | finalize_pending() | ||
| 583 | { | ||
| 584 | ✗ | if(impl_) | |
| 585 | ✗ | impl_->finalize_pending(); | |
| 586 | ✗ | } | |
| 587 | |||
| 588 | void | ||
| 589 | 4 | router_base:: | |
| 590 | set_options_handler_impl( | ||
| 591 | options_handler_ptr p) | ||
| 592 | { | ||
| 593 | 4 | impl_->options_handler_ = std::move(p); | |
| 594 | 4 | } | |
| 595 | |||
| 596 | //------------------------------------------------ | ||
| 597 | // | ||
| 598 | // dispatch | ||
| 599 | // | ||
| 600 | //------------------------------------------------ | ||
| 601 | |||
| 602 | route_task | ||
| 603 | 109 | router_base:: | |
| 604 | dispatch( | ||
| 605 | http::method verb, | ||
| 606 | urls::url_view const& url, | ||
| 607 | route_params& p) const | ||
| 608 | { | ||
| 609 | 109 | if(verb == http::method::unknown) | |
| 610 | 1 | throw_invalid_argument(); | |
| 611 | |||
| 612 | 108 | impl_->ensure_finalized(); | |
| 613 | |||
| 614 | // Handle OPTIONS * before normal dispatch | ||
| 615 | 113 | if(verb == http::method::options && | |
| 616 | 113 | url.encoded_path() == "*") | |
| 617 | { | ||
| 618 | 1 | if(impl_->options_handler_) | |
| 619 | { | ||
| 620 | 1 | return impl_->options_handler_->invoke( | |
| 621 | 1 | p, impl_->global_allow_header_); | |
| 622 | } | ||
| 623 | } | ||
| 624 | |||
| 625 | // Initialize params | ||
| 626 | 107 | auto& pv = *route_params_access{p}; | |
| 627 | 107 | pv.kind_ = is_plain; | |
| 628 | 107 | pv.verb_ = verb; | |
| 629 | 107 | pv.verb_str_.clear(); | |
| 630 | 107 | pv.ec_.clear(); | |
| 631 | 107 | pv.ep_ = nullptr; | |
| 632 | 107 | p.params.clear(); | |
| 633 | 107 | pv.decoded_path_ = pct_decode_path(url.encoded_path()); | |
| 634 | 107 | if(pv.decoded_path_.empty() || pv.decoded_path_.back() != '/') | |
| 635 | { | ||
| 636 | 70 | pv.decoded_path_.push_back('/'); | |
| 637 | 70 | pv.addedSlash_ = true; | |
| 638 | } | ||
| 639 | else | ||
| 640 | { | ||
| 641 | 37 | pv.addedSlash_ = false; | |
| 642 | } | ||
| 643 | 107 | p.base_path = { pv.decoded_path_.data(), 0 }; | |
| 644 | 107 | auto const subtract = (pv.addedSlash_ && pv.decoded_path_.size() > 1) ? 1 : 0; | |
| 645 | 107 | p.path = { pv.decoded_path_.data(), pv.decoded_path_.size() - subtract }; | |
| 646 | |||
| 647 | 107 | return impl_->dispatch_loop(p, verb == http::method::options); | |
| 648 | } | ||
| 649 | |||
| 650 | route_task | ||
| 651 | 6 | router_base:: | |
| 652 | dispatch( | ||
| 653 | std::string_view verb, | ||
| 654 | urls::url_view const& url, | ||
| 655 | route_params& p) const | ||
| 656 | { | ||
| 657 | 6 | if(verb.empty()) | |
| 658 | 1 | throw_invalid_argument(); | |
| 659 | |||
| 660 | 5 | impl_->ensure_finalized(); | |
| 661 | |||
| 662 | 5 | auto const method = http::string_to_method(verb); | |
| 663 | 5 | bool const is_options = (method == http::method::options); | |
| 664 | |||
| 665 | // Handle OPTIONS * before normal dispatch | ||
| 666 | 5 | 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 | 5 | auto& pv = *route_params_access{p}; | |
| 677 | 5 | pv.kind_ = is_plain; | |
| 678 | 5 | pv.verb_ = method; | |
| 679 | 5 | if(pv.verb_ == http::method::unknown) | |
| 680 | 4 | pv.verb_str_ = verb; | |
| 681 | else | ||
| 682 | 1 | pv.verb_str_.clear(); | |
| 683 | 5 | pv.ec_.clear(); | |
| 684 | 5 | pv.ep_ = nullptr; | |
| 685 | 5 | p.params.clear(); | |
| 686 | 5 | pv.decoded_path_ = pct_decode_path(url.encoded_path()); | |
| 687 | 5 | 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 | 5 | pv.addedSlash_ = false; | |
| 695 | } | ||
| 696 | 5 | p.base_path = { pv.decoded_path_.data(), 0 }; | |
| 697 | 5 | auto const subtract = (pv.addedSlash_ && pv.decoded_path_.size() > 1) ? 1 : 0; | |
| 698 | 5 | p.path = { pv.decoded_path_.data(), pv.decoded_path_.size() - subtract }; | |
| 699 | |||
| 700 | 5 | return impl_->dispatch_loop(p, is_options); | |
| 701 | } | ||
| 702 | |||
| 703 | } // detail | ||
| 704 | } // http | ||
| 705 | } // boost | ||
| 706 |