Line data Source code
1 : #include "http_server_base.hpp"
2 : #include "server_connection.hpp"
3 : #include "request.hpp"
4 : #include "response.hpp"
5 : #include "../../util/logger.hpp"
6 : #include "../../util/base64.hpp"
7 : #include <filesystem>
8 :
9 : namespace thinger::http {
10 :
11 : // Route registration methods - GET
12 133 : route& http_server_base::get(const std::string& path, route_callback_response_only handler) {
13 133 : return router_[method::GET][path] = handler;
14 : }
15 :
16 0 : route& http_server_base::get(const std::string& path, route_callback_json_response handler) {
17 0 : return router_[method::GET][path] = handler;
18 : }
19 :
20 3788 : route& http_server_base::get(const std::string& path, route_callback_request_response handler) {
21 3788 : return router_[method::GET][path] = handler;
22 : }
23 :
24 0 : route& http_server_base::get(const std::string& path, route_callback_request_json_response handler) {
25 0 : return router_[method::GET][path] = handler;
26 : }
27 :
28 : // Route registration methods - POST
29 26 : route& http_server_base::post(const std::string& path, route_callback_response_only handler) {
30 26 : return router_[method::POST][path] = handler;
31 : }
32 :
33 11 : route& http_server_base::post(const std::string& path, route_callback_json_response handler) {
34 11 : return router_[method::POST][path] = handler;
35 : }
36 :
37 816 : route& http_server_base::post(const std::string& path, route_callback_request_response handler) {
38 816 : return router_[method::POST][path] = handler;
39 : }
40 :
41 0 : route& http_server_base::post(const std::string& path, route_callback_request_json_response handler) {
42 0 : return router_[method::POST][path] = handler;
43 : }
44 :
45 : // Route registration methods - PUT
46 10 : route& http_server_base::put(const std::string& path, route_callback_response_only handler) {
47 10 : return router_[method::PUT][path] = handler;
48 : }
49 :
50 0 : route& http_server_base::put(const std::string& path, route_callback_json_response handler) {
51 0 : return router_[method::PUT][path] = handler;
52 : }
53 :
54 303 : route& http_server_base::put(const std::string& path, route_callback_request_response handler) {
55 303 : return router_[method::PUT][path] = handler;
56 : }
57 :
58 11 : route& http_server_base::put(const std::string& path, route_callback_request_json_response handler) {
59 11 : return router_[method::PUT][path] = handler;
60 : }
61 :
62 : // Route registration methods - DELETE
63 22 : route& http_server_base::del(const std::string& path, route_callback_response_only handler) {
64 22 : return router_[method::DELETE][path] = handler;
65 : }
66 :
67 0 : route& http_server_base::del(const std::string& path, route_callback_json_response handler) {
68 0 : return router_[method::DELETE][path] = handler;
69 : }
70 :
71 287 : route& http_server_base::del(const std::string& path, route_callback_request_response handler) {
72 287 : return router_[method::DELETE][path] = handler;
73 : }
74 :
75 2 : route& http_server_base::del(const std::string& path, route_callback_request_json_response handler) {
76 2 : return router_[method::DELETE][path] = handler;
77 : }
78 :
79 : // Route registration methods - PATCH
80 10 : route& http_server_base::patch(const std::string& path, route_callback_response_only handler) {
81 10 : return router_[method::PATCH][path] = handler;
82 : }
83 :
84 0 : route& http_server_base::patch(const std::string& path, route_callback_json_response handler) {
85 0 : return router_[method::PATCH][path] = handler;
86 : }
87 :
88 285 : route& http_server_base::patch(const std::string& path, route_callback_request_response handler) {
89 285 : return router_[method::PATCH][path] = handler;
90 : }
91 :
92 0 : route& http_server_base::patch(const std::string& path, route_callback_request_json_response handler) {
93 0 : return router_[method::PATCH][path] = handler;
94 : }
95 :
96 : // Route registration methods - HEAD
97 4 : route& http_server_base::head(const std::string& path, route_callback_response_only handler) {
98 4 : return router_[method::HEAD][path] = handler;
99 : }
100 :
101 4 : route& http_server_base::head(const std::string& path, route_callback_request_response handler) {
102 4 : return router_[method::HEAD][path] = handler;
103 : }
104 :
105 : // Route registration methods - OPTIONS
106 2 : route& http_server_base::options(const std::string& path, route_callback_response_only handler) {
107 2 : return router_[method::OPTIONS][path] = handler;
108 : }
109 :
110 0 : route& http_server_base::options(const std::string& path, route_callback_request_response handler) {
111 0 : return router_[method::OPTIONS][path] = handler;
112 : }
113 :
114 : // Middleware
115 22 : void http_server_base::use(middleware_function middleware) {
116 22 : middlewares_.push_back(std::move(middleware));
117 22 : }
118 :
119 : // Basic Auth helpers
120 8 : void http_server_base::set_basic_auth(const std::string& path_prefix,
121 : const std::string& realm,
122 : auth_verify_function verify) {
123 8 : use([path_prefix, realm, verify](request& req, response& res, std::function<void()> next) {
124 : // Get the request path
125 8 : auto http_request = req.get_http_request();
126 8 : if (!http_request) {
127 0 : next();
128 0 : return;
129 : }
130 :
131 8 : const std::string& path = http_request->get_uri();
132 :
133 : // Check if this path requires auth
134 8 : if (!path.starts_with(path_prefix)) {
135 2 : next();
136 2 : return;
137 : }
138 :
139 : // Check for Authorization header
140 6 : if (!http_request->has_header("Authorization")) {
141 2 : res.status(http_response::status::unauthorized);
142 6 : res.header("WWW-Authenticate", "Basic realm=\"" + realm + "\"");
143 6 : res.send("Authentication required");
144 2 : return;
145 : }
146 :
147 4 : auto auth_header = http_request->get_header("Authorization");
148 4 : if (!auth_header.starts_with("Basic ")) {
149 0 : res.status(http_response::status::unauthorized);
150 0 : res.header("WWW-Authenticate", "Basic realm=\"" + realm + "\"");
151 0 : res.send("Invalid authentication");
152 0 : return;
153 : }
154 :
155 : // Decode base64 credentials
156 4 : auto encoded = auth_header.substr(6);
157 4 : std::string decoded;
158 : try {
159 4 : decoded = ::thinger::util::base64::decode(encoded);
160 0 : } catch (...) {
161 0 : res.status(http_response::status::unauthorized);
162 0 : res.send("Invalid credentials format");
163 0 : return;
164 0 : }
165 :
166 : // Parse username:password
167 4 : auto colon_pos = decoded.find(':');
168 4 : if (colon_pos == std::string::npos) {
169 0 : res.status(http_response::status::unauthorized);
170 0 : res.send("Invalid credentials format");
171 0 : return;
172 : }
173 :
174 4 : std::string username = decoded.substr(0, colon_pos);
175 4 : std::string password = decoded.substr(colon_pos + 1);
176 :
177 : // Verify credentials
178 4 : if (verify(username, password)) {
179 2 : req.set_auth_user(username);
180 2 : next();
181 : } else {
182 2 : res.status(http_response::status::unauthorized);
183 6 : res.header("WWW-Authenticate", "Basic realm=\"" + realm + "\"");
184 8 : res.send("Invalid username or password");
185 : }
186 8 : });
187 8 : }
188 :
189 8 : void http_server_base::set_basic_auth(const std::string& path_prefix,
190 : const std::string& realm,
191 : const std::string& username,
192 : const std::string& password) {
193 8 : set_basic_auth(path_prefix, realm,
194 16 : [username, password](const std::string& u, const std::string& p) {
195 4 : return u == username && p == password;
196 : });
197 8 : }
198 :
199 0 : void http_server_base::set_basic_auth(const std::string& path_prefix,
200 : const std::string& realm,
201 : const std::map<std::string, std::string>& users) {
202 0 : set_basic_auth(path_prefix, realm,
203 0 : [users](const std::string& u, const std::string& p) {
204 0 : auto it = users.find(u);
205 0 : return it != users.end() && it->second == p;
206 : });
207 0 : }
208 :
209 : // Fallback handlers
210 2 : void http_server_base::set_not_found_handler(route_callback_response_only handler) {
211 2 : router_.set_fallback_handler([handler](request& req, response& res) {
212 0 : handler(res);
213 0 : });
214 2 : }
215 :
216 8 : void http_server_base::set_not_found_handler(route_callback_request_response handler) {
217 8 : router_.set_fallback_handler(handler);
218 8 : }
219 :
220 : // Configuration
221 14 : void http_server_base::enable_cors(bool enabled) {
222 14 : cors_enabled_ = enabled;
223 14 : }
224 :
225 68 : void http_server_base::enable_ssl(bool enabled) {
226 68 : ssl_enabled_ = enabled;
227 68 : }
228 :
229 14 : void http_server_base::set_connection_timeout(std::chrono::seconds timeout) {
230 14 : connection_timeout_ = timeout;
231 14 : }
232 :
233 12 : void http_server_base::set_max_body_size(size_t size) {
234 12 : max_body_size_ = size;
235 12 : }
236 :
237 20 : void http_server_base::set_max_listening_attempts(int attempts) {
238 20 : max_listening_attempts_ = attempts;
239 20 : }
240 :
241 : // Static file serving
242 12 : void http_server_base::serve_static(const std::string& url_prefix,
243 : const std::string& directory,
244 : bool fallback_to_index) {
245 : namespace fs = std::filesystem;
246 :
247 18 : get(url_prefix + "/:path*", [directory, fallback_to_index](request& req, response& res) {
248 : // Get the requested path
249 6 : std::string path = req["path"];
250 :
251 : // Construct full file path
252 6 : fs::path file_path = fs::path(directory) / path;
253 :
254 : // Security: ensure the resolved path is within the directory
255 6 : auto canonical_dir = fs::canonical(directory);
256 6 : auto canonical_file = fs::weakly_canonical(file_path);
257 :
258 6 : if (!canonical_file.string().starts_with(canonical_dir.string())) {
259 0 : res.status(http_response::status::forbidden);
260 0 : res.send("Access denied");
261 0 : return;
262 : }
263 :
264 : // Check if file exists
265 6 : if (fs::exists(canonical_file)) {
266 4 : if (fs::is_regular_file(canonical_file)) {
267 2 : res.send_file(canonical_file);
268 2 : } else if (fs::is_directory(canonical_file) && fallback_to_index) {
269 : // Try index.html in the directory
270 2 : auto index_file = canonical_file / "index.html";
271 2 : if (fs::exists(index_file) && fs::is_regular_file(index_file)) {
272 2 : res.send_file(index_file);
273 : } else {
274 0 : res.status(http_response::status::not_found);
275 0 : res.send("Not found");
276 : }
277 2 : } else {
278 0 : res.status(http_response::status::not_found);
279 0 : res.send("Not found");
280 : }
281 : } else {
282 2 : res.status(http_response::status::not_found);
283 8 : res.send("Not found");
284 : }
285 6 : });
286 12 : }
287 :
288 : // Server control
289 571 : bool http_server_base::listen(const std::string& host, uint16_t port) {
290 571 : host_ = host;
291 571 : port_ = std::to_string(port);
292 571 : use_unix_socket_ = false;
293 :
294 : // Create socket server using virtual method
295 571 : socket_server_ = create_socket_server(host, port_);
296 571 : if (!socket_server_) {
297 0 : LOG_ERROR("Failed to create socket server");
298 0 : return false;
299 : }
300 :
301 : // Configure socket server
302 571 : socket_server_->set_max_listening_attempts(max_listening_attempts_);
303 :
304 : // Setup connection handler
305 571 : setup_connection_handler();
306 :
307 : // Start listening
308 571 : return socket_server_->start();
309 : }
310 :
311 39 : bool http_server_base::listen_unix(const std::string& unix_path) {
312 39 : unix_path_ = unix_path;
313 39 : use_unix_socket_ = true;
314 :
315 : // Create Unix socket server using virtual method
316 39 : socket_server_ = create_unix_socket_server(unix_path);
317 39 : if (!socket_server_) {
318 0 : LOG_ERROR("Failed to create Unix socket server");
319 0 : return false;
320 : }
321 :
322 : // Configure socket server
323 39 : socket_server_->set_max_listening_attempts(max_listening_attempts_);
324 :
325 : // Setup connection handler
326 39 : setup_connection_handler();
327 :
328 : // Start listening
329 39 : return socket_server_->start();
330 : }
331 :
332 0 : bool http_server_base::start(uint16_t port) {
333 0 : return start("0.0.0.0", port);
334 : }
335 :
336 0 : bool http_server_base::start(uint16_t port, const std::function<void()> &on_listening) {
337 0 : return start("0.0.0.0", port, on_listening);
338 : }
339 :
340 0 : bool http_server_base::start(const std::string& host, uint16_t port) {
341 0 : return start(host, port, nullptr);
342 : }
343 :
344 0 : bool http_server_base::start(const std::string& host, uint16_t port, const std::function<void()> &on_listening) {
345 0 : if (!listen(host, port)) {
346 0 : return false;
347 : }
348 0 : if (on_listening) {
349 0 : on_listening();
350 : }
351 0 : wait();
352 0 : return true;
353 : }
354 :
355 0 : bool http_server_base::start_unix(const std::string& unix_path) {
356 0 : return start_unix(unix_path, nullptr);
357 : }
358 :
359 0 : bool http_server_base::start_unix(const std::string& unix_path, const std::function<void()> &on_listening) {
360 0 : if (!listen_unix(unix_path)) {
361 0 : return false;
362 : }
363 0 : if (on_listening) {
364 0 : on_listening();
365 : }
366 0 : wait();
367 0 : return true;
368 : }
369 :
370 652 : bool http_server_base::stop() {
371 652 : if (socket_server_) {
372 604 : bool result = socket_server_->stop();
373 604 : socket_server_.reset();
374 604 : return result;
375 : }
376 48 : return false;
377 : }
378 :
379 78 : bool http_server_base::is_listening() const {
380 78 : return socket_server_ != nullptr && socket_server_->is_running();
381 : }
382 :
383 540 : uint16_t http_server_base::local_port() const {
384 540 : return socket_server_ ? socket_server_->local_port() : 0;
385 : }
386 :
387 :
388 : // Private methods
389 610 : void http_server_base::setup_connection_handler() {
390 610 : socket_server_->set_handler([this](std::shared_ptr<asio::socket> socket) {
391 : // Create HTTP connection
392 846 : auto connection = std::make_shared<server_connection>(socket);
393 :
394 : // Set request handler — awaitable coroutine with three-way dispatch
395 1752 : connection->set_handler([this](std::shared_ptr<request> req) -> awaitable<void> {
396 : auto http_connection = req->get_http_connection();
397 : auto stream = req->get_http_stream();
398 : auto http_request = req->get_http_request();
399 :
400 : if (!http_connection || !stream) {
401 : LOG_ERROR("Invalid connection or stream");
402 : co_return;
403 : }
404 :
405 : // 1. Match route
406 : auto* matched_route = router_.find_route(req);
407 :
408 : // 2. Run middlewares (synchronous)
409 : bool passed = false;
410 902 : execute_middlewares(*req, stream, 0, [&passed]() { passed = true; });
411 : if (!passed) co_return;
412 :
413 : // 3. Three-way dispatch
414 : response res(http_connection, stream, http_request, cors_enabled_);
415 :
416 : if (!matched_route) {
417 : // No route matched → fallback / 404
418 : router_.handle_unmatched(req);
419 : } else if (matched_route->is_deferred_body()) {
420 : // DEFERRED: handler reads body at its discretion
421 : co_await matched_route->handle_request_coro(*req, res);
422 : } else if (http_request->has_pending_body()) {
423 : // PENDING BODY: check size limit, read, then dispatch
424 : if (!http_request->is_chunked_transfer() && req->content_length() > max_body_size_) {
425 : res.error(http_response::status::payload_too_large, "Payload Too Large");
426 : co_return;
427 : }
428 : req->set_max_body_size(max_body_size_);
429 : bool ok = co_await req->read_body();
430 : if (!ok) {
431 : res.error(http_response::status::payload_too_large, "Payload Too Large");
432 : co_return;
433 : }
434 : matched_route->handle_request(*req, res);
435 : } else {
436 : // NO BODY: dispatch directly
437 : matched_route->handle_request(*req, res);
438 : }
439 :
440 : co_return;
441 1812 : });
442 :
443 : // Start handling the connection with configured timeout
444 846 : connection->start(connection_timeout_);
445 846 : });
446 610 : }
447 :
448 910 : void http_server_base::execute_middlewares(request& req, std::shared_ptr<http_stream> stream,
449 : size_t index, std::function<void()> final_handler) {
450 910 : if (index >= middlewares_.size()) {
451 : // All middlewares executed, call final handler
452 902 : final_handler();
453 902 : return;
454 : }
455 :
456 : // Create response object for middleware
457 8 : auto connection = req.get_http_connection();
458 8 : auto http_request = req.get_http_request();
459 :
460 8 : if (connection) {
461 8 : response res(connection, stream, http_request, cors_enabled_);
462 :
463 : // Execute current middleware
464 8 : middlewares_[index](req, res, [this, &req, stream, index, final_handler]() {
465 : // Middleware called next(), execute next middleware
466 4 : execute_middlewares(req, stream, index + 1, final_handler);
467 4 : });
468 8 : }
469 8 : }
470 :
471 : } // namespace thinger::http
|