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