/src/crow/include/crow/routing.h
Line | Count | Source |
1 | | #pragma once |
2 | | |
3 | | #include <cstdint> |
4 | | #include <limits> |
5 | | #include <utility> |
6 | | #include <tuple> |
7 | | #include <unordered_map> |
8 | | #include <memory> |
9 | | #include <vector> |
10 | | #include <algorithm> |
11 | | #include <type_traits> |
12 | | #include <optional> |
13 | | |
14 | | #include "crow/common.h" |
15 | | #include "crow/http_response.h" |
16 | | #include "crow/http_request.h" |
17 | | #include "crow/utility.h" |
18 | | #include "crow/logging.h" |
19 | | #include "crow/exceptions.h" |
20 | | #include "crow/websocket.h" |
21 | | #include "crow/mustache.h" |
22 | | #include "crow/middleware.h" |
23 | | |
24 | | namespace crow // NOTE: Already documented in "crow/app.h" |
25 | | { |
26 | | |
27 | | constexpr size_t INVALID_BP_ID{SIZE_MAX}; |
28 | | |
29 | | namespace detail |
30 | | { |
31 | | /// Typesafe wrapper for storing lists of middleware as their indices in the App |
32 | | struct middleware_indices |
33 | | { |
34 | | template<typename App> |
35 | | void push() |
36 | | {} |
37 | | |
38 | | template<typename App, typename MW, typename... Middlewares> |
39 | | void push() |
40 | | { |
41 | | using MwContainer = typename App::mw_container_t; |
42 | | static_assert(black_magic::has_type<MW, MwContainer>::value, "Middleware must be present in app"); |
43 | | static_assert(std::is_base_of<crow::ILocalMiddleware, MW>::value, "Middleware must extend ILocalMiddleware"); |
44 | | int idx = black_magic::tuple_index<MW, MwContainer>::value; |
45 | | indices_.push_back(idx); |
46 | | push<App, Middlewares...>(); |
47 | | } |
48 | | |
49 | | void merge_front(const detail::middleware_indices& other) |
50 | 0 | { |
51 | 0 | indices_.insert(indices_.begin(), other.indices_.cbegin(), other.indices_.cend()); |
52 | 0 | } |
53 | | |
54 | | void merge_back(const detail::middleware_indices& other) |
55 | 0 | { |
56 | 0 | indices_.insert(indices_.end(), other.indices_.cbegin(), other.indices_.cend()); |
57 | 0 | } |
58 | | |
59 | | void pop_back(const detail::middleware_indices& other) |
60 | 0 | { |
61 | 0 | indices_.resize(indices_.size() - other.indices_.size()); |
62 | 0 | } |
63 | | |
64 | | bool empty() const |
65 | 0 | { |
66 | 0 | return indices_.empty(); |
67 | 0 | } |
68 | | |
69 | | // Sorts indices and filters out duplicates to allow fast lookups with traversal |
70 | | void pack() |
71 | 0 | { |
72 | 0 | std::sort(indices_.begin(), indices_.end()); |
73 | 0 | indices_.erase(std::unique(indices_.begin(), indices_.end()), indices_.end()); |
74 | 0 | } |
75 | | |
76 | | const std::vector<int>& indices() |
77 | 0 | { |
78 | 0 | return indices_; |
79 | 0 | } |
80 | | |
81 | | private: |
82 | | std::vector<int> indices_; |
83 | | }; |
84 | | } // namespace detail |
85 | | |
86 | | /// A base class for all rules. |
87 | | |
88 | | /// |
89 | | /// Used to provide a common interface for code dealing with different types of rules.<br> |
90 | | /// A Rule provides a URL, allowed HTTP methods, and handlers. |
91 | | class BaseRule |
92 | | { |
93 | | public: |
94 | | BaseRule(std::string rule): |
95 | | rule_(std::move(rule)) |
96 | 0 | {} |
97 | | |
98 | | virtual ~BaseRule()=default; |
99 | | |
100 | | virtual void validate() = 0; |
101 | | |
102 | | void set_added() |
103 | 0 | { |
104 | 0 | added_ = true; |
105 | 0 | } |
106 | | |
107 | | bool is_added() |
108 | 0 | { |
109 | 0 | return added_; |
110 | 0 | } |
111 | | |
112 | | std::unique_ptr<BaseRule> upgrade() |
113 | 0 | { |
114 | 0 | if (rule_to_upgrade_) |
115 | 0 | return std::move(rule_to_upgrade_); |
116 | 0 | return {}; |
117 | 0 | } |
118 | | |
119 | | virtual void handle(request&, response&, const routing_params&) = 0; |
120 | | virtual void handle_upgrade(const request&, response& res, SocketAdaptor&&) |
121 | 0 | { |
122 | 0 | res = response(404); |
123 | 0 | res.end(); |
124 | 0 | } |
125 | | virtual void handle_upgrade(const request&, response& res, UnixSocketAdaptor&&) |
126 | 0 | { |
127 | 0 | res = response(404); |
128 | 0 | res.end(); |
129 | 0 | } |
130 | | #ifdef CROW_ENABLE_SSL |
131 | | virtual void handle_upgrade(const request&, response& res, SSLAdaptor&&) |
132 | | { |
133 | | res = response(404); |
134 | | res.end(); |
135 | | } |
136 | | #endif |
137 | | |
138 | | uint64_t get_methods() |
139 | 0 | { |
140 | 0 | return methods_; |
141 | 0 | } |
142 | | |
143 | | template<typename F> |
144 | | void foreach_method(F f) |
145 | 0 | { |
146 | 0 | for (uint64_t method = 0, method_bit = 1; method < static_cast<uint64_t>(HTTPMethod::InternalMethodCount); method++, method_bit <<= 1) |
147 | 0 | { |
148 | 0 | if (methods_ & method_bit) |
149 | 0 | f(method); |
150 | 0 | } |
151 | 0 | } Unexecuted instantiation: void crow::BaseRule::foreach_method<crow::Router::internal_add_rule_object(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, crow::BaseRule*, unsigned long const&, std::__1::vector<crow::Blueprint*, std::__1::allocator<crow::Blueprint*> >&)::{lambda(int)#1}>(crow::Router::internal_add_rule_object(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, crow::BaseRule*, unsigned long const&, std::__1::vector<crow::Blueprint*, std::__1::allocator<crow::Blueprint*> >&)::{lambda(int)#1})Unexecuted instantiation: void crow::BaseRule::foreach_method<crow::Router::get_recursive_child_methods(crow::Blueprint*, std::__1::vector<crow::HTTPMethod, std::__1::allocator<crow::HTTPMethod> >&)::{lambda(unsigned int)#1}>(crow::Router::get_recursive_child_methods(crow::Blueprint*, std::__1::vector<crow::HTTPMethod, std::__1::allocator<crow::HTTPMethod> >&)::{lambda(unsigned int)#1}) |
152 | | |
153 | | std::string custom_templates_base; |
154 | | |
155 | 0 | const std::string& rule() { return rule_; } |
156 | | |
157 | | protected: |
158 | | uint64_t methods_{1ULL << static_cast<int>(HTTPMethod::Get)}; |
159 | | |
160 | | std::string rule_; |
161 | | std::string name_; |
162 | | bool added_{false}; |
163 | | |
164 | | std::unique_ptr<BaseRule> rule_to_upgrade_; |
165 | | |
166 | | detail::middleware_indices mw_indices_; |
167 | | |
168 | | friend class Router; |
169 | | friend class Blueprint; |
170 | | template<typename T> |
171 | | friend struct RuleParameterTraits; |
172 | | }; |
173 | | |
174 | | |
175 | | namespace detail |
176 | | { |
177 | | namespace routing_handler_call_helper |
178 | | { |
179 | | template<typename T, int Pos> |
180 | | struct call_pair |
181 | | { |
182 | | using type = T; |
183 | | static const int pos = Pos; |
184 | | }; |
185 | | |
186 | | template<typename H1> |
187 | | struct call_params |
188 | | { |
189 | | H1& handler; |
190 | | const routing_params& params; |
191 | | request& req; |
192 | | response& res; |
193 | | }; |
194 | | |
195 | | template<typename F, int NInt, int NUint, int NDouble, int NString, typename S1, typename S2> |
196 | | struct call |
197 | | {}; |
198 | | |
199 | | template<typename F, int NInt, int NUint, int NDouble, int NString, typename... Args1, typename... Args2> |
200 | | struct call<F, NInt, NUint, NDouble, NString, black_magic::S<int64_t, Args1...>, black_magic::S<Args2...>> |
201 | | { |
202 | | void operator()(F cparams) |
203 | | { |
204 | | using pushed = typename black_magic::S<Args2...>::template push_back<call_pair<int64_t, NInt>>; |
205 | | call<F, NInt + 1, NUint, NDouble, NString, black_magic::S<Args1...>, pushed>()(cparams); |
206 | | } |
207 | | }; |
208 | | |
209 | | template<typename F, int NInt, int NUint, int NDouble, int NString, typename... Args1, typename... Args2> |
210 | | struct call<F, NInt, NUint, NDouble, NString, black_magic::S<uint64_t, Args1...>, black_magic::S<Args2...>> |
211 | | { |
212 | | void operator()(F cparams) |
213 | | { |
214 | | using pushed = typename black_magic::S<Args2...>::template push_back<call_pair<uint64_t, NUint>>; |
215 | | call<F, NInt, NUint + 1, NDouble, NString, black_magic::S<Args1...>, pushed>()(cparams); |
216 | | } |
217 | | }; |
218 | | |
219 | | template<typename F, int NInt, int NUint, int NDouble, int NString, typename... Args1, typename... Args2> |
220 | | struct call<F, NInt, NUint, NDouble, NString, black_magic::S<double, Args1...>, black_magic::S<Args2...>> |
221 | | { |
222 | | void operator()(F cparams) |
223 | | { |
224 | | using pushed = typename black_magic::S<Args2...>::template push_back<call_pair<double, NDouble>>; |
225 | | call<F, NInt, NUint, NDouble + 1, NString, black_magic::S<Args1...>, pushed>()(cparams); |
226 | | } |
227 | | }; |
228 | | |
229 | | template<typename F, int NInt, int NUint, int NDouble, int NString, typename... Args1, typename... Args2> |
230 | | struct call<F, NInt, NUint, NDouble, NString, black_magic::S<std::string, Args1...>, black_magic::S<Args2...>> |
231 | | { |
232 | | void operator()(F cparams) |
233 | | { |
234 | | using pushed = typename black_magic::S<Args2...>::template push_back<call_pair<std::string, NString>>; |
235 | | call<F, NInt, NUint, NDouble, NString + 1, black_magic::S<Args1...>, pushed>()(cparams); |
236 | | } |
237 | | }; |
238 | | |
239 | | template<typename F, int NInt, int NUint, int NDouble, int NString, typename... Args1> |
240 | | struct call<F, NInt, NUint, NDouble, NString, black_magic::S<>, black_magic::S<Args1...>> |
241 | | { |
242 | | void operator()(F cparams) |
243 | 0 | { |
244 | 0 | cparams.handler( |
245 | 0 | cparams.req, |
246 | 0 | cparams.res, |
247 | 0 | cparams.params.template get<typename Args1::type>(Args1::pos)...); |
248 | 0 | } |
249 | | }; |
250 | | |
251 | | template<typename Func, typename... ArgsWrapped> |
252 | | struct Wrapped |
253 | | { |
254 | | template<typename... Args> |
255 | | void set_(Func f, typename std::enable_if<!std::is_same<typename std::tuple_element<0, std::tuple<Args..., void>>::type, const request&>::value, int>::type = 0) |
256 | | { |
257 | | handler_ = ([f = std::move(f)](const request&, response& res, Args... args) { |
258 | | res = response(f(args...)); |
259 | | res.end(); |
260 | | }); |
261 | | } |
262 | | |
263 | | template<typename Req, typename... Args> |
264 | | struct req_handler_wrapper |
265 | | { |
266 | | req_handler_wrapper(Func fun): |
267 | | f(std::move(fun)) |
268 | | { |
269 | | } |
270 | | |
271 | | void operator()(const request& req, response& res, Args... args) |
272 | | { |
273 | | res = response(f(req, args...)); |
274 | | res.end(); |
275 | | } |
276 | | |
277 | | Func f; |
278 | | }; |
279 | | |
280 | | template<typename... Args> |
281 | | void set_(Func f, typename std::enable_if< |
282 | | std::is_same<typename std::tuple_element<0, std::tuple<Args..., void>>::type, const request&>::value && |
283 | | !std::is_same<typename std::tuple_element<1, std::tuple<Args..., void, void>>::type, response&>::value, |
284 | | int>::type = 0) |
285 | | { |
286 | | handler_ = req_handler_wrapper<Args...>(std::move(f)); |
287 | | /*handler_ = ( |
288 | | [f = std::move(f)] |
289 | | (const request& req, response& res, Args... args){ |
290 | | res = response(f(req, args...)); |
291 | | res.end(); |
292 | | });*/ |
293 | | } |
294 | | |
295 | | template<typename... Args> |
296 | | void set_(Func f, typename std::enable_if< |
297 | | std::is_same<typename std::tuple_element<0, std::tuple<Args..., void>>::type, const request&>::value && |
298 | | std::is_same<typename std::tuple_element<1, std::tuple<Args..., void, void>>::type, response&>::value, |
299 | | int>::type = 0) |
300 | | { |
301 | | handler_ = std::move(f); |
302 | | } |
303 | | |
304 | | template<typename... Args> |
305 | | struct handler_type_helper |
306 | | { |
307 | | using type = std::function<void(const crow::request&, crow::response&, Args...)>; |
308 | | using args_type = black_magic::S<typename black_magic::promote_t<Args>...>; |
309 | | }; |
310 | | |
311 | | template<typename... Args> |
312 | | struct handler_type_helper<const request&, Args...> |
313 | | { |
314 | | using type = std::function<void(const crow::request&, crow::response&, Args...)>; |
315 | | using args_type = black_magic::S<typename black_magic::promote_t<Args>...>; |
316 | | }; |
317 | | |
318 | | template<typename... Args> |
319 | | struct handler_type_helper<const request&, response&, Args...> |
320 | | { |
321 | | using type = std::function<void(const crow::request&, crow::response&, Args...)>; |
322 | | using args_type = black_magic::S<typename black_magic::promote_t<Args>...>; |
323 | | }; |
324 | | |
325 | | typename handler_type_helper<ArgsWrapped...>::type handler_; |
326 | | |
327 | | void operator()(request& req, response& res, const routing_params& params) |
328 | | { |
329 | | detail::routing_handler_call_helper::call< |
330 | | detail::routing_handler_call_helper::call_params< |
331 | | decltype(handler_)>, |
332 | | 0, 0, 0, 0, |
333 | | typename handler_type_helper<ArgsWrapped...>::args_type, |
334 | | black_magic::S<>>()( |
335 | | detail::routing_handler_call_helper::call_params< |
336 | | decltype(handler_)>{handler_, params, req, res}); |
337 | | } |
338 | | }; |
339 | | |
340 | | } // namespace routing_handler_call_helper |
341 | | } // namespace detail |
342 | | |
343 | | |
344 | | class CatchallRule |
345 | | { |
346 | | public: |
347 | | /// @cond SKIP |
348 | 0 | CatchallRule() {} |
349 | | |
350 | | template<typename Func> |
351 | | typename std::enable_if<black_magic::CallHelper<Func, black_magic::S<>>::value, void>::type |
352 | | operator()(Func&& f) |
353 | | { |
354 | | static_assert(!std::is_same<void, decltype(f())>::value, |
355 | | "Handler function cannot have void return type; valid return types: string, int, crow::response, crow::returnable"); |
356 | | |
357 | | handler_ = ([f = std::move(f)](const request&, response& res) { |
358 | | res = response(f()); |
359 | | }); |
360 | | } |
361 | | |
362 | | template<typename Func> |
363 | | typename std::enable_if< |
364 | | !black_magic::CallHelper<Func, black_magic::S<>>::value && |
365 | | black_magic::CallHelper<Func, black_magic::S<crow::request>>::value, |
366 | | void>::type |
367 | | operator()(Func&& f) |
368 | | { |
369 | | static_assert(!std::is_same<void, decltype(f(std::declval<crow::request>()))>::value, |
370 | | "Handler function cannot have void return type; valid return types: string, int, crow::response, crow::returnable"); |
371 | | |
372 | | handler_ = ([f = std::move(f)](const request& req, response& res) { |
373 | | res = response(f(req)); |
374 | | }); |
375 | | } |
376 | | |
377 | | template<typename Func> |
378 | | typename std::enable_if< |
379 | | !black_magic::CallHelper<Func, black_magic::S<>>::value && |
380 | | !black_magic::CallHelper<Func, black_magic::S<crow::request>>::value && |
381 | | black_magic::CallHelper<Func, black_magic::S<crow::response&>>::value, |
382 | | void>::type |
383 | | operator()(Func&& f) |
384 | | { |
385 | | static_assert(std::is_same<void, decltype(f(std::declval<crow::response&>()))>::value, |
386 | | "Handler function with response argument should have void return type"); |
387 | | handler_ = ([f = std::move(f)](const request&, response& res) { |
388 | | f(res); |
389 | | }); |
390 | | } |
391 | | |
392 | | template<typename Func> |
393 | | typename std::enable_if< |
394 | | !black_magic::CallHelper<Func, black_magic::S<>>::value && |
395 | | !black_magic::CallHelper<Func, black_magic::S<crow::request>>::value && |
396 | | !black_magic::CallHelper<Func, black_magic::S<crow::response&>>::value, |
397 | | void>::type |
398 | | operator()(Func&& f) |
399 | | { |
400 | | static_assert(std::is_same<void, decltype(f(std::declval<crow::request>(), std::declval<crow::response&>()))>::value, |
401 | | "Handler function with response argument should have void return type"); |
402 | | |
403 | | handler_ = std::move(f); |
404 | | } |
405 | | /// @endcond |
406 | | bool has_handler() |
407 | 0 | { |
408 | 0 | return (handler_ != nullptr); |
409 | 0 | } |
410 | | |
411 | | protected: |
412 | | friend class Router; |
413 | | |
414 | | private: |
415 | | std::function<void(const crow::request&, crow::response&)> handler_; |
416 | | }; |
417 | | |
418 | | |
419 | | /// A rule dealing with websockets. |
420 | | |
421 | | /// |
422 | | /// Provides the interface for the user to put in the necessary handlers for a websocket to work. |
423 | | template<typename App> |
424 | | class WebSocketRule : public BaseRule |
425 | | { |
426 | | using self_t = WebSocketRule; |
427 | | |
428 | | public: |
429 | | WebSocketRule(std::string rule, App* app): |
430 | | BaseRule(std::move(rule)), |
431 | | app_(app), |
432 | | max_payload_(UINT64_MAX) |
433 | | {} |
434 | | |
435 | | void validate() override |
436 | | {} |
437 | | |
438 | | void handle(request&, response& res, const routing_params&) override |
439 | | { |
440 | | res = response(404); |
441 | | res.end(); |
442 | | } |
443 | | |
444 | | void handle_upgrade(const request& req, response&, SocketAdaptor&& adaptor) override |
445 | | { |
446 | | max_payload_ = max_payload_override_ ? max_payload_ : app_->websocket_max_payload(); |
447 | | crow::websocket::Connection<SocketAdaptor, App>::create(req, std::move(adaptor), app_, max_payload_, subprotocols_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_, mirror_protocols_); |
448 | | } |
449 | | |
450 | | void handle_upgrade(const request& req, response&, UnixSocketAdaptor&& adaptor) override |
451 | | { |
452 | | max_payload_ = max_payload_override_ ? max_payload_ : app_->websocket_max_payload(); |
453 | | crow::websocket::Connection<UnixSocketAdaptor, App>::create(req, std::move(adaptor), app_, max_payload_, subprotocols_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_, mirror_protocols_); |
454 | | } |
455 | | |
456 | | #ifdef CROW_ENABLE_SSL |
457 | | void handle_upgrade(const request& req, response&, SSLAdaptor&& adaptor) override |
458 | | { |
459 | | crow::websocket::Connection<SSLAdaptor, App>::create(req, std::move(adaptor), app_, max_payload_, subprotocols_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_, mirror_protocols_); |
460 | | } |
461 | | #endif |
462 | | |
463 | | /// Override the global payload limit for this single WebSocket rule |
464 | | self_t& max_payload(uint64_t max_payload) |
465 | | { |
466 | | max_payload_ = max_payload; |
467 | | max_payload_override_ = true; |
468 | | return *this; |
469 | | } |
470 | | |
471 | | self_t& subprotocols(const std::vector<std::string>& subprotocols) |
472 | | { |
473 | | subprotocols_ = subprotocols; |
474 | | return *this; |
475 | | } |
476 | | |
477 | | /// \brief Set functor that process a client request to open a WebSocket. |
478 | | /// The required interface is: |
479 | | /// void(crow::websocket::connection& conn) |
480 | | /// |
481 | | /// \param f Functor to set. |
482 | | /// |
483 | | template<typename Func> |
484 | | self_t& onopen(Func f) |
485 | | { |
486 | | open_handler_ = f; |
487 | | return *this; |
488 | | } |
489 | | |
490 | | /// \brief Set functor that process a client message. |
491 | | /// The required interface is: |
492 | | /// void(crow::websocket::connection& conn, const std::string& msgData, bool is_binary) |
493 | | /// |
494 | | /// \param f Functor to set. |
495 | | /// |
496 | | template<typename Func> |
497 | | self_t& onmessage(Func f) |
498 | | { |
499 | | message_handler_ = f; |
500 | | return *this; |
501 | | } |
502 | | |
503 | | /// \brief Set functor that process a client close. |
504 | | /// The required interface is: |
505 | | /// void(crow::websocket::connection& conn, const std::string& reason, uint16_t status_code) |
506 | | /// |
507 | | /// \param f Functor to set. |
508 | | /// |
509 | | template<typename Func> |
510 | | self_t& onclose(Func f) |
511 | | { |
512 | | close_handler_ = f; |
513 | | return *this; |
514 | | } |
515 | | |
516 | | /// \brief Set functor that process an error on this WebSocket. |
517 | | /// The required interface is: |
518 | | /// void(crow::websocket::connection& conn, const std::string& error_message) |
519 | | /// |
520 | | /// \param f Functor to set. |
521 | | /// |
522 | | template<typename Func> |
523 | | self_t& onerror(Func f) |
524 | | { |
525 | | error_handler_ = f; |
526 | | return *this; |
527 | | } |
528 | | |
529 | | /// \brief Set functor that process a client request to start a WebSocket. |
530 | | /// The required interface is: |
531 | | /// const crow::request& conn, std::optional<crow::response>& response, void** userData) |
532 | | /// |
533 | | /// \param callback Functor to set. |
534 | | /// |
535 | | self_t& onaccept(std::function<void(const crow::request&, std::optional<crow::response>&, void**)>&& callback) |
536 | | { |
537 | | accept_handler_ = std::move(callback); |
538 | | return *this; |
539 | | } |
540 | | |
541 | | /// \brief Set functor that process a client request to start a WebSocket. |
542 | | /// The required interface is (**without response**): |
543 | | /// const crow::request& conn, void** userData) |
544 | | /// |
545 | | /// \param callback Functor to set. |
546 | | /// |
547 | | self_t& onaccept(std::function<bool(const crow::request&, void**)>&& callback) |
548 | | { |
549 | | onaccept([callback](const crow::request& req, std::optional<crow::response>& res, void** p) { |
550 | | if (!callback(req, p)) |
551 | | { |
552 | | res = crow::response(400); |
553 | | } |
554 | | }); |
555 | | return *this; |
556 | | } |
557 | | |
558 | | self_t& mirrorprotocols(bool mirror_protocols = true) |
559 | | { |
560 | | mirror_protocols_ = mirror_protocols; |
561 | | return *this; |
562 | | } |
563 | | |
564 | | protected: |
565 | | App* app_; |
566 | | std::function<void(crow::websocket::connection&)> open_handler_; |
567 | | std::function<void(crow::websocket::connection&, const std::string&, bool)> message_handler_; |
568 | | std::function<void(crow::websocket::connection&, const std::string&, uint16_t)> close_handler_; |
569 | | std::function<void(crow::websocket::connection&, const std::string&)> error_handler_; |
570 | | std::function<void(const crow::request&, std::optional<crow::response>&, void**)> accept_handler_; |
571 | | bool mirror_protocols_ = false; |
572 | | uint64_t max_payload_; |
573 | | bool max_payload_override_ = false; |
574 | | std::vector<std::string> subprotocols_; |
575 | | }; |
576 | | |
577 | | /// Allows the user to assign parameters using functions. |
578 | | |
579 | | /// |
580 | | /// `rule.name("name").methods(HTTPMethod::POST)` |
581 | | template<typename T> |
582 | | struct RuleParameterTraits |
583 | | { |
584 | | using self_t = T; |
585 | | |
586 | | template<typename App> |
587 | | WebSocketRule<App>& websocket(App* app) |
588 | | { |
589 | | auto p = new WebSocketRule<App>(static_cast<self_t*>(this)->rule_, app); |
590 | | static_cast<self_t*>(this)->rule_to_upgrade_.reset(p); |
591 | | return *p; |
592 | | } |
593 | | |
594 | | self_t& name(std::string name) noexcept |
595 | | { |
596 | | static_cast<self_t*>(this)->name_ = std::move(name); |
597 | | return static_cast<self_t&>(*this); |
598 | | } |
599 | | |
600 | | self_t& methods(HTTPMethod method) |
601 | | { |
602 | | static_cast<self_t*>(this)->methods_ = 1ULL << static_cast<int>(method); |
603 | | return static_cast<self_t&>(*this); |
604 | | } |
605 | | |
606 | | template<typename... MethodArgs> |
607 | | self_t& methods(HTTPMethod method, MethodArgs... args_method) |
608 | | { |
609 | | methods(args_method...); |
610 | | static_cast<self_t*>(this)->methods_ |= 1ULL << static_cast<int>(method); |
611 | | return static_cast<self_t&>(*this); |
612 | | } |
613 | | |
614 | | /// Enable local middleware for this handler |
615 | | template<typename App, typename... Middlewares> |
616 | | self_t& middlewares() |
617 | | { |
618 | | static_cast<self_t*>(this)->mw_indices_.template push<App, Middlewares...>(); |
619 | | return static_cast<self_t&>(*this); |
620 | | } |
621 | | }; |
622 | | |
623 | | /// A rule that can change its parameters during runtime. |
624 | | class DynamicRule : public BaseRule, public RuleParameterTraits<DynamicRule> |
625 | | { |
626 | | public: |
627 | | DynamicRule(std::string rule): |
628 | | BaseRule(std::move(rule)) |
629 | 0 | {} |
630 | | |
631 | | void validate() override |
632 | 0 | { |
633 | 0 | if (!erased_handler_) |
634 | 0 | { |
635 | 0 | throw std::runtime_error(name_ + (!name_.empty() ? ": " : "") + "no handler for url " + rule_); |
636 | 0 | } |
637 | 0 | } |
638 | | |
639 | | void handle(request& req, response& res, const routing_params& params) override |
640 | 0 | { |
641 | 0 | if (!custom_templates_base.empty()) |
642 | 0 | mustache::set_base(custom_templates_base); |
643 | 0 | else if (mustache::detail::get_template_base_directory_ref() != "templates") |
644 | 0 | mustache::set_base("templates"); |
645 | 0 | erased_handler_(req, res, params); |
646 | 0 | } |
647 | | |
648 | | template<typename Func> |
649 | | void operator()(Func f) |
650 | | { |
651 | | #ifdef CROW_MSVC_WORKAROUND |
652 | | using function_t = utility::function_traits<decltype(&Func::operator())>; |
653 | | #else |
654 | | using function_t = utility::function_traits<Func>; |
655 | | #endif |
656 | | erased_handler_ = wrap(std::move(f), black_magic::gen_seq<function_t::arity>()); |
657 | | } |
658 | | |
659 | | // enable_if Arg1 == request && Arg2 == response |
660 | | // enable_if Arg1 == request && Arg2 != resposne |
661 | | // enable_if Arg1 != request |
662 | | #ifdef CROW_MSVC_WORKAROUND |
663 | | template<typename Func, size_t... Indices> |
664 | | #else |
665 | | template<typename Func, unsigned... Indices> |
666 | | #endif |
667 | | std::function<void(request&, response&, const routing_params&)> |
668 | | wrap(Func f, black_magic::seq<Indices...>) |
669 | | { |
670 | | #ifdef CROW_MSVC_WORKAROUND |
671 | | using function_t = utility::function_traits<decltype(&Func::operator())>; |
672 | | #else |
673 | | using function_t = utility::function_traits<Func>; |
674 | | #endif |
675 | | if (!black_magic::is_parameter_tag_compatible( |
676 | | black_magic::get_parameter_tag_runtime(rule_.c_str()), |
677 | | black_magic::compute_parameter_tag_from_args_list< |
678 | | typename function_t::template arg<Indices>...>::value)) |
679 | | { |
680 | | throw std::runtime_error("route_dynamic: Handler type is mismatched with URL parameters: " + rule_); |
681 | | } |
682 | | auto ret = detail::routing_handler_call_helper::Wrapped<Func, typename function_t::template arg<Indices>...>(); |
683 | | ret.template set_< |
684 | | typename function_t::template arg<Indices>...>(std::move(f)); |
685 | | return ret; |
686 | | } |
687 | | |
688 | | template<typename Func> |
689 | | void operator()(std::string name, Func&& f) |
690 | | { |
691 | | name_ = std::move(name); |
692 | | (*this).template operator()<Func>(std::forward(f)); |
693 | | } |
694 | | |
695 | | private: |
696 | | std::function<void(request&, response&, const routing_params&)> erased_handler_; |
697 | | }; |
698 | | |
699 | | /// Default rule created when CROW_ROUTE is called. |
700 | | template<typename... Args> |
701 | | class TaggedRule : public BaseRule, public RuleParameterTraits<TaggedRule<Args...>> |
702 | | { |
703 | | public: |
704 | | using self_t = TaggedRule<Args...>; |
705 | | |
706 | | TaggedRule(std::string rule): |
707 | | BaseRule(std::move(rule)) |
708 | | {} |
709 | | |
710 | | void validate() override |
711 | 0 | { |
712 | 0 | if (rule_.at(0) != '/') |
713 | 0 | throw std::runtime_error("Internal error: Routes must start with a '/'"); |
714 | 0 |
|
715 | 0 | if (!handler_) |
716 | 0 | { |
717 | 0 | throw std::runtime_error(name_ + (!name_.empty() ? ": " : "") + "no handler for url " + rule_); |
718 | 0 | } |
719 | 0 | } |
720 | | |
721 | | template<typename Func> |
722 | | void operator()(Func&& f) |
723 | | { |
724 | | handler_ = ([f = std::move(f)](request& req, response& res, Args... args) { |
725 | | detail::wrapped_handler_call(req, res, f, std::forward<Args>(args)...); |
726 | | }); |
727 | | } |
728 | | |
729 | | template<typename Func> |
730 | | void operator()(std::string name, Func&& f) |
731 | | { |
732 | | name_ = std::move(name); |
733 | | (*this).template operator()<Func>(std::forward(f)); |
734 | | } |
735 | | |
736 | | void handle(request& req, response& res, const routing_params& params) override |
737 | 0 | { |
738 | 0 | if (!custom_templates_base.empty()) |
739 | 0 | mustache::set_base(custom_templates_base); |
740 | 0 | else if (mustache::detail::get_template_base_directory_ref() != mustache::detail::get_global_template_base_directory_ref()) |
741 | 0 | mustache::set_base(mustache::detail::get_global_template_base_directory_ref()); |
742 | 0 |
|
743 | 0 | detail::routing_handler_call_helper::call< |
744 | 0 | detail::routing_handler_call_helper::call_params<decltype(handler_)>, |
745 | 0 | 0, 0, 0, 0, |
746 | 0 | black_magic::S<Args...>, |
747 | 0 | black_magic::S<>>()( |
748 | 0 | detail::routing_handler_call_helper::call_params<decltype(handler_)>{handler_, params, req, res}); |
749 | 0 | } |
750 | | |
751 | | private: |
752 | | std::function<void(crow::request&, crow::response&, Args...)> handler_; |
753 | | }; |
754 | | |
755 | | using StaticRule = TaggedRule<>; |
756 | | |
757 | | constexpr size_t RULE_SPECIAL_REDIRECT_SLASH = 1; |
758 | | |
759 | | /// A search tree. |
760 | | class Trie |
761 | | { |
762 | | public: |
763 | | struct Node |
764 | | { |
765 | | size_t rule_index{}; |
766 | | // Assign the index to the maximum 32 unsigned integer value by default so that any other number (specifically 0) is a valid BP id. |
767 | | size_t blueprint_index{INVALID_BP_ID}; |
768 | | std::string key; |
769 | | ParamType param = ParamType::MAX; // MAX = No param. |
770 | | std::vector<Node> children; |
771 | | |
772 | | bool IsSimpleNode() const |
773 | 0 | { |
774 | 0 | return !rule_index && |
775 | 0 | blueprint_index == INVALID_BP_ID && |
776 | 0 | children.size() < 2 && |
777 | 0 | param == ParamType::MAX && |
778 | 0 | std::all_of(std::begin(children), std::end(children), [](const Node& x) { |
779 | 0 | return x.param == ParamType::MAX; |
780 | 0 | }); |
781 | 0 | } |
782 | | |
783 | | Node& add_child_node() |
784 | 0 | { |
785 | 0 | children.emplace_back(); |
786 | 0 | return children.back(); |
787 | 0 | } |
788 | | }; |
789 | | |
790 | | |
791 | | Trie() |
792 | 0 | {} |
793 | | |
794 | | /// Check whether or not the trie is empty. |
795 | | bool is_empty() |
796 | 0 | { |
797 | 0 | return head_.children.empty(); |
798 | 0 | } |
799 | | |
800 | | void optimize() |
801 | 0 | { |
802 | 0 | for (auto& child : head_.children) |
803 | 0 | { |
804 | 0 | optimizeNode(child); |
805 | 0 | } |
806 | 0 | } |
807 | | |
808 | | |
809 | | private: |
810 | | void optimizeNode(Node& node) |
811 | 0 | { |
812 | 0 | if (node.children.empty()) |
813 | 0 | return; |
814 | 0 | if (node.IsSimpleNode()) |
815 | 0 | { |
816 | 0 | auto children_temp = std::move(node.children); |
817 | 0 | auto& child_temp = children_temp[0]; |
818 | 0 | node.key += child_temp.key; |
819 | 0 | node.rule_index = child_temp.rule_index; |
820 | 0 | node.blueprint_index = child_temp.blueprint_index; |
821 | 0 | node.children = std::move(child_temp.children); |
822 | 0 | optimizeNode(node); |
823 | 0 | } |
824 | 0 | else |
825 | 0 | { |
826 | 0 | for (auto& child : node.children) |
827 | 0 | { |
828 | 0 | optimizeNode(child); |
829 | 0 | } |
830 | 0 | } |
831 | 0 | } |
832 | | |
833 | | void debug_node_print(const Node& node, size_t level) |
834 | 0 | { |
835 | 0 | if (node.param != ParamType::MAX) |
836 | 0 | { |
837 | 0 | switch (node.param) |
838 | 0 | { |
839 | 0 | case ParamType::INT: |
840 | 0 | CROW_LOG_DEBUG << std::string(3 * level, ' ') << "└➝ " |
841 | 0 | << "<int>"; |
842 | 0 | break; |
843 | 0 | case ParamType::UINT: |
844 | 0 | CROW_LOG_DEBUG << std::string(3 * level, ' ') << "└➝ " |
845 | 0 | << "<uint>"; |
846 | 0 | break; |
847 | 0 | case ParamType::DOUBLE: |
848 | 0 | CROW_LOG_DEBUG << std::string(3 * level, ' ') << "└➝ " |
849 | 0 | << "<double>"; |
850 | 0 | break; |
851 | 0 | case ParamType::STRING: |
852 | 0 | CROW_LOG_DEBUG << std::string(3 * level, ' ') << "└➝ " |
853 | 0 | << "<string>"; |
854 | 0 | break; |
855 | 0 | case ParamType::PATH: |
856 | 0 | CROW_LOG_DEBUG << std::string(3 * level, ' ') << "└➝ " |
857 | 0 | << "<path>"; |
858 | 0 | break; |
859 | 0 | default: |
860 | 0 | CROW_LOG_DEBUG << std::string(3 * level, ' ') << "└➝ " |
861 | 0 | << "<ERROR>"; |
862 | 0 | break; |
863 | 0 | } |
864 | 0 | } |
865 | 0 | else |
866 | 0 | CROW_LOG_DEBUG << std::string(3 * level, ' ') << "└➝ " << node.key; |
867 | 0 |
|
868 | 0 | for (const auto& child : node.children) |
869 | 0 | { |
870 | 0 | debug_node_print(child, level + 1); |
871 | 0 | } |
872 | 0 | } |
873 | | |
874 | | public: |
875 | | void debug_print() |
876 | 0 | { |
877 | 0 | CROW_LOG_DEBUG << "└➙ ROOT"; |
878 | 0 | for (const auto& child : head_.children) |
879 | 0 | debug_node_print(child, 1); |
880 | 0 | } |
881 | | |
882 | | void validate() |
883 | 0 | { |
884 | 0 | if (!head_.IsSimpleNode()) |
885 | 0 | throw std::runtime_error("Internal error: Trie header should be simple!"); |
886 | 0 | optimize(); |
887 | 0 | } |
888 | | |
889 | | //Rule_index, Blueprint_index, routing_params |
890 | | routing_handle_result find(const std::string& req_url, const Node& node, size_t pos = 0, routing_params* params = nullptr, std::vector<size_t>* blueprints = nullptr) const |
891 | 0 | { |
892 | 0 | //start params as an empty struct |
893 | 0 | routing_params empty; |
894 | 0 | if (params == nullptr) |
895 | 0 | params = ∅ |
896 | 0 | //same for blueprint vector |
897 | 0 | std::vector<size_t> MT; |
898 | 0 | if (blueprints == nullptr) |
899 | 0 | blueprints = &MT; |
900 | 0 |
|
901 | 0 | size_t found{}; //The rule index to be found |
902 | 0 | std::vector<size_t> found_BP; //The Blueprint indices to be found |
903 | 0 | routing_params match_params; //supposedly the final matched parameters |
904 | 0 |
|
905 | 0 | auto update_found = [&found, &found_BP, &match_params](routing_handle_result& ret) { |
906 | 0 | found_BP = std::move(ret.blueprint_indices); |
907 | 0 | if (ret.rule_index && (!found || found > ret.rule_index)) |
908 | 0 | { |
909 | 0 | found = ret.rule_index; |
910 | 0 | match_params = std::move(ret.r_params); |
911 | 0 | } |
912 | 0 | }; |
913 | 0 |
|
914 | 0 | //if the function was called on a node at the end of the string (the last recursion), return the nodes rule index, and whatever params were passed to the function |
915 | 0 | if (pos == req_url.size()) |
916 | 0 | { |
917 | 0 | found_BP = std::move(*blueprints); |
918 | 0 | return routing_handle_result{node.rule_index, *blueprints, *params}; |
919 | 0 | } |
920 | 0 |
|
921 | 0 | bool found_fragment = false; |
922 | 0 |
|
923 | 0 | for (const auto& child : node.children) |
924 | 0 | { |
925 | 0 | if (child.param != ParamType::MAX) |
926 | 0 | { |
927 | 0 | if (child.param == ParamType::INT) |
928 | 0 | { |
929 | 0 | char c = req_url[pos]; |
930 | 0 | if ((c >= '0' && c <= '9') || c == '+' || c == '-') |
931 | 0 | { |
932 | 0 | char* eptr; |
933 | 0 | errno = 0; |
934 | 0 | long long int value = strtoll(req_url.data() + pos, &eptr, 10); |
935 | 0 | if (errno != ERANGE && eptr != req_url.data() + pos) |
936 | 0 | { |
937 | 0 | found_fragment = true; |
938 | 0 | params->int_params.push_back(value); |
939 | 0 | if (child.blueprint_index != INVALID_BP_ID) blueprints->push_back(child.blueprint_index); |
940 | 0 | auto ret = find(req_url, child, eptr - req_url.data(), params, blueprints); |
941 | 0 | update_found(ret); |
942 | 0 | params->int_params.pop_back(); |
943 | 0 | if (!blueprints->empty()) blueprints->pop_back(); |
944 | 0 | } |
945 | 0 | } |
946 | 0 | } |
947 | 0 |
|
948 | 0 | else if (child.param == ParamType::UINT) |
949 | 0 | { |
950 | 0 | char c = req_url[pos]; |
951 | 0 | if ((c >= '0' && c <= '9') || c == '+') |
952 | 0 | { |
953 | 0 | char* eptr; |
954 | 0 | errno = 0; |
955 | 0 | unsigned long long int value = strtoull(req_url.data() + pos, &eptr, 10); |
956 | 0 | if (errno != ERANGE && eptr != req_url.data() + pos) |
957 | 0 | { |
958 | 0 | found_fragment = true; |
959 | 0 | params->uint_params.push_back(value); |
960 | 0 | if (child.blueprint_index != INVALID_BP_ID) blueprints->push_back(child.blueprint_index); |
961 | 0 | auto ret = find(req_url, child, eptr - req_url.data(), params, blueprints); |
962 | 0 | update_found(ret); |
963 | 0 | params->uint_params.pop_back(); |
964 | 0 | if (!blueprints->empty()) blueprints->pop_back(); |
965 | 0 | } |
966 | 0 | } |
967 | 0 | } |
968 | 0 |
|
969 | 0 | else if (child.param == ParamType::DOUBLE) |
970 | 0 | { |
971 | 0 | char c = req_url[pos]; |
972 | 0 | if ((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.') |
973 | 0 | { |
974 | 0 | char* eptr; |
975 | 0 | errno = 0; |
976 | 0 | double value = strtod(req_url.data() + pos, &eptr); |
977 | 0 | if (errno != ERANGE && eptr != req_url.data() + pos) |
978 | 0 | { |
979 | 0 | found_fragment = true; |
980 | 0 | params->double_params.push_back(value); |
981 | 0 | if (child.blueprint_index != INVALID_BP_ID) blueprints->push_back(child.blueprint_index); |
982 | 0 | auto ret = find(req_url, child, eptr - req_url.data(), params, blueprints); |
983 | 0 | update_found(ret); |
984 | 0 | params->double_params.pop_back(); |
985 | 0 | if (!blueprints->empty()) blueprints->pop_back(); |
986 | 0 | } |
987 | 0 | } |
988 | 0 | } |
989 | 0 |
|
990 | 0 | else if (child.param == ParamType::STRING) |
991 | 0 | { |
992 | 0 | size_t epos = pos; |
993 | 0 | for (; epos < req_url.size(); epos++) |
994 | 0 | { |
995 | 0 | if (req_url[epos] == '/') |
996 | 0 | break; |
997 | 0 | } |
998 | 0 |
|
999 | 0 | if (epos != pos) |
1000 | 0 | { |
1001 | 0 | found_fragment = true; |
1002 | 0 | params->string_params.push_back(req_url.substr(pos, epos - pos)); |
1003 | 0 | if (child.blueprint_index != INVALID_BP_ID) blueprints->push_back(child.blueprint_index); |
1004 | 0 | auto ret = find(req_url, child, epos, params, blueprints); |
1005 | 0 | update_found(ret); |
1006 | 0 | params->string_params.pop_back(); |
1007 | 0 | if (!blueprints->empty()) blueprints->pop_back(); |
1008 | 0 | } |
1009 | 0 | } |
1010 | 0 |
|
1011 | 0 | else if (child.param == ParamType::PATH) |
1012 | 0 | { |
1013 | 0 | size_t epos = req_url.size(); |
1014 | 0 |
|
1015 | 0 | if (epos != pos) |
1016 | 0 | { |
1017 | 0 | found_fragment = true; |
1018 | 0 | params->string_params.push_back(req_url.substr(pos, epos - pos)); |
1019 | 0 | if (child.blueprint_index != INVALID_BP_ID) blueprints->push_back(child.blueprint_index); |
1020 | 0 | auto ret = find(req_url, child, epos, params, blueprints); |
1021 | 0 | update_found(ret); |
1022 | 0 | params->string_params.pop_back(); |
1023 | 0 | if (!blueprints->empty()) blueprints->pop_back(); |
1024 | 0 | } |
1025 | 0 | } |
1026 | 0 | } |
1027 | 0 |
|
1028 | 0 | else |
1029 | 0 | { |
1030 | 0 | const std::string& fragment = child.key; |
1031 | 0 | if (req_url.compare(pos, fragment.size(), fragment) == 0) |
1032 | 0 | { |
1033 | 0 | found_fragment = true; |
1034 | 0 | if (child.blueprint_index != INVALID_BP_ID) blueprints->push_back(child.blueprint_index); |
1035 | 0 | auto ret = find(req_url, child, pos + fragment.size(), params, blueprints); |
1036 | 0 | update_found(ret); |
1037 | 0 | if (!blueprints->empty()) blueprints->pop_back(); |
1038 | 0 | } |
1039 | 0 | } |
1040 | 0 | } |
1041 | 0 |
|
1042 | 0 | if (!found_fragment) |
1043 | 0 | found_BP = std::move(*blueprints); |
1044 | 0 |
|
1045 | 0 | return routing_handle_result{found, found_BP, match_params}; //Called after all the recursions have been done |
1046 | 0 | } |
1047 | | |
1048 | | routing_handle_result find(const std::string& req_url) const |
1049 | 0 | { |
1050 | 0 | return find(req_url, head_); |
1051 | 0 | } |
1052 | | |
1053 | | //This functions assumes any blueprint info passed is valid |
1054 | | void add(const std::string& url, size_t rule_index, unsigned bp_prefix_length = 0, size_t blueprint_index = INVALID_BP_ID) |
1055 | 0 | { |
1056 | 0 | auto idx = &head_; |
1057 | 0 |
|
1058 | 0 | bool has_blueprint = bp_prefix_length != 0 && blueprint_index != INVALID_BP_ID; |
1059 | 0 |
|
1060 | 0 | for (unsigned i = 0; i < url.size(); i++) |
1061 | 0 | { |
1062 | 0 | char c = url[i]; |
1063 | 0 | if (c == '<') |
1064 | 0 | { |
1065 | 0 | static struct ParamTraits |
1066 | 0 | { |
1067 | 0 | ParamType type; |
1068 | 0 | std::string name; |
1069 | 0 | } paramTraits[] = |
1070 | 0 | { |
1071 | 0 | {ParamType::INT, "<int>"}, |
1072 | 0 | {ParamType::UINT, "<uint>"}, |
1073 | 0 | {ParamType::DOUBLE, "<float>"}, |
1074 | 0 | {ParamType::DOUBLE, "<double>"}, |
1075 | 0 | {ParamType::STRING, "<str>"}, |
1076 | 0 | {ParamType::STRING, "<string>"}, |
1077 | 0 | {ParamType::PATH, "<path>"}, |
1078 | 0 | }; |
1079 | 0 |
|
1080 | 0 | for (const auto& x : paramTraits) |
1081 | 0 | { |
1082 | 0 | if (url.compare(i, x.name.size(), x.name) == 0) |
1083 | 0 | { |
1084 | 0 | bool found = false; |
1085 | 0 | for (auto& child : idx->children) |
1086 | 0 | { |
1087 | 0 | if (child.param == x.type) |
1088 | 0 | { |
1089 | 0 | idx = &child; |
1090 | 0 | i += x.name.size(); |
1091 | 0 | found = true; |
1092 | 0 | break; |
1093 | 0 | } |
1094 | 0 | } |
1095 | 0 | if (found) |
1096 | 0 | break; |
1097 | 0 |
|
1098 | 0 | auto new_node_idx = &idx->add_child_node(); |
1099 | 0 | new_node_idx->param = x.type; |
1100 | 0 | idx = new_node_idx; |
1101 | 0 | i += x.name.size(); |
1102 | 0 | break; |
1103 | 0 | } |
1104 | 0 | } |
1105 | 0 |
|
1106 | 0 | i--; |
1107 | 0 | } |
1108 | 0 | else |
1109 | 0 | { |
1110 | 0 | //This part assumes the tree is unoptimized (every node has a max 1 character key) |
1111 | 0 | bool piece_found = false; |
1112 | 0 | for (auto& child : idx->children) |
1113 | 0 | { |
1114 | 0 | if (child.key[0] == c) |
1115 | 0 | { |
1116 | 0 | idx = &child; |
1117 | 0 | piece_found = true; |
1118 | 0 | break; |
1119 | 0 | } |
1120 | 0 | } |
1121 | 0 | if (!piece_found) |
1122 | 0 | { |
1123 | 0 | auto new_node_idx = &idx->add_child_node(); |
1124 | 0 | new_node_idx->key = c; |
1125 | 0 | //The assumption here is that you'd only need to add a blueprint index if the tree didn't have the BP prefix. |
1126 | 0 | if (has_blueprint && i == bp_prefix_length) |
1127 | 0 | new_node_idx->blueprint_index = blueprint_index; |
1128 | 0 | idx = new_node_idx; |
1129 | 0 | } |
1130 | 0 | } |
1131 | 0 | } |
1132 | 0 |
|
1133 | 0 | //check if the last node already has a value (exact url already in Trie) |
1134 | 0 | if (idx->rule_index) |
1135 | 0 | throw std::runtime_error("handler already exists for " + url); |
1136 | 0 | idx->rule_index = rule_index; |
1137 | 0 | } |
1138 | | |
1139 | | private: |
1140 | | Node head_; |
1141 | | }; |
1142 | | |
1143 | | /// A blueprint can be considered a smaller section of a Crow app, specifically where the router is concerned. |
1144 | | |
1145 | | /// |
1146 | | /// You can use blueprints to assign a common prefix to rules' prefix, set custom static and template folders, and set a custom catchall route. |
1147 | | /// You can also assign nest blueprints for maximum Compartmentalization. |
1148 | | class Blueprint |
1149 | | { |
1150 | | public: |
1151 | | Blueprint(const std::string& prefix) |
1152 | | : prefix_(prefix), |
1153 | | static_dir_(prefix), |
1154 | | templates_dir_(prefix) |
1155 | 0 | {} |
1156 | | |
1157 | | Blueprint(const std::string& prefix, const std::string& static_dir): |
1158 | 0 | prefix_(prefix), static_dir_(static_dir){} |
1159 | | |
1160 | | Blueprint(const std::string& prefix, const std::string& static_dir, const std::string& templates_dir): |
1161 | 0 | prefix_(prefix), static_dir_(static_dir), templates_dir_(templates_dir){} |
1162 | | |
1163 | | /* |
1164 | | Blueprint(Blueprint& other) |
1165 | | { |
1166 | | prefix_ = std::move(other.prefix_); |
1167 | | all_rules_ = std::move(other.all_rules_); |
1168 | | } |
1169 | | |
1170 | | Blueprint(const Blueprint& other) |
1171 | | { |
1172 | | prefix_ = other.prefix_; |
1173 | | all_rules_ = other.all_rules_; |
1174 | | } |
1175 | | */ |
1176 | | Blueprint(Blueprint&& value) |
1177 | 0 | { |
1178 | 0 | *this = std::move(value); |
1179 | 0 | } |
1180 | | |
1181 | | Blueprint& operator=(const Blueprint& value) = delete; |
1182 | | |
1183 | | Blueprint& operator=(Blueprint&& value) noexcept |
1184 | 0 | { |
1185 | 0 | prefix_ = std::move(value.prefix_); |
1186 | 0 | static_dir_ = std::move(value.static_dir_); |
1187 | 0 | templates_dir_ = std::move(value.templates_dir_); |
1188 | 0 | all_rules_ = std::move(value.all_rules_); |
1189 | 0 | catchall_rule_ = std::move(value.catchall_rule_); |
1190 | 0 | blueprints_ = std::move(value.blueprints_); |
1191 | 0 | mw_indices_ = std::move(value.mw_indices_); |
1192 | 0 | return *this; |
1193 | 0 | } |
1194 | | |
1195 | | bool operator==(const Blueprint& value) |
1196 | 0 | { |
1197 | 0 | return value.prefix() == prefix_; |
1198 | 0 | } |
1199 | | |
1200 | | bool operator!=(const Blueprint& value) |
1201 | 0 | { |
1202 | 0 | return value.prefix() != prefix_; |
1203 | 0 | } |
1204 | | |
1205 | | std::string prefix() const |
1206 | 0 | { |
1207 | 0 | return prefix_; |
1208 | 0 | } |
1209 | | |
1210 | | std::string static_dir() const |
1211 | 0 | { |
1212 | 0 | return static_dir_; |
1213 | 0 | } |
1214 | | |
1215 | | void set_added() |
1216 | 0 | { |
1217 | 0 | added_ = true; |
1218 | 0 | } |
1219 | | |
1220 | | bool is_added() |
1221 | 0 | { |
1222 | 0 | return added_; |
1223 | 0 | } |
1224 | | |
1225 | | DynamicRule& new_rule_dynamic(const std::string& rule) |
1226 | 0 | { |
1227 | 0 | std::string new_rule = '/' + prefix_ + rule; |
1228 | 0 | auto ruleObject = new DynamicRule(std::move(new_rule)); |
1229 | 0 | ruleObject->custom_templates_base = templates_dir_; |
1230 | 0 | all_rules_.emplace_back(ruleObject); |
1231 | 0 |
|
1232 | 0 | return *ruleObject; |
1233 | 0 | } |
1234 | | |
1235 | | template<uint64_t N> |
1236 | | typename black_magic::arguments<N>::type::template rebind<TaggedRule>& new_rule_tagged(const std::string& rule) |
1237 | | { |
1238 | | std::string new_rule = '/' + prefix_ + rule; |
1239 | | using RuleT = typename black_magic::arguments<N>::type::template rebind<TaggedRule>; |
1240 | | |
1241 | | auto ruleObject = new RuleT(std::move(new_rule)); |
1242 | | ruleObject->custom_templates_base = templates_dir_; |
1243 | | all_rules_.emplace_back(ruleObject); |
1244 | | |
1245 | | return *ruleObject; |
1246 | | } |
1247 | | |
1248 | | void register_blueprint(Blueprint& blueprint) |
1249 | 0 | { |
1250 | 0 | if (blueprints_.empty() || std::find(blueprints_.begin(), blueprints_.end(), &blueprint) == blueprints_.end()) |
1251 | 0 | { |
1252 | 0 | apply_blueprint(blueprint); |
1253 | 0 | blueprints_.emplace_back(&blueprint); |
1254 | 0 | } |
1255 | 0 | else |
1256 | 0 | throw std::runtime_error("blueprint \"" + blueprint.prefix_ + "\" already exists in blueprint \"" + prefix_ + '\"'); |
1257 | 0 | } |
1258 | | |
1259 | | |
1260 | | CatchallRule& catchall_rule() |
1261 | 0 | { |
1262 | 0 | return catchall_rule_; |
1263 | 0 | } |
1264 | | |
1265 | | template<typename App, typename... Middlewares> |
1266 | | void middlewares() |
1267 | | { |
1268 | | mw_indices_.push<App, Middlewares...>(); |
1269 | | } |
1270 | | |
1271 | | private: |
1272 | | void apply_blueprint(Blueprint& blueprint) |
1273 | 0 | { |
1274 | 0 |
|
1275 | 0 | blueprint.prefix_ = prefix_ + '/' + blueprint.prefix_; |
1276 | 0 | blueprint.static_dir_ = static_dir_ + '/' + blueprint.static_dir_; |
1277 | 0 | blueprint.templates_dir_ = templates_dir_ + '/' + blueprint.templates_dir_; |
1278 | 0 | for (auto& rule : blueprint.all_rules_) |
1279 | 0 | { |
1280 | 0 | std::string new_rule = '/' + prefix_ + rule->rule_; |
1281 | 0 | rule->rule_ = new_rule; |
1282 | 0 | } |
1283 | 0 | for (Blueprint* bp_child : blueprint.blueprints_) |
1284 | 0 | { |
1285 | 0 | Blueprint& bp_ref = *bp_child; |
1286 | 0 | apply_blueprint(bp_ref); |
1287 | 0 | } |
1288 | 0 | } |
1289 | | |
1290 | | std::string prefix_; |
1291 | | std::string static_dir_; |
1292 | | std::string templates_dir_; |
1293 | | std::vector<std::unique_ptr<BaseRule>> all_rules_; |
1294 | | CatchallRule catchall_rule_; |
1295 | | std::vector<Blueprint*> blueprints_; |
1296 | | detail::middleware_indices mw_indices_; |
1297 | | bool added_{false}; |
1298 | | |
1299 | | friend class Router; |
1300 | | }; |
1301 | | |
1302 | | /// Handles matching requests to existing rules and upgrade requests. |
1303 | | class Router { |
1304 | | public: |
1305 | | bool using_ssl; |
1306 | | |
1307 | | Router() : using_ssl(false) |
1308 | 0 | {} |
1309 | | |
1310 | | DynamicRule& new_rule_dynamic(const std::string& rule) |
1311 | 0 | { |
1312 | 0 | auto ruleObject = new DynamicRule(rule); |
1313 | 0 | all_rules_.emplace_back(ruleObject); |
1314 | 0 |
|
1315 | 0 | return *ruleObject; |
1316 | 0 | } |
1317 | | |
1318 | | template<uint64_t N> |
1319 | | typename black_magic::arguments<N>::type::template rebind<TaggedRule>& new_rule_tagged(const std::string& rule) |
1320 | | { |
1321 | | using RuleT = typename black_magic::arguments<N>::type::template rebind<TaggedRule>; |
1322 | | |
1323 | | return new_rule<RuleT>(rule); |
1324 | | } |
1325 | | |
1326 | | template<typename RuleT=StaticRule> |
1327 | | auto& new_rule(const std::string& rule) |
1328 | 0 | { |
1329 | 0 | auto ruleObject = new RuleT(rule); |
1330 | 0 | all_rules_.emplace_back(ruleObject); |
1331 | 0 |
|
1332 | 0 | return *ruleObject; |
1333 | 0 | } |
1334 | | |
1335 | | CatchallRule& catchall_rule() |
1336 | 0 | { |
1337 | 0 | return catchall_rule_; |
1338 | 0 | } |
1339 | | |
1340 | | void internal_add_rule_object(const std::string& rule, BaseRule* ruleObject) |
1341 | 0 | { |
1342 | 0 | internal_add_rule_object(rule, ruleObject, INVALID_BP_ID, blueprints_); |
1343 | 0 | } |
1344 | | |
1345 | | void internal_add_rule_object(const std::string& rule, BaseRule* ruleObject, const size_t& BP_index, std::vector<Blueprint*>& blueprints) |
1346 | 0 | { |
1347 | 0 | bool has_trailing_slash = false; |
1348 | 0 | std::string rule_without_trailing_slash; |
1349 | 0 | if (rule.size() > 1 && rule.back() == '/') |
1350 | 0 | { |
1351 | 0 | has_trailing_slash = true; |
1352 | 0 | rule_without_trailing_slash = rule; |
1353 | 0 | rule_without_trailing_slash.pop_back(); |
1354 | 0 | } |
1355 | 0 |
|
1356 | 0 | ruleObject->mw_indices_.pack(); |
1357 | 0 |
|
1358 | 0 | ruleObject->foreach_method([&](int method) { |
1359 | 0 | per_methods_[method].rules.emplace_back(ruleObject); |
1360 | 0 | per_methods_[method].trie.add(rule, per_methods_[method].rules.size() - 1, |
1361 | 0 | BP_index != INVALID_BP_ID ? blueprints[BP_index]->prefix().length() : 0, |
1362 | 0 | BP_index); |
1363 | 0 |
|
1364 | 0 | // directory case: |
1365 | 0 | // request to '/about' url matches '/about/' rule |
1366 | 0 | if (has_trailing_slash) |
1367 | 0 | { |
1368 | 0 | per_methods_[method].trie.add(rule_without_trailing_slash, RULE_SPECIAL_REDIRECT_SLASH, BP_index != INVALID_BP_ID ? blueprints[BP_index]->prefix().length() : 0, BP_index); |
1369 | 0 | } |
1370 | 0 | }); |
1371 | 0 |
|
1372 | 0 | ruleObject->set_added(); |
1373 | 0 | } |
1374 | | |
1375 | | void register_blueprint(Blueprint& blueprint) |
1376 | 0 | { |
1377 | 0 | if (std::find(blueprints_.begin(), blueprints_.end(), &blueprint) == blueprints_.end()) |
1378 | 0 | { |
1379 | 0 | blueprints_.emplace_back(&blueprint); |
1380 | 0 | } |
1381 | 0 | else |
1382 | 0 | throw std::runtime_error("blueprint \"" + blueprint.prefix_ + "\" already exists in router"); |
1383 | 0 | } |
1384 | | |
1385 | | void get_recursive_child_methods(Blueprint* blueprint, std::vector<HTTPMethod>& methods) |
1386 | 0 | { |
1387 | 0 | //we only need to deal with children if the blueprint has absolutely no methods (meaning its index won't be added to the trie) |
1388 | 0 | if (blueprint->static_dir_.empty() && blueprint->all_rules_.empty()) |
1389 | 0 | { |
1390 | 0 | for (Blueprint* bp : blueprint->blueprints_) |
1391 | 0 | { |
1392 | 0 | get_recursive_child_methods(bp, methods); |
1393 | 0 | } |
1394 | 0 | } |
1395 | 0 | else if (!blueprint->static_dir_.empty()) |
1396 | 0 | methods.emplace_back(HTTPMethod::Get); |
1397 | 0 | for (auto& rule : blueprint->all_rules_) |
1398 | 0 | { |
1399 | 0 | rule->foreach_method([&methods](unsigned method) { |
1400 | 0 | HTTPMethod method_final = static_cast<HTTPMethod>(method); |
1401 | 0 | if (std::find(methods.begin(), methods.end(), method_final) == methods.end()) |
1402 | 0 | methods.emplace_back(method_final); |
1403 | 0 | }); |
1404 | 0 | } |
1405 | 0 | } |
1406 | | |
1407 | | void validate_bp() |
1408 | 0 | { |
1409 | 0 | //Take all the routes from the registered blueprints and add them to `all_rules_` to be processed. |
1410 | 0 | detail::middleware_indices blueprint_mw; |
1411 | 0 | validate_bp(blueprints_, blueprint_mw); |
1412 | 0 | } |
1413 | | |
1414 | | void validate_bp(std::vector<Blueprint*> blueprints, detail::middleware_indices& current_mw) |
1415 | 0 | { |
1416 | 0 | for (unsigned i = 0; i < blueprints.size(); i++) |
1417 | 0 | { |
1418 | 0 | Blueprint* blueprint = blueprints[i]; |
1419 | 0 |
|
1420 | 0 | if (blueprint->is_added()) continue; |
1421 | 0 |
|
1422 | 0 | if (blueprint->static_dir_ == "" && blueprint->all_rules_.empty()) |
1423 | 0 | { |
1424 | 0 | std::vector<HTTPMethod> methods; |
1425 | 0 | get_recursive_child_methods(blueprint, methods); |
1426 | 0 | for (HTTPMethod x : methods) |
1427 | 0 | { |
1428 | 0 | int method_index = static_cast<int>(x); |
1429 | 0 | per_methods_[method_index].trie.add(blueprint->prefix(), 0, blueprint->prefix().length(), method_index); |
1430 | 0 | } |
1431 | 0 | } |
1432 | 0 |
|
1433 | 0 | current_mw.merge_back(blueprint->mw_indices_); |
1434 | 0 | for (auto& rule : blueprint->all_rules_) |
1435 | 0 | { |
1436 | 0 | if (rule && !rule->is_added()) |
1437 | 0 | { |
1438 | 0 | auto upgraded = rule->upgrade(); |
1439 | 0 | if (upgraded) |
1440 | 0 | rule = std::move(upgraded); |
1441 | 0 | rule->validate(); |
1442 | 0 | rule->mw_indices_.merge_front(current_mw); |
1443 | 0 | internal_add_rule_object(rule->rule(), rule.get(), i, blueprints); |
1444 | 0 | } |
1445 | 0 | } |
1446 | 0 | validate_bp(blueprint->blueprints_, current_mw); |
1447 | 0 | current_mw.pop_back(blueprint->mw_indices_); |
1448 | 0 | blueprint->set_added(); |
1449 | 0 | } |
1450 | 0 | } |
1451 | | |
1452 | | void validate() |
1453 | 0 | { |
1454 | 0 | for (auto& rule : all_rules_) |
1455 | 0 | { |
1456 | 0 | if (rule && !rule->is_added()) |
1457 | 0 | { |
1458 | 0 | auto upgraded = rule->upgrade(); |
1459 | 0 | if (upgraded) |
1460 | 0 | rule = std::move(upgraded); |
1461 | 0 | rule->validate(); |
1462 | 0 | internal_add_rule_object(rule->rule(), rule.get()); |
1463 | 0 | } |
1464 | 0 | } |
1465 | 0 | for (auto& per_method : per_methods_) |
1466 | 0 | { |
1467 | 0 | per_method.trie.validate(); |
1468 | 0 | } |
1469 | 0 | } |
1470 | | |
1471 | | // TODO maybe add actual_method |
1472 | | template<typename Adaptor> |
1473 | | void handle_upgrade(const request& req, response& res, Adaptor&& adaptor) |
1474 | | { |
1475 | | if (req.method >= HTTPMethod::InternalMethodCount) |
1476 | | return; |
1477 | | |
1478 | | auto& per_method = per_methods_[static_cast<int>(req.method)]; |
1479 | | auto& rules = per_method.rules; |
1480 | | size_t rule_index = per_method.trie.find(req.url).rule_index; |
1481 | | |
1482 | | if (!rule_index) |
1483 | | { |
1484 | | for (auto& method : per_methods_) |
1485 | | { |
1486 | | if (method.trie.find(req.url).rule_index) |
1487 | | { |
1488 | | CROW_LOG_DEBUG << "Cannot match method " << req.url << " " << method_name(req.method); |
1489 | | res = response(405); |
1490 | | res.end(); |
1491 | | return; |
1492 | | } |
1493 | | } |
1494 | | |
1495 | | CROW_LOG_INFO << "Cannot match rules " << req.url; |
1496 | | res = response(404); |
1497 | | res.end(); |
1498 | | return; |
1499 | | } |
1500 | | |
1501 | | if (rule_index >= rules.size()) |
1502 | | throw std::runtime_error("Trie internal structure corrupted!"); |
1503 | | |
1504 | | if (rule_index == RULE_SPECIAL_REDIRECT_SLASH) |
1505 | | { |
1506 | | CROW_LOG_INFO << "Redirecting to a url with trailing slash: " << req.url; |
1507 | | res = response(301); |
1508 | | res.add_header("Location", req.url + "/"); |
1509 | | res.end(); |
1510 | | return; |
1511 | | } |
1512 | | |
1513 | | CROW_LOG_DEBUG << "Matched rule (upgrade) '" << rules[rule_index]->rule_ << "' " |
1514 | | << static_cast<uint64_t>(req.method) << " / " |
1515 | | << rules[rule_index]->get_methods(); |
1516 | | |
1517 | | try |
1518 | | { |
1519 | | rules[rule_index]->handle_upgrade(req, res, std::move(adaptor)); |
1520 | | } |
1521 | | catch (...) |
1522 | | { |
1523 | | exception_handler_(res); |
1524 | | res.end(); |
1525 | | return; |
1526 | | } |
1527 | | } |
1528 | | |
1529 | | void get_found_bp(const std::vector<size_t>& bp_i, const std::vector<Blueprint*>& blueprints, std::vector<Blueprint*>& found_bps, size_t index = 0) |
1530 | 0 | { |
1531 | 0 | // This statement makes 3 assertions: |
1532 | 0 | // 1. The index is above 0. |
1533 | 0 | // 2. The index does not lie outside the given blueprint list. |
1534 | 0 | // 3. The next blueprint we're adding has a prefix that starts the same as the already added blueprint + a slash (the rest is irrelevant). |
1535 | 0 | // |
1536 | 0 | // This is done to prevent a blueprint that has a prefix of "bp_prefix2" to be assumed as a child of one that has "bp_prefix". |
1537 | 0 | // |
1538 | 0 | // If any of the assertions is untrue, we delete the last item added, and continue using the blueprint list of the blueprint found before, the topmost being the router's list |
1539 | 0 | auto verify_prefix = [&bp_i, &index, &blueprints, &found_bps]() { |
1540 | 0 | return index > 0 && |
1541 | 0 | bp_i[index] < blueprints.size() && |
1542 | 0 | blueprints[bp_i[index]]->prefix().substr(0, found_bps[index - 1]->prefix().length() + 1).compare(std::string(found_bps[index - 1]->prefix() + '/')) == 0; |
1543 | 0 | }; |
1544 | 0 | if (index < bp_i.size()) |
1545 | 0 | { |
1546 | 0 |
|
1547 | 0 | if (verify_prefix()) |
1548 | 0 | { |
1549 | 0 | found_bps.push_back(blueprints[bp_i[index]]); |
1550 | 0 | get_found_bp(bp_i, found_bps.back()->blueprints_, found_bps, ++index); |
1551 | 0 | } |
1552 | 0 | else |
1553 | 0 | { |
1554 | 0 | if (found_bps.size() < 2) |
1555 | 0 | { |
1556 | 0 | found_bps.clear(); |
1557 | 0 | found_bps.push_back(blueprints_[bp_i[index]]); |
1558 | 0 | } |
1559 | 0 | else |
1560 | 0 | { |
1561 | 0 | found_bps.pop_back(); |
1562 | 0 | Blueprint* last_element = found_bps.back(); |
1563 | 0 | found_bps.push_back(last_element->blueprints_[bp_i[index]]); |
1564 | 0 | } |
1565 | 0 | get_found_bp(bp_i, found_bps.back()->blueprints_, found_bps, ++index); |
1566 | 0 | } |
1567 | 0 | } |
1568 | 0 | } |
1569 | | |
1570 | 0 | CatchallRule& get_catch_all(const routing_handle_result& found) { |
1571 | 0 | std::vector<Blueprint*> bps_found; |
1572 | 0 | get_found_bp(found.blueprint_indices, blueprints_, bps_found); |
1573 | 0 | if (!bps_found.empty()) { |
1574 | 0 | for (size_t i = bps_found.size() - 1; i > 0; i--) |
1575 | 0 | { |
1576 | 0 | if (bps_found[i]->catchall_rule().has_handler()) { |
1577 | 0 | return bps_found[i]->catchall_rule(); |
1578 | 0 | } |
1579 | 0 | } |
1580 | 0 | } |
1581 | 0 | return catchall_rule_; |
1582 | 0 | } |
1583 | | |
1584 | | std::string get_error(const routing_handle_result& found) |
1585 | 0 | { |
1586 | 0 | const std::string EMPTY; |
1587 | 0 |
|
1588 | 0 | std::vector<Blueprint*> bps_found; |
1589 | 0 | get_found_bp(found.blueprint_indices, blueprints_, bps_found); |
1590 | 0 | if (!bps_found.empty()) { |
1591 | 0 | for (size_t i = bps_found.size() - 1; i > 0; i--) { |
1592 | 0 | if (bps_found[i]->catchall_rule().has_handler()) { |
1593 | 0 | #ifdef CROW_ENABLE_DEBUG |
1594 | 0 | return std::string("Redirected to Blueprint \"" + bps_found[i]->prefix() + "\" Catchall rule"); |
1595 | 0 | #else |
1596 | 0 | return EMPTY; |
1597 | 0 | #endif |
1598 | 0 | } |
1599 | 0 | } |
1600 | 0 | } else if (catchall_rule_.has_handler()) { |
1601 | 0 | #ifdef CROW_ENABLE_DEBUG |
1602 | 0 | return std::string("Redirected to global Catchall rule"); |
1603 | 0 | #else |
1604 | 0 | return EMPTY; |
1605 | 0 | #endif |
1606 | 0 | } |
1607 | 0 | return EMPTY; |
1608 | 0 | } |
1609 | | |
1610 | | std::unique_ptr<routing_handle_result> handle_initial(request& req, response& res) |
1611 | 0 | { |
1612 | 0 | HTTPMethod method_actual = req.method; |
1613 | 0 |
|
1614 | 0 | std::unique_ptr<routing_handle_result> found{ |
1615 | 0 | new routing_handle_result( |
1616 | 0 | 0, |
1617 | 0 | std::vector<size_t>(), |
1618 | 0 | routing_params(), |
1619 | 0 | HTTPMethod::InternalMethodCount)}; // This is always returned to avoid a null pointer dereference. |
1620 | 0 |
|
1621 | 0 | // NOTE(EDev): This most likely will never run since the parser should handle this situation and close the connection before it gets here. |
1622 | 0 | if (CROW_UNLIKELY(req.method >= HTTPMethod::InternalMethodCount)) |
1623 | 0 | return found; |
1624 | 0 | else if (req.method == HTTPMethod::Head) |
1625 | 0 | { |
1626 | 0 | *found = per_methods_[static_cast<int>(method_actual)].trie.find(req.url); |
1627 | 0 | // support HEAD requests using GET if not defined as method for the requested URL |
1628 | 0 | if (!found->rule_index) |
1629 | 0 | { |
1630 | 0 | method_actual = HTTPMethod::Get; |
1631 | 0 | *found = per_methods_[static_cast<int>(method_actual)].trie.find(req.url); |
1632 | 0 | if (!found->rule_index) // If a route is still not found, return a 404 without executing the rest of the HEAD specific code. |
1633 | 0 | { |
1634 | 0 | CROW_LOG_DEBUG << "Cannot match rules " << req.url; |
1635 | 0 | res = response(404); //TODO(EDev): Should this redirect to catchall? |
1636 | 0 | res.end(); |
1637 | 0 | return found; |
1638 | 0 | } |
1639 | 0 | } |
1640 | 0 |
|
1641 | 0 | res.skip_body = true; |
1642 | 0 | found->method = method_actual; |
1643 | 0 | return found; |
1644 | 0 | } |
1645 | 0 | else if (req.method == HTTPMethod::Options) |
1646 | 0 | { |
1647 | 0 | std::string allow = "OPTIONS, HEAD"; |
1648 | 0 |
|
1649 | 0 | if (req.url == "/*") |
1650 | 0 | { |
1651 | 0 | for (int i = 0; i < static_cast<int>(HTTPMethod::InternalMethodCount); i++) |
1652 | 0 | { |
1653 | 0 | if (static_cast<int>(HTTPMethod::Head) == i) |
1654 | 0 | continue; // HEAD is always allowed |
1655 | 0 |
|
1656 | 0 | if (!per_methods_[i].trie.is_empty()) |
1657 | 0 | { |
1658 | 0 | allow.append(", "); |
1659 | 0 | allow.append(method_name(static_cast<HTTPMethod>(i))); |
1660 | 0 | } |
1661 | 0 | } |
1662 | 0 | #ifdef CROW_RETURNS_OK_ON_HTTP_OPTIONS_REQUEST |
1663 | 0 | res = response(crow::status::OK); |
1664 | 0 | #else |
1665 | 0 | res = response(crow::status::NO_CONTENT); |
1666 | 0 | #endif |
1667 | 0 |
|
1668 | 0 | res.set_header("Allow", allow); |
1669 | 0 | res.end(); |
1670 | 0 | found->method = method_actual; |
1671 | 0 | return found; |
1672 | 0 | } |
1673 | 0 | else |
1674 | 0 | { |
1675 | 0 | bool rules_matched = false; |
1676 | 0 | for (int i = 0; i < static_cast<int>(HTTPMethod::InternalMethodCount); i++) |
1677 | 0 | { |
1678 | 0 | if (per_methods_[i].trie.find(req.url).rule_index) |
1679 | 0 | { |
1680 | 0 | rules_matched = true; |
1681 | 0 |
|
1682 | 0 | if (static_cast<int>(HTTPMethod::Head) == i) |
1683 | 0 | continue; // HEAD is always allowed |
1684 | 0 |
|
1685 | 0 | allow.append(", "); |
1686 | 0 | allow.append(method_name(static_cast<HTTPMethod>(i))); |
1687 | 0 | } |
1688 | 0 | } |
1689 | 0 | if (rules_matched) |
1690 | 0 | { |
1691 | 0 | #ifdef CROW_RETURNS_OK_ON_HTTP_OPTIONS_REQUEST |
1692 | 0 | res = response(crow::status::OK); |
1693 | 0 | #else |
1694 | 0 | res = response(crow::status::NO_CONTENT); |
1695 | 0 | #endif |
1696 | 0 | res.set_header("Allow", allow); |
1697 | 0 | res.end(); |
1698 | 0 | found->method = method_actual; |
1699 | 0 | return found; |
1700 | 0 | } |
1701 | 0 | else |
1702 | 0 | { |
1703 | 0 | CROW_LOG_DEBUG << "Cannot match rules " << req.url; |
1704 | 0 | res = response(404); //TODO(EDev): Should this redirect to catchall? |
1705 | 0 | res.end(); |
1706 | 0 | return found; |
1707 | 0 | } |
1708 | 0 | } |
1709 | 0 | } |
1710 | 0 | else // Every request that isn't a HEAD or OPTIONS request |
1711 | 0 | { |
1712 | 0 | *found = per_methods_[static_cast<int>(method_actual)].trie.find(req.url); |
1713 | 0 | // TODO(EDev): maybe ending the else here would allow the requests coming from above (after removing the return statement) to be checked on whether they actually point to a route |
1714 | 0 | if (!found->rule_index) |
1715 | 0 | { |
1716 | 0 | for (auto& per_method : per_methods_) |
1717 | 0 | { |
1718 | 0 | if (per_method.trie.find(req.url).rule_index) //Route found, but in another method |
1719 | 0 | { |
1720 | 0 | res.code = 405; |
1721 | 0 | found->catch_all = true; |
1722 | 0 | CROW_LOG_DEBUG << "Cannot match method " << req.url << " " |
1723 | 0 | << method_name(method_actual) << ". " << get_error(*found); |
1724 | 0 | return found; |
1725 | 0 | } |
1726 | 0 | } |
1727 | 0 | //Route does not exist anywhere |
1728 | 0 |
|
1729 | 0 | res.code = 404; |
1730 | 0 | found->catch_all = true; |
1731 | 0 | CROW_LOG_DEBUG << "Cannot match rules " << req.url << ". " << get_error(*found); |
1732 | 0 | return found; |
1733 | 0 | } |
1734 | 0 |
|
1735 | 0 | found->method = method_actual; |
1736 | 0 | return found; |
1737 | 0 | } |
1738 | 0 | } |
1739 | | |
1740 | | template<typename App> |
1741 | | void handle(request& req, response& res, routing_handle_result found) |
1742 | | { |
1743 | | if (found.catch_all) { |
1744 | | auto catch_all = get_catch_all(found); |
1745 | | if (catch_all.has_handler()) { |
1746 | | try |
1747 | | { |
1748 | | catch_all.handler_(req, res); |
1749 | | } |
1750 | | catch (...) |
1751 | | { |
1752 | | exception_handler_(res); |
1753 | | } |
1754 | | } |
1755 | | res.end(); |
1756 | | } else { |
1757 | | HTTPMethod method_actual = found.method; |
1758 | | const auto& rules = per_methods_[static_cast<int>(method_actual)].rules; |
1759 | | const size_t rule_index = found.rule_index; |
1760 | | |
1761 | | if (rule_index >= rules.size()) |
1762 | | throw std::runtime_error("Trie internal structure corrupted!"); |
1763 | | if (rule_index == RULE_SPECIAL_REDIRECT_SLASH) { |
1764 | | CROW_LOG_INFO << "Redirecting to a url with trailing slash: " << req.url; |
1765 | | res = response(301); |
1766 | | res.add_header("Location", req.url + "/"); |
1767 | | res.end(); |
1768 | | } else { |
1769 | | CROW_LOG_DEBUG << "Matched rule '" << rules[rule_index]->rule_ << "' " << static_cast<uint64_t>(req. |
1770 | | method) << " / " << rules[rule_index]->get_methods(); |
1771 | | |
1772 | | try { |
1773 | | BaseRule &rule = *rules[rule_index]; |
1774 | | handle_rule<App>(rule, req, res, found.r_params); |
1775 | | } catch (...) { |
1776 | | exception_handler_(res); |
1777 | | res.end(); |
1778 | | } |
1779 | | } |
1780 | | } |
1781 | | } |
1782 | | |
1783 | | template<typename App> |
1784 | | typename std::enable_if<std::tuple_size<typename App::mw_container_t>::value != 0, void>::type |
1785 | | handle_rule(BaseRule& rule, crow::request& req, crow::response& res, const crow::routing_params& rp) |
1786 | | { |
1787 | | if (!rule.mw_indices_.empty()) |
1788 | | { |
1789 | | auto& ctx = *reinterpret_cast<typename App::context_t*>(req.middleware_context); |
1790 | | auto& container = *reinterpret_cast<typename App::mw_container_t*>(req.middleware_container); |
1791 | | detail::middleware_call_criteria_dynamic<false> crit_fwd(rule.mw_indices_.indices()); |
1792 | | |
1793 | | auto glob_completion_handler = std::move(res.complete_request_handler_); |
1794 | | res.complete_request_handler_ = [] {}; |
1795 | | |
1796 | | detail::middleware_call_helper<decltype(crit_fwd), |
1797 | | 0, typename App::context_t, typename App::mw_container_t>(crit_fwd, container, req, res, ctx); |
1798 | | |
1799 | | if (res.completed_) |
1800 | | { |
1801 | | glob_completion_handler(); |
1802 | | return; |
1803 | | } |
1804 | | |
1805 | | res.complete_request_handler_ = [&rule, &ctx, &container, &req, &res, glob_completion_handler] { |
1806 | | detail::middleware_call_criteria_dynamic<true> crit_bwd(rule.mw_indices_.indices()); |
1807 | | |
1808 | | detail::after_handlers_call_helper< |
1809 | | decltype(crit_bwd), |
1810 | | std::tuple_size<typename App::mw_container_t>::value - 1, |
1811 | | typename App::context_t, |
1812 | | typename App::mw_container_t>(crit_bwd, container, ctx, req, res); |
1813 | | glob_completion_handler(); |
1814 | | }; |
1815 | | } |
1816 | | rule.handle(req, res, rp); |
1817 | | } |
1818 | | |
1819 | | template<typename App> |
1820 | | typename std::enable_if<std::tuple_size<typename App::mw_container_t>::value == 0, void>::type |
1821 | | handle_rule(BaseRule& rule, crow::request& req, crow::response& res, const crow::routing_params& rp) |
1822 | | { |
1823 | | rule.handle(req, res, rp); |
1824 | | } |
1825 | | |
1826 | | void debug_print() |
1827 | 0 | { |
1828 | 0 | for (int i = 0; i < static_cast<int>(HTTPMethod::InternalMethodCount); i++) |
1829 | 0 | { |
1830 | 0 | Trie& trie_ = per_methods_[i].trie; |
1831 | 0 | if (!trie_.is_empty()) |
1832 | 0 | { |
1833 | 0 | CROW_LOG_DEBUG << method_name(static_cast<HTTPMethod>(i)); |
1834 | 0 | trie_.debug_print(); |
1835 | 0 | } |
1836 | 0 | } |
1837 | 0 | } |
1838 | | |
1839 | | std::vector<Blueprint*>& blueprints() |
1840 | 0 | { |
1841 | 0 | return blueprints_; |
1842 | 0 | } |
1843 | | |
1844 | | std::function<void(crow::response&)>& exception_handler() |
1845 | 0 | { |
1846 | 0 | return exception_handler_; |
1847 | 0 | } |
1848 | | |
1849 | | static void default_exception_handler(response& res) |
1850 | 0 | { |
1851 | 0 | // any uncaught exceptions become 500s |
1852 | 0 | res = response(500); |
1853 | 0 |
|
1854 | 0 | try |
1855 | 0 | { |
1856 | 0 | throw; |
1857 | 0 | } |
1858 | 0 | catch (const bad_request& e) |
1859 | 0 | { |
1860 | 0 | res = response (400); |
1861 | 0 | res.body = e.what(); |
1862 | 0 | } |
1863 | 0 | catch (const std::exception& e) |
1864 | 0 | { |
1865 | 0 | CROW_LOG_ERROR << "An uncaught exception occurred: " << e.what(); |
1866 | 0 | } |
1867 | 0 | catch (...) |
1868 | 0 | { |
1869 | 0 | CROW_LOG_ERROR << "An uncaught exception occurred. The type was unknown so no information was available."; |
1870 | 0 | } |
1871 | 0 | } |
1872 | | |
1873 | | private: |
1874 | | CatchallRule catchall_rule_; |
1875 | | |
1876 | | struct PerMethod |
1877 | | { |
1878 | | std::vector<BaseRule*> rules; |
1879 | | Trie trie; |
1880 | | |
1881 | | // rule index 0, 1 has special meaning; preallocate it to avoid duplication. |
1882 | | PerMethod(): |
1883 | 0 | rules(2) {} |
1884 | | }; |
1885 | | std::array<PerMethod, static_cast<int>(HTTPMethod::InternalMethodCount)> per_methods_; |
1886 | | std::vector<std::unique_ptr<BaseRule>> all_rules_; |
1887 | | std::vector<Blueprint*> blueprints_; |
1888 | | std::function<void(crow::response&)> exception_handler_ = &default_exception_handler; |
1889 | | }; |
1890 | | } // namespace crow |