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