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