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