include/boost/http/server/route_handler.hpp

92.9% Lines (26/0/28) 91.7% List of functions (11/0/12)
route_handler.hpp
f(x) Functions (12)
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.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
8 //
9
10 #ifndef BOOST_HTTP_SERVER_ROUTE_HANDLER_HPP
11 #define BOOST_HTTP_SERVER_ROUTE_HANDLER_HPP
12
13 #include <boost/http/detail/config.hpp>
14 #include <boost/http/method.hpp>
15 #include <boost/http/detail/except.hpp>
16 #include <boost/http/datastore.hpp>
17 #include <boost/http/request.hpp>
18 #include <boost/http/response.hpp>
19 #include <boost/core/detail/string_view.hpp>
20 #include <boost/capy/buffers.hpp>
21 #include <boost/capy/buffers/make_buffer.hpp>
22 #include <boost/capy/io_result.hpp>
23 #include <boost/capy/io_task.hpp>
24 #include <boost/capy/task.hpp>
25 #include <boost/capy/write.hpp>
26 #include <boost/http/io/any_buffer_source.hpp>
27 #include <boost/http/io/any_buffer_sink.hpp>
28 #include <boost/url/url_view.hpp>
29 #include <system_error>
30 #include <concepts>
31 #include <exception>
32 #include <memory>
33 #include <span>
34 #include <string>
35 #include <type_traits>
36 #include <utility>
37 #include <vector>
38
39 namespace boost {
40 namespace http {
41
42 /** Directive values for route handler results.
43
44 These values indicate how the router should proceed
45 after a handler completes. Handlers return one of
46 the predefined constants (@ref route_done, @ref route_next,
47 @ref route_next_route, @ref route_close) or an error code.
48
49 @see route_result, route_task
50 */
51 enum class route_what
52 {
53 /// Handler completed successfully, response was sent
54 done,
55
56 /// Handler declined, try next handler in the route
57 next,
58
59 /// Handler declined, skip to next matching route
60 next_route,
61
62 /// Handler requests connection closure
63 close,
64
65 /// Handler encountered an error
66 error
67 };
68
69 //------------------------------------------------
70
71 /** The result type returned by route handlers.
72
73 This class represents the outcome of a route handler.
74 Handlers return this type to indicate how the router
75 should proceed. Construct from a directive constant
76 or an error code:
77
78 @code
79 route_task my_handler(route_params& p)
80 {
81 if(! authorized(p))
82 co_return route_next; // try next handler
83
84 if(auto ec = process(p); ec)
85 co_return ec; // return error
86
87 co_return route_done; // success
88 }
89 @endcode
90
91 @par Checking Results
92
93 Use @ref what() to determine the directive, and
94 @ref error() to retrieve any error code:
95
96 @code
97 route_result rv = co_await handler(p);
98 if(rv.what() == route_what::error)
99 handle_error(rv.error());
100 @endcode
101
102 @see route_task, route_what, route_done, route_next
103 */
104 class BOOST_HTTP_DECL
105 route_result
106 {
107 std::error_code ec_;
108
109 template<route_what T>
110 struct what_t {};
111
112 route_result(std::error_code ec);
113 void set(route_what w);
114
115 public:
116 227x route_result() = default;
117
118 /** Construct from a directive constant.
119
120 This constructor allows implicit conversion from
121 the predefined constants (@ref route_done, @ref route_next,
122 @ref route_next_route, @ref route_close).
123
124 @code
125 route_task handler(route_params& p)
126 {
127 co_return route_done; // implicitly converts
128 }
129 @endcode
130 */
131 template<route_what W>
132 132x route_result(what_t<W>)
133 132x {
134 static_assert(W != route_what::error);
135 132x set(W);
136 132x }
137
138 /** Return the directive for this result.
139
140 Call this to determine how the router should proceed:
141
142 @code
143 route_result rv = co_await handler(p);
144 switch(rv.what())
145 {
146 case route_what::done:
147 // response sent, done with request
148 break;
149 case route_what::next:
150 // try next handler
151 break;
152 case route_what::error:
153 log_error(rv.error());
154 break;
155 }
156 @endcode
157
158 @return The directive value.
159 */
160 auto
161 what() const noexcept ->
162 route_what;
163
164 /** Return the error code, if any.
165
166 If @ref what() returns `route_what::error`, this
167 returns the underlying error code. Otherwise returns
168 a default-constructed (non-failing) error code.
169
170 @return The error code, or a non-failing code.
171 */
172 auto
173 error() const noexcept ->
174 std::error_code;
175
176 /** Return true if the result indicates an error.
177
178 @return `true` if @ref what() equals `route_what::error`.
179 */
180 1x bool failed() const noexcept
181 {
182 1x return what() == route_what::error;
183 }
184
185 static constexpr route_result::what_t<route_what::done> route_done{};
186 static constexpr route_result::what_t<route_what::next> route_next{};
187 static constexpr route_result::what_t<route_what::next_route> route_next_route{};
188 static constexpr route_result::what_t<route_what::close> route_close{};
189 friend route_result route_error(std::error_code ec) noexcept;
190
191 template<class E>
192 friend auto route_error(E e) noexcept ->
193 std::enable_if_t<
194 std::is_error_code_enum<E>::value,
195 route_result>;
196 };
197
198 //------------------------------------------------
199
200 /** Handler completed successfully.
201
202 Return this from a handler to indicate the response
203 was sent and the request is complete:
204
205 @code
206 route_task handler(route_params& p)
207 {
208 p.res.set(field::content_type, "text/plain");
209 co_await p.send("Hello, World!");
210 co_return route_done;
211 }
212 @endcode
213 */
214 inline constexpr decltype(auto) route_done = route_result::route_done;
215
216 /** Handler declined, try next handler.
217
218 Return this from a handler to decline processing
219 and allow the next handler in the route to try:
220
221 @code
222 route_task auth_handler(route_params& p)
223 {
224 if(! p.req.exists(field::authorization))
225 co_return route_next; // let another handler try
226
227 // process authenticated request...
228 co_return route_done;
229 }
230 @endcode
231 */
232 inline constexpr decltype(auto) route_next = route_result::route_next;
233
234 /** Handler declined, skip to next route.
235
236 Return this from a handler to skip all remaining
237 handlers in the current route and proceed to the
238 next matching route:
239
240 @code
241 route_task version_check(route_params& p)
242 {
243 if(p.req.version() < 11)
244 co_return route_next_route; // skip this route
245
246 co_return route_next; // continue with this route
247 }
248 @endcode
249 */
250 inline constexpr decltype(auto) route_next_route = route_result::route_next_route;
251
252 /** Handler requests connection closure.
253
254 Return this from a handler to immediately close
255 the connection without sending a response:
256
257 @code
258 route_task ban_check(route_params& p)
259 {
260 if(is_banned(p.req.remote_address()))
261 co_return route_close; // drop connection
262
263 co_return route_next;
264 }
265 @endcode
266 */
267 inline constexpr decltype(auto) route_close = route_result::route_close;
268
269 /** Construct from an error code.
270
271 Use this constructor to return an error from a handler.
272 The error code must represent a failure condition.
273
274 @param ec The error code to return.
275
276 @throw std::invalid_argument if `!ec` (non-failing code).
277 */
278 11x inline route_result route_error(std::error_code ec) noexcept
279 {
280 11x return route_result(ec);
281 }
282
283 /** Construct from an error enum.
284
285 Use this overload to return an error from a handler
286 using any type satisfying `is_error_code_enum`.
287
288 @param e The error enum value to return.
289 */
290 template<class E>
291 2x auto route_error(E e) noexcept ->
292 std::enable_if_t<
293 std::is_error_code_enum<E>::value,
294 route_result>
295 {
296 2x return route_result(make_error_code(e));
297 }
298
299 //------------------------------------------------
300
301 /** Convenience alias for route handler return type.
302
303 Route handlers are coroutines that return a @ref route_result
304 indicating how the router should proceed. This alias simplifies
305 handler declarations:
306
307 @code
308 route_task my_handler(route_params& p)
309 {
310 // process request...
311 co_return route_done;
312 }
313
314 route_task auth_middleware(route_params& p)
315 {
316 if(! check_token(p))
317 {
318 p.res.set_status(status::unauthorized);
319 co_await p.send();
320 co_return route_done;
321 }
322 co_return route_next; // continue to next handler
323 }
324 @endcode
325
326 @see route_result, route_params
327 */
328 using route_task = capy::task<route_result>;
329
330 //------------------------------------------------
331
332 template<class, class> class router;
333
334 namespace detail {
335
336 struct route_params_access;
337 class router_base;
338
339 struct route_params_base_privates
340 {
341 std::string verb_str_;
342 std::string decoded_path_;
343 std::error_code ec_;
344 std::exception_ptr ep_;
345 std::size_t pos_ = 0;
346 std::size_t resume_ = 0;
347 http::method verb_ =
348 http::method::unknown;
349 bool addedSlash_ = false;
350 bool case_sensitive = false;
351 bool strict = false;
352 char kind_ = 0;
353 };
354
355 } // detail
356
357 //------------------------------------------------
358
359 /** Parameters object for HTTP route handlers.
360
361 This structure holds all the context needed for a route
362 handler to process an HTTP request and generate a response.
363
364 @par Example
365 @code
366 route_task my_handler(route_params& p)
367 {
368 p.res.set(field::content_type, "text/plain");
369 co_await p.send("Hello, World!");
370 co_return route_done;
371 }
372 @endcode
373
374 @see route_task, route_result
375 */
376 class BOOST_HTTP_SYMBOL_VISIBLE
377 route_params
378 {
379 detail::route_params_base_privates priv_;
380
381 public:
382 struct match_result;
383
384 /** Return true if the request method matches `m`
385 */
386 bool is_method(
387 http::method m) const noexcept
388 {
389 return priv_.verb_ == m;
390 }
391
392 /** Return true if the request method matches `s`
393 */
394 BOOST_HTTP_DECL
395 bool is_method(
396 core::string_view s) const noexcept;
397
398 /** The mount path of the current router
399
400 This is the portion of the request path
401 which was matched to select the handler.
402 The remaining portion is available in
403 @ref path.
404 */
405 core::string_view base_path;
406
407 /** The current pathname, relative to the base path
408 */
409 core::string_view path;
410
411 /** Captured route parameters
412
413 Contains name-value pairs extracted from the path
414 by matching :param and *wildcard tokens.
415 */
416 std::vector<std::pair<std::string, std::string>> params;
417
418 /// The complete request target
419 urls::url_view url;
420
421 /// The HTTP request
422 http::request req;
423
424 /// The HTTP response
425 http::response res;
426
427 /// Provides access to the request body
428 http::any_buffer_source req_body;
429
430 /// Provides access to the response body
431 http::any_buffer_sink res_body;
432
433 /// Arbitrary per-route data
434 http::datastore route_data;
435
436 /// Arbitrary per-session data
437 http::datastore session_data;
438
439 BOOST_HTTP_DECL ~route_params();
440 BOOST_HTTP_DECL void reset();
441 BOOST_HTTP_DECL route_params& status(http::status code);
442
443 /** Send the response with an optional body.
444 */
445 BOOST_HTTP_DECL capy::io_task<> send(std::string_view body = {});
446
447 private:
448 template<class, class>
449 friend class router;
450 friend class detail::router_base;
451 friend struct detail::route_params_access;
452
453 route_params& operator=(
454 route_params const&) = delete;
455 };
456
457 struct route_params::
458 match_result
459 {
460 std::vector<std::pair<std::string, std::string>> params_;
461
462 136x void adjust_path(
463 route_params& p,
464 std::size_t n)
465 {
466 136x n_ = n;
467 136x if(n_ == 0)
468 50x return;
469 86x p.base_path = {
470 p.base_path.data(),
471 86x p.base_path.size() + n_ };
472 86x if(n_ < p.path.size())
473 {
474 28x p.path.remove_prefix(n_);
475 }
476 else
477 {
478 // append a soft slash
479 58x p.path = { p.priv_.decoded_path_.data() +
480 58x p.priv_.decoded_path_.size() - 1, 1};
481 58x BOOST_ASSERT(p.path == "/");
482 }
483 }
484
485 void restore_path(
486 route_params& p)
487 {
488 if( n_ > 0 &&
489 p.priv_.addedSlash_ &&
490 p.path.data() ==
491 p.priv_.decoded_path_.data() +
492 p.priv_.decoded_path_.size() - 1)
493 {
494 // remove soft slash
495 p.path = {
496 p.base_path.data() +
497 p.base_path.size(), 0 };
498 }
499 p.base_path.remove_suffix(n_);
500 p.path = {
501 p.path.data() - n_,
502 p.path.size() + n_ };
503 }
504
505 private:
506 std::size_t n_ = 0; // chars moved from path to base_path
507 };
508
509 //------------------------------------------------
510
511 namespace detail {
512
513 template<class H, class... Args>
514 concept returns_route_task = std::same_as<
515 std::invoke_result_t<H, Args...>, route_task>;
516
517 struct route_params_access
518 {
519 route_params& rp;
520
521 360x route_params_base_privates& operator*() const noexcept
522 {
523 360x return rp.priv_;
524 }
525
526 62x route_params_base_privates* operator->() const noexcept
527 {
528 62x return &rp.priv_;
529 }
530 };
531
532 } // detail
533
534 } // http
535 } // boost
536
537 #endif
538