Line data Source code
1 : #include "route_handler.hpp"
2 : #include "../response.hpp"
3 : #include "../../../util/logger.hpp"
4 : #include <regex>
5 :
6 : namespace thinger::http {
7 :
8 725 : route_handler::route_handler() = default;
9 :
10 5728 : route_builder route_handler::operator[](method http_method) {
11 5728 : return route_builder(http_method, routes_[http_method]);
12 : }
13 :
14 0 : void route_handler::enable_cors(bool enabled) {
15 0 : cors_enabled_ = enabled;
16 :
17 0 : if (enabled) {
18 : // Add OPTIONS handler for all routes
19 0 : (*this)[method::OPTIONS][".*"] = [](request& req, response& res) {
20 0 : auto response = std::make_shared<http_response>();
21 0 : response->set_status(http_response::status::no_content);
22 :
23 : // Add CORS headers
24 0 : response->add_header("Access-Control-Allow-Origin", "*");
25 0 : response->add_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH");
26 0 : response->add_header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
27 0 : response->add_header("Access-Control-Max-Age", "86400");
28 :
29 0 : res.send_response(response);
30 0 : };
31 : }
32 0 : }
33 :
34 10 : void route_handler::set_fallback_handler(std::function<void(request&, response&)> handler) {
35 10 : fallback_handler_ = std::move(handler);
36 10 : }
37 :
38 20 : void route_handler::send_error_response(std::shared_ptr<request> req, http_response::status status) {
39 20 : auto connection = req->get_http_connection();
40 20 : auto stream = req->get_http_stream();
41 20 : auto http_request = req->get_http_request();
42 :
43 20 : if (connection && stream && http_request) {
44 20 : response res(connection, stream, http_request, cors_enabled_);
45 20 : res.status(status);
46 60 : res.send("");
47 20 : }
48 20 : }
49 :
50 906 : const route* route_handler::find_route(std::shared_ptr<request> req) {
51 906 : auto http_request = req->get_http_request();
52 906 : const auto& request_method = http_request->get_method();
53 906 : const auto path = http_request->get_path();
54 :
55 906 : LOG_DEBUG("Finding route for {} {}", get_method(request_method), path);
56 :
57 : // Find routes for this method
58 906 : auto method_routes = routes_.find(request_method);
59 906 : if (method_routes == routes_.end()) {
60 8 : LOG_DEBUG("No routes registered for method {}", get_method(request_method));
61 8 : return nullptr;
62 : }
63 :
64 : // Try to match against registered routes
65 2292 : for (auto& route : method_routes->second) {
66 2277 : std::smatch matches;
67 2277 : if (route.matches(path, matches)) {
68 883 : LOG_DEBUG("Matched route: {}", route.get_pattern());
69 :
70 : // Extract parameters from the match
71 1123 : for (size_t i = 0; i < route.get_parameters().size(); ++i) {
72 240 : const auto& param = route.get_parameters()[i];
73 240 : if (i + 1 < matches.size() && matches[i + 1].matched) {
74 231 : req->set_uri_parameter(param, matches[i + 1].str());
75 : }
76 : }
77 :
78 : // Set the matched route in request
79 883 : req->set_matched_route(&route);
80 :
81 : // Check authorization if required
82 883 : if (route.get_auth_level() != auth_level::PUBLIC) {
83 0 : LOG_DEBUG("Route requires authentication level: {}",
84 : static_cast<int>(route.get_auth_level()));
85 : }
86 :
87 883 : return &route;
88 : }
89 2277 : }
90 :
91 15 : LOG_DEBUG("No matching route found for {}", path);
92 15 : return nullptr;
93 906 : }
94 :
95 23 : void route_handler::handle_unmatched(std::shared_ptr<request> req) {
96 23 : auto http_request = req->get_http_request();
97 :
98 23 : if (fallback_handler_) {
99 3 : auto connection = req->get_http_connection();
100 3 : auto stream = req->get_http_stream();
101 3 : if (connection && stream) {
102 3 : response res(connection, stream, http_request, cors_enabled_);
103 3 : fallback_handler_(*req, res);
104 3 : return;
105 3 : }
106 6 : }
107 :
108 : // Check if the method has no routes at all → 405, otherwise 404
109 20 : const auto& request_method = http_request->get_method();
110 20 : auto method_routes = routes_.find(request_method);
111 20 : if (method_routes == routes_.end()) {
112 8 : send_error_response(req, http_response::status::not_allowed);
113 : } else {
114 12 : send_error_response(req, http_response::status::not_found);
115 : }
116 23 : }
117 :
118 0 : bool route_handler::handle_request(std::shared_ptr<request> request) {
119 0 : auto* matched = find_route(request);
120 :
121 0 : if (!matched) {
122 0 : handle_unmatched(request);
123 0 : return true;
124 : }
125 :
126 : // Handle the request
127 : try {
128 0 : auto connection = request->get_http_connection();
129 0 : auto stream = request->get_http_stream();
130 0 : auto http_request = request->get_http_request();
131 0 : if (!connection || !stream) {
132 0 : LOG_ERROR("No connection or stream available");
133 0 : return false;
134 : }
135 :
136 0 : response res(connection, stream, http_request, cors_enabled_);
137 0 : matched->handle_request(*request, res);
138 0 : return true;
139 0 : } catch (const std::exception& e) {
140 0 : LOG_ERROR("Exception handling route: {}", e.what());
141 0 : send_error_response(request, http_response::status::internal_server_error);
142 0 : return true;
143 0 : }
144 : }
145 :
146 : } // namespace thinger::http
|