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