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