include/boost/http/server/router.hpp

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