TLA Line data 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/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 HIT 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 MIS 0 : : ht(ht_)
517 : {
518 HIT 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
|