Line data Source code
1 : //
2 : // Copyright (c) 2025 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_proto
8 : //
9 :
10 : #include "src/server/route_rule.hpp"
11 : #include <boost/http_proto/server/basic_router.hpp>
12 : #include <boost/http_proto/server/route_handler.hpp>
13 : #include <boost/http_proto/error.hpp>
14 : #include <boost/http_proto/detail/except.hpp>
15 : #include <boost/url/grammar/ci_string.hpp>
16 : #include <boost/url/grammar/hexdig_chars.hpp>
17 : #include <boost/assert.hpp>
18 : #include <atomic>
19 : #include <string>
20 : #include <vector>
21 :
22 : namespace boost {
23 : namespace http_proto {
24 :
25 : //namespace detail {
26 :
27 : // VFALCO Temporarily here until Boost.URL merges the fix
28 : static
29 : bool
30 96 : ci_is_equal(
31 : core::string_view s0,
32 : core::string_view s1) noexcept
33 : {
34 96 : auto n = s0.size();
35 96 : if(s1.size() != n)
36 0 : return false;
37 96 : auto p1 = s0.data();
38 96 : auto p2 = s1.data();
39 : char a, b;
40 : // fast loop
41 387 : while(n--)
42 : {
43 313 : a = *p1++;
44 313 : b = *p2++;
45 313 : if(a != b)
46 22 : goto slow;
47 : }
48 74 : return true;
49 : do
50 : {
51 3 : a = *p1++;
52 3 : b = *p2++;
53 25 : slow:
54 50 : if( grammar::to_lower(a) !=
55 25 : grammar::to_lower(b))
56 19 : return false;
57 : }
58 6 : while(n--);
59 3 : return true;
60 : }
61 :
62 :
63 : //------------------------------------------------
64 : /*
65 :
66 : pattern target path(use) path(get)
67 : -------------------------------------------------
68 : / / /
69 : / /api /api
70 : /api /api / /api
71 : /api /api/ / /api/
72 : /api /api/ / no-match strict
73 : /api /api/v0 /v0 no-match
74 : /api/ /api / /api
75 : /api/ /api / no-match strict
76 : /api/ /api/ / /api/
77 : /api/ /api/v0 /v0 no-match
78 :
79 : */
80 :
81 : //------------------------------------------------
82 :
83 : /*
84 : static
85 : void
86 : make_lower(std::string& s)
87 : {
88 : for(auto& c : s)
89 : c = grammar::to_lower(c);
90 : }
91 : */
92 :
93 : // decode all percent escapes
94 : static
95 : std::string
96 248 : pct_decode(
97 : urls::pct_string_view s)
98 : {
99 248 : std::string result;
100 248 : core::string_view sv(s);
101 248 : result.reserve(s.size());
102 248 : auto it = sv.data();
103 248 : auto const end = it + sv.size();
104 : for(;;)
105 : {
106 771 : if(it == end)
107 248 : break;
108 523 : if(*it != '%')
109 : {
110 521 : result.push_back(*it++);
111 521 : continue;
112 : }
113 2 : ++it;
114 : #if 0
115 : // pct_string_view can never have invalid pct-encodings
116 : if(it == end)
117 : goto invalid;
118 : #endif
119 2 : auto d0 = urls::grammar::hexdig_value(*it++);
120 : #if 0
121 : // pct_string_view can never have invalid pct-encodings
122 : if( d0 < 0 ||
123 : it == end)
124 : goto invalid;
125 : #endif
126 2 : auto d1 = urls::grammar::hexdig_value(*it++);
127 : #if 0
128 : // pct_string_view can never have invalid pct-encodings
129 : if(d1 < 0)
130 : goto invalid;
131 : #endif
132 2 : result.push_back(d0 * 16 + d1);
133 523 : }
134 496 : return result;
135 : #if 0
136 : invalid:
137 : // can't get here, as received a pct_string_view
138 : detail::throw_invalid_argument();
139 : #endif
140 0 : }
141 :
142 : // decode all percent escapes except slashes '/' and '\'
143 : static
144 : std::string
145 173 : pct_decode_path(
146 : urls::pct_string_view s)
147 : {
148 173 : std::string result;
149 173 : core::string_view sv(s);
150 173 : result.reserve(s.size());
151 173 : auto it = sv.data();
152 173 : auto const end = it + sv.size();
153 : for(;;)
154 : {
155 601 : if(it == end)
156 173 : break;
157 428 : if(*it != '%')
158 : {
159 424 : result.push_back(*it++);
160 424 : continue;
161 : }
162 4 : ++it;
163 : #if 0
164 : // pct_string_view can never have invalid pct-encodings
165 : if(it == end)
166 : goto invalid;
167 : #endif
168 4 : auto d0 = urls::grammar::hexdig_value(*it++);
169 : #if 0
170 : // pct_string_view can never have invalid pct-encodings
171 : if( d0 < 0 ||
172 : it == end)
173 : goto invalid;
174 : #endif
175 4 : auto d1 = urls::grammar::hexdig_value(*it++);
176 : #if 0
177 : // pct_string_view can never have invalid pct-encodings
178 : if(d1 < 0)
179 : goto invalid;
180 : #endif
181 4 : char c = d0 * 16 + d1;
182 4 : if( c != '/' &&
183 : c != '\\')
184 : {
185 2 : result.push_back(c);
186 2 : continue;
187 : }
188 2 : result.append(it - 3, 3);
189 428 : }
190 346 : return result;
191 : #if 0
192 : invalid:
193 : // can't get here, as received a pct_string_view
194 : detail::throw_invalid_argument();
195 : #endif
196 0 : }
197 :
198 : //------------------------------------------------
199 :
200 : //} // detail
201 :
202 : struct basic_request::
203 : match_result
204 : {
205 246 : void adjust_path(
206 : basic_request& req,
207 : std::size_t n)
208 : {
209 246 : n_ = n;
210 246 : if(n_ == 0)
211 165 : return;
212 81 : req.base_path = {
213 : req.base_path.data(),
214 81 : req.base_path.size() + n_ };
215 81 : if(n_ < req.path.size())
216 : {
217 27 : req.path.remove_prefix(n_);
218 : }
219 : else
220 : {
221 : // append a soft slash
222 54 : req.path = { req.decoded_path_.data() +
223 54 : req.decoded_path_.size() - 1, 1};
224 54 : BOOST_ASSERT(req.path == "/");
225 : }
226 : }
227 :
228 115 : void restore_path(
229 : basic_request& req)
230 : {
231 256 : if( n_ > 0 &&
232 140 : req.addedSlash_ &&
233 25 : req.path.data() ==
234 25 : req.decoded_path_.data() +
235 25 : req.decoded_path_.size() - 1)
236 : {
237 : // remove soft slash
238 19 : req.path = {
239 19 : req.base_path.data() +
240 19 : req.base_path.size(), 0 };
241 : }
242 115 : req.base_path.remove_suffix(n_);
243 345 : req.path = {
244 115 : req.path.data() - n_,
245 115 : req.path.size() + n_ };
246 115 : }
247 :
248 : private:
249 : std::size_t n_ = 0; // chars moved from path to base_path
250 : };
251 :
252 : //------------------------------------------------
253 :
254 : //namespace detail {
255 :
256 : // Matches a path against a pattern
257 : struct any_router::matcher
258 : {
259 : bool const end; // false for middleware
260 :
261 248 : matcher(
262 : core::string_view pat,
263 : bool end_)
264 248 : : end(end_)
265 248 : , decoded_pat_(
266 0 : [&pat]
267 : {
268 248 : auto s = pct_decode(pat);
269 248 : if( s.size() > 1
270 248 : && s.back() == '/')
271 6 : s.pop_back();
272 248 : return s;
273 496 : }())
274 248 : , slash_(pat == "/")
275 : {
276 248 : if(! slash_)
277 116 : pv_ = grammar::parse(
278 116 : decoded_pat_, path_rule).value();
279 248 : }
280 :
281 : /** Return true if req.path is a match
282 : */
283 281 : bool operator()(
284 : basic_request& req,
285 : match_result& mr) const
286 : {
287 281 : BOOST_ASSERT(! req.path.empty());
288 446 : if( slash_ && (
289 219 : ! end ||
290 335 : req.path == "/"))
291 : {
292 : // params = {};
293 165 : mr.adjust_path(req, 0);
294 165 : return true;
295 : }
296 116 : auto it = req.path.data();
297 116 : auto pit = pv_.segs.begin();
298 116 : auto const end_ = it + req.path.size();
299 116 : auto const pend = pv_.segs.end();
300 197 : while(it != end_ && pit != pend)
301 : {
302 : // prefix has to match
303 116 : auto s = core::string_view(it, end_);
304 116 : if(! req.case_sensitive)
305 : {
306 110 : if(pit->prefix.size() > s.size())
307 35 : return false;
308 96 : s = s.substr(0, pit->prefix.size());
309 : //if(! grammar::ci_is_equal(s, pit->prefix))
310 96 : if(! ci_is_equal(s, pit->prefix))
311 19 : return false;
312 : }
313 : else
314 : {
315 6 : if(! s.starts_with(pit->prefix))
316 2 : return false;
317 : }
318 81 : it += pit->prefix.size();
319 81 : ++pit;
320 : }
321 81 : if(end)
322 : {
323 : // require full match
324 42 : if( it != end_ ||
325 21 : pit != pend)
326 0 : return false;
327 : }
328 60 : else if(pit != pend)
329 : {
330 0 : return false;
331 : }
332 : // number of matching characters
333 81 : auto const n = it - req.path.data();
334 81 : mr.adjust_path(req, n);
335 81 : return true;
336 : }
337 :
338 : private:
339 : stable_string decoded_pat_;
340 : path_rule_t::value_type pv_;
341 : bool slash_;
342 : };
343 :
344 : //------------------------------------------------
345 :
346 : struct any_router::layer
347 : {
348 : struct entry
349 : {
350 : handler_ptr handler;
351 :
352 : // only for end routes
353 : http_proto::method verb =
354 : http_proto::method::unknown;
355 : std::string verb_str;
356 : bool all;
357 :
358 250 : explicit entry(
359 : handler_ptr h) noexcept
360 250 : : handler(std::move(h))
361 250 : , all(true)
362 : {
363 250 : }
364 :
365 70 : entry(
366 : http_proto::method verb_,
367 : handler_ptr h) noexcept
368 70 : : handler(std::move(h))
369 70 : , verb(verb_)
370 70 : , all(false)
371 : {
372 70 : BOOST_ASSERT(verb !=
373 : http_proto::method::unknown);
374 70 : }
375 :
376 9 : entry(
377 : core::string_view verb_str_,
378 : handler_ptr h) noexcept
379 9 : : handler(std::move(h))
380 9 : , verb(http_proto::string_to_method(verb_str_))
381 9 : , all(false)
382 : {
383 9 : if(verb != http_proto::method::unknown)
384 2 : return;
385 7 : verb_str = verb_str_;
386 : }
387 :
388 107 : bool match_method(
389 : basic_request const& req) const noexcept
390 : {
391 107 : if(all)
392 12 : return true;
393 95 : if(verb != http_proto::method::unknown)
394 80 : return req.verb_ == verb;
395 15 : if(req.verb_ != http_proto::method::unknown)
396 1 : return false;
397 14 : return req.verb_str_ == verb_str;
398 : }
399 : };
400 :
401 : matcher match;
402 : std::vector<entry> entries;
403 :
404 : // middleware layer
405 186 : layer(
406 : core::string_view pat,
407 : handler_list handlers)
408 186 : : match(pat, false)
409 : {
410 186 : entries.reserve(handlers.n);
411 422 : for(std::size_t i = 0; i < handlers.n; ++i)
412 236 : entries.emplace_back(std::move(handlers.p[i]));
413 186 : }
414 :
415 : // route layer
416 62 : explicit layer(
417 : core::string_view pat)
418 62 : : match(pat, true)
419 : {
420 62 : }
421 :
422 45 : std::size_t count() const noexcept
423 : {
424 45 : std::size_t n = 0;
425 96 : for(auto const& e : entries)
426 51 : n += e.handler->count();
427 45 : return n;
428 : }
429 : };
430 :
431 : //------------------------------------------------
432 :
433 : struct any_router::impl
434 : {
435 : std::atomic<std::size_t> refs{1};
436 : std::vector<layer> layers;
437 : opt_flags opt;
438 :
439 137 : explicit impl(
440 : opt_flags opt_) noexcept
441 137 : : opt(opt_)
442 : {
443 137 : }
444 : };
445 :
446 : //------------------------------------------------
447 :
448 153 : any_router::
449 137 : ~any_router()
450 : {
451 153 : if(! impl_)
452 16 : return;
453 137 : if(--impl_->refs == 0)
454 135 : delete impl_;
455 153 : }
456 :
457 137 : any_router::
458 : any_router(
459 137 : opt_flags opt)
460 137 : : impl_(new impl(opt))
461 : {
462 137 : }
463 :
464 15 : any_router::
465 15 : any_router(any_router&& other) noexcept
466 15 : :impl_(other.impl_)
467 : {
468 15 : other.impl_ = nullptr;
469 15 : }
470 :
471 1 : any_router::
472 1 : any_router(any_router const& other) noexcept
473 : {
474 1 : impl_ = other.impl_;
475 1 : ++impl_->refs;
476 1 : }
477 :
478 : any_router&
479 1 : any_router::
480 : operator=(any_router&& other) noexcept
481 : {
482 1 : auto p = impl_;
483 1 : impl_ = other.impl_;
484 1 : other.impl_ = nullptr;
485 1 : if(p && --p->refs == 0)
486 1 : delete p;
487 1 : return *this;
488 : }
489 :
490 : any_router&
491 1 : any_router::
492 : operator=(any_router const& other) noexcept
493 : {
494 1 : auto p = impl_;
495 1 : impl_ = other.impl_;
496 1 : ++impl_->refs;
497 1 : if(p && --p->refs == 0)
498 1 : delete p;
499 1 : return *this;
500 : }
501 :
502 : //------------------------------------------------
503 :
504 : std::size_t
505 4 : any_router::
506 : count() const noexcept
507 : {
508 4 : std::size_t n = 0;
509 8 : for(auto const& i : impl_->layers)
510 20 : for(auto const& e : i.entries)
511 16 : n += e.handler->count();
512 4 : return n;
513 : }
514 :
515 : auto
516 63 : any_router::
517 : new_layer(
518 : core::string_view pattern) -> layer&
519 : {
520 : // the pattern must not be empty
521 63 : if(pattern.empty())
522 1 : detail::throw_invalid_argument();
523 : // delete the last route if it is empty,
524 : // this happens if they call route() without
525 : // adding anything
526 92 : if(! impl_->layers.empty() &&
527 30 : impl_->layers.back().entries.empty())
528 1 : impl_->layers.pop_back();
529 62 : impl_->layers.emplace_back(pattern);
530 62 : return impl_->layers.back();
531 : };
532 :
533 : void
534 186 : any_router::
535 : add_impl(
536 : core::string_view pattern,
537 : handler_list const& handlers)
538 : {
539 186 : if( pattern.empty())
540 106 : pattern = "/";
541 186 : impl_->layers.emplace_back(
542 186 : pattern, std::move(handlers));
543 186 : }
544 :
545 : void
546 68 : any_router::
547 : add_impl(
548 : layer& e,
549 : http_proto::method verb,
550 : handler_list const& handlers)
551 : {
552 : // cannot be unknown
553 68 : if(verb == http_proto::method::unknown)
554 1 : detail::throw_invalid_argument();
555 :
556 67 : e.entries.reserve(e.entries.size() + handlers.n);
557 137 : for(std::size_t i = 0; i < handlers.n; ++i)
558 70 : e.entries.emplace_back(verb,
559 70 : std::move(handlers.p[i]));
560 67 : }
561 :
562 : void
563 23 : any_router::
564 : add_impl(
565 : layer& e,
566 : core::string_view verb_str,
567 : handler_list const& handlers)
568 : {
569 23 : e.entries.reserve(e.entries.size() + handlers.n);
570 :
571 23 : if(verb_str.empty())
572 : {
573 : // all
574 28 : for(std::size_t i = 0; i < handlers.n; ++i)
575 14 : e.entries.emplace_back(
576 14 : std::move(handlers.p[i]));
577 14 : return;
578 : }
579 :
580 : // possibly custom string
581 18 : for(std::size_t i = 0; i < handlers.n; ++i)
582 9 : e.entries.emplace_back(verb_str,
583 9 : std::move(handlers.p[i]));
584 : }
585 :
586 : //------------------------------------------------
587 :
588 : auto
589 9 : any_router::
590 : resume_impl(
591 : basic_request& req, basic_response& res,
592 : route_result ec) const ->
593 : route_result
594 : {
595 9 : BOOST_ASSERT(res.resume_ > 0);
596 17 : if( ec == route::send ||
597 17 : ec == route::close ||
598 16 : ec == route::complete)
599 3 : return ec;
600 6 : if(! is_route_result(ec))
601 : {
602 : // must indicate failure
603 2 : if(! ec.failed())
604 2 : detail::throw_invalid_argument();
605 : }
606 :
607 : // restore base_path and path
608 4 : req.base_path = { req.decoded_path_.data(), 0 };
609 4 : req.path = req.decoded_path_;
610 4 : if(req.addedSlash_)
611 1 : req.path.remove_suffix(1);
612 :
613 : // resume_ was set in the handler's wrapper
614 4 : BOOST_ASSERT(res.resume_ == res.pos_);
615 4 : res.pos_ = 0;
616 4 : res.ec_ = ec;
617 4 : return do_dispatch(req, res);
618 : }
619 :
620 : // top-level dispatch that gets called first
621 : route_result
622 173 : any_router::
623 : dispatch_impl(
624 : http_proto::method verb,
625 : core::string_view verb_str,
626 : urls::url_view const& url,
627 : basic_request& req,
628 : basic_response& res) const
629 : {
630 : // VFALCO we could reuse the string storage by not clearing them
631 : // set req.case_sensitive, req.strict to default of false
632 173 : req = {};
633 173 : if(verb == http_proto::method::unknown)
634 : {
635 33 : BOOST_ASSERT(! verb_str.empty());
636 33 : verb = http_proto::string_to_method(verb_str);
637 33 : if(verb == http_proto::method::unknown)
638 21 : req.verb_str_ = verb_str;
639 : }
640 : else
641 : {
642 140 : BOOST_ASSERT(verb_str.empty());
643 : }
644 173 : req.verb_ = verb;
645 : // VFALCO use reusing-StringToken
646 : req.decoded_path_ =
647 173 : pct_decode_path(url.encoded_path());
648 173 : BOOST_ASSERT(! req.decoded_path_.empty());
649 173 : req.base_path = { req.decoded_path_.data(), 0 };
650 173 : req.path = req.decoded_path_;
651 173 : if(req.decoded_path_.back() != '/')
652 : {
653 55 : req.decoded_path_.push_back('/');
654 55 : req.addedSlash_ = true;
655 : }
656 173 : BOOST_ASSERT(req.case_sensitive == false);
657 173 : BOOST_ASSERT(req.strict == false);
658 :
659 173 : res = {};
660 :
661 : // we cannot do anything after do_dispatch returns,
662 : // other than return the route_result, or else we
663 : // could race with the detached operation trying to resume.
664 173 : return do_dispatch(req, res);
665 : }
666 :
667 : // recursive dispatch
668 : route_result
669 193 : any_router::
670 : dispatch_impl(
671 : basic_request& req,
672 : basic_response& res) const
673 : {
674 : // options are recursive and need to be restored on
675 : // exception or when returning to a calling router.
676 : struct option_saver
677 : {
678 193 : option_saver(
679 : basic_request& req) noexcept
680 193 : : req_(&req)
681 193 : , case_sensitive_(req.case_sensitive)
682 193 : , strict_(req.strict)
683 : {
684 193 : }
685 :
686 193 : ~option_saver()
687 179 : {
688 193 : if(! req_)
689 14 : return;
690 179 : req_->case_sensitive = case_sensitive_;
691 179 : req_->strict = strict_;
692 193 : };
693 :
694 14 : void cancel() noexcept
695 : {
696 14 : req_ = nullptr;
697 14 : }
698 :
699 : private:
700 : basic_request* req_;
701 : bool case_sensitive_;
702 : bool strict_;
703 : };
704 :
705 193 : option_saver restore_options(req);
706 :
707 : // inherit or apply options
708 193 : if((impl_->opt & 2) != 0)
709 4 : req.case_sensitive = true;
710 189 : else if((impl_->opt & 4) != 0)
711 2 : req.case_sensitive = false;
712 :
713 193 : if((impl_->opt & 8) != 0)
714 0 : req.strict = true;
715 193 : else if((impl_->opt & 16) != 0)
716 0 : req.strict = false;
717 :
718 : // nested routers count as 1 call
719 : //++res.pos_;
720 :
721 193 : match_result mr;
722 347 : for(auto const& i : impl_->layers)
723 : {
724 285 : if(res.resume_ > 0)
725 : {
726 9 : auto const n = i.count(); // handlers in layer
727 9 : if(res.pos_ + n < res.resume_)
728 : {
729 3 : res.pos_ += n; // skip layer
730 3 : continue;
731 : }
732 : // repeat match to recreate the stack
733 6 : bool is_match = i.match(req, mr);
734 6 : BOOST_ASSERT(is_match);
735 : (void)is_match;
736 : }
737 : else
738 : {
739 276 : if(i.match.end && res.ec_.failed())
740 : {
741 : // routes can't have error handlers
742 1 : res.pos_ += i.count(); // skip layer
743 1 : continue;
744 : }
745 275 : if(! i.match(req, mr))
746 : {
747 : // not a match
748 35 : res.pos_ += i.count(); // skip layer
749 35 : continue;
750 : }
751 : }
752 246 : for(auto it = i.entries.begin();
753 426 : it != i.entries.end(); ++it)
754 : {
755 317 : auto const& e(*it);
756 317 : if(res.resume_)
757 : {
758 8 : auto const n = e.handler->count();
759 8 : if(res.pos_ + n < res.resume_)
760 : {
761 2 : res.pos_ += n; // skip entry
762 180 : continue;
763 : }
764 6 : BOOST_ASSERT(e.match_method(req));
765 : }
766 309 : else if(i.match.end)
767 : {
768 : // check verb for match
769 101 : if(! e.match_method(req))
770 : {
771 51 : res.pos_ += e.handler->count(); // skip entry
772 51 : continue;
773 : }
774 : }
775 :
776 264 : route_result rv;
777 : // increment before invoke
778 264 : ++res.pos_;
779 264 : if(res.pos_ != res.resume_)
780 : {
781 : // call the handler
782 260 : rv = e.handler->invoke(req, res);
783 : // res.pos_ can be incremented further
784 : // inside the above call to invoke.
785 260 : if(rv == route::detach)
786 : {
787 : // It is essential that we return immediately, without
788 : // doing anything after route::detach is returned,
789 : // otherwise we could race with the detached operation
790 : // attempting to call resume().
791 14 : restore_options.cancel();
792 127 : return rv;
793 : }
794 : }
795 : else
796 : {
797 : // a subrouter never detaches on its own
798 4 : BOOST_ASSERT(e.handler->count() == 1);
799 : // can't detach on resume
800 4 : if(res.ec_ == route::detach)
801 1 : detail::throw_invalid_argument();
802 : // do resume
803 3 : res.resume_ = 0;
804 3 : rv = res.ec_;
805 3 : res.ec_ = {};
806 : }
807 387 : if( rv == route::send ||
808 387 : rv == route::complete ||
809 386 : rv == route::close)
810 113 : return rv;
811 136 : if(rv == route::next)
812 98 : continue; // next entry
813 38 : if(rv == route::next_route)
814 : {
815 : // middleware can't return next_route
816 2 : if(! i.match.end)
817 1 : detail::throw_invalid_argument();
818 6 : while(++it != i.entries.end())
819 5 : res.pos_ += it->handler->count();
820 6 : break; // skip remaining entries
821 : }
822 : // we must handle all route enums
823 36 : BOOST_ASSERT(! is_route_result(rv));
824 36 : if(! rv.failed())
825 : {
826 : // handler must return non-successful error_code
827 2 : detail::throw_invalid_argument();
828 : }
829 : // error handling mode
830 34 : res.ec_ = rv;
831 34 : if(! i.match.end)
832 29 : continue; // next entry
833 : // routes don't have error handlers
834 11 : while(++it != i.entries.end())
835 6 : res.pos_ += it->handler->count();
836 5 : break; // skip remaining entries
837 : }
838 :
839 115 : mr.restore_path(req);
840 : }
841 :
842 62 : return route::next;
843 193 : }
844 :
845 : route_result
846 177 : any_router::
847 : do_dispatch(
848 : basic_request& req,
849 : basic_response& res) const
850 : {
851 177 : auto rv = dispatch_impl(req, res);
852 173 : BOOST_ASSERT(is_route_result(rv));
853 173 : BOOST_ASSERT(rv != route::next_route);
854 173 : if(rv != route::next)
855 : {
856 : // when rv == route::detach we must return immediately,
857 : // without attempting to perform any additional operations.
858 119 : return rv;
859 : }
860 54 : if(! res.ec_.failed())
861 : {
862 : // unhandled route
863 48 : return route::next;
864 : }
865 : // error condition
866 6 : return res.ec_;
867 : }
868 :
869 : //} // detail
870 :
871 : } // http_proto
872 : } // boost
|