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/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 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 : 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 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 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 0 : : ht(ht_)
519 : {
520 218 : p = v;
521 218 : n = sizeof...(HN);
522 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 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 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 222 : set_options_handler(
604 11 : [](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 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 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 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 : 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 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 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 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 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 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 18 : owner_.add_to_route(route_idx_, std::string_view{},
1151 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 198 : owner_.add_to_route(route_idx_, verb,
1182 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 6 : owner_.add_to_route(route_idx_, verb,
1214 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
|