libs/http/include/boost/http/server/basic_router.hpp

96.6% Lines (85/88) 87.8% Functions (480/547) 100.0% Branches (42/42)
libs/http/include/boost/http/server/basic_router.hpp
Line Branch Hits 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
8 //
9
10 #ifndef BOOST_HTTP_SERVER_BASIC_ROUTER_HPP
11 #define BOOST_HTTP_SERVER_BASIC_ROUTER_HPP
12
13 #include <boost/http/detail/config.hpp>
14 #include <boost/http/server/router_types.hpp>
15 #include <boost/http/server/detail/router_base.hpp>
16 #include <boost/http/method.hpp>
17 #include <boost/url/url_view.hpp>
18 #include <boost/mp11/algorithm.hpp>
19 #include <boost/assert.hpp>
20 #include <exception>
21 #include <string_view>
22 #include <type_traits>
23
24 namespace boost {
25 namespace http {
26
27 template<class> class basic_router;
28
29 /** Configuration options for HTTP routers.
30 */
31 struct router_options
32 {
33 /** Constructor.
34
35 Routers constructed with default options inherit the values of
36 @ref case_sensitive and @ref strict from the parent router.
37 If there is no parent, both default to `false`.
38 The value of @ref merge_params always defaults to `false`
39 and is never inherited.
40 */
41 158 router_options() = default;
42
43 /** Set whether to merge parameters from parent routers.
44
45 This setting controls whether route parameters defined on parent
46 routers are made available in nested routers. It is not inherited
47 and always defaults to `false`.
48
49 @par Example
50 @code
51 basic_router r( router_options()
52 .merge_params( true )
53 .case_sensitive( true )
54 .strict( false ) );
55 @endcode
56
57 @param value `true` to merge parameters from parent routers.
58
59 @return A reference to `*this` for chaining.
60 */
61 router_options&
62 merge_params(
63 bool value) noexcept
64 {
65 v_ = (v_ & ~1) | (value ? 1 : 0);
66 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 basic_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
83 @return A reference to `*this` for chaining.
84 */
85 router_options&
86 6 case_sensitive(
87 bool value) noexcept
88 {
89
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 if(value)
90 4 v_ = (v_ & ~6) | 2;
91 else
92 2 v_ = (v_ & ~6) | 4;
93 6 return *this;
94 }
95
96 /** Set whether pattern matching is strict.
97
98 When this option is not set explicitly, the value is inherited
99 from the parent router or defaults to `false` if there is no parent.
100 Strict matching treats a trailing slash as significant:
101 the pattern `"/api"` matches `"/api"` but not `"/api/"`.
102 When strict matching is disabled, these paths are treated
103 as equivalent.
104
105 @par Example
106 @code
107 basic_router r( router_options()
108 .strict( true )
109 .case_sensitive( false ) );
110 @endcode
111
112 @param value `true` to enable strict path matching.
113
114 @return A reference to `*this` for chaining.
115 */
116 router_options&
117 2 strict(
118 bool value) noexcept
119 {
120
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 if(value)
121 1 v_ = (v_ & ~24) | 8;
122 else
123 1 v_ = (v_ & ~24) | 16;
124 2 return *this;
125 }
126
127 private:
128 template<class> friend class basic_router;
129 unsigned int v_ = 0;
130 };
131
132 //-----------------------------------------------
133
134 /** A container for HTTP route handlers.
135
136 `basic_router` objects store and dispatch route handlers based on the
137 HTTP method and path of an incoming request. Routes are added with a
138 path pattern, method, and an associated handler, and the router is then
139 used to dispatch the appropriate handler.
140
141 Patterns used to create route definitions have percent-decoding applied
142 when handlers are mounted. A literal "%2F" in the pattern string is
143 indistinguishable from a literal '/'. For example, "/x%2Fz" is the
144 same as "/x/z" when used as a pattern.
145
146 @par Example
147 @code
148 using router_type = basic_router<route_params>;
149 router_type router;
150 router.get( "/hello",
151 []( route_params& p )
152 {
153 p.res.status( status::ok );
154 p.res.set_body( "Hello, world!" );
155 return route_done;
156 } );
157 @endcode
158
159 Router objects are lightweight, shared references to their contents.
160 Copies of a router obtained through construction, conversion, or
161 assignment do not create new instances; they all refer to the same
162 underlying data.
163
164 @par Path Pattern Syntax
165
166 Route patterns define which request paths match a route. Patterns
167 support literal text, named parameters, wildcards, and optional
168 groups. The syntax is inspired by Express.js path-to-regexp.
169
170 @code
171 path = *token
172 token = text / param / wildcard / group
173 text = 1*( char / escaped ) ; literal characters
174 param = ":" name ; captures segment until '/'
175 wildcard = "*" name ; captures everything to end
176 group = "{" *token "}" ; optional section
177 name = identifier / quoted ; plain or quoted name
178 identifier = ( "$" / "_" / ALPHA ) *( "$" / "_" / ALNUM )
179 quoted = DQUOTE 1*qchar DQUOTE ; allows spaces, punctuation
180 escaped = "\" CHAR ; literal special character
181 @endcode
182
183 Named parameters capture path segments. A parameter matches any
184 characters except `/` and must capture at least one character:
185
186 - `/users/:id` matches `/users/42`, capturing `id = "42"`
187 - `/users/:userId/posts/:postId` matches `/users/5/posts/99`
188 - `/:from-:to` matches `/LAX-JFK`, capturing `from = "LAX"`, `to = "JFK"`
189
190 Wildcards capture everything from their position to the end of
191 the path, including `/` characters. Optional groups match
192 all-or-nothing:
193
194 - `/api{/v:version}` matches both `/api` and `/api/v2`
195 - `/file{.:ext}` matches `/file` and `/file.json`
196
197 Reserved characters `( ) [ ] + ? !` are not allowed in patterns.
198 For wildcards, escaping, and quoted names, see the Route Patterns
199 documentation.
200
201 @par Handlers
202
203 Regular handlers are invoked for matching routes and have this
204 equivalent signature:
205 @code
206 route_result handler( Params& p )
207 @endcode
208
209 The return value is a @ref route_result used to indicate the desired
210 action through @ref route enum values, or to indicate that a failure
211 occurred. Failures are represented by error codes for which
212 `system::error_code::failed()` returns `true`.
213
214 When a failing error code is produced and remains unhandled, the
215 router enters error-dispatching mode. In this mode, only error
216 handlers are invoked. Error handlers are registered globally or
217 for specific paths and execute in the order of registration whenever
218 a failing error code is present in the response.
219
220 Error handlers have this equivalent signature:
221 @code
222 route_result error_handler( Params& p, system::error_code ec )
223 @endcode
224
225 Each error handler may return any failing @ref system::error_code,
226 which is equivalent to calling:
227 @code
228 p.next( ec ); // with ec == true
229 @endcode
230
231 Returning @ref route_next indicates that control should proceed to
232 the next matching error handler. Returning a different failing code
233 replaces the current error and continues dispatch in error mode using
234 that new code. Error handlers are invoked until one returns a result
235 other than @ref route_next.
236
237 Exception handlers have this equivalent signature:
238 @code
239 route_result exception_handler( Params& p, E ex )
240 @endcode
241
242 Where `E` is the type of exception caught. Handlers installed for an
243 exception of type `E` will also be called when the exception type is
244 a derived class of `E`. Exception handlers are invoked in the order
245 of registration whenever an exception is present in the request.
246
247 The prefix match is not strict: middleware attached to `"/api"`
248 will also match `"/api/users"` and `"/api/data"`. When registered
249 before route handlers for the same prefix, middleware runs before
250 those routes. This is analogous to `app.use( path, ... )` in
251 Express.js.
252
253 @par Thread Safety
254
255 Member functions marked `const` such as @ref dispatch and @ref resume
256 may be called concurrently on routers that refer to the same data.
257 Modification of routers through calls to non-`const` member functions
258 is not thread-safe and must not be performed concurrently with any
259 other member function.
260
261 @par Nesting Depth
262
263 Routers may be nested to a maximum depth of `max_path_depth` (16 levels).
264 Exceeding this limit throws `std::length_error` when the nested router
265 is added via @ref use. This limit ensures that @ref flat_router dispatch
266 never overflows its fixed-size tracking arrays.
267
268 @par Constraints
269
270 `Params` must be publicly derived from @ref route_params_base.
271
272 @tparam Params The type of the parameters object passed to handlers.
273 */
274 template<class P>
275 class basic_router : public detail::router_base
276 {
277 static_assert(std::derived_from<P, route_params_base>);
278
279 template<class T>
280 static inline constexpr char handler_kind =
281 []() -> char
282 {
283 if constexpr (detail::returns_route_task<T, P&>)
284 {
285 return is_plain;
286 }
287 else if constexpr (detail::returns_route_task<
288 T, P&, system::error_code>)
289 {
290 return is_error;
291 }
292 else if constexpr(
293 std::is_base_of_v<router_base, T> &&
294 std::is_convertible_v<T const volatile*,
295 router_base const volatile*> &&
296 std::is_constructible_v<T, basic_router<P>>)
297 {
298 return is_router;
299 }
300 else if constexpr (detail::returns_route_task<
301 T, P&, std::exception_ptr>)
302 {
303 return is_exception;
304 }
305 else
306 {
307 return is_invalid;
308 }
309 }();
310
311 template<class... Ts>
312 static inline constexpr bool handler_crvals =
313 ((!std::is_lvalue_reference_v<Ts> ||
314 std::is_const_v<std::remove_reference_t<Ts>> ||
315 std::is_function_v<std::remove_reference_t<Ts>>) && ...);
316
317 template<char Mask, class... Ts>
318 static inline constexpr bool handler_check =
319 (((handler_kind<Ts> & Mask) != 0) && ...);
320
321 template<class H>
322 struct handler_impl : handler
323 {
324 std::decay_t<H> h;
325
326 template<class H_>
327 179 explicit handler_impl(H_ h_)
328 : handler(handler_kind<H>)
329 179 , h(std::forward<H_>(h_))
330 {
331 179 }
332
333 111 auto invoke(route_params_base& rp) const ->
334 route_task override
335 {
336 if constexpr (detail::returns_route_task<H, P&>)
337 {
338 99 return h(static_cast<P&>(rp));
339 }
340 else if constexpr (detail::returns_route_task<
341 H, P&, system::error_code>)
342 {
343 9 return h(static_cast<P&>(rp), rp.ec_);
344 }
345 else if constexpr (detail::returns_route_task<
346 H, P&, std::exception_ptr>)
347 {
348
1/1
✓ Branch 2 taken 3 times.
3 return h(static_cast<P&>(rp), rp.ep_);
349 }
350 else
351 {
352 // impossible with flat router
353 std::terminate();
354 }
355 }
356
357 detail::router_base*
358 316 get_router() noexcept override
359 {
360 if constexpr (std::is_base_of_v<
361 detail::router_base, std::decay_t<H>>)
362 316 return &h;
363 else
364 return nullptr;
365 }
366 };
367
368 template<class H>
369 179 static handler_ptr make_handler(H&& h)
370 {
371
1/1
✓ Branch 2 taken 179 times.
179 return std::make_unique<handler_impl<H>>(std::forward<H>(h));
372 }
373
374 template<class H>
375 struct options_handler_impl : options_handler
376 {
377 std::decay_t<H> h;
378
379 template<class H_>
380 4 explicit options_handler_impl(H_&& h_)
381 4 : h(std::forward<H_>(h_))
382 {
383 4 }
384
385 3 route_task invoke(
386 route_params_base& rp,
387 std::string_view allow) const override
388 {
389 3 return h(static_cast<P&>(rp), allow);
390 }
391 };
392
393 template<std::size_t N>
394 struct handlers_impl : handlers
395 {
396 handler_ptr v[N];
397
398 template<class... HN>
399 170 explicit handlers_impl(HN&&... hn)
400 {
401 170 p = v;
402 170 n = sizeof...(HN);
403
3/3
✓ Branch 2 taken 164 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 3 times.
170 assign<0>(std::forward<HN>(hn)...);
404 170 }
405
406 private:
407 template<std::size_t I, class H1, class... HN>
408 179 void assign(H1&& h1, HN&&... hn)
409 {
410
1/1
✓ Branch 2 taken 179 times.
179 v[I] = make_handler(std::forward<H1>(h1));
411 179 assign<I+1>(std::forward<HN>(hn)...);
412 179 }
413
414 template<std::size_t>
415 170 void assign(int = 0)
416 {
417 170 }
418 };
419
420 template<class... HN>
421 170 static auto make_handlers(HN&&... hn)
422 {
423 return handlers_impl<sizeof...(HN)>(
424 170 std::forward<HN>(hn)...);
425 }
426
427 public:
428 /** The type of params used in handlers.
429 */
430 using params_type = P;
431
432 /** A fluent interface for defining handlers on a specific route.
433
434 This type represents a single route within the router and
435 provides a chainable API for registering handlers associated
436 with particular HTTP methods or for all methods collectively.
437
438 Typical usage registers one or more handlers for a route:
439 @code
440 router.route( "/users/:id" )
441 .get( show_user )
442 .put( update_user )
443 .all( log_access );
444 @endcode
445
446 Each call appends handlers in registration order.
447 */
448 class fluent_route;
449
450 basic_router(basic_router const&) = delete;
451 basic_router& operator=(basic_router const&) = delete;
452
453 /** Constructor.
454
455 Creates an empty router with the specified configuration.
456 Routers constructed with default options inherit the values
457 of @ref router_options::case_sensitive and
458 @ref router_options::strict from the parent router, or default
459 to `false` if there is no parent. The value of
460 @ref router_options::merge_params defaults to `false` and
461 is never inherited.
462
463 @param options The configuration options to use.
464 */
465 explicit
466 158 basic_router(
467 router_options options = {})
468 158 : router_base(options.v_)
469 {
470 158 }
471
472 /** Construct a router from another router with compatible types.
473
474 This constructs a router that shares the same underlying routing
475 state as another router whose params type is a base class of `Params`.
476
477 The resulting router participates in shared ownership of the
478 implementation; copying the router does not duplicate routes or
479 handlers, and changes visible through one router are visible
480 through all routers that share the same underlying state.
481
482 @par Constraints
483
484 `Params` must be derived from `OtherParams`.
485
486 @param other The router to construct from.
487
488 @tparam OtherParams The params type of the source router.
489 */
490 template<class OtherP>
491 requires std::derived_from<OtherP, P>
492 100 basic_router(
493 basic_router<OtherP>&& other) noexcept
494 100 : router_base(std::move(other))
495 {
496 100 }
497
498 /** Add middleware handlers for a path prefix.
499
500 Each handler registered with this function participates in the
501 routing and error-dispatch process for requests whose path begins
502 with the specified prefix, as described in the @ref basic_router
503 class documentation. Handlers execute in the order they are added
504 and may return @ref route_next to transfer control to the
505 subsequent handler in the chain.
506
507 @par Example
508 @code
509 router.use( "/api",
510 []( route_params& p )
511 {
512 if( ! authenticate( p ) )
513 {
514 p.res.status( 401 );
515 p.res.set_body( "Unauthorized" );
516 return route_done;
517 }
518 return route_next;
519 },
520 []( route_params& p )
521 {
522 p.res.set_header( "X-Powered-By", "MyServer" );
523 return route_next;
524 } );
525 @endcode
526
527 @par Preconditions
528
529 @p pattern must be a valid path prefix; it may be empty to
530 indicate the root scope.
531
532 @param pattern The pattern to match.
533
534 @param h1 The first handler to add.
535
536 @param hn Additional handlers to add, invoked after @p h1 in
537 registration order.
538 */
539 template<class H1, class... HN>
540 100 void use(
541 std::string_view pattern,
542 H1&& h1, HN&&... hn)
543 {
544 static_assert(handler_crvals<H1, HN...>,
545 "pass handlers by value or std::move()");
546 static_assert(! handler_check<8, H1, HN...>,
547 "cannot use exception handlers here");
548 static_assert(handler_check<7, H1, HN...>,
549 "invalid handler signature");
550
6/6
✓ Branch 2 taken 95 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 94 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 3 times.
100 add_impl(pattern, make_handlers(
551 std::forward<H1>(h1), std::forward<HN>(hn)...));
552 99 }
553
554 /** Add global middleware handlers.
555
556 Each handler registered with this function participates in the
557 routing and error-dispatch process as described in the
558 @ref basic_router class documentation. Handlers execute in the
559 order they are added and may return @ref route_next to transfer
560 control to the next handler in the chain.
561
562 This is equivalent to writing:
563 @code
564 use( "/", h1, hn... );
565 @endcode
566
567 @par Example
568 @code
569 router.use(
570 []( Params& p )
571 {
572 p.res.erase( "X-Powered-By" );
573 return route_next;
574 } );
575 @endcode
576
577 @par Constraints
578
579 @p h1 must not be convertible to @ref std::string_view.
580
581 @param h1 The first handler to add.
582
583 @param hn Additional handlers to add, invoked after @p h1 in
584 registration order.
585 */
586 template<class H1, class... HN>
587 19 void use(H1&& h1, HN&&... hn)
588 requires (!std::convertible_to<H1, std::string_view>)
589 {
590 static_assert(handler_crvals<H1, HN...>,
591 "pass handlers by value or std::move()");
592 static_assert(! handler_check<8, H1, HN...>,
593 "cannot use exception handlers here");
594 static_assert(handler_check<7, H1, HN...>,
595 "invalid handler signature");
596
3/3
✓ Branch 3 taken 14 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 3 times.
19 use(std::string_view(),
597 std::forward<H1>(h1), std::forward<HN>(hn)...);
598 19 }
599
600 /** Add exception handlers for a route pattern.
601
602 Registers one or more exception handlers that will be invoked
603 when an exception is thrown during request processing for routes
604 matching the specified pattern.
605
606 Handlers are invoked in the order provided until one handles
607 the exception.
608
609 @par Example
610 @code
611 app.except( "/api*",
612 []( route_params& p, std::exception const& ex )
613 {
614 p.res.set_status( 500 );
615 return route_done;
616 } );
617 @endcode
618
619 @param pattern The route pattern to match, or empty to match
620 all routes.
621
622 @param h1 The first exception handler.
623
624 @param hn Additional exception handlers.
625 */
626 template<class H1, class... HN>
627 2 void except(
628 std::string_view pattern,
629 H1&& h1, HN&&... hn)
630 {
631 static_assert(handler_crvals<H1, HN...>,
632 "pass handlers by value or std::move()");
633 static_assert(handler_check<8, H1, HN...>,
634 "only exception handlers are allowed here");
635
4/4
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 1 time.
2 add_impl(pattern, make_handlers(
636 std::forward<H1>(h1), std::forward<HN>(hn)...));
637 2 }
638
639 /** Add global exception handlers.
640
641 Registers one or more exception handlers that will be invoked
642 when an exception is thrown during request processing for any
643 route.
644
645 Equivalent to calling `except( "", h1, hn... )`.
646
647 @par Example
648 @code
649 app.except(
650 []( route_params& p, std::exception const& ex )
651 {
652 p.res.set_status( 500 );
653 return route_done;
654 } );
655 @endcode
656
657 @param h1 The first exception handler.
658
659 @param hn Additional exception handlers.
660 */
661 template<class H1, class... HN>
662 2 void except(H1&& h1, HN&&... hn)
663 requires (!std::convertible_to<H1, std::string_view>)
664 {
665 static_assert(handler_crvals<H1, HN...>,
666 "pass handlers by value or std::move()");
667 static_assert(handler_check<8, H1, HN...>,
668 "only exception handlers are allowed here");
669
2/2
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
2 except(std::string_view(),
670 std::forward<H1>(h1), std::forward<HN>(hn)...);
671 2 }
672
673 /** Add handlers for all HTTP methods matching a path pattern.
674
675 This registers regular handlers for the specified path pattern,
676 participating in dispatch as described in the @ref basic_router
677 class documentation. Handlers run when the route matches,
678 regardless of HTTP method, and execute in registration order.
679 Error handlers and routers cannot be passed here. A new route
680 object is created even if the pattern already exists.
681
682 @par Example
683 @code
684 router.route( "/status" )
685 .add( method::head, check_headers )
686 .add( method::get, send_status )
687 .all( log_access );
688 @endcode
689
690 @par Preconditions
691
692 @p pattern must be a valid path pattern; it must not be empty.
693
694 @param pattern The path pattern to match.
695
696 @param h1 The first handler to add.
697
698 @param hn Additional handlers to add, invoked after @p h1 in
699 registration order.
700 */
701 template<class H1, class... HN>
702 7 void all(
703 std::string_view pattern,
704 H1&& h1, HN&&... hn)
705 {
706 static_assert(handler_crvals<H1, HN...>,
707 "pass handlers by value or std::move()");
708 static_assert(handler_check<1, H1, HN...>,
709 "only normal route handlers are allowed here");
710
2/2
✓ Branch 1 taken 7 times.
✓ Branch 5 taken 7 times.
7 this->route(pattern).all(
711 std::forward<H1>(h1), std::forward<HN>(hn)...);
712 7 }
713
714 /** Add route handlers for a method and pattern.
715
716 This registers regular handlers for the specified HTTP verb and
717 path pattern, participating in dispatch as described in the
718 @ref basic_router class documentation. Error handlers and
719 routers cannot be passed here.
720
721 @param verb The known HTTP method to match.
722
723 @param pattern The path pattern to match.
724
725 @param h1 The first handler to add.
726
727 @param hn Additional handlers to add, invoked after @p h1 in
728 registration order.
729 */
730 template<class H1, class... HN>
731 61 void add(
732 http::method verb,
733 std::string_view pattern,
734 H1&& h1, HN&&... hn)
735 {
736 static_assert(handler_crvals<H1, HN...>,
737 "pass handlers by value or std::move()");
738 static_assert(handler_check<1, H1, HN...>,
739 "only normal route handlers are allowed here");
740
2/2
✓ Branch 1 taken 49 times.
✓ Branch 5 taken 49 times.
61 this->route(pattern).add(verb,
741 std::forward<H1>(h1), std::forward<HN>(hn)...);
742 49 }
743
744 /** Add route handlers for a method string and pattern.
745
746 This registers regular handlers for the specified HTTP verb and
747 path pattern, participating in dispatch as described in the
748 @ref basic_router class documentation. Error handlers and
749 routers cannot be passed here.
750
751 @param verb The HTTP method string to match.
752
753 @param pattern The path pattern to match.
754
755 @param h1 The first handler to add.
756
757 @param hn Additional handlers to add, invoked after @p h1 in
758 registration order.
759 */
760 template<class H1, class... HN>
761 2 void add(
762 std::string_view verb,
763 std::string_view pattern,
764 H1&& h1, HN&&... hn)
765 {
766 static_assert(handler_crvals<H1, HN...>,
767 "pass handlers by value or std::move()");
768 static_assert(handler_check<1, H1, HN...>,
769 "only normal route handlers are allowed here");
770
2/2
✓ Branch 1 taken 2 times.
✓ Branch 5 taken 2 times.
2 this->route(pattern).add(verb,
771 std::forward<H1>(h1), std::forward<HN>(hn)...);
772 2 }
773
774 /** Return a fluent route for the specified path pattern.
775
776 Adds a new route to the router for the given pattern.
777 A new route object is always created, even if another
778 route with the same pattern already exists. The returned
779 @ref fluent_route reference allows method-specific handler
780 registration (such as GET or POST) or catch-all handlers
781 with @ref fluent_route::all.
782
783 @param pattern The path expression to match against request
784 targets. This may include parameters or wildcards following
785 the router's pattern syntax. May not be empty.
786
787 @return A fluent route interface for chaining handler
788 registrations.
789 */
790 auto
791 77 route(
792 std::string_view pattern) -> fluent_route
793 {
794
1/1
✓ Branch 1 taken 65 times.
77 return fluent_route(*this, pattern);
795 }
796
797 /** Set the handler for automatic OPTIONS responses.
798
799 When an OPTIONS request matches a route but no explicit OPTIONS
800 handler is registered, this handler is invoked with the pre-built
801 Allow header value. This follows Express.js semantics where
802 explicit OPTIONS handlers take priority.
803
804 @param h A callable with signature `route_task(P&, std::string_view)`
805 where the string_view contains the pre-built Allow header value.
806 */
807 template<class H>
808 4 void set_options_handler(H&& h)
809 {
810 static_assert(
811 std::is_invocable_r_v<route_task, const std::decay_t<H>&, P&, std::string_view>,
812 "Handler must have signature: route_task(P&, std::string_view)");
813
1/1
✓ Branch 2 taken 4 times.
4 this->options_handler_ = std::make_unique<options_handler_impl<H>>(
814 std::forward<H>(h));
815 4 }
816 };
817
818 template<class P>
819 class basic_router<P>::
820 fluent_route
821 {
822 public:
823 fluent_route(fluent_route const&) = default;
824
825 /** Add handlers that apply to all HTTP methods.
826
827 This registers regular handlers that run for any request matching
828 the route's pattern, regardless of HTTP method. Handlers are
829 appended to the route's handler sequence and are invoked in
830 registration order whenever a preceding handler returns
831 @ref route_next. Error handlers and routers cannot be passed here.
832
833 This function returns a @ref fluent_route, allowing additional
834 method registrations to be chained. For example:
835 @code
836 router.route( "/resource" )
837 .all( log_request )
838 .add( method::get, show_resource )
839 .add( method::post, update_resource );
840 @endcode
841
842 @param h1 The first handler to add.
843
844 @param hn Additional handlers to add, invoked after @p h1 in
845 registration order.
846
847 @return A reference to `*this` for chained registrations.
848 */
849 template<class H1, class... HN>
850 8 auto all(
851 H1&& h1, HN&&... hn) ->
852 fluent_route
853 {
854 static_assert(handler_check<1, H1, HN...>);
855
3/3
✓ Branch 2 taken 8 times.
✓ Branch 6 taken 8 times.
✓ Branch 9 taken 8 times.
8 owner_.add_impl(owner_.get_layer(layer_idx_), std::string_view{},
856 owner_.make_handlers(
857 std::forward<H1>(h1), std::forward<HN>(hn)...));
858 8 return *this;
859 }
860
861 /** Add handlers for a specific HTTP method.
862
863 This registers regular handlers for the given method on the
864 current route, participating in dispatch as described in the
865 @ref basic_router class documentation. Handlers are appended
866 to the route's handler sequence and invoked in registration
867 order whenever a preceding handler returns @ref route_next.
868 Error handlers and routers cannot be passed here.
869
870 @param verb The HTTP method to match.
871
872 @param h1 The first handler to add.
873
874 @param hn Additional handlers to add, invoked after @p h1 in
875 registration order.
876
877 @return A reference to `*this` for chained registrations.
878 */
879 template<class H1, class... HN>
880 58 auto add(
881 http::method verb,
882 H1&& h1, HN&&... hn) ->
883 fluent_route
884 {
885 static_assert(handler_check<1, H1, HN...>);
886
3/3
✓ Branch 2 taken 58 times.
✓ Branch 5 taken 58 times.
✓ Branch 8 taken 58 times.
58 owner_.add_impl(owner_.get_layer(layer_idx_), verb, owner_.make_handlers(
887 std::forward<H1>(h1), std::forward<HN>(hn)...));
888 58 return *this;
889 }
890
891 /** Add handlers for a method string.
892
893 This registers regular handlers for the given HTTP method string
894 on the current route, participating in dispatch as described in
895 the @ref basic_router class documentation. This overload is
896 intended for methods not represented by @ref http::method.
897 Handlers are appended to the route's handler sequence and invoked
898 in registration order whenever a preceding handler returns
899 @ref route_next. Error handlers and routers cannot be passed here.
900
901 @param verb The HTTP method string to match.
902
903 @param h1 The first handler to add.
904
905 @param hn Additional handlers to add, invoked after @p h1 in
906 registration order.
907
908 @return A reference to `*this` for chained registrations.
909 */
910 template<class H1, class... HN>
911 2 auto add(
912 std::string_view verb,
913 H1&& h1, HN&&... hn) ->
914 fluent_route
915 {
916 static_assert(handler_check<1, H1, HN...>);
917
3/3
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 2 times.
✓ Branch 8 taken 2 times.
2 owner_.add_impl(owner_.get_layer(layer_idx_), verb, owner_.make_handlers(
918 std::forward<H1>(h1), std::forward<HN>(hn)...));
919 2 return *this;
920 }
921
922 private:
923 friend class basic_router;
924 77 fluent_route(
925 basic_router& owner,
926 std::string_view pattern)
927 77 : layer_idx_(owner.new_layer_idx(pattern))
928 65 , owner_(owner)
929 {
930 65 }
931
932 std::size_t layer_idx_;
933 basic_router& owner_;
934 };
935
936 } // http
937 } // boost
938
939 #endif
940