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