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