libs/http/include/boost/http/server/router_types.hpp

100.0% Lines (22/22) 100.0% Functions (9/9) 83.3% Branches (5/6)
libs/http/include/boost/http/server/router_types.hpp
Line Branch Hits 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
2/2
✓ Branch 0 taken 41 times.
✓ Branch 1 taken 86 times.
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
2/2
✓ Branch 1 taken 28 times.
✓ Branch 2 taken 58 times.
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
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 58 times.
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
467