Line data Source code
1 : //
2 : // Copyright (c) 2026 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 : #include <boost/http/server/flat_router.hpp>
11 : #include <boost/http/server/router.hpp>
12 : #include <boost/http/detail/except.hpp>
13 :
14 : #include "src/server/detail/router_base.hpp"
15 : #include "src/server/detail/pct_decode.hpp"
16 : #include "src/server/detail/route_match.hpp"
17 :
18 : #include <system_error>
19 :
20 : namespace boost {
21 : namespace http {
22 :
23 : //------------------------------------------------
24 :
25 : struct flat_router::impl
26 : {
27 : using entry = detail::router_base::entry;
28 : using layer = detail::router_base::layer;
29 : using handler = detail::router_base::handler;
30 : using matcher = detail::router_base::matcher;
31 : using opt_flags = detail::router_base::opt_flags;
32 : using handler_ptr = detail::router_base::handler_ptr;
33 : using match_result = route_params_base::match_result;
34 :
35 : std::vector<entry> entries;
36 : std::vector<matcher> matchers;
37 :
38 : // RAII scope tracker sets matcher's skip_ when scope ends
39 : struct scope_tracker
40 : {
41 : std::vector<matcher>& matchers_;
42 : std::vector<entry>& entries_;
43 : std::size_t matcher_idx_;
44 :
45 99 : scope_tracker(
46 : std::vector<matcher>& m,
47 : std::vector<entry>& e,
48 : std::size_t idx)
49 99 : : matchers_(m)
50 99 : , entries_(e)
51 99 : , matcher_idx_(idx)
52 : {
53 99 : }
54 :
55 99 : ~scope_tracker()
56 : {
57 99 : matchers_[matcher_idx_].skip_ = entries_.size();
58 99 : }
59 : };
60 :
61 : static opt_flags
62 84 : compute_effective_opts(
63 : opt_flags parent,
64 : opt_flags child)
65 : {
66 84 : opt_flags result = parent;
67 :
68 : // case_sensitive: bits 1-2 (2=true, 4=false)
69 84 : if(child & 2)
70 3 : result = (result & ~6) | 2;
71 81 : else if(child & 4)
72 1 : result = (result & ~6) | 4;
73 :
74 : // strict: bits 3-4 (8=true, 16=false)
75 84 : if(child & 8)
76 0 : result = (result & ~24) | 8;
77 84 : else if(child & 16)
78 0 : result = (result & ~24) | 16;
79 :
80 84 : return result;
81 : }
82 :
83 : void
84 60 : flatten(detail::router_base::impl& src)
85 : {
86 60 : flatten_recursive(src, opt_flags{}, 0);
87 60 : }
88 :
89 : void
90 84 : flatten_recursive(
91 : detail::router_base::impl& src,
92 : opt_flags parent_opts,
93 : std::uint32_t depth)
94 : {
95 84 : opt_flags eff = compute_effective_opts(parent_opts, src.opt);
96 :
97 183 : for(auto& layer : src.layers)
98 : {
99 : // Move matcher, set flat router fields
100 99 : std::size_t matcher_idx = matchers.size();
101 99 : matchers.emplace_back(std::move(layer.match));
102 99 : auto& m = matchers.back();
103 99 : m.first_entry_ = entries.size();
104 99 : m.effective_opts_ = eff;
105 99 : m.depth_ = depth;
106 : // m.skip_ set by scope_tracker dtor
107 :
108 99 : scope_tracker scope(matchers, entries, matcher_idx);
109 :
110 211 : for(auto& e : layer.entries)
111 : {
112 112 : if(e.h->kind == detail::router_base::is_router)
113 : {
114 : // Recurse into nested router
115 24 : auto* nested = e.h->get_router();
116 24 : if(nested && nested->impl_)
117 24 : flatten_recursive(*nested->impl_, eff, depth + 1);
118 : }
119 : else
120 : {
121 : // Set matcher_idx, then move entire entry
122 88 : e.matcher_idx = matcher_idx;
123 88 : entries.emplace_back(std::move(e));
124 : }
125 : }
126 : // ~scope_tracker sets matchers[matcher_idx].skip_
127 99 : }
128 84 : }
129 :
130 : // Restore path to a given base_path length
131 : static void
132 20 : restore_path(
133 : route_params_base& p,
134 : std::size_t base_len)
135 : {
136 20 : p.base_path = { p.decoded_path_.data(), base_len };
137 : // Account for the addedSlash_ when computing path length
138 20 : auto const path_len = p.decoded_path_.size() - (p.addedSlash_ ? 1 : 0);
139 20 : if(base_len < path_len)
140 19 : p.path = { p.decoded_path_.data() + base_len,
141 : path_len - base_len };
142 : else
143 2 : p.path = { p.decoded_path_.data() +
144 1 : p.decoded_path_.size() - 1, 1 }; // soft slash
145 20 : }
146 :
147 : capy::task<route_result>
148 58 : dispatch_loop(route_params_base& p) const
149 : {
150 : // All checks happen BEFORE co_await to minimize coroutine launches.
151 : // Avoid touching p.ep_ (expensive atomic on Windows) - use p.kind_ for mode checks.
152 :
153 : std::size_t last_matched = SIZE_MAX;
154 : std::uint32_t current_depth = 0;
155 :
156 : // Stack of base_path lengths at each depth level.
157 : // path_stack[d] = base_path.size() before any matcher at depth d was tried.
158 : std::size_t path_stack[detail::router_base::max_path_depth];
159 : path_stack[0] = 0;
160 :
161 : // Track which matcher index is matched at each depth level.
162 : // matched_at_depth[d] = matcher index that successfully matched at depth d.
163 : std::size_t matched_at_depth[detail::router_base::max_path_depth];
164 : for(std::size_t d = 0; d < detail::router_base::max_path_depth; ++d)
165 : matched_at_depth[d] = SIZE_MAX;
166 :
167 : for(std::size_t i = 0; i < entries.size(); )
168 : {
169 : auto const& e = entries[i];
170 : auto const& m = matchers[e.matcher_idx];
171 : auto const target_depth = m.depth_;
172 :
173 : //--------------------------------------------------
174 : // Pre-invoke checks (no coroutine yet)
175 : //--------------------------------------------------
176 :
177 : // For nested routes: verify ancestors at depths 0..target_depth-1 are matched.
178 : // For siblings: if moving to same depth with different matcher, restore path.
179 : bool ancestors_ok = true;
180 :
181 : // Check if we need to match new ancestor matchers for this entry.
182 : // We iterate through matchers from last_matched+1 to e.matcher_idx,
183 : // but ONLY process those that are at depths we need (ancestors or self).
184 : std::size_t start_idx = (last_matched == SIZE_MAX) ? 0 : last_matched + 1;
185 :
186 : for(std::size_t check_idx = start_idx;
187 : check_idx <= e.matcher_idx && ancestors_ok;
188 : ++check_idx)
189 : {
190 : auto const& cm = matchers[check_idx];
191 :
192 : // Only check matchers that are:
193 : // 1. Ancestors (depth < target_depth) that we haven't matched yet, or
194 : // 2. The entry's own matcher
195 : bool is_needed_ancestor = (cm.depth_ < target_depth) &&
196 : (matched_at_depth[cm.depth_] == SIZE_MAX);
197 : bool is_self = (check_idx == e.matcher_idx);
198 :
199 : if(!is_needed_ancestor && !is_self)
200 : continue;
201 :
202 : // Restore path if moving to same or shallower depth
203 : if(cm.depth_ <= current_depth && current_depth > 0)
204 : {
205 : restore_path(p, path_stack[cm.depth_]);
206 : }
207 :
208 : // In error/exception mode, skip end routes
209 : if(cm.end_ && p.kind_ != detail::router_base::is_plain)
210 : {
211 : i = cm.skip_;
212 : ancestors_ok = false;
213 : break;
214 : }
215 :
216 : // Apply effective_opts for this matcher
217 : p.case_sensitive = (cm.effective_opts_ & 2) != 0;
218 : p.strict = (cm.effective_opts_ & 8) != 0;
219 :
220 : // Save path state before trying this matcher
221 : if(cm.depth_ < detail::router_base::max_path_depth)
222 : path_stack[cm.depth_] = p.base_path.size();
223 :
224 : match_result mr;
225 : if(!cm(p, mr))
226 : {
227 : // Clear matched_at_depth for this depth and deeper
228 : for(std::size_t d = cm.depth_; d < detail::router_base::max_path_depth; ++d)
229 : matched_at_depth[d] = SIZE_MAX;
230 : i = cm.skip_;
231 : ancestors_ok = false;
232 : break;
233 : }
234 :
235 : // Mark this depth as matched
236 : if(cm.depth_ < detail::router_base::max_path_depth)
237 : matched_at_depth[cm.depth_] = check_idx;
238 :
239 : last_matched = check_idx;
240 : current_depth = cm.depth_ + 1;
241 :
242 : // Save state for next depth level
243 : if(current_depth < detail::router_base::max_path_depth)
244 : path_stack[current_depth] = p.base_path.size();
245 : }
246 :
247 : if(!ancestors_ok)
248 : continue;
249 :
250 : // Check method match (only for end routes)
251 : if(m.end_ && !e.match_method(
252 : const_cast<route_params_base&>(p)))
253 : {
254 : ++i;
255 : continue;
256 : }
257 :
258 : // Check kind match (cheap char comparison)
259 : if(e.h->kind != p.kind_)
260 : {
261 : ++i;
262 : continue;
263 : }
264 :
265 : //--------------------------------------------------
266 : // Invoke handler (coroutine starts here)
267 : //--------------------------------------------------
268 :
269 : route_result rv;
270 : try
271 : {
272 : rv = co_await e.h->invoke(
273 : const_cast<route_params_base&>(p));
274 : }
275 : catch(...)
276 : {
277 : // Only touch ep_ when actually catching
278 : p.ep_ = std::current_exception();
279 : p.kind_ = detail::router_base::is_exception;
280 : ++i;
281 : continue;
282 : }
283 :
284 : //--------------------------------------------------
285 : // Handle result
286 : //
287 : // Coroutines invert control - handler does the send.
288 : // Success = !rv.failed() (handler completed request)
289 : // route::next = continue to next handler
290 : // route::next_route = skip to next route
291 : // Failing error_code = enter error mode
292 : //--------------------------------------------------
293 :
294 : if(rv == route::next)
295 : {
296 : ++i;
297 : continue;
298 : }
299 :
300 : if(rv == route::next_route)
301 : {
302 : // next_route only valid for end routes, not middleware
303 : if(!m.end_)
304 : co_return make_error_code(std::errc::invalid_argument);
305 : i = m.skip_;
306 : continue;
307 : }
308 :
309 : if(!rv.failed())
310 : {
311 : // Success - handler completed the request
312 : co_return rv;
313 : }
314 :
315 : // Failing error_code - transition to error mode
316 : p.ec_ = rv;
317 : p.kind_ = detail::router_base::is_error;
318 :
319 : if(m.end_)
320 : {
321 : // End routes don't have error handlers
322 : i = m.skip_;
323 : continue;
324 : }
325 :
326 : ++i;
327 : }
328 :
329 : // Final state
330 : if(p.kind_ == detail::router_base::is_exception)
331 : co_return error::unhandled_exception;
332 : if(p.kind_ == detail::router_base::is_error)
333 : co_return p.ec_;
334 :
335 : co_return route::next; // no handler matched
336 116 : }
337 : };
338 :
339 : //------------------------------------------------
340 :
341 60 : flat_router::
342 : flat_router(
343 60 : detail::router_base&& src)
344 60 : : impl_(std::make_shared<impl>())
345 : {
346 60 : impl_->flatten(*src.impl_);
347 60 : }
348 :
349 : capy::task<route_result>
350 54 : flat_router::
351 : dispatch(
352 : http::method verb,
353 : urls::url_view const& url,
354 : route_params_base& p) const
355 : {
356 54 : if(verb == http::method::unknown)
357 1 : detail::throw_invalid_argument();
358 :
359 : // Initialize params
360 53 : p.kind_ = detail::router_base::is_plain;
361 53 : p.verb_ = verb;
362 53 : p.verb_str_.clear();
363 53 : p.ec_.clear();
364 53 : p.ep_ = nullptr;
365 53 : p.decoded_path_ = detail::pct_decode_path(url.encoded_path());
366 53 : p.base_path = { p.decoded_path_.data(), 0 };
367 53 : p.path = p.decoded_path_;
368 53 : if(p.decoded_path_.back() != '/')
369 : {
370 25 : p.decoded_path_.push_back('/');
371 25 : p.addedSlash_ = true;
372 : }
373 : else
374 : {
375 28 : p.addedSlash_ = false;
376 : }
377 :
378 53 : return impl_->dispatch_loop(p);
379 : }
380 :
381 : capy::task<route_result>
382 6 : flat_router::
383 : dispatch(
384 : std::string_view verb,
385 : urls::url_view const& url,
386 : route_params_base& p) const
387 : {
388 6 : if(verb.empty())
389 1 : detail::throw_invalid_argument();
390 :
391 : // Initialize params
392 5 : p.kind_ = detail::router_base::is_plain;
393 5 : p.verb_ = http::string_to_method(verb);
394 5 : if(p.verb_ == http::method::unknown)
395 4 : p.verb_str_ = verb;
396 : else
397 1 : p.verb_str_.clear();
398 5 : p.ec_.clear();
399 5 : p.ep_ = nullptr;
400 5 : p.decoded_path_ = detail::pct_decode_path(url.encoded_path());
401 5 : p.base_path = { p.decoded_path_.data(), 0 };
402 5 : p.path = p.decoded_path_;
403 5 : if(p.decoded_path_.back() != '/')
404 : {
405 0 : p.decoded_path_.push_back('/');
406 0 : p.addedSlash_ = true;
407 : }
408 : else
409 : {
410 5 : p.addedSlash_ = false;
411 : }
412 :
413 5 : return impl_->dispatch_loop(p);
414 : }
415 :
416 : } // http
417 : } // boost
418 :
|