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