TLA Line data 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 HIT 227 : 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 132 : route_result(what_t<W>)
133 132 : {
134 : static_assert(W != route_what::error);
135 132 : set(W);
136 132 : }
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 1 : bool failed() const noexcept
181 : {
182 1 : 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 11 : inline route_result route_error(std::error_code ec) noexcept
279 : {
280 11 : 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 2 : auto route_error(E e) noexcept ->
292 : std::enable_if_t<
293 : std::is_error_code_enum<E>::value,
294 : route_result>
295 : {
296 2 : 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 MIS 0 : bool is_method(
387 : http::method m) const noexcept
388 : {
389 0 : 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 HIT 136 : void adjust_path(
463 : route_params& p,
464 : std::size_t n)
465 : {
466 136 : n_ = n;
467 136 : if(n_ == 0)
468 50 : return;
469 86 : p.base_path = {
470 : p.base_path.data(),
471 86 : p.base_path.size() + n_ };
472 86 : if(n_ < p.path.size())
473 : {
474 28 : p.path.remove_prefix(n_);
475 : }
476 : else
477 : {
478 : // append a soft slash
479 58 : p.path = { p.priv_.decoded_path_.data() +
480 58 : p.priv_.decoded_path_.size() - 1, 1};
481 58 : 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 360 : route_params_base_privates& operator*() const noexcept
522 : {
523 360 : return rp.priv_;
524 : }
525 :
526 62 : route_params_base_privates* operator->() const noexcept
527 : {
528 62 : return &rp.priv_;
529 : }
530 : };
531 :
532 : } // detail
533 :
534 : } // http
535 : } // boost
536 :
537 : #endif
|