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

99.1% Lines (109/110) 97.3% Functions (683/702) 86.3% Branches (44/51)
libs/http/include/boost/http/server/router.hpp
Line Branch Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.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_ROUTER_HPP
11 #define BOOST_HTTP_SERVER_ROUTER_HPP
12
13 #include <boost/http/detail/config.hpp>
14 #include <boost/http/server/route_handler.hpp>
15 #include <boost/http/server/detail/router_base.hpp>
16 #include <boost/http/field.hpp>
17 #include <boost/http/method.hpp>
18 #include <boost/http/status.hpp>
19 #include <boost/url/url_view.hpp>
20 #include <boost/mp11/algorithm.hpp>
21 #include <boost/assert.hpp>
22 #include <exception>
23 #include <string_view>
24 #include <type_traits>
25
26 namespace boost {
27 namespace http {
28
29 template<class, class> class router;
30
31 /** Configuration options for HTTP routers.
32 */
33 struct router_options
34 {
35 /** Constructor.
36
37 Routers constructed with default options inherit the values of
38 @ref case_sensitive and @ref strict from the parent router.
39 If there is no parent, both default to `false`.
40 The value of @ref merge_params always defaults to `false`
41 and is never inherited.
42 */
43 222 router_options() = default;
44
45 /** Set whether to merge parameters from parent routers.
46
47 This setting controls whether route parameters defined on parent
48 routers are made available in nested routers. It is not inherited
49 and always defaults to `false`.
50
51 @par Example
52 @code
53 router r( router_options()
54 .merge_params( true )
55 .case_sensitive( true )
56 .strict( false ) );
57 @endcode
58
59 @param value `true` to merge parameters from parent routers.
60
61 @return A reference to `*this` for chaining.
62 */
63 router_options&
64 merge_params(
65 bool value) noexcept
66 {
67 v_ = (v_ & ~1) | (value ? 1 : 0);
68 return *this;
69 }
70
71 /** Set whether pattern matching is case-sensitive.
72
73 When this option is not set explicitly, the value is inherited
74 from the parent router or defaults to `false` if there is no parent.
75
76 @par Example
77 @code
78 router r( router_options()
79 .case_sensitive( true )
80 .strict( true ) );
81 @endcode
82
83 @param value `true` to perform case-sensitive path matching.
84
85 @return A reference to `*this` for chaining.
86 */
87 router_options&
88 6 case_sensitive(
89 bool value) noexcept
90 {
91
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 if(value)
92 4 v_ = (v_ & ~6) | 2;
93 else
94 2 v_ = (v_ & ~6) | 4;
95 6 return *this;
96 }
97
98 /** Set whether pattern matching is strict.
99
100 When this option is not set explicitly, the value is inherited
101 from the parent router or defaults to `false` if there is no parent.
102 Strict matching treats a trailing slash as significant:
103 the pattern `"/api"` matches `"/api"` but not `"/api/"`.
104 When strict matching is disabled, these paths are treated
105 as equivalent.
106
107 @par Example
108 @code
109 router r( router_options()
110 .strict( true )
111 .case_sensitive( false ) );
112 @endcode
113
114 @param value `true` to enable strict path matching.
115
116 @return A reference to `*this` for chaining.
117 */
118 router_options&
119 2 strict(
120 bool value) noexcept
121 {
122
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 if(value)
123 1 v_ = (v_ & ~24) | 8;
124 else
125 1 v_ = (v_ & ~24) | 16;
126 2 return *this;
127 }
128
129 private:
130 template<class, class> friend class router;
131 unsigned int v_ = 0;
132 };
133
134 //-----------------------------------------------
135
136 /** The default handler transform.
137
138 Passes each handler through unchanged. This is the
139 default value of the `HT` template parameter on
140 @ref router.
141 */
142 struct identity
143 {
144 template< class T >
145 208 T operator()( T&& t ) const
146 {
147 208 return std::forward<T>(t);
148 }
149 };
150
151 /** A container for HTTP route handlers.
152
153 `router` objects store and dispatch route handlers based on the
154 HTTP method and path of an incoming request. Routes are added with a
155 path pattern, method, and an associated handler, and the router is then
156 used to dispatch the appropriate handler.
157
158 Routes are flattened into contiguous arrays as they are added, so
159 dispatch is always cache-friendly regardless of nesting depth.
160
161 Patterns used to create route definitions have percent-decoding applied
162 when handlers are mounted. A literal "%2F" in the pattern string is
163 indistinguishable from a literal '/'. For example, "/x%2Fz" is the
164 same as "/x/z" when used as a pattern.
165
166 @par Example
167 @code
168 router<route_params> r;
169 r.get( "/hello",
170 []( route_params& p )
171 {
172 p.res.status( status::ok );
173 p.res.set_body( "Hello, world!" );
174 return route_done;
175 } );
176 @endcode
177
178 Router objects use shared ownership via `shared_ptr`. Copies refer
179 to the same underlying data. Modifying a router after it has been
180 copied is not permitted and results in undefined behavior.
181
182 @par Path Pattern Syntax
183
184 Route patterns define which request paths match a route. Patterns
185 support literal text, named parameters, wildcards, and optional
186 groups. The syntax is inspired by Express.js path-to-regexp.
187
188 @code
189 path = *token
190 token = text / param / wildcard / group
191 text = 1*( char / escaped ) ; literal characters
192 param = ":" name ; captures segment until '/'
193 wildcard = "*" name ; captures everything to end
194 group = "{" *token "}" ; optional section
195 name = identifier / quoted ; plain or quoted name
196 identifier = ( "$" / "_" / ALPHA ) *( "$" / "_" / ALNUM )
197 quoted = DQUOTE 1*qchar DQUOTE ; allows spaces, punctuation
198 escaped = "\" CHAR ; literal special character
199 @endcode
200
201 Named parameters capture path segments. A parameter matches any
202 characters except `/` and must capture at least one character:
203
204 - `/users/:id` matches `/users/42`, capturing `id = "42"`
205 - `/users/:userId/posts/:postId` matches `/users/5/posts/99`
206 - `/:from-:to` matches `/LAX-JFK`, capturing `from = "LAX"`, `to = "JFK"`
207
208 Wildcards capture everything from their position to the end of
209 the path, including `/` characters. Optional groups match
210 all-or-nothing:
211
212 - `/api{/v:version}` matches both `/api` and `/api/v2`
213 - `/file{.:ext}` matches `/file` and `/file.json`
214
215 Reserved characters `( ) [ ] + ? !` are not allowed in patterns.
216 For wildcards, escaping, and quoted names, see the Route Patterns
217 documentation.
218
219 @par Handlers
220
221 Regular handlers are invoked for matching routes and have this
222 equivalent signature:
223 @code
224 route_result handler( Params& p )
225 @endcode
226
227 The return value is a @ref route_result used to indicate the desired
228 action through @ref route enum values, or to indicate that a failure
229 occurred. Failures are represented by error codes for which
230 `system::error_code::failed()` returns `true`.
231
232 When a failing error code is produced and remains unhandled, the
233 router enters error-dispatching mode. In this mode, only error
234 handlers are invoked. Error handlers are registered globally or
235 for specific paths and execute in the order of registration whenever
236 a failing error code is present in the response.
237
238 Error handlers have this equivalent signature:
239 @code
240 route_result error_handler( Params& p, system::error_code ec )
241 @endcode
242
243 Each error handler may return any failing @ref system::error_code,
244 which is equivalent to calling:
245 @code
246 p.next( ec ); // with ec == true
247 @endcode
248
249 Returning @ref route_next indicates that control should proceed to
250 the next matching error handler. Returning a different failing code
251 replaces the current error and continues dispatch in error mode using
252 that new code. Error handlers are invoked until one returns a result
253 other than @ref route_next.
254
255 Exception handlers have this equivalent signature:
256 @code
257 route_result exception_handler( Params& p, E ex )
258 @endcode
259
260 Where `E` is the type of exception caught. Handlers installed for an
261 exception of type `E` will also be called when the exception type is
262 a derived class of `E`. Exception handlers are invoked in the order
263 of registration whenever an exception is present in the request.
264
265 The prefix match is not strict: middleware attached to `"/api"`
266 will also match `"/api/users"` and `"/api/data"`. When registered
267 before route handlers for the same prefix, middleware runs before
268 those routes. This is analogous to `app.use( path, ... )` in
269 Express.js.
270
271 @par Handler Transforms
272
273 The second template parameter `HT` is a <em>handler transform</em>.
274 A handler transform is a callable object that the router applies to
275 each plain handler at registration time, producing a new callable
276 with the canonical signature `route_task(Params&)`.
277
278 When a handler is registered that does not directly satisfy
279 `route_task(Params&)`, the router applies `ht(handler)` to adapt
280 it. The transform is invoked <em>once</em> at registration time;
281 the returned callable is stored and invoked each time the route
282 matches at dispatch time.
283
284 Error handlers and exception handlers are never transformed.
285 Only plain route handlers pass through the transform.
286
287 The default transform is @ref identity, which passes handlers
288 through unchanged.
289
290 A transform `HT` must satisfy the following contract: for any
291 handler `h` passed to the router, the expression `ht(h)` must
292 return a callable `g` such that `g(Params&)` returns
293 @ref route_task.
294
295 @par Example: Logging Transform
296 @code
297 struct log_transform
298 {
299 template<class Handler>
300 auto operator()(Handler h) const
301 {
302 struct wrapper
303 {
304 Handler h_;
305
306 route_task operator()(route_params& p) const
307 {
308 auto t0 = steady_clock::now();
309 auto rv = co_await h_(p);
310 log_elapsed(steady_clock::now() - t0);
311 co_return rv;
312 }
313 };
314 return wrapper{ std::move(h) };
315 }
316 };
317
318 router<route_params> base;
319 auto r = base.with_transform( log_transform{} );
320
321 // The lambda is wrapped by log_transform at registration.
322 // At dispatch, log_transform::wrapper::operator() runs,
323 // which invokes the original lambda and logs elapsed time.
324 r.get( "/hello", []( route_params& p ) -> route_task {
325 co_return route_done;
326 });
327 @endcode
328
329 @par Example: Dependency Injection Transform
330
331 A transform can adapt handlers whose parameters are not
332 `Params&` at all. The transform resolves each parameter
333 from a service container at dispatch time:
334
335 @code
336 struct inject_transform
337 {
338 template<class Handler>
339 auto operator()(Handler h) const
340 {
341 struct wrapper
342 {
343 Handler h_;
344
345 route_task operator()(route_params& p) const
346 {
347 // Look up each of h_'s parameter types
348 // in p.route_data. Return route_next if
349 // any are missing.
350 co_return dynamic_invoke(p.route_data, h_);
351 }
352 };
353 return wrapper{ std::move(h) };
354 }
355 };
356
357 router<route_params> base;
358 auto r = base.with_transform( inject_transform{} );
359
360 // Parameters are resolved from p.route_data automatically.
361 // If UserService or Config are not in route_data, the
362 // handler is skipped and route_next is returned.
363 r.get( "/users", [](
364 UserService& svc,
365 Config const& cfg) -> route_result
366 {
367 // use svc and cfg...
368 return route_done;
369 });
370 @endcode
371
372 @par Thread Safety
373
374 Member functions marked `const` such as @ref dispatch
375 may be called concurrently on routers that refer to the same data.
376 Modification of routers through calls to non-`const` member functions
377 is not thread-safe and must not be performed concurrently with any
378 other member function.
379
380 @par Nesting Depth
381
382 Routers may be nested to a maximum depth of `max_path_depth` (16 levels).
383 Exceeding this limit throws `std::length_error` when the nested router
384 is added via @ref use. This limit ensures that dispatch never overflows
385 its fixed-size tracking arrays.
386
387 @par Constraints
388
389 `Params` must be publicly derived from @ref route_params.
390
391 @tparam Params The type of the parameters object passed to handlers.
392 */
393 template<class P = route_params, class HT = identity>
394 class router : public detail::router_base
395 {
396 template<class, class> friend class router;
397
398 HT ht_{};
399
400 static_assert(std::derived_from<P, route_params>);
401
402 template<class T>
403 static inline constexpr char handler_kind =
404 []() -> char
405 {
406 if constexpr (detail::returns_route_task<
407 T, P&, system::error_code>)
408 {
409 return is_error;
410 }
411 else if constexpr (detail::returns_route_task<
412 T, P&, std::exception_ptr>)
413 {
414 return is_exception;
415 }
416 else if constexpr (detail::returns_route_task<T, P&>)
417 {
418 return is_plain;
419 }
420 else if constexpr (
421 std::is_invocable_v<HT const&, T> &&
422 detail::returns_route_task<
423 std::invoke_result_t<HT const&, T>, P&>)
424 {
425 return is_plain;
426 }
427 else
428 {
429 return is_invalid;
430 }
431 }();
432
433 template<class T>
434 static inline constexpr bool is_sub_router =
435 std::is_base_of_v<detail::router_base, std::decay_t<T>> &&
436 std::is_convertible_v<std::decay_t<T> const volatile*,
437 detail::router_base const volatile*>;
438
439 template<class... Ts>
440 static inline constexpr bool handler_crvals =
441 ((!std::is_lvalue_reference_v<Ts> ||
442 std::is_const_v<std::remove_reference_t<Ts>> ||
443 std::is_function_v<std::remove_reference_t<Ts>>) && ...);
444
445 template<char Mask, class... Ts>
446 static inline constexpr bool handler_check =
447 (((handler_kind<Ts> & Mask) != 0) && ...);
448
449 template<class H>
450 struct handler_impl : handler
451 {
452 std::decay_t<H> h;
453
454 template<class H_>
455 227 explicit handler_impl(H_ h_)
456 : handler(handler_kind<H>)
457 227 , h(std::forward<H_>(h_))
458 {
459 227 }
460
461 220 auto invoke(route_params& rp) const ->
462 route_task override
463 {
464 if constexpr (detail::returns_route_task<H, P&>)
465 {
466 208 return h(static_cast<P&>(rp));
467 }
468 else if constexpr (detail::returns_route_task<
469 H, P&, system::error_code>)
470 {
471 9 return h(static_cast<P&>(rp), rp.priv_.ec_);
472 }
473 else if constexpr (detail::returns_route_task<
474 H, P&, std::exception_ptr>)
475 {
476
1/1
✓ Branch 2 taken 3 times.
3 return h(static_cast<P&>(rp), rp.priv_.ep_);
477 }
478 else
479 {
480 std::terminate();
481 }
482 }
483 };
484
485 template<class H>
486 227 static handler_ptr make_handler(H&& h)
487 {
488
1/1
✓ Branch 2 taken 227 times.
227 return std::make_unique<handler_impl<H>>(std::forward<H>(h));
489 }
490
491 template<class H>
492 struct options_handler_impl : options_handler
493 {
494 std::decay_t<H> h;
495
496 template<class H_>
497 224 explicit options_handler_impl(H_&& h_)
498 224 : h(std::forward<H_>(h_))
499 {
500 224 }
501
502 12 route_task invoke(
503 route_params& rp,
504 std::string_view allow) const override
505 {
506 12 return h(static_cast<P&>(rp), allow);
507 }
508 };
509
510 template<class T, std::size_t N>
511 struct handlers_impl : handlers
512 {
513 T const& ht;
514 handler_ptr v[N];
515
516 template<class... HN>
517 218 explicit handlers_impl(T const& ht_, HN&&... hn)
518 : ht(ht_)
519 {
520 218 p = v;
521 218 n = sizeof...(HN);
522
3/3
✓ Branch 2 taken 212 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 3 times.
218 assign<0>(std::forward<HN>(hn)...);
523 218 }
524
525 private:
526 template<std::size_t I, class H1, class... HN>
527 227 void assign(H1&& h1, HN&&... hn)
528 {
529 if constexpr (
530 detail::returns_route_task<
531 H1, P&, system::error_code> ||
532 detail::returns_route_task<
533 H1, P&, std::exception_ptr>)
534 {
535
1/1
✓ Branch 2 taken 12 times.
12 v[I] = make_handler(std::forward<H1>(h1));
536 }
537 else if constexpr (detail::returns_route_task<
538 decltype(std::declval<T const&>()(std::declval<H1>())), P&>)
539 {
540
1/1
✓ Branch 3 taken 215 times.
215 v[I] = make_handler(ht(std::forward<H1>(h1)));
541 }
542 227 assign<I+1>(std::forward<HN>(hn)...);
543 227 }
544
545 template<std::size_t>
546 218 void assign(int = 0)
547 {
548 218 }
549 };
550
551 template<class T, class... HN>
552 218 static auto make_handlers(T const& ht, HN&&... hn)
553 {
554 return handlers_impl<T, sizeof...(HN)>(ht,
555 218 std::forward<HN>(hn)...);
556 }
557
558 public:
559 /** The type of params used in handlers.
560 */
561 using params_type = P;
562
563 /** A fluent interface for defining handlers on a specific route.
564
565 This type represents a single route within the router and
566 provides a chainable API for registering handlers associated
567 with particular HTTP methods or for all methods collectively.
568
569 Typical usage registers one or more handlers for a route:
570 @code
571 r.route( "/users/:id" )
572 .get( show_user )
573 .put( update_user )
574 .all( log_access );
575 @endcode
576
577 Each call appends handlers in registration order.
578 */
579 class fluent_route;
580
581 72 router(router const&) = default;
582 2 router& operator=(router const&) = default;
583 68 router(router&&) = default;
584 router& operator=(router&&) = default;
585
586 /** Constructor.
587
588 Creates an empty router with the specified configuration.
589 Routers constructed with default options inherit the values
590 of @ref router_options::case_sensitive and
591 @ref router_options::strict from the parent router, or default
592 to `false` if there is no parent. The value of
593 @ref router_options::merge_params defaults to `false` and
594 is never inherited.
595
596 @param options The configuration options to use.
597 */
598 explicit
599 222 router(
600 router_options options = {})
601 222 : detail::router_base(options.v_)
602 {
603
1/1
✓ Branch 1 taken 222 times.
222 set_options_handler(
604
2/7
✓ Branch 3 taken 4 times.
✓ Branch 6 taken 4 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 16 not taken.
4 [](P& rp, std::string_view allow) -> route_task {
605 rp.status(status::no_content);
606 rp.res.set(field::allow, allow);
607 (void)(co_await rp.send());
608 co_return route_done;
609 });
610 222 }
611
612 /** Construct a router from another router with compatible types.
613
614 This constructs a router that shares the same underlying routing
615 state as another router whose params and handler transform types
616 may differ.
617
618 The handler transform is initialized as follows:
619 - If `HT` is constructible from `OtherHT`, the transform is
620 move-constructed from the source router's transform.
621 - Otherwise, if `HT` is default-constructible, the transform
622 is value-initialized.
623
624 @par Constraints
625
626 `OtherParams` must be derived from `Params`, and `HT` must be
627 either constructible from `OtherHT` or default-constructible.
628
629 @param other The router to construct from.
630
631 @tparam OtherParams The params type of the source router.
632
633 @tparam OtherHT The handler transform type of the source router.
634 */
635 template<class OtherP, class OtherHT>
636 requires std::derived_from<OtherP, P> &&
637 std::constructible_from<HT, OtherHT>
638 2 router(
639 router<OtherP, OtherHT>&& other) noexcept
640 2 : detail::router_base(std::move(other))
641 2 , ht_(std::move(other.ht_))
642 {
643 2 }
644
645 /// @copydoc router(router<OtherP,OtherHT>&&)
646 template<class OtherP, class OtherHT>
647 requires std::derived_from<OtherP, P> &&
648 (!std::constructible_from<HT, OtherHT>) &&
649 std::default_initializable<HT>
650 2 router(
651 router<OtherP, OtherHT>&& other) noexcept
652 2 : detail::router_base(std::move(other))
653 {
654 2 }
655
656 /** Construct a router with a handler transform.
657
658 Creates a router that shares the routing state of @p other
659 but applies @p ht to each plain handler before installation.
660
661 @param other The router whose routing state to share.
662
663 @param ht The handler transform to apply.
664 */
665 template<class OtherHT>
666 3 router(router<P, OtherHT> const& other, HT ht)
667 : detail::router_base(other)
668 3 , ht_(std::move(ht))
669 {
670 3 }
671
672 /** Return a router that applies a transform to plain handlers.
673
674 Creates a new router that shares the same underlying routing
675 table but applies @p f to each plain handler before it is
676 stored. Error and exception handlers are not affected by
677 the transform.
678
679 The transform is invoked once at handler registration time.
680 For each plain handler `h` passed to the returned router,
681 `f(h)` must produce a callable `g` such that `g(Params&)`
682 returns @ref route_task. The callable `g` is what gets
683 stored and invoked at dispatch time.
684
685 @par Shared State
686
687 The returned router shares the same routing table as
688 `*this`. Routes added through either router are visible
689 during dispatch from both. The transform only controls
690 how new handlers are wrapped when they are registered
691 through the returned router.
692
693 @par Example: Simple Transform
694 @code
695 // A transform that logs before each handler runs
696 auto r = base.with_transform(
697 []( auto handler )
698 {
699 struct wrapper
700 {
701 decltype(handler) h_;
702 route_task operator()(route_params& p) const
703 {
704 std::cout << "dispatching\n";
705 co_return co_await h_(p);
706 }
707 };
708 return wrapper{ std::move(handler) };
709 });
710 @endcode
711
712 @par Example: Chaining Transforms
713 @code
714 auto r1 = base.with_transform( first_transform{} );
715 auto r2 = base.with_transform( second_transform{} );
716
717 // r1 applies first_transform to its handlers
718 // r2 applies second_transform to its handlers
719 // Both share the same routing table
720 @endcode
721
722 @par Constraints
723
724 `f(handler)` must return a callable `g` where
725 `g(Params&)` returns @ref route_task.
726
727 @param f The handler transform to apply.
728
729 @return A router with the transform applied.
730 */
731 template<class F>
732 3 auto with_transform(F&& f) const ->
733 router<P, std::decay_t<F>>
734 {
735 return router<P, std::decay_t<F>>(
736 3 *this, std::forward<F>(f));
737 }
738
739 /** Dispatch a request using a known HTTP method.
740
741 @param verb The HTTP method to match. Must not be
742 @ref http::method::unknown.
743
744 @param url The full request target used for route matching.
745
746 @param p The typed params to pass to handlers.
747
748 @return A task yielding the @ref route_result describing
749 how routing completed.
750
751 @throws std::invalid_argument If @p verb is
752 @ref http::method::unknown.
753 */
754 route_task
755 202 dispatch(
756 http::method verb,
757 urls::url_view const& url,
758 P& p) const
759 {
760 return detail::router_base::dispatch(
761 202 verb, url, static_cast<route_params&>(p));
762 }
763
764 /** Dispatch a request using a method string.
765
766 @param verb The HTTP method string to match. Must not be empty.
767
768 @param url The full request target used for route matching.
769
770 @param p The typed params to pass to handlers.
771
772 @return A task yielding the @ref route_result describing
773 how routing completed.
774
775 @throws std::invalid_argument If @p verb is empty.
776 */
777 route_task
778 6 dispatch(
779 std::string_view verb,
780 urls::url_view const& url,
781 P& p) const
782 {
783 return detail::router_base::dispatch(
784 6 verb, url, static_cast<route_params&>(p));
785 }
786
787 /** Add middleware handlers for a path prefix.
788
789 Each handler registered with this function participates in the
790 routing and error-dispatch process for requests whose path begins
791 with the specified prefix, as described in the @ref router
792 class documentation. Handlers execute in the order they are added
793 and may return @ref route_next to transfer control to the
794 subsequent handler in the chain.
795
796 @par Example
797 @code
798 r.use( "/api",
799 []( route_params& p )
800 {
801 if( ! authenticate( p ) )
802 {
803 p.res.status( 401 );
804 p.res.set_body( "Unauthorized" );
805 return route_done;
806 }
807 return route_next;
808 },
809 []( route_params& p )
810 {
811 p.res.set_header( "X-Powered-By", "MyServer" );
812 return route_next;
813 } );
814 @endcode
815
816 @par Preconditions
817
818 @p pattern must be a valid path prefix; it may be empty to
819 indicate the root scope.
820
821 @param pattern The pattern to match.
822
823 @param h1 The first handler to add.
824
825 @param hn Additional handlers to add, invoked after @p h1 in
826 registration order.
827 */
828 template<class H1, class... HN>
829 160 void use(
830 std::string_view pattern,
831 H1&& h1, HN&&... hn)
832 {
833 // Single sub-router case
834 if constexpr(sizeof...(HN) == 0 && is_sub_router<H1>)
835 {
836 static_assert(!std::is_lvalue_reference_v<H1>,
837 "pass sub-routers by value or std::move()");
838 55 this->inline_router(pattern,
839 55 std::forward<H1>(h1));
840 }
841 else
842 {
843 static_assert(handler_crvals<H1, HN...>,
844 "pass handlers by value or std::move()");
845 static_assert(! handler_check<8, H1, HN...>,
846 "cannot use exception handlers here");
847 static_assert(handler_check<3, H1, HN...>,
848 "invalid handler signature");
849
6/6
✓ Branch 2 taken 100 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 100 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 3 times.
105 this->add_middleware(pattern, make_handlers(ht_,
850 std::forward<H1>(h1), std::forward<HN>(hn)...));
851 }
852 159 }
853
854 /** Add global middleware handlers.
855
856 Each handler registered with this function participates in the
857 routing and error-dispatch process as described in the
858 @ref router class documentation. Handlers execute in the
859 order they are added and may return @ref route_next to transfer
860 control to the next handler in the chain.
861
862 This is equivalent to writing:
863 @code
864 use( "/", h1, hn... );
865 @endcode
866
867 @par Example
868 @code
869 r.use(
870 []( Params& p )
871 {
872 p.res.erase( "X-Powered-By" );
873 return route_next;
874 } );
875 @endcode
876
877 @par Constraints
878
879 @p h1 must not be convertible to @ref std::string_view.
880
881 @param h1 The first handler to add.
882
883 @param hn Additional handlers to add, invoked after @p h1 in
884 registration order.
885 */
886 template<class H1, class... HN>
887 74 void use(H1&& h1, HN&&... hn)
888 requires (!std::convertible_to<H1, std::string_view>)
889 {
890
3/3
✓ Branch 3 taken 69 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 3 times.
74 use(std::string_view(),
891 std::forward<H1>(h1), std::forward<HN>(hn)...);
892 74 }
893
894 /** Add exception handlers for a route pattern.
895
896 Registers one or more exception handlers that will be invoked
897 when an exception is thrown during request processing for routes
898 matching the specified pattern.
899
900 Handlers are invoked in the order provided until one handles
901 the exception.
902
903 @par Example
904 @code
905 app.except( "/api*",
906 []( route_params& p, std::exception const& ex )
907 {
908 p.res.set_status( 500 );
909 return route_done;
910 } );
911 @endcode
912
913 @param pattern The route pattern to match, or empty to match
914 all routes.
915
916 @param h1 The first exception handler.
917
918 @param hn Additional exception handlers.
919 */
920 template<class H1, class... HN>
921 2 void except(
922 std::string_view pattern,
923 H1&& h1, HN&&... hn)
924 {
925 static_assert(handler_crvals<H1, HN...>,
926 "pass handlers by value or std::move()");
927 static_assert(handler_check<8, H1, HN...>,
928 "only exception handlers are allowed here");
929
4/4
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 1 time.
2 this->add_middleware(pattern, make_handlers(ht_,
930 std::forward<H1>(h1), std::forward<HN>(hn)...));
931 2 }
932
933 /** Add global exception handlers.
934
935 Registers one or more exception handlers that will be invoked
936 when an exception is thrown during request processing for any
937 route.
938
939 Equivalent to calling `except( "", h1, hn... )`.
940
941 @par Example
942 @code
943 app.except(
944 []( route_params& p, std::exception const& ex )
945 {
946 p.res.set_status( 500 );
947 return route_done;
948 } );
949 @endcode
950
951 @param h1 The first exception handler.
952
953 @param hn Additional exception handlers.
954 */
955 template<class H1, class... HN>
956 2 void except(H1&& h1, HN&&... hn)
957 requires (!std::convertible_to<H1, std::string_view>)
958 {
959 static_assert(handler_crvals<H1, HN...>,
960 "pass handlers by value or std::move()");
961 static_assert(handler_check<8, H1, HN...>,
962 "only exception handlers are allowed here");
963
2/2
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
2 except(std::string_view(),
964 std::forward<H1>(h1), std::forward<HN>(hn)...);
965 2 }
966
967 /** Add handlers for all HTTP methods matching a path pattern.
968
969 This registers regular handlers for the specified path pattern,
970 participating in dispatch as described in the @ref router
971 class documentation. Handlers run when the route matches,
972 regardless of HTTP method, and execute in registration order.
973 Error handlers and routers cannot be passed here. A new route
974 object is created even if the pattern already exists.
975
976 @par Example
977 @code
978 r.route( "/status" )
979 .add( method::head, check_headers )
980 .add( method::get, send_status )
981 .all( log_access );
982 @endcode
983
984 @par Preconditions
985
986 @p pattern must be a valid path pattern; it must not be empty.
987
988 @param pattern The path pattern to match.
989
990 @param h1 The first handler to add.
991
992 @param hn Additional handlers to add, invoked after @p h1 in
993 registration order.
994 */
995 template<class H1, class... HN>
996 8 void all(
997 std::string_view pattern,
998 H1&& h1, HN&&... hn)
999 {
1000 static_assert(handler_crvals<H1, HN...>,
1001 "pass handlers by value or std::move()");
1002 static_assert(handler_check<1, H1, HN...>,
1003 "only normal route handlers are allowed here");
1004
2/2
✓ Branch 1 taken 8 times.
✓ Branch 5 taken 8 times.
8 this->route(pattern).all(
1005 std::forward<H1>(h1), std::forward<HN>(hn)...);
1006 8 }
1007
1008 /** Add route handlers for a method and pattern.
1009
1010 This registers regular handlers for the specified HTTP verb and
1011 path pattern, participating in dispatch as described in the
1012 @ref router class documentation. Error handlers and
1013 routers cannot be passed here.
1014
1015 @param verb The known HTTP method to match.
1016
1017 @param pattern The path pattern to match.
1018
1019 @param h1 The first handler to add.
1020
1021 @param hn Additional handlers to add, invoked after @p h1 in
1022 registration order.
1023 */
1024 template<class H1, class... HN>
1025 95 void add(
1026 http::method verb,
1027 std::string_view pattern,
1028 H1&& h1, HN&&... hn)
1029 {
1030 static_assert(handler_crvals<H1, HN...>,
1031 "pass handlers by value or std::move()");
1032 static_assert(handler_check<1, H1, HN...>,
1033 "only normal route handlers are allowed here");
1034
2/2
✓ Branch 1 taken 83 times.
✓ Branch 5 taken 83 times.
95 this->route(pattern).add(verb,
1035 std::forward<H1>(h1), std::forward<HN>(hn)...);
1036 83 }
1037
1038 /** Add route handlers for a method string and pattern.
1039
1040 This registers regular handlers for the specified HTTP verb and
1041 path pattern, participating in dispatch as described in the
1042 @ref router class documentation. Error handlers and
1043 routers cannot be passed here.
1044
1045 @param verb The HTTP method string to match.
1046
1047 @param pattern The path pattern to match.
1048
1049 @param h1 The first handler to add.
1050
1051 @param hn Additional handlers to add, invoked after @p h1 in
1052 registration order.
1053 */
1054 template<class H1, class... HN>
1055 3 void add(
1056 std::string_view verb,
1057 std::string_view pattern,
1058 H1&& h1, HN&&... hn)
1059 {
1060 static_assert(handler_crvals<H1, HN...>,
1061 "pass handlers by value or std::move()");
1062 static_assert(handler_check<1, H1, HN...>,
1063 "only normal route handlers are allowed here");
1064
2/2
✓ Branch 1 taken 3 times.
✓ Branch 5 taken 3 times.
3 this->route(pattern).add(verb,
1065 std::forward<H1>(h1), std::forward<HN>(hn)...);
1066 3 }
1067
1068 /** Return a fluent route for the specified path pattern.
1069
1070 Adds a new route to the router for the given pattern.
1071 A new route object is always created, even if another
1072 route with the same pattern already exists. The returned
1073 @ref fluent_route reference allows method-specific handler
1074 registration (such as GET or POST) or catch-all handlers
1075 with @ref fluent_route::all.
1076
1077 @param pattern The path expression to match against request
1078 targets. This may include parameters or wildcards following
1079 the router's pattern syntax. May not be empty.
1080
1081 @return A fluent route interface for chaining handler
1082 registrations.
1083 */
1084 auto
1085 116 route(
1086 std::string_view pattern) -> fluent_route
1087 {
1088
1/1
✓ Branch 1 taken 104 times.
116 return fluent_route(*this, pattern);
1089 }
1090
1091 /** Set the handler for automatic OPTIONS responses.
1092
1093 When an OPTIONS request matches a route but no explicit OPTIONS
1094 handler is registered, this handler is invoked with the pre-built
1095 Allow header value. This follows Express.js semantics where
1096 explicit OPTIONS handlers take priority.
1097
1098 @param h A callable with signature `route_task(P&, std::string_view)`
1099 where the string_view contains the pre-built Allow header value.
1100 */
1101 template<class H>
1102 224 void set_options_handler(H&& h)
1103 {
1104 static_assert(
1105 std::is_invocable_r_v<route_task, const std::decay_t<H>&, P&, std::string_view>,
1106 "Handler must have signature: route_task(P&, std::string_view)");
1107
2/2
✓ Branch 2 taken 224 times.
✓ Branch 6 taken 224 times.
224 this->set_options_handler_impl(
1108 std::make_unique<options_handler_impl<H>>(
1109 std::forward<H>(h)));
1110 224 }
1111 };
1112
1113 template<class P, class HT>
1114 class router<P, HT>::
1115 fluent_route
1116 {
1117 public:
1118 fluent_route(fluent_route const&) = default;
1119
1120 /** Add handlers that apply to all HTTP methods.
1121
1122 This registers regular handlers that run for any request matching
1123 the route's pattern, regardless of HTTP method. Handlers are
1124 appended to the route's handler sequence and are invoked in
1125 registration order whenever a preceding handler returns
1126 @ref route_next. Error handlers and routers cannot be passed here.
1127
1128 This function returns a @ref fluent_route, allowing additional
1129 method registrations to be chained. For example:
1130 @code
1131 r.route( "/resource" )
1132 .all( log_request )
1133 .add( method::get, show_resource )
1134 .add( method::post, update_resource );
1135 @endcode
1136
1137 @param h1 The first handler to add.
1138
1139 @param hn Additional handlers to add, invoked after @p h1 in
1140 registration order.
1141
1142 @return A reference to `*this` for chained registrations.
1143 */
1144 template<class H1, class... HN>
1145 9 auto all(
1146 H1&& h1, HN&&... hn) ->
1147 fluent_route
1148 {
1149 static_assert(handler_check<1, H1, HN...>);
1150
1/1
✓ Branch 3 taken 9 times.
18 owner_.add_to_route(route_idx_, std::string_view{},
1151
1/1
✓ Branch 1 taken 9 times.
9 owner_.make_handlers(owner_.ht_,
1152 std::forward<H1>(h1), std::forward<HN>(hn)...));
1153 9 return *this;
1154 }
1155
1156 /** Add handlers for a specific HTTP method.
1157
1158 This registers regular handlers for the given method on the
1159 current route, participating in dispatch as described in the
1160 @ref router class documentation. Handlers are appended
1161 to the route's handler sequence and invoked in registration
1162 order whenever a preceding handler returns @ref route_next.
1163 Error handlers and routers cannot be passed here.
1164
1165 @param verb The HTTP method to match.
1166
1167 @param h1 The first handler to add.
1168
1169 @param hn Additional handlers to add, invoked after @p h1 in
1170 registration order.
1171
1172 @return A reference to `*this` for chained registrations.
1173 */
1174 template<class H1, class... HN>
1175 99 auto add(
1176 http::method verb,
1177 H1&& h1, HN&&... hn) ->
1178 fluent_route
1179 {
1180 static_assert(handler_check<1, H1, HN...>);
1181
1/1
✓ Branch 2 taken 99 times.
198 owner_.add_to_route(route_idx_, verb,
1182
1/1
✓ Branch 1 taken 99 times.
99 owner_.make_handlers(owner_.ht_,
1183 std::forward<H1>(h1), std::forward<HN>(hn)...));
1184 99 return *this;
1185 }
1186
1187 /** Add handlers for a method string.
1188
1189 This registers regular handlers for the given HTTP method string
1190 on the current route, participating in dispatch as described in
1191 the @ref router class documentation. This overload is
1192 intended for methods not represented by @ref http::method.
1193 Handlers are appended to the route's handler sequence and invoked
1194 in registration order whenever a preceding handler returns
1195 @ref route_next. Error handlers and routers cannot be passed here.
1196
1197 @param verb The HTTP method string to match.
1198
1199 @param h1 The first handler to add.
1200
1201 @param hn Additional handlers to add, invoked after @p h1 in
1202 registration order.
1203
1204 @return A reference to `*this` for chained registrations.
1205 */
1206 template<class H1, class... HN>
1207 3 auto add(
1208 std::string_view verb,
1209 H1&& h1, HN&&... hn) ->
1210 fluent_route
1211 {
1212 static_assert(handler_check<1, H1, HN...>);
1213
1/1
✓ Branch 2 taken 3 times.
6 owner_.add_to_route(route_idx_, verb,
1214
1/1
✓ Branch 1 taken 3 times.
3 owner_.make_handlers(owner_.ht_,
1215 std::forward<H1>(h1), std::forward<HN>(hn)...));
1216 3 return *this;
1217 }
1218
1219 private:
1220 friend class router;
1221 116 fluent_route(
1222 router& owner,
1223 std::string_view pattern)
1224 116 : route_idx_(owner.new_route(pattern))
1225 104 , owner_(owner)
1226 {
1227 104 }
1228
1229 std::size_t route_idx_;
1230 router& owner_;
1231 };
1232
1233 } // http
1234 } // boost
1235
1236 #endif
1237