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 863 : route_handler::route_handler() = default;
9 :
10 6844 : route_builder route_handler::operator[](method http_method) {
11 6844 : 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 22 : void route_handler::send_error_response(std::shared_ptr<request> req, http_response::status status) {
39 22 : auto connection = req->get_http_connection();
40 22 : auto stream = req->get_http_stream();
41 22 : auto http_request = req->get_http_request();
42 :
43 22 : if (connection && stream && http_request) {
44 22 : response res(connection, stream, http_request, cors_enabled_);
45 22 : res.status(status);
46 66 : res.send("");
47 22 : }
48 22 : }
49 :
50 1052 : const route* route_handler::find_route(std::shared_ptr<request> req) {
51 1052 : auto http_request = req->get_http_request();
52 1052 : const auto& request_method = http_request->get_method();
53 1052 : const auto path = http_request->get_path();
54 :
55 1052 : LOG_DEBUG("Finding route for {} {}", get_method(request_method), path);
56 :
57 : // Find routes for this method
58 1052 : auto method_routes = routes_.find(request_method);
59 1052 : if (method_routes == routes_.end()) {
60 10 : LOG_DEBUG("No routes registered for method {}", get_method(request_method));
61 10 : return nullptr;
62 : }
63 :
64 : // Try to match against registered routes
65 2726 : for (auto& route : method_routes->second) {
66 2709 : std::smatch matches;
67 2709 : if (route.matches(path, matches)) {
68 1025 : LOG_DEBUG("Matched route: {}", route.get_pattern());
69 :
70 : // Extract parameters from the match
71 1299 : for (size_t i = 0; i < route.get_parameters().size(); ++i) {
72 274 : const auto& param = route.get_parameters()[i];
73 274 : if (i + 1 < matches.size() && matches[i + 1].matched) {
74 251 : req->set_uri_parameter(param, matches[i + 1].str());
75 : }
76 : }
77 :
78 : // Set the matched route in request
79 1025 : req->set_matched_route(&route);
80 :
81 : // Check authorization if required
82 1025 : 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 1025 : return &route;
88 : }
89 2709 : }
90 :
91 17 : LOG_DEBUG("No matching route found for {}", path);
92 17 : return nullptr;
93 1052 : }
94 :
95 27 : void route_handler::handle_unmatched(std::shared_ptr<request> req) {
96 27 : auto http_request = req->get_http_request();
97 :
98 27 : if (fallback_handler_) {
99 5 : auto connection = req->get_http_connection();
100 5 : auto stream = req->get_http_stream();
101 5 : if (connection && stream) {
102 5 : response res(connection, stream, http_request, cors_enabled_);
103 5 : fallback_handler_(*req, res);
104 5 : return;
105 5 : }
106 10 : }
107 :
108 : // Check if the method has no routes at all → 405, otherwise 404
109 22 : const auto& request_method = http_request->get_method();
110 22 : auto method_routes = routes_.find(request_method);
111 22 : if (method_routes == routes_.end()) {
112 10 : send_error_response(req, http_response::status::not_allowed);
113 : } else {
114 12 : send_error_response(req, http_response::status::not_found);
115 : }
116 27 : }
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
|