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