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