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