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.append(", ");
1596 allow.append(method_name(static_cast<HTTPMethod>(i)));
1597 }
1598 }
1599#ifdef CROW_RETURNS_OK_ON_HTTP_OPTIONS_REQUEST
1600 res = response(crow::status::OK);
1601#else
1602 res = response(crow::status::NO_CONTENT);
1603#endif
1604
1605 res.set_header("Allow", allow);
1606 res.end();
1607 found->method = method_actual;
1608 return found;
1609 }
1610 else
1611 {
1612 bool rules_matched = false;
1613 for (int i = 0; i < static_cast<int>(HTTPMethod::InternalMethodCount); i++)
1614 {
1615 if (per_methods_[i].trie.find(req.url).rule_index)
1616 {
1617 rules_matched = true;
1618
1619 if (static_cast<int>(HTTPMethod::Head) == i)
1620 continue; // HEAD is always allowed
1621
1622 allow.append(", ");
1623 allow.append(method_name(static_cast<HTTPMethod>(i)));
1624 }
1625 }
1626 if (rules_matched)
1627 {
1628#ifdef CROW_RETURNS_OK_ON_HTTP_OPTIONS_REQUEST
1629 res = response(crow::status::OK);
1630#else
1631 res = response(crow::status::NO_CONTENT);
1632#endif
1633 res.set_header("Allow", allow);
1634 res.end();
1635 found->method = method_actual;
1636 return found;
1637 }
1638 else
1639 {
1640 CROW_LOG_DEBUG << "Cannot match rules " << req.url;
1641 res = response(404); //TODO(EDev): Should this redirect to catchall?
1642 res.end();
1643 return found;
1644 }
1645 }
1646 }
1647 else // Every request that isn't a HEAD or OPTIONS request
1648 {
1649 *found = per_methods_[static_cast<int>(method_actual)].trie.find(req.url);
1650 // 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
1651 if (!found->rule_index)
1652 {
1653 for (auto& per_method : per_methods_)
1654 {
1655 if (per_method.trie.find(req.url).rule_index) //Route found, but in another method
1656 {
1657 const std::string error_message(get_error(405, *found, req, res));
1658 CROW_LOG_DEBUG << "Cannot match method " << req.url << " " << method_name(method_actual) << ". " << error_message;
1659 res.end();
1660 return found;
1661 }
1662 }
1663 //Route does not exist anywhere
1664
1665 const std::string error_message(get_error(404, *found, req, res));
1666 CROW_LOG_DEBUG << "Cannot match rules " << req.url << ". " << error_message;
1667 res.end();
1668 return found;
1669 }
1670
1671 found->method = method_actual;
1672 return found;
1673 }
1674 }
1675
1676 template<typename App>
1677 void handle(request& req, response& res, routing_handle_result found)
1678 {
1679 HTTPMethod method_actual = found.method;
1680 auto& rules = per_methods_[static_cast<int>(method_actual)].rules;
1681 unsigned rule_index = found.rule_index;
1682
1683 if (rule_index >= rules.size())
1684 throw std::runtime_error("Trie internal structure corrupted!");
1685
1686 if (rule_index == RULE_SPECIAL_REDIRECT_SLASH)
1687 {
1688 CROW_LOG_INFO << "Redirecting to a url with trailing slash: " << req.url;
1689 res = response(301);
1690 res.add_header("Location", req.url + "/");
1691 res.end();
1692 return;
1693 }
1694
1695 CROW_LOG_DEBUG << "Matched rule '" << rules[rule_index]->rule_ << "' " << static_cast<uint32_t>(req.method) << " / " << rules[rule_index]->get_methods();
1696
1697 try
1698 {
1699 BaseRule& rule = *rules[rule_index];
1700 handle_rule<App>(rule, req, res, found.r_params);
1701 }
1702 catch (...)
1703 {
1704 exception_handler_(res);
1705 res.end();
1706 return;
1707 }
1708 }
1709
1710 template<typename App>
1711 typename std::enable_if<std::tuple_size<typename App::mw_container_t>::value != 0, void>::type
1712 handle_rule(BaseRule& rule, crow::request& req, crow::response& res, const crow::routing_params& rp)
1713 {
1714 if (!rule.mw_indices_.empty())
1715 {
1716 auto& ctx = *reinterpret_cast<typename App::context_t*>(req.middleware_context);
1717 auto& container = *reinterpret_cast<typename App::mw_container_t*>(req.middleware_container);
1718 detail::middleware_call_criteria_dynamic<false> crit_fwd(rule.mw_indices_.indices());
1719
1720 auto glob_completion_handler = std::move(res.complete_request_handler_);
1721 res.complete_request_handler_ = [] {};
1722
1723 detail::middleware_call_helper<decltype(crit_fwd),
1724 0, typename App::context_t, typename App::mw_container_t>(crit_fwd, container, req, res, ctx);
1725
1726 if (res.completed_)
1727 {
1728 glob_completion_handler();
1729 return;
1730 }
1731
1732 res.complete_request_handler_ = [&rule, &ctx, &container, &req, &res, glob_completion_handler] {
1733 detail::middleware_call_criteria_dynamic<true> crit_bwd(rule.mw_indices_.indices());
1734
1735 detail::after_handlers_call_helper<
1736 decltype(crit_bwd),
1737 std::tuple_size<typename App::mw_container_t>::value - 1,
1738 typename App::context_t,
1739 typename App::mw_container_t>(crit_bwd, container, ctx, req, res);
1740 glob_completion_handler();
1741 };
1742 }
1743 rule.handle(req, res, rp);
1744 }
1745
1746 template<typename App>
1747 typename std::enable_if<std::tuple_size<typename App::mw_container_t>::value == 0, void>::type
1748 handle_rule(BaseRule& rule, crow::request& req, crow::response& res, const crow::routing_params& rp)
1749 {
1750 rule.handle(req, res, rp);
1751 }
1752
1753 void debug_print()
1754 {
1755 for (int i = 0; i < static_cast<int>(HTTPMethod::InternalMethodCount); i++)
1756 {
1757 Trie& trie_ = per_methods_[i].trie;
1758 if (!trie_.is_empty())
1759 {
1760 CROW_LOG_DEBUG << method_name(static_cast<HTTPMethod>(i));
1761 trie_.debug_print();
1762 }
1763 }
1764 }
1765
1766 std::vector<Blueprint*>& blueprints()
1767 {
1768 return blueprints_;
1769 }
1770
1771 std::function<void(crow::response&)>& exception_handler()
1772 {
1773 return exception_handler_;
1774 }
1775
1776 static void default_exception_handler(response& res)
1777 {
1778 // any uncaught exceptions become 500s
1779 res = response(500);
1780
1781 try
1782 {
1783 throw;
1784 }
1785 catch (const bad_request& e)
1786 {
1787 res = response (400);
1788 res.body = e.what();
1789 }
1790 catch (const std::exception& e)
1791 {
1792 CROW_LOG_ERROR << "An uncaught exception occurred: " << e.what();
1793 }
1794 catch (...)
1795 {
1796 CROW_LOG_ERROR << "An uncaught exception occurred. The type was unknown so no information was available.";
1797 }
1798 }
1799
1800 private:
1801 CatchallRule catchall_rule_;
1802
1803 struct PerMethod
1804 {
1805 std::vector<BaseRule*> rules;
1806 Trie trie;
1807
1808 // rule index 0, 1 has special meaning; preallocate it to avoid duplication.
1809 PerMethod():
1810 rules(2) {}
1811 };
1812 std::array<PerMethod, static_cast<int>(HTTPMethod::InternalMethodCount)> per_methods_;
1813 std::vector<std::unique_ptr<BaseRule>> all_rules_;
1814 std::vector<Blueprint*> blueprints_;
1815 std::function<void(crow::response&)> exception_handler_ = &default_exception_handler;
1816 };
1817} // 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