GCC Code Coverage Report


Directory: ./
File: libs/http_proto/include/boost/http_proto/server/basic_router.hpp
Date: 2025-12-02 19:05:14
Exec Total Coverage
Lines: 163 172 94.8%
Functions: 243 263 92.4%
Branches: 74 84 88.1%

Line Branch Exec Source
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 #ifndef BOOST_HTTP_PROTO_SERVER_BASIC_ROUTER_HPP
11 #define BOOST_HTTP_PROTO_SERVER_BASIC_ROUTER_HPP
12
13 #include <boost/http_proto/detail/config.hpp>
14 #include <boost/http_proto/detail/type_traits.hpp>
15 #include <boost/http_proto/server/router_types.hpp>
16 #include <boost/http_proto/server/route_handler.hpp>
17 #include <boost/http_proto/method.hpp>
18 #include <boost/url/url_view.hpp>
19 #include <boost/capy/detail/call_traits.hpp>
20 #include <boost/core/detail/string_view.hpp>
21 #include <boost/core/detail/static_assert.hpp>
22 #include <type_traits>
23
24 namespace boost {
25 namespace http_proto {
26
27 template<class, class>
28 class basic_router;
29
30 /** Configuration options for routers.
31 */
32 struct router_options
33 {
34 /** Constructor
35
36 Routers constructed with default options inherit the values of
37 @ref case_sensitive and @ref strict from the parent router.
38 If there is no parent, both default to `false`.
39 The value of @ref merge_params always defaults to `false`
40 and is never inherited.
41 */
42 144 router_options() = default;
43
44 /** Set whether to merge parameters from parent routers.
45
46 This setting controls whether route parameters defined on parent
47 routers are made available in nested routers. It is not inherited
48 and always defaults to `false`.
49
50 @par Example
51 @code
52 router r(router_options()
53 .merge_params(true)
54 .case_sensitive(true)
55 .strict(false));
56 @endcode
57
58 @param value `true` to merge parameters from parent routers.
59 @return A reference to `*this` for chaining.
60 */
61 router_options&
62 1 merge_params(
63 bool value) noexcept
64 {
65
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 v_ = (v_ & ~1) | (value ? 1 : 0);
66 1 return *this;
67 }
68
69 /** Set whether pattern matching is case-sensitive.
70
71 When this option is not set explicitly, the value is inherited
72 from the parent router or defaults to `false` if there is no parent.
73
74 @par Example
75 @code
76 router r(router_options()
77 .case_sensitive(true)
78 .strict(true));
79 @endcode
80
81 @param value `true` to perform case-sensitive path matching.
82 @return A reference to `*this` for chaining.
83 */
84 router_options&
85 7 case_sensitive(
86 bool value) noexcept
87 {
88
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2 times.
7 if(value)
89 5 v_ = (v_ & ~6) | 2;
90 else
91 2 v_ = (v_ & ~6) | 4;
92 7 return *this;
93 }
94
95 /** Set whether pattern matching is strict.
96
97 When this option is not set explicitly, the value is inherited
98 from the parent router or defaults to `false` if there is no parent.
99 Strict matching treats a trailing slash as significant:
100 the pattern `"/api"` matches `"/api"` but not `"/api/"`.
101 When strict matching is disabled, these paths are treated
102 as equivalent.
103
104 @par Example
105 @code
106 router r(router_options()
107 .strict(true)
108 .case_sensitive(false));
109 @endcode
110
111 @param value `true` to enable strict path matching.
112 @return A reference to `*this` for chaining.
113 */
114 router_options&
115 1 strict(
116 bool value) noexcept
117 {
118
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if(value)
119 v_ = (v_ & ~24) | 8;
120 else
121 1 v_ = (v_ & ~24) | 16;
122 1 return *this;
123 }
124
125 private:
126 template<class, class> friend class basic_router;
127 unsigned int v_ = 0;
128 };
129
130 //-----------------------------------------------
131
132 //namespace detail {
133
134 class any_router;
135
136 //-----------------------------------------------
137
138 // implementation for all routers
139 class any_router
140 {
141 private:
142 template<class, class>
143 friend class http_proto::basic_router;
144 using opt_flags = unsigned int;
145
146 struct BOOST_HTTP_PROTO_DECL any_handler
147 {
148 696 virtual ~any_handler() = default;
149 virtual std::size_t count() const noexcept = 0;
150 virtual route_result invoke(
151 basic_request&, basic_response&) const = 0;
152 };
153
154 using handler_ptr = std::unique_ptr<any_handler>;
155
156 struct handler_list
157 {
158 std::size_t n;
159 handler_ptr* p;
160 };
161
162 using match_result = basic_request::match_result;
163 struct matcher;
164 struct layer;
165 struct impl;
166
167 BOOST_HTTP_PROTO_DECL ~any_router();
168 BOOST_HTTP_PROTO_DECL any_router(opt_flags);
169 BOOST_HTTP_PROTO_DECL any_router(any_router&&) noexcept;
170 BOOST_HTTP_PROTO_DECL any_router(any_router const&) noexcept;
171 BOOST_HTTP_PROTO_DECL any_router& operator=(any_router&&) noexcept;
172 BOOST_HTTP_PROTO_DECL any_router& operator=(any_router const&) noexcept;
173 BOOST_HTTP_PROTO_DECL std::size_t count() const noexcept;
174 BOOST_HTTP_PROTO_DECL layer& new_layer(core::string_view pattern);
175 BOOST_HTTP_PROTO_DECL void add_impl(core::string_view, handler_list const&);
176 BOOST_HTTP_PROTO_DECL void add_impl(layer&,
177 http_proto::method, handler_list const&);
178 BOOST_HTTP_PROTO_DECL void add_impl(layer&,
179 core::string_view, handler_list const&);
180 BOOST_HTTP_PROTO_DECL route_result resume_impl(
181 basic_request&, basic_response&, route_result ec) const;
182 BOOST_HTTP_PROTO_DECL route_result dispatch_impl(http_proto::method,
183 core::string_view, urls::url_view const&,
184 basic_request&, basic_response&) const;
185 BOOST_HTTP_PROTO_DECL route_result dispatch_impl(
186 basic_request&, basic_response&) const;
187 route_result do_dispatch(basic_request&, basic_response&) const;
188
189 impl* impl_ = nullptr;
190 };
191
192 //} // detail
193
194 //-----------------------------------------------
195
196 /** A container for HTTP route handlers.
197
198 `basic_router` objects store and dispatch route handlers based on the
199 HTTP method and path of an incoming request. Routes are added with a
200 path pattern and an associated handler, and the router is then used to
201 dispatch the appropriate handler.
202
203 Patterns used to create route definitions have percent-decoding applied
204 when handlers are mounted. A literal "%2F" in the pattern string is
205 indistinguishable from a literal '/'. For example, "/x%2Fz" is the
206 same as "/x/z" when used as a pattern.
207
208 @par Example
209 @code
210 using router_type = basic_router<Req, Res>;
211 router_type router;
212 router.get("/hello",
213 [](Req& req, Res& res)
214 {
215 res.status(http_proto::status::ok);
216 res.set_body("Hello, world!");
217 return route::send;
218 });
219 @endcode
220
221 Router objects are lightweight, shared references to their contents.
222 Copies of a router obtained through construction, conversion, or
223 assignment do not create new instances; they all refer to the same
224 underlying data.
225
226 @par Handlers
227 Regular handlers are invoked for matching routes and have this
228 equivalent signature:
229 @code
230 route_result handler( Req& req, Res& res )
231 @endcode
232
233 The return value is a @ref route_result used to indicate the desired
234 action through @ref route enum values, or to indicate that a failure
235 occurred. Failures are represented by error codes for which
236 `system::error_code::failed()` returns `true`.
237
238 When a failing error code is produced and remains unhandled, the
239 router enters error-dispatching mode. In this mode, only error
240 handlers are invoked. Error handlers are registered globally or
241 for specific paths and execute in the order of registration whenever
242 a failing error code is present in the response.
243
244 Error handlers have this equivalent signature:
245 @code
246 route_result error_handler( Req& req, Res& res, system::error_code ec )
247 @endcode
248
249 Each error handler may return any failing @ref system::error_code,
250 which is equivalent to calling:
251 @code
252 res.next(ec); // with ec.failed() == true
253 @endcode
254 Returning @ref route::next indicates that control should proceed to
255 the next matching error handler. Returning a different failing code
256 replaces the current error and continues dispatch in error mode using
257 that new code. Error handlers are invoked until one returns a result
258 other than @ref route::next.
259
260 @par Handler requirements
261 Regular handlers must be callable with:
262 @code
263 route_result( Req&, Res& )
264 @endcode
265
266 Error handlers must be callable with:
267 @code
268 route_result( Req&, Res&, system::error_code )
269 @endcode
270 Error handlers are invoked only when the response has a failing
271 error code, and execute in sequence until one returns a result
272 other than @ref route::next.
273
274 The prefix match is not strict: middleware attached to `"/api"`
275 will also match `"/api/users"` and `"/api/data"`. When registered
276 before route handlers for the same prefix, middleware runs before
277 those routes. This is analogous to `app.use(path, ...)` in
278 Express.js.
279
280 @par Thread Safety
281 Member functions marked `const` such as @ref dispatch and @ref resume
282 may be called concurrently on routers that refer to the same data.
283 Modification of routers through calls to non-`const` member functions
284 is not thread-safe and must not be performed concurrently with any
285 other member function.
286
287 @par Constraints
288 `Req` must be publicly derived from @ref basic_request.
289 `Res` must be publicly derived from @ref basic_response.
290
291 @tparam Req The type of request object.
292 @tparam Res The type of response object.
293 */
294 template<class Req, class Res>
295 class basic_router : public /*detail::*/any_router
296 {
297 // Req must be publicly derived from basic_request
298 BOOST_CORE_STATIC_ASSERT(
299 detail::derived_from<basic_request, Req>::value);
300
301 // Res must be publicly derived from basic_response
302 BOOST_CORE_STATIC_ASSERT(
303 detail::derived_from<basic_response, Res>::value);
304
305 // 0 = unrecognized
306 // 1 = normal handler (Req&, Res&)
307 // 2 = error handler (Req&, Res&, error_code)
308 // 4 = basic_router<Req, Res>
309
310 template<class T, class = void>
311 struct handler_type
312 : std::integral_constant<int, 0>
313 {
314 };
315
316 // route_result( Req&, Res& ) const
317 template<class T>
318 struct handler_type<T, typename
319 std::enable_if<std::is_convertible<
320 decltype(std::declval<T const&>()(
321 std::declval<Req&>(),
322 std::declval<Res&>())),
323 route_result>::value
324 >::type> : std::integral_constant<int, 1> {};
325
326 // route_result( Req&, Res&, system::error_code const& ) const
327 template<class T>
328 struct handler_type<T, typename
329 std::enable_if<std::is_convertible<
330 decltype(std::declval<T const&>()(
331 std::declval<Req&>(),
332 std::declval<Res&>(),
333 std::declval<system::error_code const&>())),
334 route_result>::value
335 >::type> : std::integral_constant<int, 2> {};
336
337 // basic_router<Req, Res>
338 template<class T>
339 struct handler_type<T, typename
340 std::enable_if<
341 std::is_base_of<any_router, T>::value &&
342 std::is_convertible<T const volatile*,
343 any_router const volatile*>::value &&
344 std::is_constructible<T, basic_router<Req, Res>>::value
345 >::type> : std::integral_constant<int, 4> {};
346
347 template<std::size_t Mask, class... Ts>
348 struct handler_check : std::true_type {};
349
350 template<std::size_t Mask, class T0, class... Ts>
351 struct handler_check<Mask, T0, Ts...>
352 : std::conditional<
353 ( (handler_type<T0>::value & Mask) != 0 ),
354 handler_check<Mask, Ts...>,
355 std::false_type
356 >::type {};
357
358 template<class H, class = void>
359 struct except_type : std::false_type {};
360
361 template<class H>
362 struct except_type<H, typename std::enable_if<
363 capy::detail::call_traits<H>{} && (
364 capy::detail::type_list_size<typename
365 capy::detail::call_traits<H>::arg_types>{} == 3) &&
366 std::is_convertible<Req&, typename capy::detail::type_at<0, typename
367 capy::detail::call_traits<H>::arg_types>::type>::value &&
368 std::is_convertible<Res&, typename capy::detail::type_at<1, typename
369 capy::detail::call_traits<H>::arg_types>::type>{}
370 >::type>
371 : std::true_type
372 {
373 using type = typename std::decay<typename
374 capy::detail::type_at<2,typename
375 capy::detail::call_traits<H>::arg_types>::type>::type;
376 };
377
378 template<int, class... Hs>
379 struct except_types;
380
381 template<int N>
382 struct except_types<N> : std::true_type {};
383
384 template<int N, class H1, class... HN>
385 struct except_types<N, H1, HN...> : std::integral_constant<bool,
386 except_type<H1>{} && except_types<N, HN...>{}>
387 {};
388
389 public:
390 /** The type of request object used in handlers
391 */
392 using request_type = Req;
393
394 /** The type of response object used in handlers
395 */
396 using response_type = Res;
397
398 /** A fluent interface for defining handlers on a specific route.
399
400 This type represents a single route within the router and
401 provides a chainable API for registering handlers associated
402 with particular HTTP methods or for all methods collectively.
403
404 Typical usage registers one or more handlers for a route:
405 @code
406 router.route("/users/:id")
407 .get(show_user)
408 .put(update_user)
409 .all(log_access);
410 @endcode
411
412 Each call appends handlers in registration order.
413 */
414 class fluent_route;
415
416 /** Constructor
417
418 Creates an empty router with the specified configuration.
419 Routers constructed with default options inherit the values
420 of @ref router_options::case_sensitive and
421 @ref router_options::strict from the parent router, or default
422 to `false` if there is no parent. The value of
423 @ref router_options::merge_params defaults to `false` and
424 is never inherited.
425
426 @param options The configuration options to use.
427 */
428 explicit
429 144 basic_router(router_options options = {})
430 144 : any_router(options.v_)
431 {
432 144 }
433
434 /** Construct a router from another router with compatible types.
435
436 This constructs a router that shares the same underlying routing
437 state as another router whose request and response types are base
438 classes of `Req` and `Res`, respectively.
439
440 The resulting router participates in shared ownership of the
441 implementation; copying the router does not duplicate routes or
442 handlers, and changes visible through one router are visible
443 through all routers that share the same underlying state.
444
445 @par Constraints
446 `Req` must be derived from `OtherReq`, and `Res` must be
447 derived from `OtherRes`.
448
449 @tparam OtherReq The request type of the source router.
450 @tparam OtherRes The response type of the source router.
451 @param other The router to copy.
452 */
453 template<
454 class OtherReq, class OtherRes,
455 class = typename std::enable_if<
456 detail::derived_from<Req, OtherReq>::value &&
457 detail::derived_from<Res, OtherRes>::value>::type
458 >
459 basic_router(
460 basic_router<OtherReq, OtherRes> const& other)
461 : any_router(other)
462 {
463 }
464
465 /** Add one or more middleware handlers for a path prefix.
466
467 Each handler registered with this function participates in the
468 routing and error-dispatch process for requests whose path begins
469 with the specified prefix, as described in the @ref basic_router
470 class documentation. Handlers execute in the order they are added
471 and may return @ref route::next to transfer control to the
472 subsequent handler in the chain.
473
474 @par Example
475 @code
476 router.use("/api",
477 [](Request& req, Response& res)
478 {
479 if (!authenticate(req))
480 {
481 res.status(401);
482 res.set_body("Unauthorized");
483 return route::send;
484 }
485 return route::next;
486 },
487 [](Request&, Response& res)
488 {
489 res.set_header("X-Powered-By", "MyServer");
490 return route::next;
491 });
492 @endcode
493
494 @par Preconditions
495 @p `pattern` must be a valid path prefix; it may be empty to
496 indicate the root scope.
497
498 @param pattern The pattern to match.
499 @param h1 The first handler to add.
500 @param hn Additional handlers to add, invoked after @p h1 in
501 registration order.
502 */
503 template<class H1, class... HN>
504 384 void use(
505 core::string_view pattern,
506 H1&& h1, HN... hn)
507 {
508 // If you get a compile error on this line it means that
509 // one or more of the provided types is not a valid handler,
510 // error handler, or router.
511 BOOST_CORE_STATIC_ASSERT(handler_check<7, H1, HN...>::value);
512
3/3
✓ Branch 11 taken 1 times.
✓ Branch 2 taken 165 times.
✓ Branch 5 taken 2 times.
384 add_impl(pattern, make_handler_list(
513 std::forward<H1>(h1), std::forward<HN>(hn)...));
514 384 }
515
516 /** Add one or more global middleware handlers.
517
518 Each handler registered with this function participates in the
519 routing and error-dispatch process as described in the
520 @ref basic_router class documentation. Handlers execute in the
521 order they are added and may return @ref route::next to transfer
522 control to the next handler in the chain.
523
524 This is equivalent to writing:
525 @code
526 use( "/", h1, hn... );
527 @endcode
528
529 @par Example
530 @code
531 router.use(
532 [](Request&, Response& res)
533 {
534 res.message.erase("X-Powered-By");
535 return route::next;
536 });
537 @endcode
538
539 @par Constraints
540 @li `h1` must not be convertible to @ref core::string_view.
541
542 @param h1 The first handler to add.
543 @param hn Additional handlers to add, invoked after @p h1 in
544 registration order.
545 */
546 template<class H1, class... HN
547 , class = typename std::enable_if<
548 ! std::is_convertible<H1, core::string_view>::value>::type>
549 215 void use(H1&& h1, HN&&... hn)
550 {
551 // If you get a compile error on this line it means that
552 // one or more of the provided types is not a valid handler,
553 // error handler, or router.
554 BOOST_CORE_STATIC_ASSERT(handler_check<7, H1, HN...>::value);
555
6/6
✓ Branch 5 taken 8 times.
✓ Branch 3 taken 92 times.
✓ Branch 7 taken 4 times.
✓ Branch 15 taken 1 times.
✓ Branch 17 taken 1 times.
✓ Branch 9 taken 2 times.
215 use(core::string_view(),
556 std::forward<H1>(h1), std::forward<HN>(hn)...);
557 215 }
558
559 /** Add a global exception handler.
560 */
561 template<class H1, class... HN>
562 22 void except(
563 core::string_view pattern,
564 H1&& h1, HN... hn)
565 {
566 // If you get a compile error on this line it means that one or
567 // more of the provided types is not a valid exception handler
568 BOOST_CORE_STATIC_ASSERT(except_types<0, H1, HN...>::value);
569
2/2
✓ Branch 2 taken 10 times.
✓ Branch 5 taken 10 times.
22 add_impl(pattern, make_except_list(
570 std::forward<H1>(h1), std::forward<HN>(hn)...));
571 22 }
572
573 template<class H1, class... HN
574 , class = typename std::enable_if<
575 ! std::is_convertible<H1, core::string_view>::value>::type>
576 22 void except(H1&& h1, HN&&... hn)
577 {
578 // If you get a compile error on this line it means that one or
579 // more of the provided types is not a valid exception handler
580 BOOST_CORE_STATIC_ASSERT(except_types<0, H1, HN...>::value);
581
2/2
✓ Branch 3 taken 10 times.
✓ Branch 5 taken 1 times.
22 except(core::string_view(),
582 std::forward<H1>(h1), std::forward<HN>(hn)...);
583 22 }
584
585 /** Add handlers for all HTTP methods matching a path pattern.
586
587 This registers regular handlers for the specified path pattern,
588 participating in dispatch as described in the @ref basic_router
589 class documentation. Handlers run when the route matches,
590 regardless of HTTP method, and execute in registration order.
591 Error handlers and routers cannot be passed here. A new route
592 object is created even if the pattern already exists.
593
594 @code
595 router.route("/status")
596 .head(check_headers)
597 .get(send_status)
598 .all(log_access);
599 @endcode
600
601 @par Preconditions
602 @p `pattern` must be a valid path pattern; it must not be empty.
603
604 @param pattern The path pattern to match.
605 @param h1 The first handler to add.
606 @param hn Additional handlers to add, invoked after @p h1 in
607 registration order.
608 */
609 template<class H1, class... HN>
610 12 void all(
611 core::string_view pattern,
612 H1&& h1, HN&&... hn)
613 {
614 // If you get a compile error on this line it means that
615 // one or more of the provided types is not a valid handler.
616 // Error handlers and routers cannot be passed here.
617 BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value);
618
2/2
✓ Branch 1 taken 11 times.
✓ Branch 5 taken 11 times.
12 this->route(pattern).all(
619 std::forward<H1>(h1), std::forward<HN>(hn)...);
620 11 }
621
622 /** Add one or more route handlers for a method and pattern.
623
624 This registers regular handlers for the specified HTTP verb and
625 path pattern, participating in dispatch as described in the
626 @ref basic_router class documentation. Error handlers and
627 routers cannot be passed here.
628
629 @param verb The known HTTP method to match.
630 @param pattern The path pattern to match.
631 @param h1 The first handler to add.
632 @param hn Additional handlers to add, invoked after @p h1 in
633 registration order.
634 */
635 template<class H1, class... HN>
636 42 void add(
637 http_proto::method verb,
638 core::string_view pattern,
639 H1&& h1, HN&&... hn)
640 {
641 // If you get a compile error on this line it means that
642 // one or more of the provided types is not a valid handler.
643 // Error handlers and routers cannot be passed here.
644 BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value);
645
2/2
✓ Branch 1 taken 21 times.
✓ Branch 5 taken 19 times.
42 this->route(pattern).add(verb,
646 std::forward<H1>(h1), std::forward<HN>(hn)...);
647 42 }
648
649 /** Add one or more route handlers for a method and pattern.
650
651 This registers regular handlers for the specified HTTP verb and
652 path pattern, participating in dispatch as described in the
653 @ref basic_router class documentation. Error handlers and
654 routers cannot be passed here.
655
656 @param verb The HTTP method string to match.
657 @param pattern The path pattern to match.
658 @param h1 The first handler to add.
659 @param hn Additional handlers to add, invoked after @p h1 in
660 registration order.
661 */
662 template<class H1, class... HN>
663 2 void add(
664 core::string_view verb,
665 core::string_view pattern,
666 H1&& h1, HN&&... hn)
667 {
668 // If you get a compile error on this line it means that
669 // one or more of the provided types is not a valid handler.
670 // Error handlers and routers cannot be passed here.
671 BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value);
672
2/2
✓ Branch 1 taken 2 times.
✓ Branch 5 taken 2 times.
2 this->route(pattern).add(verb,
673 std::forward<H1>(h1), std::forward<HN>(hn)...);
674 2 }
675
676 /** Return a fluent route for the specified path pattern.
677
678 Adds a new route to the router for the given pattern.
679 A new route object is always created, even if another
680 route with the same pattern already exists. The returned
681 @ref fluent_route reference allows method-specific handler
682 registration (such as GET or POST) or catch-all handlers
683 with @ref fluent_route::all.
684
685 @param pattern The path expression to match against request
686 targets. This may include parameters or wildcards following
687 the router's pattern syntax. May not be empty.
688 @return A fluent route interface for chaining handler registrations.
689 */
690 auto
691 63 route(
692 core::string_view pattern) -> fluent_route
693 {
694
1/1
✓ Branch 1 taken 62 times.
63 return fluent_route(*this, pattern);
695 }
696
697 //--------------------------------------------
698
699 /** Dispatch a request to the appropriate handler.
700
701 This runs the routing and error-dispatch logic for the given HTTP
702 method and target URL, as described in the @ref basic_router class
703 documentation.
704
705 @par Thread Safety
706 This function may be called concurrently on the same object along
707 with other `const` member functions. Each concurrent invocation
708 must use distinct request and response objects.
709
710 @param verb The HTTP method to match. This must not be
711 @ref http_proto::method::unknown.
712 @param url The full request target used for route matching.
713 @param req The request to pass to handlers.
714 @param res The response to pass to handlers.
715 @return The @ref route_result describing how routing completed.
716 @throws std::invalid_argument If @p verb is
717 @ref http_proto::method::unknown.
718 */
719 auto
720 148 dispatch(
721 http_proto::method verb,
722 urls::url_view const& url,
723 Req& req, Res& res) const ->
724 route_result
725 {
726
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 147 times.
148 if(verb == http_proto::method::unknown)
727 1 detail::throw_invalid_argument();
728
1/1
✓ Branch 2 taken 144 times.
291 return dispatch_impl(verb,
729 288 core::string_view(), url, req, res);
730 }
731
732 /** Dispatch a request to the appropriate handler using a method string.
733
734 This runs the routing and error-dispatch logic for the given HTTP
735 method string and target URL, as described in the @ref basic_router
736 class documentation. This overload is intended for method tokens
737 that are not represented by @ref http_proto::method.
738
739 @par Thread Safety
740 This function may be called concurrently on the same object along
741 with other `const` member functions. Each concurrent invocation
742 must use distinct request and response objects.
743
744 @param verb The HTTP method string to match. This must not be empty.
745 @param url The full request target used for route matching.
746 @param req The request to pass to handlers.
747 @param res The response to pass to handlers.
748 @return The @ref route_result describing how routing completed.
749 @throws std::invalid_argument If @p verb is empty.
750 */
751 auto
752 34 dispatch(
753 core::string_view verb,
754 urls::url_view const& url,
755 Req& req, Res& res) ->
756 route_result
757 {
758 // verb cannot be empty
759
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 33 times.
34 if(verb.empty())
760 1 detail::throw_invalid_argument();
761 33 return dispatch_impl(
762 http_proto::method::unknown,
763 33 verb, url, req, res);
764 }
765
766 /** Resume dispatch after a detached handler.
767
768 This continues routing after a previous call to @ref dispatch
769 returned @ref route::detach. It recreates the routing state and
770 resumes as if the handler that detached had instead returned
771 the specified @p ec from its body. The regular routing and
772 error-dispatch logic then proceeds as described in the
773 @ref basic_router class documentation. For example, if @p ec is
774 @ref route::next, the next matching handlers are invoked.
775
776 @par Thread Safety
777 This function may be called concurrently on the same object along
778 with other `const` member functions. Each concurrent invocation
779 must use distinct request and response objects.
780
781 @param req The request to pass to handlers.
782 @param res The response to pass to handlers.
783 @param rv The @ref route_result to resume with, as if returned
784 by the detached handler.
785 @return The @ref route_result describing how routing completed.
786 */
787 auto
788 9 resume(
789 Req& req, Res& res,
790 route_result const& rv) const ->
791 route_result
792 {
793 9 return resume_impl(req, res, rv);
794 }
795
796 private:
797 struct undo_resume
798 {
799 std::size_t& resume;
800 bool undo_ = true;
801 232 ~undo_resume()
802 {
803
2/2
✓ Branch 0 taken 220 times.
✓ Branch 1 taken 12 times.
232 if(undo_)
804 220 resume = 0;
805 232 }
806 232 undo_resume(
807 std::size_t& resume_) noexcept
808 232 : resume(resume_)
809 {
810 232 }
811 12 void cancel() noexcept
812 {
813 12 undo_ = false;
814 12 }
815 };
816
817 // wrapper for route handlers
818 template<
819 class H,
820 class Ty = handler_type<typename
821 std::decay<H>::type > >
822 struct handler_impl : any_handler
823 {
824 typename std::decay<H>::type h;
825
826 template<class... Args>
827 672 explicit handler_impl(Args&&... args)
828 672 : h(std::forward<Args>(args)...)
829 {
830 672 }
831
832 std::size_t
833 282 count() const noexcept override
834 {
835 282 return count(Ty{});
836 }
837
838 route_result
839 532 invoke(
840 basic_request& req,
841 basic_response& res) const override
842 {
843
1/1
✓ Branch 1 taken 91 times.
1052 return invoke(
844 static_cast<Req&>(req),
845 702 static_cast<Res&>(res), Ty{});
846 }
847
848 private:
849 std::size_t count(
850 std::integral_constant<int, 0>) = delete;
851
852 262 std::size_t count(
853 std::integral_constant<int, 1>) const noexcept
854 {
855 262 return 1;
856 }
857
858 6 std::size_t count(
859 std::integral_constant<int, 2>) const noexcept
860 {
861 6 return 1;
862 }
863
864 4 std::size_t count(
865 std::integral_constant<int, 4>) const noexcept
866 {
867 4 return 1 + h.count();
868 }
869
870 route_result invoke(Req&, Res&,
871 std::integral_constant<int, 0>) const = delete;
872
873 // (Req, Res)
874 406 route_result invoke(Req& req, Res& res,
875 std::integral_constant<int, 1>) const
876 {
877 406 auto& res_ = static_cast<
878 basic_response&>(res);
879
5/6
✓ Branch 1 taken 191 times.
✓ Branch 2 taken 12 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 191 times.
✓ Branch 6 taken 12 times.
✓ Branch 7 taken 191 times.
406 if( res_.ec_.failed() ||
880 res_.ep_)
881 24 return http_proto::route::next;
882 // avoid racing on res_.resume_
883 382 undo_resume u(res_.resume_);
884 382 res_.resume_ = res_.pos_;
885
1/1
✓ Branch 1 taken 28 times.
382 auto rv = h(req, res);
886
3/4
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 159 times.
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
370 if(rv == http_proto::route::detach)
887 {
888 24 u.cancel();
889 24 return rv;
890 }
891 346 return rv;
892 382 }
893
894 // (Req&, Res&, error_code)
895 route_result
896 46 invoke(Req& req, Res& res,
897 std::integral_constant<int, 2>) const
898 {
899 46 auto& res_ = static_cast<
900 basic_response&>(res);
901
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 38 times.
46 if(! res_.ec_.failed())
902 8 return http_proto::route::next;
903 // avoid racing on res.resume_
904 38 res_.resume_ = res_.pos_;
905 38 undo_resume u(res_.resume_);
906
1/1
✓ Branch 1 taken 38 times.
38 auto rv = h(req, res, res_.ec_);
907
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 38 times.
38 if(rv == http_proto::route::detach)
908 {
909 u.cancel();
910 return rv;
911 }
912 38 return rv;
913 38 }
914
915 // any_router
916 17 route_result invoke(Req& req, Res& res,
917 std::integral_constant<int, 4>) const
918 {
919 17 auto const& res_ = static_cast<
920 basic_response const&>(res);
921
4/4
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 16 times.
✓ Branch 3 taken 1 times.
32 if( res_.resume_ > 0 ||
922
2/2
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 1 times.
15 ( ! res_.ec_.failed() &&
923
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 ! res_.ep_))
924 16 return h.dispatch_impl(req, res);
925 1 return http_proto::route::next;
926 }
927 };
928
929 template<class H, class E = typename
930 except_type<typename std::decay<H>::type>::type>
931 struct except_impl : any_handler
932 {
933 typename std::decay<H>::type h;
934
935 template<class... Args>
936 24 explicit except_impl(Args&&... args)
937 24 : h(std::forward<Args>(args)...)
938 {
939 24 }
940
941 std::size_t
942 count() const noexcept override
943 {
944 return 1;
945 }
946
947 route_result
948 24 invoke(Req& req, Res& res) const override
949 {
950 #ifndef BOOST_NO_EXCEPTIONS
951 24 auto& res_ = static_cast<
952 basic_response&>(res);
953
3/4
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 6 times.
48 if( ! res_.ec_.failed() &&
954
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 6 times.
24 ! res_.ep_)
955 12 return http_proto::route::next;
956 12 volatile int dummy = sizeof(E);
957 12 (void)(dummy);
958 try
959 {
960 24 std::rethrow_exception(res_.ep_);
961 }
962
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
18 catch(E const& ex)
963 {
964 // avoid racing on res.resume_
965 6 res_.resume_ = res_.pos_;
966 6 undo_resume u(res_.resume_);
967 // VFALCO What if h throws?
968 6 auto rv = h(req, res, ex);
969
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
6 if(rv == http_proto::route::detach)
970 {
971 u.cancel();
972 return rv;
973 }
974 6 return rv;
975 6 }
976 6 catch(...)
977 {
978 6 res_.ep_ = std::current_exception();
979 6 return http_proto::route::next;
980 }
981 #else
982 (void)req;
983 (void)res;
984 return http_proto::route::next;
985 #endif
986 }
987 };
988
989 template<std::size_t N>
990 struct handler_list_impl : handler_list
991 {
992 template<class... HN>
993 566 explicit handler_list_impl(HN&&... hn)
994 {
995 566 n = sizeof...(HN);
996 566 p = v;
997
2/2
✓ Branch 8 taken 1 times.
✓ Branch 2 taken 253 times.
566 assign<0>(std::forward<HN>(hn)...);
998 566 }
999
1000 // exception handlers
1001 template<class... HN>
1002 22 explicit handler_list_impl(int, HN&&... hn)
1003 {
1004 22 n = sizeof...(HN);
1005 22 p = v;
1006
1/1
✓ Branch 2 taken 10 times.
22 assign<0>(0, std::forward<HN>(hn)...);
1007 22 }
1008
1009 private:
1010 template<std::size_t I, class H1, class... HN>
1011 672 void assign(H1&& h1, HN&&... hn)
1012 {
1013
2/4
✓ Branch 1 taken 336 times.
✓ Branch 5 taken 6 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
672 v[I] = handler_ptr(new handler_impl<H1>(
1014 std::forward<H1>(h1)));
1015 672 assign<I+1>(std::forward<HN>(hn)...);
1016 672 }
1017
1018 // exception handlers
1019 template<std::size_t I, class H1, class... HN>
1020 24 void assign(int, H1&& h1, HN&&... hn)
1021 {
1022
1/1
✓ Branch 1 taken 12 times.
24 v[I] = handler_ptr(new except_impl<H1>(
1023 std::forward<H1>(h1)));
1024 24 assign<I+1>(0, std::forward<HN>(hn)...);
1025 24 }
1026
1027 template<std::size_t>
1028 588 void assign(int = 0)
1029 {
1030 588 }
1031
1032 handler_ptr v[N];
1033 };
1034
1035 template<class... HN>
1036 static auto
1037 566 make_handler_list(HN&&... hn) ->
1038 handler_list_impl<sizeof...(HN)>
1039 {
1040 return handler_list_impl<sizeof...(HN)>(
1041 566 std::forward<HN>(hn)...);
1042 }
1043
1044 template<class... HN>
1045 static auto
1046 22 make_except_list(HN&&... hn) ->
1047 handler_list_impl<sizeof...(HN)>
1048 {
1049 return handler_list_impl<sizeof...(HN)>(
1050 22 0, std::forward<HN>(hn)...);
1051 }
1052
1053 void append(layer& e,
1054 http_proto::method verb,
1055 handler_list const& handlers)
1056 {
1057 add_impl(e, verb, handlers);
1058 }
1059 };
1060
1061 //-----------------------------------------------
1062
1063 template<class Req, class Res>
1064 class basic_router<Req, Res>::
1065 fluent_route
1066 {
1067 public:
1068 fluent_route(fluent_route const&) = default;
1069
1070 /** Add handlers that apply to all HTTP methods.
1071
1072 This registers regular handlers that run for any request matching
1073 the route's pattern, regardless of HTTP method. Handlers are
1074 appended to the route's handler sequence and are invoked in
1075 registration order whenever a preceding handler returns
1076 @ref route::next. Error handlers and routers cannot be passed here.
1077
1078 This function returns a @ref fluent_route, allowing additional
1079 method registrations to be chained. For example:
1080 @code
1081 router.route("/resource")
1082 .all(log_request)
1083 .get(show_resource)
1084 .post(update_resource);
1085 @endcode
1086
1087 @param h1 The first handler to add.
1088 @param hn Additional handlers to add, invoked after @p h1 in
1089 registration order.
1090 @return The @ref fluent_route for further chained registrations.
1091 */
1092 template<class H1, class... HN>
1093 14 auto all(
1094 H1&& h1, HN&&... hn) ->
1095 fluent_route
1096 {
1097 // If you get a compile error on this line it means that
1098 // one or more of the provided types is not a valid handler.
1099 // Error handlers and routers cannot be passed here.
1100 BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value);
1101
2/2
✓ Branch 2 taken 14 times.
✓ Branch 6 taken 14 times.
14 owner_.add_impl(e_, core::string_view(), make_handler_list(
1102 std::forward<H1>(h1), std::forward<HN>(hn)...));
1103 14 return *this;
1104 }
1105
1106 /** Add handlers for a specific HTTP method.
1107
1108 This registers regular handlers for the given method on the
1109 current route, participating in dispatch as described in the
1110 @ref basic_router class documentation. Handlers are appended
1111 to the route's handler sequence and invoked in registration
1112 order whenever a preceding handler returns @ref route::next.
1113 Error handlers and routers cannot be passed here.
1114
1115 @param verb The HTTP method to match.
1116 @param h1 The first handler to add.
1117 @param hn Additional handlers to add, invoked after @p h1 in
1118 registration order.
1119 @return The @ref fluent_route for further chained registrations.
1120 */
1121 template<class H1, class... HN>
1122 135 auto add(
1123 http_proto::method verb,
1124 H1&& h1, HN&&... hn) ->
1125 fluent_route
1126 {
1127 // If you get a compile error on this line it means that
1128 // one or more of the provided types is not a valid handler.
1129 // Error handlers and routers cannot be passed here.
1130 BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value);
1131
4/4
✓ Branch 2 taken 65 times.
✓ Branch 5 taken 64 times.
✓ Branch 3 taken 1 times.
✓ Branch 6 taken 1 times.
135 owner_.add_impl(e_, verb, make_handler_list(
1132 std::forward<H1>(h1), std::forward<HN>(hn)...));
1133 133 return *this;
1134 }
1135
1136 /** Add handlers for a method name.
1137
1138 This registers regular handlers for the given HTTP method string
1139 on the current route, participating in dispatch as described in
1140 the @ref basic_router class documentation. This overload is
1141 intended for methods not represented by @ref http_proto::method.
1142 Handlers are appended to the route's handler sequence and invoked
1143 in registration order whenever a preceding handler returns
1144 @ref route::next.
1145
1146 @par Constraints
1147 @li Each handler must be a regular handler; error handlers and
1148 routers cannot be passed.
1149
1150 @param verb The HTTP method string to match.
1151 @param h1 The first handler to add.
1152 @param hn Additional handlers to add, invoked after @p h1 in
1153 registration order.
1154 @return The @ref fluent_route for further chained registrations.
1155 */
1156 template<class H1, class... HN>
1157 9 auto add(
1158 core::string_view verb,
1159 H1&& h1, HN&&... hn) ->
1160 fluent_route
1161 {
1162 // If you get a compile error on this line it means that
1163 // one or more of the provided types is not a valid handler.
1164 // Error handlers and routers cannot be passed here.
1165 BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value);
1166
2/2
✓ Branch 2 taken 9 times.
✓ Branch 5 taken 9 times.
9 owner_.add_impl(e_, verb, make_handler_list(
1167 std::forward<H1>(h1), std::forward<HN>(hn)...));
1168 9 return *this;
1169 }
1170
1171 private:
1172 friend class basic_router;
1173 63 fluent_route(
1174 basic_router& owner,
1175 core::string_view pattern)
1176 63 : e_(owner.new_layer(pattern))
1177 62 , owner_(owner)
1178 {
1179 62 }
1180
1181 layer& e_;
1182 basic_router& owner_;
1183 };
1184
1185 } // http_proto
1186 } // boost
1187
1188 #endif
1189