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