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_proto
8 : //
9 :
10 : #ifndef BOOST_HTTP_PROTO_SERVER_BASIC_ROUTER_HPP
11 : #define BOOST_HTTP_PROTO_SERVER_BASIC_ROUTER_HPP
12 :
13 : #include <boost/http_proto/detail/config.hpp>
14 : #include <boost/http_proto/detail/type_traits.hpp>
15 : #include <boost/http_proto/server/router_types.hpp>
16 : #include <boost/http_proto/method.hpp>
17 : #include <boost/capy/detail/call_traits.hpp> // VFALCO fix
18 : #include <boost/core/detail/string_view.hpp>
19 : #include <boost/url/url_view.hpp>
20 : #include <boost/core/detail/static_assert.hpp>
21 : #include <boost/mp11/algorithm.hpp>
22 : #include <boost/assert.hpp>
23 : #include <type_traits>
24 :
25 : namespace boost {
26 : namespace http_proto {
27 :
28 : template<class>
29 : class basic_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 144 : 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 1 : merge_params(
65 : bool value) noexcept
66 : {
67 1 : v_ = (v_ & ~1) | (value ? 1 : 0);
68 1 : 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 7 : case_sensitive(
89 : bool value) noexcept
90 : {
91 7 : if(value)
92 5 : v_ = (v_ & ~6) | 2;
93 : else
94 2 : v_ = (v_ & ~6) | 4;
95 7 : 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 1 : strict(
120 : bool value) noexcept
121 : {
122 1 : if(value)
123 0 : v_ = (v_ & ~24) | 8;
124 : else
125 1 : v_ = (v_ & ~24) | 16;
126 1 : return *this;
127 : }
128 :
129 : private:
130 : template<class> friend class basic_router;
131 : unsigned int v_ = 0;
132 : };
133 :
134 : //-----------------------------------------------
135 :
136 : //namespace detail {
137 :
138 : class any_router;
139 :
140 : //-----------------------------------------------
141 :
142 : // implementation for all routers
143 : class any_router
144 : {
145 : private:
146 : template<class>
147 : friend class http_proto::basic_router;
148 : using opt_flags = unsigned int;
149 :
150 : struct BOOST_HTTP_PROTO_DECL any_handler
151 : {
152 348 : virtual ~any_handler() = default;
153 : virtual std::size_t count() const noexcept = 0;
154 : virtual route_result invoke(
155 : route_params_base&) const = 0;
156 : };
157 :
158 : using handler_ptr = std::unique_ptr<any_handler>;
159 :
160 : struct handler_list
161 : {
162 : std::size_t n;
163 : handler_ptr* p;
164 : };
165 :
166 : using match_result = route_params_base::match_result;
167 : struct matcher;
168 : struct layer;
169 : struct impl;
170 :
171 : BOOST_HTTP_PROTO_DECL ~any_router();
172 : BOOST_HTTP_PROTO_DECL any_router(opt_flags);
173 : BOOST_HTTP_PROTO_DECL any_router(any_router&&) noexcept;
174 : BOOST_HTTP_PROTO_DECL any_router(any_router const&) noexcept;
175 : BOOST_HTTP_PROTO_DECL any_router& operator=(any_router&&) noexcept;
176 : BOOST_HTTP_PROTO_DECL any_router& operator=(any_router const&) noexcept;
177 : BOOST_HTTP_PROTO_DECL std::size_t count() const noexcept;
178 : BOOST_HTTP_PROTO_DECL layer& new_layer(core::string_view pattern);
179 : BOOST_HTTP_PROTO_DECL void add_impl(core::string_view, handler_list const&);
180 : BOOST_HTTP_PROTO_DECL void add_impl(layer&,
181 : http_proto::method, handler_list const&);
182 : BOOST_HTTP_PROTO_DECL void add_impl(layer&,
183 : core::string_view, handler_list const&);
184 : BOOST_HTTP_PROTO_DECL route_result resume_impl(
185 : route_params_base&, route_result ec) const;
186 : BOOST_HTTP_PROTO_DECL route_result dispatch_impl(http_proto::method,
187 : core::string_view, urls::url_view const&,
188 : route_params_base&) const;
189 : BOOST_HTTP_PROTO_DECL route_result dispatch_impl(
190 : route_params_base&) const;
191 : route_result do_dispatch(route_params_base&) const;
192 :
193 : impl* impl_ = nullptr;
194 : };
195 :
196 : //} // detail
197 :
198 : //-----------------------------------------------
199 :
200 : /** A container for HTTP route handlers.
201 :
202 : `basic_router` objects store and dispatch route handlers based on the
203 : HTTP method and path of an incoming request. Routes are added with a
204 : path pattern, method, and an associated handler, and the router is then
205 : used to dispatch the appropriate handler.
206 :
207 : Patterns used to create route definitions have percent-decoding applied
208 : when handlers are mounted. A literal "%2F" in the pattern string is
209 : indistinguishable from a literal '/'. For example, "/x%2Fz" is the
210 : same as "/x/z" when used as a pattern.
211 :
212 : @par Example
213 : @code
214 : using router_type = basic_router<route_params>;
215 : router_type router;
216 : router.get( "/hello",
217 : []( route_params& p )
218 : {
219 : p.res.status( status::ok );
220 : p.res.set_body( "Hello, world!" );
221 : return route::send;
222 : } );
223 : @endcode
224 :
225 : Router objects are lightweight, shared references to their contents.
226 : Copies of a router obtained through construction, conversion, or
227 : assignment do not create new instances; they all refer to the same
228 : underlying data.
229 :
230 : @par Handlers
231 :
232 : Regular handlers are invoked for matching routes and have this
233 : equivalent signature:
234 : @code
235 : route_result handler( Params& p )
236 : @endcode
237 :
238 : The return value is a @ref route_result used to indicate the desired
239 : action through @ref route enum values, or to indicate that a failure
240 : occurred. Failures are represented by error codes for which
241 : `system::error_code::failed()` returns `true`.
242 :
243 : When a failing error code is produced and remains unhandled, the
244 : router enters error-dispatching mode. In this mode, only error
245 : handlers are invoked. Error handlers are registered globally or
246 : for specific paths and execute in the order of registration whenever
247 : a failing error code is present in the response.
248 :
249 : Error handlers have this equivalent signature:
250 : @code
251 : route_result error_handler( Params& p, system::error_code ec )
252 : @endcode
253 :
254 : Each error handler may return any failing @ref system::error_code,
255 : which is equivalent to calling:
256 : @code
257 : p.next( ec ); // with ec.failed() == true
258 : @endcode
259 :
260 : Returning @ref route::next indicates that control should proceed to
261 : the next matching error handler. Returning a different failing code
262 : replaces the current error and continues dispatch in error mode using
263 : that new code. Error handlers are invoked until one returns a result
264 : other than @ref route::next.
265 :
266 : Exception handlers have this equivalent signature:
267 : @code
268 : route_result exception_handler( Params& p, E ex )
269 : @endcode
270 :
271 : Where `E` is the type of exception caught. Handlers installed for an
272 : exception of type `E` will also be called when the exception type is
273 : a derived class of `E`. Exception handlers are invoked in the order
274 : of registration whenever an exception is present in the request.
275 :
276 : The prefix match is not strict: middleware attached to `"/api"`
277 : will also match `"/api/users"` and `"/api/data"`. When registered
278 : before route handlers for the same prefix, middleware runs before
279 : those routes. This is analogous to `app.use( path, ... )` in
280 : Express.js.
281 :
282 : @par Thread Safety
283 :
284 : Member functions marked `const` such as @ref dispatch and @ref resume
285 : may be called concurrently on routers that refer to the same data.
286 : Modification of routers through calls to non-`const` member functions
287 : is not thread-safe and must not be performed concurrently with any
288 : other member function.
289 :
290 : @par Constraints
291 :
292 : `Params` must be publicly derived from @ref route_params_base.
293 :
294 : @tparam Params The type of the parameters object passed to handlers.
295 : */
296 : template<class Params>
297 : class basic_router : public /*detail::*/any_router
298 : {
299 : // Params must be publicly derived from route_params_base
300 : BOOST_CORE_STATIC_ASSERT(
301 : detail::derived_from<route_params_base, Params>::value);
302 :
303 : // 0 = unrecognized
304 : // 1 = normal handler (Params&)
305 : // 2 = error handler (Params&, error_code)
306 : // 4 = basic_router<Params>
307 :
308 : template<class T, class = void>
309 : struct handler_type
310 : : std::integral_constant<int, 0>
311 : {
312 : };
313 :
314 : // route_result( Params& ) const
315 : template<class T>
316 : struct handler_type<T, typename
317 : std::enable_if<std::is_convertible<
318 : decltype(std::declval<T const&>()(
319 : std::declval<Params&>())),
320 : route_result>::value
321 : >::type> : std::integral_constant<int, 1> {};
322 :
323 : // route_result( Params&, system::error_code const& ) const
324 : template<class T>
325 : struct handler_type<T, typename
326 : std::enable_if<std::is_convertible<
327 : decltype(std::declval<T const&>()(
328 : std::declval<Params&>(),
329 : std::declval<system::error_code const&>())),
330 : route_result>::value
331 : >::type> : std::integral_constant<int, 2> {};
332 :
333 : // basic_router<Params>
334 : template<class T>
335 : struct handler_type<T, typename
336 : std::enable_if<
337 : std::is_base_of<any_router, T>::value &&
338 : std::is_convertible<T const volatile*,
339 : any_router const volatile*>::value &&
340 : std::is_constructible<T, basic_router<Params>>::value
341 : >::type> : std::integral_constant<int, 4> {};
342 :
343 : template<std::size_t Mask, class... Ts>
344 : struct handler_check : std::true_type {};
345 :
346 : template<std::size_t Mask, class T0, class... Ts>
347 : struct handler_check<Mask, T0, Ts...>
348 : : std::conditional<
349 : ( (handler_type<T0>::value & Mask) != 0 ),
350 : handler_check<Mask, Ts...>,
351 : std::false_type
352 : >::type {};
353 :
354 : // exception handler (Params&, E)
355 :
356 : template<class H, class = void>
357 : struct except_type : std::false_type {};
358 :
359 : template<class H>
360 : struct except_type<H, typename std::enable_if<
361 : capy::detail::call_traits<H>{} && (
362 : mp11::mp_size<typename
363 : capy::detail::call_traits<H>::arg_types>{} == 2) &&
364 : std::is_convertible<Params&, mp11::mp_first<typename
365 : capy::detail::call_traits<H>::arg_types>>::value
366 : >::type>
367 : : std::true_type
368 : {
369 : // type of exception
370 : using type = typename std::decay<mp11::mp_second<typename
371 : capy::detail::call_traits<H>::arg_types>>::type;
372 : };
373 :
374 : template<class... Hs> using except_types =
375 : mp11::mp_all< except_type<Hs>... >;
376 :
377 : public:
378 : /** The type of params used in handlers.
379 : */
380 : using params_type = Params;
381 :
382 : /** A fluent interface for defining handlers on a specific route.
383 :
384 : This type represents a single route within the router and
385 : provides a chainable API for registering handlers associated
386 : with particular HTTP methods or for all methods collectively.
387 :
388 : Typical usage registers one or more handlers for a route:
389 : @code
390 : router.route( "/users/:id" )
391 : .get( show_user )
392 : .put( update_user )
393 : .all( log_access );
394 : @endcode
395 :
396 : Each call appends handlers in registration order.
397 : */
398 : class fluent_route;
399 :
400 : /** Constructor.
401 :
402 : Creates an empty router with the specified configuration.
403 : Routers constructed with default options inherit the values
404 : of @ref router_options::case_sensitive and
405 : @ref router_options::strict from the parent router, or default
406 : to `false` if there is no parent. The value of
407 : @ref router_options::merge_params defaults to `false` and
408 : is never inherited.
409 :
410 : @param options The configuration options to use.
411 : */
412 : explicit
413 144 : basic_router(
414 : router_options options = {})
415 144 : : any_router(options.v_)
416 : {
417 144 : }
418 :
419 : /** Construct a router from another router with compatible types.
420 :
421 : This constructs a router that shares the same underlying routing
422 : state as another router whose params type is a base class of `Params`.
423 :
424 : The resulting router participates in shared ownership of the
425 : implementation; copying the router does not duplicate routes or
426 : handlers, and changes visible through one router are visible
427 : through all routers that share the same underlying state.
428 :
429 : @par Constraints
430 :
431 : `Params` must be derived from `OtherParams`.
432 :
433 : @param other The router to construct from.
434 :
435 : @tparam OtherParams The params type of the source router.
436 : */
437 : template<
438 : class OtherParams,
439 : class = typename std::enable_if<
440 : detail::derived_from<Params, OtherParams>::value>::type
441 : >
442 : basic_router(
443 : basic_router<OtherParams> const& other) noexcept
444 : : any_router(other)
445 : {
446 : }
447 :
448 : /** Add middleware handlers for a path prefix.
449 :
450 : Each handler registered with this function participates in the
451 : routing and error-dispatch process for requests whose path begins
452 : with the specified prefix, as described in the @ref basic_router
453 : class documentation. Handlers execute in the order they are added
454 : and may return @ref route::next to transfer control to the
455 : subsequent handler in the chain.
456 :
457 : @par Example
458 : @code
459 : router.use( "/api",
460 : []( route_params& p )
461 : {
462 : if( ! authenticate( p ) )
463 : {
464 : p.res.status( 401 );
465 : p.res.set_body( "Unauthorized" );
466 : return route::send;
467 : }
468 : return route::next;
469 : },
470 : []( route_params& p )
471 : {
472 : p.res.set_header( "X-Powered-By", "MyServer" );
473 : return route::next;
474 : } );
475 : @endcode
476 :
477 : @par Preconditions
478 :
479 : @p pattern must be a valid path prefix; it may be empty to
480 : indicate the root scope.
481 :
482 : @param pattern The pattern to match.
483 :
484 : @param h1 The first handler to add.
485 :
486 : @param hn Additional handlers to add, invoked after @p h1 in
487 : registration order.
488 : */
489 : template<class H1, class... HN>
490 192 : void use(
491 : core::string_view pattern,
492 : H1&& h1, HN... hn)
493 : {
494 : // If you get a compile error on this line it means that
495 : // one or more of the provided types is not a valid handler,
496 : // error handler, or router.
497 : BOOST_CORE_STATIC_ASSERT(handler_check<7, H1, HN...>::value);
498 192 : add_impl(pattern, make_handler_list(
499 : std::forward<H1>(h1), std::forward<HN>(hn)...));
500 192 : }
501 :
502 : /** Add global middleware handlers.
503 :
504 : Each handler registered with this function participates in the
505 : routing and error-dispatch process as described in the
506 : @ref basic_router class documentation. Handlers execute in the
507 : order they are added and may return @ref route::next to transfer
508 : control to the next handler in the chain.
509 :
510 : This is equivalent to writing:
511 : @code
512 : use( "/", h1, hn... );
513 : @endcode
514 :
515 : @par Example
516 : @code
517 : router.use(
518 : []( Params& p )
519 : {
520 : p.res.erase( "X-Powered-By" );
521 : return route::next;
522 : } );
523 : @endcode
524 :
525 : @par Constraints
526 :
527 : @p h1 must not be convertible to @ref core::string_view.
528 :
529 : @param h1 The first handler to add.
530 :
531 : @param hn Additional handlers to add, invoked after @p h1 in
532 : registration order.
533 : */
534 : template<class H1, class... HN
535 : , class = typename std::enable_if<
536 : ! std::is_convertible<H1, core::string_view>::value>::type>
537 108 : void use(H1&& h1, HN&&... hn)
538 : {
539 : // If you get a compile error on this line it means that
540 : // one or more of the provided types is not a valid handler,
541 : // error handler, or router.
542 : BOOST_CORE_STATIC_ASSERT(handler_check<7, H1, HN...>::value);
543 108 : use(core::string_view(),
544 : std::forward<H1>(h1), std::forward<HN>(hn)...);
545 108 : }
546 :
547 : /** Add exception handlers for a route pattern.
548 :
549 : Registers one or more exception handlers that will be invoked
550 : when an exception is thrown during request processing for routes
551 : matching the specified pattern.
552 :
553 : Handlers are invoked in the order provided until one handles
554 : the exception.
555 :
556 : @par Example
557 : @code
558 : app.except( "/api*",
559 : []( route_params& p, std::exception const& ex )
560 : {
561 : p.res.set_status( 500 );
562 : return route::send;
563 : } );
564 : @endcode
565 :
566 : @param pattern The route pattern to match, or empty to match
567 : all routes.
568 :
569 : @param h1 The first exception handler.
570 :
571 : @param hn Additional exception handlers.
572 : */
573 : template<class H1, class... HN>
574 11 : void except(
575 : core::string_view pattern,
576 : H1&& h1, HN... hn)
577 : {
578 : // If you get a compile error on this line it means that one or
579 : // more of the provided types is not a valid exception handler
580 : BOOST_CORE_STATIC_ASSERT(except_types<H1, HN...>::value);
581 11 : add_impl(pattern, make_except_list(
582 : std::forward<H1>(h1), std::forward<HN>(hn)...));
583 11 : }
584 :
585 : /** Add global exception handlers.
586 :
587 : Registers one or more exception handlers that will be invoked
588 : when an exception is thrown during request processing for any
589 : route.
590 :
591 : Equivalent to calling `except( "", h1, hn... )`.
592 :
593 : @par Example
594 : @code
595 : app.except(
596 : []( route_params& p, std::exception const& ex )
597 : {
598 : p.res.set_status( 500 );
599 : return route::send;
600 : } );
601 : @endcode
602 :
603 : @param h1 The first exception handler.
604 :
605 : @param hn Additional exception handlers.
606 : */
607 : template<class H1, class... HN
608 : , class = typename std::enable_if<
609 : ! std::is_convertible<H1, core::string_view>::value>::type>
610 11 : void except(H1&& h1, HN&&... hn)
611 : {
612 : // If you get a compile error on this line it means that one or
613 : // more of the provided types is not a valid exception handler
614 : BOOST_CORE_STATIC_ASSERT(except_types<H1, HN...>::value);
615 11 : except(core::string_view(),
616 : std::forward<H1>(h1), std::forward<HN>(hn)...);
617 11 : }
618 :
619 : /** Add handlers for all HTTP methods matching a path pattern.
620 :
621 : This registers regular handlers for the specified path pattern,
622 : participating in dispatch as described in the @ref basic_router
623 : class documentation. Handlers run when the route matches,
624 : regardless of HTTP method, and execute in registration order.
625 : Error handlers and routers cannot be passed here. A new route
626 : object is created even if the pattern already exists.
627 :
628 : @par Example
629 : @code
630 : router.route( "/status" )
631 : .add( method::head, check_headers )
632 : .add( method::get, send_status )
633 : .all( log_access );
634 : @endcode
635 :
636 : @par Preconditions
637 :
638 : @p pattern must be a valid path pattern; it must not be empty.
639 :
640 : @param pattern The path pattern to match.
641 :
642 : @param h1 The first handler to add.
643 :
644 : @param hn Additional handlers to add, invoked after @p h1 in
645 : registration order.
646 : */
647 : template<class H1, class... HN>
648 12 : void all(
649 : core::string_view pattern,
650 : H1&& h1, HN&&... hn)
651 : {
652 : // If you get a compile error on this line it means that
653 : // one or more of the provided types is not a valid handler.
654 : // Error handlers and routers cannot be passed here.
655 : BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value);
656 12 : this->route(pattern).all(
657 : std::forward<H1>(h1), std::forward<HN>(hn)...);
658 11 : }
659 :
660 : /** Add route handlers for a method and pattern.
661 :
662 : This registers regular handlers for the specified HTTP verb and
663 : path pattern, participating in dispatch as described in the
664 : @ref basic_router class documentation. Error handlers and
665 : routers cannot be passed here.
666 :
667 : @param verb The known HTTP method to match.
668 :
669 : @param pattern The path pattern to match.
670 :
671 : @param h1 The first handler to add.
672 :
673 : @param hn Additional handlers to add, invoked after @p h1 in
674 : registration order.
675 : */
676 : template<class H1, class... HN>
677 21 : void add(
678 : http_proto::method verb,
679 : core::string_view pattern,
680 : H1&& h1, HN&&... hn)
681 : {
682 : // If you get a compile error on this line it means that
683 : // one or more of the provided types is not a valid handler.
684 : // Error handlers and routers cannot be passed here.
685 : BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value);
686 21 : this->route(pattern).add(verb,
687 : std::forward<H1>(h1), std::forward<HN>(hn)...);
688 21 : }
689 :
690 : /** Add route handlers for a method string and pattern.
691 :
692 : This registers regular handlers for the specified HTTP verb and
693 : path pattern, participating in dispatch as described in the
694 : @ref basic_router class documentation. Error handlers and
695 : routers cannot be passed here.
696 :
697 : @param verb The HTTP method string to match.
698 :
699 : @param pattern The path pattern to match.
700 :
701 : @param h1 The first handler to add.
702 :
703 : @param hn Additional handlers to add, invoked after @p h1 in
704 : registration order.
705 : */
706 : template<class H1, class... HN>
707 2 : void add(
708 : core::string_view verb,
709 : core::string_view pattern,
710 : H1&& h1, HN&&... hn)
711 : {
712 : // If you get a compile error on this line it means that
713 : // one or more of the provided types is not a valid handler.
714 : // Error handlers and routers cannot be passed here.
715 : BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value);
716 2 : this->route(pattern).add(verb,
717 : std::forward<H1>(h1), std::forward<HN>(hn)...);
718 2 : }
719 :
720 : /** Return a fluent route for the specified path pattern.
721 :
722 : Adds a new route to the router for the given pattern.
723 : A new route object is always created, even if another
724 : route with the same pattern already exists. The returned
725 : @ref fluent_route reference allows method-specific handler
726 : registration (such as GET or POST) or catch-all handlers
727 : with @ref fluent_route::all.
728 :
729 : @param pattern The path expression to match against request
730 : targets. This may include parameters or wildcards following
731 : the router's pattern syntax. May not be empty.
732 :
733 : @return A fluent route interface for chaining handler
734 : registrations.
735 : */
736 : auto
737 63 : route(
738 : core::string_view pattern) -> fluent_route
739 : {
740 63 : return fluent_route(*this, pattern);
741 : }
742 :
743 : //--------------------------------------------
744 :
745 : /** Dispatch a request to the appropriate handler.
746 :
747 : This runs the routing and error-dispatch logic for the given HTTP
748 : method and target URL, as described in the @ref basic_router class
749 : documentation.
750 :
751 : @par Thread Safety
752 :
753 : This function may be called concurrently on the same object along
754 : with other `const` member functions. Each concurrent invocation
755 : must use distinct params objects.
756 :
757 : @param verb The HTTP method to match. This must not be
758 : @ref http_proto::method::unknown.
759 :
760 : @param url The full request target used for route matching.
761 :
762 : @param p The params to pass to handlers.
763 :
764 : @return The @ref route_result describing how routing completed.
765 :
766 : @throws std::invalid_argument If @p verb is
767 : @ref http_proto::method::unknown.
768 : */
769 : auto
770 148 : dispatch(
771 : http_proto::method verb,
772 : urls::url_view const& url,
773 : Params& p) const ->
774 : route_result
775 : {
776 148 : if(verb == http_proto::method::unknown)
777 1 : detail::throw_invalid_argument();
778 291 : return dispatch_impl(verb,
779 288 : core::string_view(), url, p);
780 : }
781 :
782 : /** Dispatch a request using a method string.
783 :
784 : This runs the routing and error-dispatch logic for the given HTTP
785 : method string and target URL, as described in the @ref basic_router
786 : class documentation. This overload is intended for method tokens
787 : that are not represented by @ref http_proto::method.
788 :
789 : @par Thread Safety
790 :
791 : This function may be called concurrently on the same object along
792 : with other `const` member functions. Each concurrent invocation
793 : must use distinct params objects.
794 :
795 : @param verb The HTTP method string to match. This must not
796 : be empty.
797 :
798 : @param url The full request target used for route matching.
799 :
800 : @param p The params to pass to handlers.
801 :
802 : @return The @ref route_result describing how routing completed.
803 :
804 : @throws std::invalid_argument If @p verb is empty.
805 : */
806 : auto
807 34 : dispatch(
808 : core::string_view verb,
809 : urls::url_view const& url,
810 : Params& p) ->
811 : route_result
812 : {
813 : // verb cannot be empty
814 34 : if(verb.empty())
815 1 : detail::throw_invalid_argument();
816 33 : return dispatch_impl(
817 : http_proto::method::unknown,
818 33 : verb, url, p);
819 : }
820 :
821 : /** Resume dispatch after a detached handler.
822 :
823 : This continues routing after a previous call to @ref dispatch
824 : returned @ref route::detach. It recreates the routing state and
825 : resumes as if the handler that detached had instead returned
826 : the specified @p rv from its body. The regular routing and
827 : error-dispatch logic then proceeds as described in the
828 : @ref basic_router class documentation. For example, if @p rv is
829 : @ref route::next, the next matching handlers are invoked.
830 :
831 : @par Thread Safety
832 :
833 : This function may be called concurrently on the same object along
834 : with other `const` member functions. Each concurrent invocation
835 : must use distinct params objects.
836 :
837 : @param p The params to pass to handlers.
838 :
839 : @param rv The @ref route_result to resume with, as if returned
840 : by the detached handler.
841 :
842 : @return The @ref route_result describing how routing completed.
843 : */
844 : auto
845 9 : resume(
846 : Params& p,
847 : route_result const& rv) const ->
848 : route_result
849 : {
850 9 : return resume_impl(p, rv);
851 : }
852 :
853 : private:
854 : // used to avoid a race when modifying p.resume_
855 : struct set_resume
856 : {
857 : std::size_t& resume;
858 : bool cancel_ = true;
859 :
860 232 : ~set_resume()
861 : {
862 232 : if(cancel_)
863 220 : resume = 0;
864 232 : }
865 :
866 232 : set_resume(
867 : route_params_base& p) noexcept
868 232 : : resume(p.resume_)
869 : {
870 232 : resume = p.pos_;
871 232 : }
872 :
873 12 : void apply() noexcept
874 : {
875 12 : cancel_ = false;
876 12 : }
877 : };
878 :
879 : // wrapper for route handlers
880 : template<
881 : class H,
882 : class Ty = handler_type<typename
883 : std::decay<H>::type > >
884 : struct handler_impl : any_handler
885 : {
886 : typename std::decay<H>::type h;
887 :
888 : template<class... Args>
889 336 : explicit handler_impl(Args&&... args)
890 336 : : h(std::forward<Args>(args)...)
891 : {
892 336 : }
893 :
894 : std::size_t
895 141 : count() const noexcept override
896 : {
897 141 : return count(Ty{});
898 : }
899 :
900 : route_result
901 266 : invoke(
902 : route_params_base& p) const override
903 : {
904 266 : return invoke(static_cast<Params&>(p), Ty{});
905 : }
906 :
907 : private:
908 : std::size_t count(
909 : std::integral_constant<int, 0>) = delete;
910 :
911 131 : std::size_t count(
912 : std::integral_constant<int, 1>) const noexcept
913 : {
914 131 : return 1;
915 : }
916 :
917 6 : std::size_t count(
918 : std::integral_constant<int, 2>) const noexcept
919 : {
920 6 : return 1;
921 : }
922 :
923 4 : std::size_t count(
924 : std::integral_constant<int, 4>) const noexcept
925 : {
926 4 : return 1 + h.count();
927 : }
928 :
929 : route_result invoke(Params&,
930 : std::integral_constant<int, 0>) const = delete;
931 :
932 : // ( Params& )
933 203 : route_result invoke(Params& p,
934 : std::integral_constant<int, 1>) const
935 : {
936 203 : route_params_base& p_(p);
937 203 : if( p_.ec_.failed() ||
938 : p_.ep_)
939 12 : return http_proto::route::next;
940 191 : set_resume u(p_);
941 191 : auto rv = h(p);
942 185 : if(rv == http_proto::route::detach)
943 : {
944 12 : u.apply();
945 12 : return rv;
946 : }
947 173 : return rv;
948 191 : }
949 :
950 : // ( Params&, error_code )
951 : route_result
952 46 : invoke(Params& p,
953 : std::integral_constant<int, 2>) const
954 : {
955 46 : route_params_base& p_(p);
956 46 : if(! p_.ec_.failed())
957 8 : return http_proto::route::next;
958 38 : set_resume u(p_);
959 38 : auto rv = h(p, p_.ec_);
960 38 : if(rv == http_proto::route::detach)
961 : {
962 0 : u.apply();
963 0 : return rv;
964 : }
965 38 : return rv;
966 38 : }
967 :
968 : // any_router
969 17 : route_result invoke(Params& p,
970 : std::integral_constant<int, 4>) const
971 : {
972 17 : route_params_base& p_(p);
973 32 : if( p_.resume_ > 0 ||
974 15 : ( ! p_.ec_.failed() &&
975 14 : ! p_.ep_))
976 16 : return h.dispatch_impl(p);
977 1 : return http_proto::route::next;
978 : }
979 : };
980 :
981 : template<class H, class E = typename
982 : except_type<typename std::decay<H>::type>::type>
983 : struct except_impl : any_handler
984 : {
985 : typename std::decay<H>::type h;
986 :
987 : template<class... Args>
988 12 : explicit except_impl(Args&&... args)
989 12 : : h(std::forward<Args>(args)...)
990 : {
991 12 : }
992 :
993 : std::size_t
994 0 : count() const noexcept override
995 : {
996 0 : return 1;
997 : }
998 :
999 : route_result
1000 12 : invoke(Params& p) const override
1001 : {
1002 : #ifndef BOOST_NO_EXCEPTIONS
1003 12 : route_params_base& p_(p);
1004 12 : if(! p_.ep_)
1005 6 : return http_proto::route::next;
1006 : try
1007 : {
1008 12 : std::rethrow_exception(p_.ep_);
1009 : }
1010 9 : catch(E const& ex)
1011 : {
1012 3 : set_resume u(p_);
1013 : // VFALCO What if h throws?
1014 3 : auto rv = h(p, ex);
1015 3 : if(rv == http_proto::route::detach)
1016 : {
1017 0 : u.apply();
1018 0 : return rv;
1019 : }
1020 3 : return rv;
1021 3 : }
1022 3 : catch(...)
1023 : {
1024 3 : BOOST_ASSERT(p_.ep_);
1025 3 : return http_proto::route::next;
1026 : }
1027 : #else
1028 : (void)p;
1029 : return http_proto::route::next;
1030 : #endif
1031 : }
1032 : };
1033 :
1034 : template<std::size_t N>
1035 : struct handler_list_impl : handler_list
1036 : {
1037 : template<class... HN>
1038 283 : explicit handler_list_impl(HN&&... hn)
1039 0 : {
1040 283 : n = sizeof...(HN);
1041 283 : p = v;
1042 283 : assign<0>(std::forward<HN>(hn)...);
1043 283 : }
1044 :
1045 : // exception handlers
1046 : template<class... HN>
1047 11 : explicit handler_list_impl(int, HN&&... hn)
1048 0 : {
1049 11 : n = sizeof...(HN);
1050 11 : p = v;
1051 11 : assign<0>(0, std::forward<HN>(hn)...);
1052 11 : }
1053 :
1054 : private:
1055 : template<std::size_t I, class H1, class... HN>
1056 336 : void assign(H1&& h1, HN&&... hn)
1057 : {
1058 336 : v[I] = handler_ptr(new handler_impl<H1>(
1059 : std::forward<H1>(h1)));
1060 336 : assign<I+1>(std::forward<HN>(hn)...);
1061 336 : }
1062 :
1063 : // exception handlers
1064 : template<std::size_t I, class H1, class... HN>
1065 12 : void assign(int, H1&& h1, HN&&... hn)
1066 : {
1067 12 : v[I] = handler_ptr(new except_impl<H1>(
1068 : std::forward<H1>(h1)));
1069 12 : assign<I+1>(0, std::forward<HN>(hn)...);
1070 12 : }
1071 :
1072 : template<std::size_t>
1073 294 : void assign(int = 0)
1074 : {
1075 294 : }
1076 :
1077 : handler_ptr v[N];
1078 : };
1079 :
1080 : template<class... HN>
1081 : static auto
1082 283 : make_handler_list(HN&&... hn) ->
1083 : handler_list_impl<sizeof...(HN)>
1084 : {
1085 : return handler_list_impl<sizeof...(HN)>(
1086 283 : std::forward<HN>(hn)...);
1087 : }
1088 :
1089 : template<class... HN>
1090 : static auto
1091 11 : make_except_list(HN&&... hn) ->
1092 : handler_list_impl<sizeof...(HN)>
1093 : {
1094 : return handler_list_impl<sizeof...(HN)>(
1095 11 : 0, std::forward<HN>(hn)...);
1096 : }
1097 :
1098 : void append(layer& e,
1099 : http_proto::method verb,
1100 : handler_list const& handlers)
1101 : {
1102 : add_impl(e, verb, handlers);
1103 : }
1104 : };
1105 :
1106 : //-----------------------------------------------
1107 :
1108 : template<class Params>
1109 : class basic_router<Params>::
1110 : fluent_route
1111 : {
1112 : public:
1113 : fluent_route(fluent_route const&) = default;
1114 :
1115 : /** Add handlers that apply to all HTTP methods.
1116 :
1117 : This registers regular handlers that run for any request matching
1118 : the route's pattern, regardless of HTTP method. Handlers are
1119 : appended to the route's handler sequence and are invoked in
1120 : registration order whenever a preceding handler returns
1121 : @ref route::next. Error handlers and routers cannot be passed here.
1122 :
1123 : This function returns a @ref fluent_route, allowing additional
1124 : method registrations to be chained. For example:
1125 : @code
1126 : router.route( "/resource" )
1127 : .all( log_request )
1128 : .add( method::get, show_resource )
1129 : .add( method::post, update_resource );
1130 : @endcode
1131 :
1132 : @param h1 The first handler to add.
1133 :
1134 : @param hn Additional handlers to add, invoked after @p h1 in
1135 : registration order.
1136 :
1137 : @return A reference to `*this` for chained registrations.
1138 : */
1139 : template<class H1, class... HN>
1140 14 : auto all(
1141 : H1&& h1, HN&&... hn) ->
1142 : fluent_route
1143 : {
1144 : // If you get a compile error on this line it means that
1145 : // one or more of the provided types is not a valid handler.
1146 : // Error handlers and routers cannot be passed here.
1147 : BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value);
1148 14 : owner_.add_impl(e_, core::string_view(), make_handler_list(
1149 : std::forward<H1>(h1), std::forward<HN>(hn)...));
1150 14 : return *this;
1151 : }
1152 :
1153 : /** Add handlers for a specific HTTP method.
1154 :
1155 : This registers regular handlers for the given method on the
1156 : current route, participating in dispatch as described in the
1157 : @ref basic_router class documentation. Handlers are appended
1158 : to the route's handler sequence and invoked in registration
1159 : order whenever a preceding handler returns @ref route::next.
1160 : Error handlers and routers cannot be passed here.
1161 :
1162 : @param verb The HTTP method to match.
1163 :
1164 : @param h1 The first handler to add.
1165 :
1166 : @param hn Additional handlers to add, invoked after @p h1 in
1167 : registration order.
1168 :
1169 : @return A reference to `*this` for chained registrations.
1170 : */
1171 : template<class H1, class... HN>
1172 68 : auto add(
1173 : http_proto::method verb,
1174 : H1&& h1, HN&&... hn) ->
1175 : fluent_route
1176 : {
1177 : // If you get a compile error on this line it means that
1178 : // one or more of the provided types is not a valid handler.
1179 : // Error handlers and routers cannot be passed here.
1180 : BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value);
1181 68 : owner_.add_impl(e_, verb, make_handler_list(
1182 : std::forward<H1>(h1), std::forward<HN>(hn)...));
1183 67 : return *this;
1184 : }
1185 :
1186 : /** Add handlers for a method string.
1187 :
1188 : This registers regular handlers for the given HTTP method string
1189 : on the current route, participating in dispatch as described in
1190 : the @ref basic_router class documentation. This overload is
1191 : intended for methods not represented by @ref http_proto::method.
1192 : Handlers are appended to the route's handler sequence and invoked
1193 : in registration order whenever a preceding handler returns
1194 : @ref route::next. Error handlers and routers cannot be passed here.
1195 :
1196 : @param verb The HTTP method string to match.
1197 :
1198 : @param h1 The first handler to add.
1199 :
1200 : @param hn Additional handlers to add, invoked after @p h1 in
1201 : registration order.
1202 :
1203 : @return A reference to `*this` for chained registrations.
1204 : */
1205 : template<class H1, class... HN>
1206 9 : auto add(
1207 : core::string_view verb,
1208 : H1&& h1, HN&&... hn) ->
1209 : fluent_route
1210 : {
1211 : // If you get a compile error on this line it means that
1212 : // one or more of the provided types is not a valid handler.
1213 : // Error handlers and routers cannot be passed here.
1214 : BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value);
1215 9 : owner_.add_impl(e_, verb, make_handler_list(
1216 : std::forward<H1>(h1), std::forward<HN>(hn)...));
1217 9 : return *this;
1218 : }
1219 :
1220 : private:
1221 : friend class basic_router;
1222 63 : fluent_route(
1223 : basic_router& owner,
1224 : core::string_view pattern)
1225 63 : : e_(owner.new_layer(pattern))
1226 62 : , owner_(owner)
1227 : {
1228 62 : }
1229 :
1230 : layer& e_;
1231 : basic_router& owner_;
1232 : };
1233 :
1234 : } // http_proto
1235 : } // boost
1236 :
1237 : #endif
|