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