GCC Code Coverage Report


Directory: ./
File: libs/beast2/include/boost/beast2/server/basic_router.hpp
Date: 2025-11-30 14:55:10
Exec Total Coverage
Lines: 110 113 97.3%
Functions: 207 222 93.2%
Branches: 49 53 92.5%

Line Branch Exec Source
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/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
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
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2 times.
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/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if(value)
119 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 371 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
3/3
✓ Branch 2 taken 159 times.
✓ Branch 11 taken 1 times.
✓ Branch 5 taken 3 times.
371 add_impl(pattern, make_handler_list(
821 std::forward<H1>(h1), std::forward<HN>(hn)...));
822 371 }
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 202 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
6/6
✓ Branch 9 taken 2 times.
✓ Branch 7 taken 4 times.
✓ Branch 5 taken 8 times.
✓ Branch 3 taken 86 times.
✓ Branch 17 taken 1 times.
✓ Branch 15 taken 1 times.
202 use(core::string_view(),
864 std::forward<H1>(h1), std::forward<HN>(hn)...);
865 202 }
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
2/2
✓ Branch 1 taken 11 times.
✓ Branch 5 taken 11 times.
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 42 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
2/2
✓ Branch 1 taken 21 times.
✓ Branch 5 taken 19 times.
42 this->route(pattern).add(verb,
928 std::forward<H1>(h1), std::forward<HN>(hn)...);
929 42 }
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/2
✓ Branch 1 taken 2 times.
✓ Branch 5 taken 2 times.
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
1/1
✓ Branch 1 taken 62 times.
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
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 141 times.
142 if(verb == http_proto::method::unknown)
1009 1 detail::throw_invalid_argument();
1010
1/1
✓ Branch 2 taken 138 times.
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
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 33 times.
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 659 explicit handler_impl(Args&&... args)
1087 659 : h(std::forward<Args>(args)...)
1088 {
1089 659 }
1090
1091 std::size_t
1092 282 count() const noexcept override
1093 {
1094 564 return count(
1095 282 handler_type<decltype(h)>{});
1096 }
1097
1098 route_result
1099 521 invoke(
1100 basic_request& req,
1101 basic_response& res) const override
1102 {
1103
1/1
✓ Branch 1 taken 92 times.
1042 return invoke(
1104 static_cast<Req&>(req),
1105 static_cast<Res&>(res),
1106 704 handler_type<decltype(h)>{});
1107 }
1108
1109 private:
1110 std::size_t count(
1111 std::integral_constant<int, 0>) = delete;
1112
1113 262 std::size_t count(
1114 std::integral_constant<int, 1>) const noexcept
1115 {
1116 262 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 395 route_result invoke(Req& req, Res& res,
1136 std::integral_constant<int, 1>) const
1137 {
1138 395 auto const& ec = static_cast<
1139 basic_response const&>(res).ec_;
1140
2/2
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 186 times.
395 if(ec.failed())
1141 24 return beast2::route::next;
1142 // avoid racing on res.resume_
1143 371 res.resume_ = res.pos_;
1144
1/1
✓ Branch 1 taken 29 times.
371 auto rv = h(req, res);
1145
3/4
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 160 times.
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
371 if(rv == beast2::route::detach)
1146 24 return rv;
1147 347 res.resume_ = 0; // revert
1148 347 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
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 38 times.
46 if(! ec.failed())
1159 8 return beast2::route::next;
1160 // avoid racing on res.resume_
1161 38 res.resume_ = res.pos_;
1162
1/1
✓ Branch 1 taken 38 times.
38 auto rv = h(req, res, ec);
1163
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 38 times.
38 if(rv == beast2::route::detach)
1164 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
2/2
✓ Branch 1 taken 16 times.
✓ Branch 2 taken 1 times.
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 553 explicit handler_list_impl(HN&&... hn)
1186 {
1187 553 n = sizeof...(HN);
1188 553 p = v;
1189
2/2
✓ Branch 2 taken 247 times.
✓ Branch 8 taken 1 times.
553 assign<0>(std::forward<HN>(hn)...);
1190 553 }
1191
1192 private:
1193 template<std::size_t I, class H1, class... HN>
1194 659 void assign(H1&& h1, HN&&... hn)
1195 {
1196
1/1
✓ Branch 1 taken 330 times.
659 v[I] = handler_ptr(new handler_impl<H1>(
1197 std::forward<H1>(h1)));
1198 659 assign<I+1>(std::forward<HN>(hn)...);
1199 659 }
1200
1201 template<std::size_t>
1202 553 void assign()
1203 {
1204 553 }
1205
1206 handler_ptr v[N];
1207 };
1208
1209 template<class... HN>
1210 static auto
1211 553 make_handler_list(HN&&... hn) ->
1212 handler_list_impl<sizeof...(HN)>
1213 {
1214 return handler_list_impl<sizeof...(HN)>(
1215 553 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
2/2
✓ Branch 2 taken 14 times.
✓ Branch 6 taken 14 times.
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 135 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
4/4
✓ Branch 2 taken 65 times.
✓ Branch 5 taken 64 times.
✓ Branch 3 taken 1 times.
✓ Branch 6 taken 1 times.
135 owner_.add_impl(e_, verb, make_handler_list(
1297 std::forward<H1>(h1), std::forward<HN>(hn)...));
1298 133 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
2/2
✓ Branch 2 taken 9 times.
✓ Branch 5 taken 9 times.
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
1354