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