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