Line data Source code
1 : #ifndef THINGER_HTTP_ROUTE_HANDLER_HPP
2 : #define THINGER_HTTP_ROUTE_HANDLER_HPP
3 :
4 : #include <deque>
5 : #include <map>
6 : #include <memory>
7 : #include "route.hpp"
8 : #include "route_builder.hpp"
9 : #include "route_tree.hpp"
10 :
11 : namespace thinger::http {
12 :
13 : class route_handler {
14 : public:
15 : // Fallback for unmatched requests
16 : using fallback_handler = route_callback_awaitable;
17 :
18 : route_handler();
19 11913 : virtual ~route_handler() = default;
20 :
21 : // Access route builders for different HTTP methods
22 : route_builder operator[](method http_method);
23 :
24 : // Find the matching route for a request (without executing the handler), setting it
25 : // and its path parameters on the request. Routes are matched by path segments, by
26 : // priority: static segments, then segments with a regex constraint, then parameters,
27 : // then parameters that may match '/' (see route_tree), whatever their registration order.
28 : // A HEAD request matching no HEAD route is matched with the GET routes (the response
29 : // drops the body), so an explicit HEAD route wins.
30 : const route* find_route(std::shared_ptr<request> req);
31 :
32 : // Handle an unmatched request through the response: the fallback handler, else 404 for
33 : // a method with routes (HEAD has those of GET), or 405 for a method without any, with the
34 : // methods of the routes matching the path in its Allow header (HEAD wherever GET)
35 : thinger::awaitable<void> handle_unmatched(request& req, response& res);
36 :
37 : // Add a catch-all handler for unmatched routes, taking (request&, response&) or
38 : // (response&), synchronous or coroutine. An empty one (nullptr, or an empty
39 : // std::function) removes it: unmatched requests are answered 404/405 again.
40 : template<request_handler_callable F>
41 848 : void set_fallback_handler(F&& handler) {
42 848 : fallback_handler_ = make_route_callback(std::forward<F>(handler));
43 848 : }
44 3 : void set_fallback_handler(std::nullptr_t) { fallback_handler_ = nullptr; }
45 :
46 : // Get all registered routes (useful for API documentation). Routes are never moved
47 : // once registered: references to them stay valid while more routes are added.
48 80 : const std::map<method, std::deque<route>>& get_routes() const { return routes_; }
49 :
50 : // Shared JSON schemas (OpenAPI components), referenced from route schemas with
51 : // {"$ref": "#/components/schemas/<name>"}. Register them before the routes using them.
52 19 : void add_schema_component(const std::string& name, nlohmann::json schema) {
53 19 : schema_components_[name] = std::move(schema);
54 19 : }
55 53 : const nlohmann::json& get_schema_components() const { return schema_components_; }
56 :
57 : private:
58 : std::map<method, std::deque<route>> routes_;
59 : // The same routes, by method, as trees of path segments for matching
60 : std::map<method, route_tree> trees_;
61 : nlohmann::json schema_components_ = nlohmann::json::object();
62 : fallback_handler fallback_handler_;
63 :
64 : // Route of a method matching a path, appending its parameters to `captures`; null if none
65 90902 : const route_tree::leaf* match(method http_method, std::string_view path,
66 : detail::route_captures& captures) const {
67 90902 : auto tree = trees_.find(http_method);
68 90902 : return tree != trees_.end() ? tree->second.find(path, captures) : nullptr;
69 : }
70 :
71 : // Friend class to allow route_builder to access routes_
72 : friend class route_builder;
73 : };
74 :
75 : } // namespace thinger::http
76 :
77 : #endif // THINGER_HTTP_ROUTE_HANDLER_HPP
|