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/beast2
8 : //
9 :
10 : #ifndef BOOST_BEAST2_SERVER_BASIC_ROUTER_HPP
11 : #define BOOST_BEAST2_SERVER_BASIC_ROUTER_HPP
12 :
13 : #include <boost/beast2/detail/config.hpp>
14 : #include <boost/beast2/detail/call_traits.hpp>
15 : #include <boost/beast2/detail/type_traits.hpp>
16 : #include <boost/beast2/server/router_types.hpp>
17 : #include <boost/beast2/server/route_handler.hpp>
18 : #include <boost/http_proto/method.hpp>
19 : #include <boost/url/url_view.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 beast2 {
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 137 : 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 beast2::basic_router;
144 : using opt_flags = unsigned int;
145 :
146 : struct BOOST_SYMBOL_VISIBLE any_handler
147 : {
148 : BOOST_BEAST2_DECL virtual ~any_handler();
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_BEAST2_DECL ~any_router();
168 : BOOST_BEAST2_DECL any_router(opt_flags);
169 : BOOST_BEAST2_DECL any_router(any_router&&) noexcept;
170 : BOOST_BEAST2_DECL any_router(any_router const&) noexcept;
171 : BOOST_BEAST2_DECL any_router& operator=(any_router&&) noexcept;
172 : BOOST_BEAST2_DECL any_router& operator=(any_router const&) noexcept;
173 : BOOST_BEAST2_DECL std::size_t count() const noexcept;
174 : BOOST_BEAST2_DECL layer& new_layer(core::string_view pattern);
175 : BOOST_BEAST2_DECL void add_impl(core::string_view, handler_list const&);
176 : BOOST_BEAST2_DECL void add_impl(layer&,
177 : http_proto::method, handler_list const&);
178 : BOOST_BEAST2_DECL void add_impl(layer&,
179 : core::string_view, handler_list const&);
180 : BOOST_BEAST2_DECL route_result resume_impl(
181 : basic_request&, basic_response&, route_result const& ec) const;
182 : BOOST_BEAST2_DECL route_result dispatch_impl(http_proto::method,
183 : core::string_view, urls::url_view const&,
184 : basic_request&, basic_response&) const;
185 : BOOST_BEAST2_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 : The `basic_router` class provides a routing mechanism inspired by
199 : Express.js, adapted to idiomatic C++. It stores and dispatches route
200 : handlers based on HTTP method and path patterns, enabling modular
201 : request processing through middleware chains and method-specific routes.
202 :
203 : @par Core Concepts
204 :
205 : Like Express.js Router, `basic_router` allows you to define handlers
206 : for specific HTTP methods and URL patterns. However, there are key
207 : differences stemming from C++'s synchronous nature versus Node.js's
208 : asynchronous I/O model:
209 :
210 : @li In Express, `res.send()` initiates an asynchronous write and
211 : returns immediately. In `basic_router`, handlers prepare the
212 : response object completely and return @ref route::send, after
213 : which the framework sends the response.
214 :
215 : @li Express handlers implicitly end the request-response cycle by
216 : calling response methods. C++ handlers explicitly return
217 : @ref route_result values to control the dispatch flow.
218 :
219 : @li Express relies on callbacks and promises for asynchronous
220 : operations. `basic_router` supports detaching from the routing
221 : context for async operations via @ref route::detach.
222 :
223 : @par Router Structure and Shared Ownership
224 :
225 : Router objects are lightweight, shared references to their underlying
226 : routing state. Copies of a router obtained through construction,
227 : conversion, or assignment do not duplicate routes or handlers; all
228 : copies refer to the same data. This matches Express.js behavior where
229 : router instances can be shared and reused.
230 :
231 : @code
232 : router_type router;
233 : router.get("/users", get_users_handler);
234 :
235 : router_type router_copy = router; // shares same routes
236 : router_copy.get("/posts", get_posts_handler); // affects both routers
237 : @endcode
238 :
239 : @par Pattern Matching
240 :
241 : Path patterns support parameters and follow similar conventions to
242 : Express.js routes. Patterns undergo percent-decoding when handlers
243 : are mounted, so `"/x%2Fz"` is treated identically to `"/x/z"`.
244 :
245 : Unlike Express.js string patterns with special characters like `*`
246 : and `?`, `basic_router` uses simpler prefix matching for middleware
247 : and exact matching for routes. Future versions may expand pattern
248 : support.
249 :
250 : When pattern matching occurs, the matched portion becomes
251 : `req.base_path` and the remaining portion becomes `req.path`,
252 : analogous to Express's `req.baseUrl` and `req.path`.
253 :
254 : @code
255 : // Pattern: "/api"
256 : // Request: "/api/users/123"
257 : // During handler execution:
258 : // req.base_path == "/api"
259 : // req.path == "/users/123"
260 : @endcode
261 :
262 : @par Middleware with use()
263 :
264 : The @ref use function registers middleware handlers that run for
265 : requests matching a path prefix, similar to Express's `app.use()`.
266 : Middleware executes in registration order and can perform
267 : authentication, logging, header manipulation, or other cross-cutting
268 : concerns.
269 :
270 : Unlike routes, middleware uses prefix matching. A middleware mounted
271 : at `"/api"` will match `"/api"`, `"/api/"`, `"/api/users"`, and any
272 : other path beginning with `"/api"`.
273 :
274 : @code
275 : router.use("/api",
276 : [](Request& req, Response& res)
277 : {
278 : // Authentication middleware
279 : if (!is_authenticated(req))
280 : {
281 : res.status(http_proto::status::unauthorized);
282 : res.set_body("Authentication required");
283 : return route::send;
284 : }
285 : return route::next; // continue to next handler
286 : },
287 : [](Request& req, Response& res)
288 : {
289 : // Logging middleware
290 : log_request(req);
291 : return route::next;
292 : });
293 : @endcode
294 :
295 : Global middleware (applying to all requests) is registered with
296 : `use()` without a path or with `"/"`:
297 :
298 : @code
299 : router.use([](Request& req, Response& res)
300 : {
301 : res.message.set("X-Powered-By", "Beast2");
302 : return route::next;
303 : });
304 : @endcode
305 :
306 : @par Routes and HTTP Methods
307 :
308 : Routes match specific HTTP methods and exact paths. Unlike middleware,
309 : routes require complete path matches (unless configured otherwise via
310 : @ref router_options::strict). Routes are added via method-specific
311 : functions or the fluent @ref route interface.
312 :
313 : @code
314 : // Direct method registration
315 : router.get("/users/:id", show_user_handler);
316 : router.post("/users", create_user_handler);
317 : router.put("/users/:id", update_user_handler);
318 : router.delete("/users/:id", delete_user_handler);
319 :
320 : // Fluent interface for multiple methods on same route
321 : router.route("/status")
322 : .get([](Request& req, Response& res)
323 : {
324 : res.set_body("OK");
325 : return route::send;
326 : })
327 : .head([](Request& req, Response& res)
328 : {
329 : res.status(http_proto::status::ok);
330 : return route::send;
331 : });
332 :
333 : // Handler for all methods
334 : router.all("/debug", debug_handler);
335 : @endcode
336 :
337 : @par Handler Types and Signatures
338 :
339 : `basic_router` supports three handler types:
340 :
341 : @li **Regular handlers**: Process normal requests
342 : @code
343 : route_result handler( Req& req, Res& res )
344 : @endcode
345 :
346 : @li **Error handlers**: Handle error conditions
347 : @code
348 : route_result error_handler( Req& req, Res& res, system::error_code ec )
349 : @endcode
350 :
351 : @li **Nested routers**: Sub-routers for modular organization
352 : @code
353 : router.use("/api", api_router);
354 : @endcode
355 :
356 : @par Return Values and Control Flow
357 :
358 : Handlers control routing flow by returning @ref route_result values.
359 : This explicit control differs from Express.js, where calling response
360 : methods implicitly ends processing.
361 :
362 : @li @ref route::send - Response is ready; framework should send it
363 : @li @ref route::next - Continue to next matching handler
364 : @li @ref route::next_route - Skip remaining handlers in current route
365 : @li @ref route::complete - Response already sent externally
366 : @li @ref route::close - Close connection after response
367 : @li @ref route::detach - Transfer ownership of connection to handler
368 :
369 : Express.js comparison:
370 : @code
371 : // Express.js
372 : app.get('/users', (req, res) => {
373 : res.json({ users: [] }); // implicitly ends request
374 : });
375 :
376 : // basic_router equivalent
377 : router.get("/users", [](Request& req, Response& res)
378 : {
379 : res.status(http_proto::status::ok);
380 : res.set_body(R"({"users":[]})");
381 : return route::send; // explicitly indicate response is ready
382 : });
383 : @endcode
384 :
385 : The @ref route::next return value is analogous to Express's `next()`
386 : function:
387 :
388 : @code
389 : // Express.js
390 : app.use((req, res, next) => {
391 : console.log('logging');
392 : next(); // continue to next handler
393 : });
394 :
395 : // basic_router equivalent
396 : router.use([](Request& req, Response& res)
397 : {
398 : log("processing request");
399 : return route::next; // continue to next handler
400 : });
401 : @endcode
402 :
403 : @par Error Handling
404 :
405 : When a handler returns a failing error code (where
406 : `system::error_code::failed()` returns `true`), the router enters
407 : error-dispatching mode. In this mode, only error handlers are invoked,
408 : similar to Express.js error middleware.
409 :
410 : Error handlers have a three-parameter signature including the error
411 : code. They execute in registration order until one returns a value
412 : other than @ref route::next.
413 :
414 : @code
415 : router.use("/api",
416 : [](Request& req, Response& res)
417 : {
418 : if (!validate_request(req))
419 : return make_error_code(errc::invalid_argument);
420 : return route::next;
421 : });
422 :
423 : // Error handler (registered like middleware)
424 : router.use([](Request& req, Response& res, system::error_code ec)
425 : {
426 : res.status(http_proto::status::internal_server_error);
427 : res.set_body("Error: " + ec.message());
428 : return route::send;
429 : });
430 : @endcode
431 :
432 : Express.js comparison:
433 : @code
434 : // Express.js error middleware (4 parameters)
435 : app.use((err, req, res, next) => {
436 : res.status(500).send('Error: ' + err.message);
437 : });
438 : @endcode
439 :
440 : @par Execution Order
441 :
442 : Handlers execute in registration order, forming a chain. This matches
443 : Express.js behavior. Middleware registered first executes first;
444 : routes are evaluated in definition order.
445 :
446 : @code
447 : router.use(logging_middleware); // runs first
448 : router.use("/api", auth_middleware); // runs second for /api paths
449 : router.get("/api/users", get_users); // runs third for GET /api/users
450 : router.use(error_handler); // runs if errors occur
451 : @endcode
452 :
453 : @par Nested Routers
454 :
455 : Routers can be mounted within other routers, enabling modular
456 : application structure similar to Express.js Router:
457 :
458 : @code
459 : // Create a sub-router for user-related routes
460 : basic_router<Request, Response> users_router;
461 : users_router.get("/", list_users);
462 : users_router.post("/", create_user);
463 : users_router.get("/:id", show_user);
464 :
465 : // Mount it on the main router
466 : router.use("/users", users_router);
467 :
468 : // These patterns now work:
469 : // GET /users -> list_users
470 : // POST /users -> create_user
471 : // GET /users/123 -> show_user
472 : @endcode
473 :
474 : When a nested router processes a request, `req.base_path` reflects the
475 : cumulative mount path, similar to Express's `req.baseUrl`.
476 :
477 : @par Router Options
478 :
479 : The @ref router_options class controls matching behavior:
480 :
481 : @li **case_sensitive**: Whether path matching is case-sensitive
482 : (default: `false`, inherited from parent)
483 : @li **strict**: Whether trailing slashes are significant
484 : (default: `false`, inherited from parent)
485 : @li **merge_params**: Whether to inherit parent route parameters
486 : (default: `false`, never inherited)
487 :
488 : @code
489 : router_options opts;
490 : opts.case_sensitive(true).strict(true);
491 : basic_router<Request, Response> router(opts);
492 :
493 : // With strict=true:
494 : // Pattern "/api" matches "/api" but NOT "/api/"
495 : @endcode
496 :
497 : @par Asynchronous Operations and Detaching
498 :
499 : Express.js handlers can perform asynchronous I/O using callbacks,
500 : promises, or async/await. In `basic_router`, handlers are synchronous
501 : by default. For asynchronous operations, handlers can detach from the
502 : routing context by returning @ref route::detach, then resume later:
503 :
504 : @code
505 : router.get("/slow", [](Request& req, Response& res)
506 : {
507 : return res.detach([&req, &res](resumer resume_fn)
508 : {
509 : // Perform async operation (e.g., with Asio)
510 : async_database_query([resume_fn, &res](error_code ec, result r)
511 : {
512 : if (ec)
513 : {
514 : resume_fn(ec); // resume with error
515 : return;
516 : }
517 : res.set_body(r.to_json());
518 : resume_fn(route::send); // resume with success
519 : });
520 : });
521 : });
522 : @endcode
523 :
524 : After detaching, the handler must eventually call the @ref resumer
525 : function to continue routing. This is conceptually similar to Express's
526 : asynchronous model but requires explicit management.
527 :
528 : @par Complete Example
529 :
530 : @code
531 : #include <boost/beast2/server/basic_router.hpp>
532 : #include <boost/beast2/server/route_handler.hpp>
533 :
534 : using namespace boost::beast2;
535 :
536 : // Create the router
537 : basic_router<Request, Response> router;
538 :
539 : // Global middleware - runs for all requests
540 : router.use([](Request& req, Response& res)
541 : {
542 : log_request(req.url);
543 : return route::next;
544 : });
545 :
546 : // Authentication middleware for /admin routes
547 : router.use("/admin", [](Request& req, Response& res)
548 : {
549 : if (!check_admin_credentials(req))
550 : {
551 : res.status(http_proto::status::forbidden);
552 : res.set_body("Access denied");
553 : return route::send;
554 : }
555 : return route::next;
556 : });
557 :
558 : // Route handlers
559 : router.get("/", [](Request& req, Response& res)
560 : {
561 : res.status(http_proto::status::ok);
562 : res.set_body("Welcome!");
563 : return route::send;
564 : });
565 :
566 : router.get("/hello/:name", [](Request& req, Response& res)
567 : {
568 : // In future versions, req.params["name"] would be available
569 : res.status(http_proto::status::ok);
570 : res.set_body("Hello!");
571 : return route::send;
572 : });
573 :
574 : // Multiple handlers for one route
575 : router.route("/users/:id")
576 : .get([](Request& req, Response& res)
577 : {
578 : res.set_body(get_user_json(req));
579 : return route::send;
580 : })
581 : .put([](Request& req, Response& res)
582 : {
583 : update_user(req);
584 : res.status(http_proto::status::no_content);
585 : return route::send;
586 : })
587 : .delete([](Request& req, Response& res)
588 : {
589 : delete_user(req);
590 : res.status(http_proto::status::no_content);
591 : return route::send;
592 : });
593 :
594 : // Error handler - runs when handlers return failing error codes
595 : router.use([](Request& req, Response& res, system::error_code ec)
596 : {
597 : res.status(http_proto::status::internal_server_error);
598 : res.set_body("Internal error: " + ec.message());
599 : return route::send;
600 : });
601 :
602 : // Dispatch a request
603 : Request req;
604 : Response res;
605 : auto result = router.dispatch(
606 : http_proto::method::get,
607 : urls::parse_uri("/hello/world").value(),
608 : req, res);
609 :
610 : if (result == route::send)
611 : {
612 : // Send response to client
613 : send_response(res);
614 : }
615 : @endcode
616 :
617 : @par Thread Safety
618 : Member functions marked `const` such as @ref dispatch and @ref resume
619 : may be called concurrently on routers that refer to the same data.
620 : Modification of routers through calls to non-`const` member functions
621 : is not thread-safe and must not be performed concurrently with any
622 : other member function.
623 :
624 : @par Constraints
625 : `Req` must be publicly derived from @ref basic_request.
626 : `Res` must be publicly derived from @ref basic_response.
627 :
628 : @tparam Req The type of request object.
629 : @tparam Res The type of response object.
630 :
631 : @see router_options, route_result, route, basic_request, basic_response
632 : */
633 : template<class Req, class Res>
634 : class basic_router : public detail::any_router
635 : {
636 : // Req must be publicly derived from basic_request
637 : BOOST_CORE_STATIC_ASSERT(
638 : detail::derived_from<basic_request, Req>::value);
639 :
640 : // Res must be publicly derived from basic_response
641 : BOOST_CORE_STATIC_ASSERT(
642 : detail::derived_from<basic_response, Res>::value);
643 :
644 : // 0 = unrecognized
645 : // 1 = normal handler (Req&, Res&)
646 : // 2 = error handler (Req&, Res&, error_code)
647 : // 4 = basic_router<Req, Res>
648 :
649 : template<class T, class = void>
650 : struct handler_type
651 : : std::integral_constant<int, 0>
652 : {
653 : };
654 :
655 : // route_result( Req&, Res& ) const
656 : template<class T>
657 : struct handler_type<T, typename
658 : std::enable_if<std::is_convertible<
659 : decltype(std::declval<T const&>()(
660 : std::declval<Req&>(),
661 : std::declval<Res&>())),
662 : route_result>::value
663 : >::type> : std::integral_constant<int, 1> {};
664 :
665 : // route_result( Req&, Res&, system::error_code const& ) const
666 : template<class T>
667 : struct handler_type<T, typename
668 : std::enable_if<std::is_convertible<
669 : decltype(std::declval<T const&>()(
670 : std::declval<Req&>(),
671 : std::declval<Res&>(),
672 : std::declval<system::error_code const&>())),
673 : route_result>::value
674 : >::type> : std::integral_constant<int, 2> {};
675 :
676 : // basic_router<Req, Res>
677 : template<class T>
678 : struct handler_type<T, typename
679 : std::enable_if<
680 : std::is_base_of<any_router, T>::value &&
681 : std::is_convertible<T const volatile*,
682 : any_router const volatile*>::value &&
683 : std::is_constructible<T, basic_router<Req, Res>>::value
684 : >::type> : std::integral_constant<int, 4> {};
685 :
686 : template<std::size_t Mask, class... Ts>
687 : struct handler_check : std::true_type {};
688 :
689 : template<std::size_t Mask, class T0, class... Ts>
690 : struct handler_check<Mask, T0, Ts...>
691 : : std::conditional<
692 : ( (handler_type<T0>::value & Mask) != 0 ),
693 : handler_check<Mask, Ts...>,
694 : std::false_type
695 : >::type {};
696 :
697 : public:
698 : /** The type of request object used in handlers
699 : */
700 : using request_type = Req;
701 :
702 : /** The type of response object used in handlers
703 : */
704 : using response_type = Res;
705 :
706 : /** A fluent interface for defining handlers on a specific route.
707 :
708 : This type represents a single route within the router and
709 : provides a chainable API for registering handlers associated
710 : with particular HTTP methods or for all methods collectively.
711 :
712 : Typical usage registers one or more handlers for a route:
713 : @code
714 : router.route("/users/:id")
715 : .get(show_user)
716 : .put(update_user)
717 : .all(log_access);
718 : @endcode
719 :
720 : Each call appends handlers in registration order.
721 : */
722 : class fluent_route;
723 :
724 : /** Constructor
725 :
726 : Creates an empty router with the specified configuration.
727 : Routers constructed with default options inherit the values
728 : of @ref router_options::case_sensitive and
729 : @ref router_options::strict from the parent router, or default
730 : to `false` if there is no parent. The value of
731 : @ref router_options::merge_params defaults to `false` and
732 : is never inherited.
733 :
734 : @param options The configuration options to use.
735 : */
736 : explicit
737 137 : basic_router(router_options options = {})
738 137 : : any_router(options.v_)
739 : {
740 137 : }
741 :
742 : /** Construct a router from another router with compatible types.
743 :
744 : This constructs a router that shares the same underlying routing
745 : state as another router whose request and response types are base
746 : classes of `Req` and `Res`, respectively.
747 :
748 : The resulting router participates in shared ownership of the
749 : implementation; copying the router does not duplicate routes or
750 : handlers, and changes visible through one router are visible
751 : through all routers that share the same underlying state.
752 :
753 : @par Constraints
754 : `Req` must be derived from `OtherReq`, and `Res` must be
755 : derived from `OtherRes`.
756 :
757 : @tparam OtherReq The request type of the source router.
758 : @tparam OtherRes The response type of the source router.
759 : @param other The router to copy.
760 : */
761 : template<
762 : class OtherReq, class OtherRes,
763 : class = typename std::enable_if<
764 : detail::derived_from<Req, OtherReq>::value &&
765 : detail::derived_from<Res, OtherRes>::value>::type
766 : >
767 : basic_router(
768 : basic_router<OtherReq, OtherRes> const& other)
769 : : any_router(other)
770 : {
771 : }
772 :
773 : /** Add one or more middleware handlers for a path prefix.
774 :
775 : Each handler registered with this function participates in the
776 : routing and error-dispatch process for requests whose path begins
777 : with the specified prefix, as described in the @ref basic_router
778 : class documentation. Handlers execute in the order they are added
779 : and may return @ref route::next to transfer control to the
780 : subsequent handler in the chain.
781 :
782 : @par Example
783 : @code
784 : router.use("/api",
785 : [](Request& req, Response& res)
786 : {
787 : if (!authenticate(req))
788 : {
789 : res.status(401);
790 : res.set_body("Unauthorized");
791 : return route::send;
792 : }
793 : return route::next;
794 : },
795 : [](Request&, Response& res)
796 : {
797 : res.set_header("X-Powered-By", "MyServer");
798 : return route::next;
799 : });
800 : @endcode
801 :
802 : @par Preconditions
803 : @p `pattern` must be a valid path prefix; it may be empty to
804 : indicate the root scope.
805 :
806 : @param pattern The pattern to match.
807 : @param h1 The first handler to add.
808 : @param hn Additional handlers to add, invoked after @p h1 in
809 : registration order.
810 : */
811 : template<class H1, class... HN>
812 186 : void use(
813 : core::string_view pattern,
814 : H1&& h1, HN... hn)
815 : {
816 : // If you get a compile error on this line it means that
817 : // one or more of the provided types is not a valid handler,
818 : // error handler, or router.
819 : BOOST_CORE_STATIC_ASSERT(handler_check<7, H1, HN...>::value);
820 186 : add_impl(pattern, make_handler_list(
821 : std::forward<H1>(h1), std::forward<HN>(hn)...));
822 186 : }
823 :
824 : /** Add one or more global middleware handlers.
825 :
826 : Each handler registered with this function participates in the
827 : routing and error-dispatch process as described in the
828 : @ref basic_router class documentation. Handlers execute in the
829 : order they are added and may return @ref route::next to transfer
830 : control to the next handler in the chain.
831 :
832 : This is equivalent to writing:
833 : @code
834 : use( "/", h1, hn... );
835 : @endcode
836 :
837 : @par Example
838 : @code
839 : router.use(
840 : [](Request&, Response& res)
841 : {
842 : res.message.erase("X-Powered-By");
843 : return route::next;
844 : });
845 : @endcode
846 :
847 : @par Constraints
848 : @li `h1` must not be convertible to @ref core::string_view.
849 :
850 : @param h1 The first handler to add.
851 : @param hn Additional handlers to add, invoked after @p h1 in
852 : registration order.
853 : */
854 : template<class H1, class... HN
855 : , class = typename std::enable_if<
856 : ! std::is_convertible<H1, core::string_view>::value>::type>
857 102 : void use(H1&& h1, HN&&... hn)
858 : {
859 : // If you get a compile error on this line it means that
860 : // one or more of the provided types is not a valid handler,
861 : // error handler, or router.
862 : BOOST_CORE_STATIC_ASSERT(handler_check<7, H1, HN...>::value);
863 102 : use(core::string_view(),
864 : std::forward<H1>(h1), std::forward<HN>(hn)...);
865 102 : }
866 :
867 : /** Add handlers for all HTTP methods matching a path pattern.
868 :
869 : This registers regular handlers for the specified path pattern,
870 : participating in dispatch as described in the @ref basic_router
871 : class documentation. Handlers run when the route matches,
872 : regardless of HTTP method, and execute in registration order.
873 : Error handlers and routers cannot be passed here. A new route
874 : object is created even if the pattern already exists.
875 :
876 : @code
877 : router.route("/status")
878 : .head(check_headers)
879 : .get(send_status)
880 : .all(log_access);
881 : @endcode
882 :
883 : @par Preconditions
884 : @p `pattern` must be a valid path pattern; it must not be empty.
885 :
886 : @param pattern The path pattern to match.
887 : @param h1 The first handler to add.
888 : @param hn Additional handlers to add, invoked after @p h1 in
889 : registration order.
890 : */
891 : template<class H1, class... HN>
892 12 : void all(
893 : core::string_view pattern,
894 : H1&& h1, HN&&... hn)
895 : {
896 : // If you get a compile error on this line it means that
897 : // one or more of the provided types is not a valid handler.
898 : // Error handlers and routers cannot be passed here.
899 : BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value);
900 12 : this->route(pattern).all(
901 : std::forward<H1>(h1), std::forward<HN>(hn)...);
902 11 : }
903 :
904 : /** Add one or more route handlers for a method and pattern.
905 :
906 : This registers regular handlers for the specified HTTP verb and
907 : path pattern, participating in dispatch as described in the
908 : @ref basic_router class documentation. Error handlers and
909 : routers cannot be passed here.
910 :
911 : @param verb The known HTTP method to match.
912 : @param pattern The path pattern to match.
913 : @param h1 The first handler to add.
914 : @param hn Additional handlers to add, invoked after @p h1 in
915 : registration order.
916 : */
917 : template<class H1, class... HN>
918 21 : void add(
919 : http_proto::method verb,
920 : core::string_view pattern,
921 : H1&& h1, HN&&... hn)
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 21 : this->route(pattern).add(verb,
928 : std::forward<H1>(h1), std::forward<HN>(hn)...);
929 21 : }
930 :
931 : /** Add one or more route handlers for a method and pattern.
932 :
933 : This registers regular handlers for the specified HTTP verb and
934 : path pattern, participating in dispatch as described in the
935 : @ref basic_router class documentation. Error handlers and
936 : routers cannot be passed here.
937 :
938 : @param verb The HTTP method string to match.
939 : @param pattern The path pattern to match.
940 : @param h1 The first handler to add.
941 : @param hn Additional handlers to add, invoked after @p h1 in
942 : registration order.
943 : */
944 : template<class H1, class... HN>
945 2 : void add(
946 : core::string_view verb,
947 : core::string_view pattern,
948 : H1&& h1, HN&&... hn)
949 : {
950 : // If you get a compile error on this line it means that
951 : // one or more of the provided types is not a valid handler.
952 : // Error handlers and routers cannot be passed here.
953 : BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value);
954 2 : this->route(pattern).add(verb,
955 : std::forward<H1>(h1), std::forward<HN>(hn)...);
956 2 : }
957 :
958 : /** Return a fluent route for the specified path pattern.
959 :
960 : Adds a new route to the router for the given pattern.
961 : A new route object is always created, even if another
962 : route with the same pattern already exists. The returned
963 : @ref fluent_route reference allows method-specific handler
964 : registration (such as GET or POST) or catch-all handlers
965 : with @ref fluent_route::all.
966 :
967 : @param pattern The path expression to match against request
968 : targets. This may include parameters or wildcards following
969 : the router's pattern syntax. May not be empty.
970 : @return A fluent route interface for chaining handler registrations.
971 : */
972 : auto
973 63 : route(
974 : core::string_view pattern) -> fluent_route
975 : {
976 63 : return fluent_route(*this, pattern);
977 : }
978 :
979 : //--------------------------------------------
980 :
981 : /** Dispatch a request to the appropriate handler.
982 :
983 : This runs the routing and error-dispatch logic for the given HTTP
984 : method and target URL, as described in the @ref basic_router class
985 : documentation.
986 :
987 : @par Thread Safety
988 : This function may be called concurrently on the same object along
989 : with other `const` member functions. Each concurrent invocation
990 : must use distinct request and response objects.
991 :
992 : @param verb The HTTP method to match. This must not be
993 : @ref http_proto::method::unknown.
994 : @param url The full request target used for route matching.
995 : @param req The request to pass to handlers.
996 : @param res The response to pass to handlers.
997 : @return The @ref route_result describing how routing completed.
998 : @throws std::invalid_argument If @p verb is
999 : @ref http_proto::method::unknown.
1000 : */
1001 : auto
1002 142 : dispatch(
1003 : http_proto::method verb,
1004 : urls::url_view const& url,
1005 : Req& req, Res& res) const ->
1006 : route_result
1007 : {
1008 142 : if(verb == http_proto::method::unknown)
1009 1 : detail::throw_invalid_argument();
1010 279 : return dispatch_impl(verb,
1011 276 : core::string_view(), url, req, res);
1012 : }
1013 :
1014 : /** Dispatch a request to the appropriate handler using a method string.
1015 :
1016 : This runs the routing and error-dispatch logic for the given HTTP
1017 : method string and target URL, as described in the @ref basic_router
1018 : class documentation. This overload is intended for method tokens
1019 : that are not represented by @ref http_proto::method.
1020 :
1021 : @par Thread Safety
1022 : This function may be called concurrently on the same object along
1023 : with other `const` member functions. Each concurrent invocation
1024 : must use distinct request and response objects.
1025 :
1026 : @param verb The HTTP method string to match. This must not be empty.
1027 : @param url The full request target used for route matching.
1028 : @param req The request to pass to handlers.
1029 : @param res The response to pass to handlers.
1030 : @return The @ref route_result describing how routing completed.
1031 : @throws std::invalid_argument If @p verb is empty.
1032 : */
1033 : auto
1034 34 : dispatch(
1035 : core::string_view verb,
1036 : urls::url_view const& url,
1037 : Req& req, Res& res) ->
1038 : route_result
1039 : {
1040 : // verb cannot be empty
1041 34 : if(verb.empty())
1042 1 : detail::throw_invalid_argument();
1043 33 : return dispatch_impl(
1044 : http_proto::method::unknown,
1045 33 : verb, url, req, res);
1046 : }
1047 :
1048 : /** Resume dispatch after a detached handler.
1049 :
1050 : This continues routing after a previous call to @ref dispatch
1051 : returned @ref route::detach. It recreates the routing state and
1052 : resumes as if the handler that detached had instead returned
1053 : the specified @p ec from its body. The regular routing and
1054 : error-dispatch logic then proceeds as described in the
1055 : @ref basic_router class documentation. For example, if @p ec is
1056 : @ref route::next, the next matching handlers are invoked.
1057 :
1058 : @par Thread Safety
1059 : This function may be called concurrently on the same object along
1060 : with other `const` member functions. Each concurrent invocation
1061 : must use distinct request and response objects.
1062 :
1063 : @param req The request to pass to handlers.
1064 : @param res The response to pass to handlers.
1065 : @param rv The @ref route_result to resume with, as if returned
1066 : by the detached handler.
1067 : @return The @ref route_result describing how routing completed.
1068 : */
1069 : auto
1070 9 : resume(
1071 : Req& req, Res& res,
1072 : route_result const& rv) const ->
1073 : route_result
1074 : {
1075 9 : return resume_impl(req, res, rv);
1076 : }
1077 :
1078 : private:
1079 : // wrapper for route handlers
1080 : template<class H>
1081 : struct handler_impl : any_handler
1082 : {
1083 : typename std::decay<H>::type h;
1084 :
1085 : template<class... Args>
1086 330 : explicit handler_impl(Args&&... args)
1087 330 : : h(std::forward<Args>(args)...)
1088 : {
1089 330 : }
1090 :
1091 : std::size_t
1092 141 : count() const noexcept override
1093 : {
1094 282 : return count(
1095 141 : handler_type<decltype(h)>{});
1096 : }
1097 :
1098 : route_result
1099 261 : invoke(
1100 : basic_request& req,
1101 : basic_response& res) const override
1102 : {
1103 522 : return invoke(
1104 : static_cast<Req&>(req),
1105 : static_cast<Res&>(res),
1106 353 : handler_type<decltype(h)>{});
1107 : }
1108 :
1109 : private:
1110 : std::size_t count(
1111 : std::integral_constant<int, 0>) = delete;
1112 :
1113 131 : std::size_t count(
1114 : std::integral_constant<int, 1>) const noexcept
1115 : {
1116 131 : return 1;
1117 : }
1118 :
1119 6 : std::size_t count(
1120 : std::integral_constant<int, 2>) const noexcept
1121 : {
1122 6 : return 1;
1123 : }
1124 :
1125 4 : std::size_t count(
1126 : std::integral_constant<int, 4>) const noexcept
1127 : {
1128 4 : return 1 + h.count();
1129 : }
1130 :
1131 : route_result invoke(Req&, Res&,
1132 : std::integral_constant<int, 0>) const = delete;
1133 :
1134 : // (Req, Res)
1135 198 : route_result invoke(Req& req, Res& res,
1136 : std::integral_constant<int, 1>) const
1137 : {
1138 198 : auto const& ec = static_cast<
1139 : basic_response const&>(res).ec_;
1140 198 : if(ec.failed())
1141 12 : return beast2::route::next;
1142 : // avoid racing on res.resume_
1143 186 : res.resume_ = res.pos_;
1144 186 : auto rv = h(req, res);
1145 186 : if(rv == beast2::route::detach)
1146 12 : return rv;
1147 174 : res.resume_ = 0; // revert
1148 174 : return rv;
1149 : }
1150 :
1151 : // (Req&, Res&, error_code)
1152 : route_result
1153 46 : invoke(Req& req, Res& res,
1154 : std::integral_constant<int, 2>) const
1155 : {
1156 46 : auto const& ec = static_cast<
1157 : basic_response const&>(res).ec_;
1158 46 : if(! ec.failed())
1159 8 : return beast2::route::next;
1160 : // avoid racing on res.resume_
1161 38 : res.resume_ = res.pos_;
1162 38 : auto rv = h(req, res, ec);
1163 38 : if(rv == beast2::route::detach)
1164 0 : return rv;
1165 38 : res.resume_ = 0; // revert
1166 38 : return rv;
1167 : }
1168 :
1169 : // any_router
1170 17 : route_result invoke(Req& req, Res& res,
1171 : std::integral_constant<int, 4>) const
1172 : {
1173 17 : auto const& ec = static_cast<
1174 : basic_response const&>(res).ec_;
1175 17 : if(! ec.failed())
1176 16 : return h.dispatch_impl(req, res);
1177 1 : return beast2::route::next;
1178 : }
1179 : };
1180 :
1181 : template<std::size_t N>
1182 : struct handler_list_impl : handler_list
1183 : {
1184 : template<class... HN>
1185 277 : explicit handler_list_impl(HN&&... hn)
1186 0 : {
1187 277 : n = sizeof...(HN);
1188 277 : p = v;
1189 277 : assign<0>(std::forward<HN>(hn)...);
1190 277 : }
1191 :
1192 : private:
1193 : template<std::size_t I, class H1, class... HN>
1194 330 : void assign(H1&& h1, HN&&... hn)
1195 : {
1196 330 : v[I] = handler_ptr(new handler_impl<H1>(
1197 : std::forward<H1>(h1)));
1198 330 : assign<I+1>(std::forward<HN>(hn)...);
1199 330 : }
1200 :
1201 : template<std::size_t>
1202 277 : void assign()
1203 : {
1204 277 : }
1205 :
1206 : handler_ptr v[N];
1207 : };
1208 :
1209 : template<class... HN>
1210 : static auto
1211 277 : make_handler_list(HN&&... hn) ->
1212 : handler_list_impl<sizeof...(HN)>
1213 : {
1214 : return handler_list_impl<sizeof...(HN)>(
1215 277 : std::forward<HN>(hn)...);
1216 : }
1217 :
1218 : void append(layer& e,
1219 : http_proto::method verb,
1220 : handler_list const& handlers)
1221 : {
1222 : add_impl(e, verb, handlers);
1223 : }
1224 : };
1225 :
1226 : //-----------------------------------------------
1227 :
1228 : template<class Req, class Res>
1229 : class basic_router<Req, Res>::
1230 : fluent_route
1231 : {
1232 : public:
1233 : fluent_route(fluent_route const&) = default;
1234 :
1235 : /** Add handlers that apply to all HTTP methods.
1236 :
1237 : This registers regular handlers that run for any request matching
1238 : the route's pattern, regardless of HTTP method. Handlers are
1239 : appended to the route's handler sequence and are invoked in
1240 : registration order whenever a preceding handler returns
1241 : @ref route::next. Error handlers and routers cannot be passed here.
1242 :
1243 : This function returns a @ref fluent_route, allowing additional
1244 : method registrations to be chained. For example:
1245 : @code
1246 : router.route("/resource")
1247 : .all(log_request)
1248 : .get(show_resource)
1249 : .post(update_resource);
1250 : @endcode
1251 :
1252 : @param h1 The first handler to add.
1253 : @param hn Additional handlers to add, invoked after @p h1 in
1254 : registration order.
1255 : @return The @ref fluent_route for further chained registrations.
1256 : */
1257 : template<class H1, class... HN>
1258 14 : auto all(
1259 : H1&& h1, HN&&... hn) ->
1260 : fluent_route
1261 : {
1262 : // If you get a compile error on this line it means that
1263 : // one or more of the provided types is not a valid handler.
1264 : // Error handlers and routers cannot be passed here.
1265 : BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value);
1266 14 : owner_.add_impl(e_, core::string_view(), make_handler_list(
1267 : std::forward<H1>(h1), std::forward<HN>(hn)...));
1268 14 : return *this;
1269 : }
1270 :
1271 : /** Add handlers for a specific HTTP method.
1272 :
1273 : This registers regular handlers for the given method on the
1274 : current route, participating in dispatch as described in the
1275 : @ref basic_router class documentation. Handlers are appended
1276 : to the route's handler sequence and invoked in registration
1277 : order whenever a preceding handler returns @ref route::next.
1278 : Error handlers and routers cannot be passed here.
1279 :
1280 : @param verb The HTTP method to match.
1281 : @param h1 The first handler to add.
1282 : @param hn Additional handlers to add, invoked after @p h1 in
1283 : registration order.
1284 : @return The @ref fluent_route for further chained registrations.
1285 : */
1286 : template<class H1, class... HN>
1287 68 : auto add(
1288 : http_proto::method verb,
1289 : H1&& h1, HN&&... hn) ->
1290 : fluent_route
1291 : {
1292 : // If you get a compile error on this line it means that
1293 : // one or more of the provided types is not a valid handler.
1294 : // Error handlers and routers cannot be passed here.
1295 : BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value);
1296 68 : owner_.add_impl(e_, verb, make_handler_list(
1297 : std::forward<H1>(h1), std::forward<HN>(hn)...));
1298 67 : return *this;
1299 : }
1300 :
1301 : /** Add handlers for a method name.
1302 :
1303 : This registers regular handlers for the given HTTP method string
1304 : on the current route, participating in dispatch as described in
1305 : the @ref basic_router class documentation. This overload is
1306 : intended for methods not represented by @ref http_proto::method.
1307 : Handlers are appended to the route's handler sequence and invoked
1308 : in registration order whenever a preceding handler returns
1309 : @ref route::next.
1310 :
1311 : @par Constraints
1312 : @li Each handler must be a regular handler; error handlers and
1313 : routers cannot be passed.
1314 :
1315 : @param verb The HTTP method string to match.
1316 : @param h1 The first handler to add.
1317 : @param hn Additional handlers to add, invoked after @p h1 in
1318 : registration order.
1319 : @return The @ref fluent_route for further chained registrations.
1320 : */
1321 : template<class H1, class... HN>
1322 9 : auto add(
1323 : core::string_view verb,
1324 : H1&& h1, HN&&... hn) ->
1325 : fluent_route
1326 : {
1327 : // If you get a compile error on this line it means that
1328 : // one or more of the provided types is not a valid handler.
1329 : // Error handlers and routers cannot be passed here.
1330 : BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value);
1331 9 : owner_.add_impl(e_, verb, make_handler_list(
1332 : std::forward<H1>(h1), std::forward<HN>(hn)...));
1333 9 : return *this;
1334 : }
1335 :
1336 : private:
1337 : friend class basic_router;
1338 63 : fluent_route(
1339 : basic_router& owner,
1340 : core::string_view pattern)
1341 63 : : e_(owner.new_layer(pattern))
1342 62 : , owner_(owner)
1343 : {
1344 62 : }
1345 :
1346 : layer& e_;
1347 : basic_router& owner_;
1348 : };
1349 :
1350 : } // beast2
1351 : } // boost
1352 :
1353 : #endif
|