src/server/any_router.cpp

91.6% Lines (218/238) 94.1% List of functions (16/17)
any_router.cpp
f(x) 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