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 214 : route& http_server_base::get(const std::string& path, route_callback_response_only handler) {
13 214 : 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 4323 : route& http_server_base::get(const std::string& path, route_callback_request_response handler) {
21 4323 : 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 35 : route& http_server_base::post(const std::string& path, route_callback_response_only handler) {
30 35 : return router_[method::POST][path] = handler;
31 : }
32 :
33 227 : route& http_server_base::post(const std::string& path, route_callback_json_response handler) {
34 227 : return router_[method::POST][path] = handler;
35 : }
36 :
37 824 : route& http_server_base::post(const std::string& path, route_callback_request_response handler) {
38 824 : return router_[method::POST][path] = handler;
39 : }
40 :
41 54 : route& http_server_base::post(const std::string& path, route_callback_request_json_response handler) {
42 54 : 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 54 : route& http_server_base::put(const std::string& path, route_callback_json_response handler) {
51 54 : return router_[method::PUT][path] = handler;
52 : }
53 :
54 320 : route& http_server_base::put(const std::string& path, route_callback_request_response handler) {
55 320 : 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 31 : route& http_server_base::del(const std::string& path, route_callback_response_only handler) {
64 31 : 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 295 : route& http_server_base::del(const std::string& path, route_callback_request_response handler) {
72 295 : 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 54 : route& http_server_base::patch(const std::string& path, route_callback_json_response handler) {
85 54 : return router_[method::PATCH][path] = handler;
86 : }
87 :
88 314 : route& http_server_base::patch(const std::string& path, route_callback_request_response handler) {
89 314 : 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 25 : route& http_server_base::head(const std::string& path, route_callback_response_only handler) {
98 25 : 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 23 : route& http_server_base::options(const std::string& path, route_callback_response_only handler) {
107 23 : 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 38 : void http_server_base::use(middleware_function middleware) {
116 38 : middlewares_.push_back(std::move(middleware));
117 38 : }
118 :
119 : // Basic Auth helpers
120 22 : void http_server_base::set_basic_auth(const std::string& path_prefix,
121 : const std::string& realm,
122 : auth_verify_function verify) {
123 22 : use([path_prefix, realm, verify](request& req, response& res, std::function<void()> next) {
124 : // Get the request path
125 22 : auto http_request = req.get_http_request();
126 22 : if (!http_request) {
127 0 : next();
128 0 : return;
129 : }
130 :
131 22 : const std::string& path = http_request->get_uri();
132 :
133 : // Check if this path requires auth
134 22 : if (!path.starts_with(path_prefix)) {
135 2 : next();
136 2 : return;
137 : }
138 :
139 : // Check for Authorization header
140 20 : if (!http_request->has_header("Authorization")) {
141 4 : res.status(http_response::status::unauthorized);
142 12 : res.header("WWW-Authenticate", "Basic realm=\"" + realm + "\"");
143 12 : res.send("Authentication required");
144 4 : return;
145 : }
146 :
147 16 : auto auth_header = http_request->get_header("Authorization");
148 16 : if (!auth_header.starts_with("Basic ")) {
149 2 : res.status(http_response::status::unauthorized);
150 6 : res.header("WWW-Authenticate", "Basic realm=\"" + realm + "\"");
151 6 : res.send("Invalid authentication");
152 2 : return;
153 : }
154 :
155 : // Decode base64 credentials
156 14 : auto encoded = auth_header.substr(6);
157 14 : std::string decoded;
158 : try {
159 14 : 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 14 : auto colon_pos = decoded.find(':');
168 14 : 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 14 : std::string username = decoded.substr(0, colon_pos);
175 14 : std::string password = decoded.substr(colon_pos + 1);
176 :
177 : // Verify credentials
178 14 : if (verify(username, password)) {
179 8 : req.set_auth_user(username);
180 8 : next();
181 : } else {
182 6 : res.status(http_response::status::unauthorized);
183 18 : res.header("WWW-Authenticate", "Basic realm=\"" + realm + "\"");
184 24 : res.send("Invalid username or password");
185 : }
186 24 : });
187 22 : }
188 :
189 12 : 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 12 : set_basic_auth(path_prefix, realm,
194 24 : [username, password](const std::string& u, const std::string& p) {
195 4 : return u == username && p == password;
196 : });
197 12 : }
198 :
199 6 : 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 6 : set_basic_auth(path_prefix, realm,
203 12 : [users](const std::string& u, const std::string& p) {
204 6 : auto it = users.find(u);
205 6 : return it != users.end() && it->second == p;
206 : });
207 6 : }
208 :
209 : // Fallback handlers
210 0 : void http_server_base::set_not_found_handler(route_callback_response_only handler) {
211 0 : router_.set_fallback_handler([handler](request& req, response& res) {
212 0 : handler(res);
213 0 : });
214 0 : }
215 :
216 10 : void http_server_base::set_not_found_handler(route_callback_request_response handler) {
217 10 : router_.set_fallback_handler(handler);
218 10 : }
219 :
220 : // Configuration
221 16 : void http_server_base::enable_cors(bool enabled) {
222 16 : cors_enabled_ = enabled;
223 16 : }
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 20 : void http_server_base::serve_static(const std::string& url_prefix,
243 : const std::string& directory,
244 : const std::string& fallback) {
245 : namespace fs = std::filesystem;
246 :
247 : // Normalize route: avoid double slash when prefix is "/"
248 20 : std::string route = url_prefix;
249 20 : if (!route.empty() && route.back() == '/') route.pop_back();
250 20 : route += "/:path(.*)";
251 :
252 34 : get(route, [directory, fallback](request& req, response& res) {
253 14 : std::string path = req["path"];
254 :
255 14 : auto canonical_dir = fs::canonical(directory);
256 14 : bool has_fallback = !fallback.empty();
257 :
258 : // Empty path means root request — try fallback file directly
259 14 : if (path.empty()) {
260 4 : if (has_fallback) {
261 2 : auto fallback_file = canonical_dir / fallback;
262 2 : if (fs::exists(fallback_file) && fs::is_regular_file(fallback_file)) {
263 2 : res.send_file(fallback_file);
264 2 : return;
265 : }
266 2 : }
267 2 : res.status(http_response::status::not_found);
268 6 : res.send("Not found");
269 2 : return;
270 : }
271 :
272 : // Construct full file path
273 10 : fs::path file_path = fs::path(directory) / path;
274 10 : auto canonical_file = fs::weakly_canonical(file_path);
275 :
276 : // Security: ensure the resolved path is within the directory
277 10 : if (!canonical_file.string().starts_with(canonical_dir.string())) {
278 0 : res.status(http_response::status::forbidden);
279 0 : res.send("Access denied");
280 0 : return;
281 : }
282 :
283 : // Serve file if it exists
284 10 : if (fs::exists(canonical_file)) {
285 6 : if (fs::is_regular_file(canonical_file)) {
286 4 : res.send_file(canonical_file);
287 4 : return;
288 : }
289 2 : if (fs::is_directory(canonical_file) && has_fallback) {
290 2 : auto fallback_file = canonical_file / fallback;
291 2 : if (fs::exists(fallback_file) && fs::is_regular_file(fallback_file)) {
292 2 : res.send_file(fallback_file);
293 2 : return;
294 : }
295 2 : }
296 : }
297 :
298 : // SPA fallback: serve root fallback file for non-existent paths
299 4 : if (has_fallback) {
300 2 : auto fallback_file = canonical_dir / fallback;
301 2 : if (fs::exists(fallback_file) && fs::is_regular_file(fallback_file)) {
302 2 : res.send_file(fallback_file);
303 2 : return;
304 : }
305 2 : }
306 :
307 2 : res.status(http_response::status::not_found);
308 6 : res.send("Not found");
309 42 : });
310 20 : }
311 :
312 : // Server control
313 708 : bool http_server_base::listen(const std::string& host, uint16_t port) {
314 708 : host_ = host;
315 708 : port_ = std::to_string(port);
316 708 : use_unix_socket_ = false;
317 :
318 : // Create socket server using virtual method
319 708 : socket_server_ = create_socket_server(host, port_);
320 708 : if (!socket_server_) {
321 0 : LOG_ERROR("Failed to create socket server");
322 0 : return false;
323 : }
324 :
325 : // Configure socket server
326 708 : socket_server_->set_max_listening_attempts(max_listening_attempts_);
327 :
328 : // Setup connection handler
329 708 : setup_connection_handler();
330 :
331 : // Start listening
332 708 : return socket_server_->start();
333 : }
334 :
335 48 : bool http_server_base::listen_unix(const std::string& unix_path) {
336 48 : unix_path_ = unix_path;
337 48 : use_unix_socket_ = true;
338 :
339 : // Create Unix socket server using virtual method
340 48 : socket_server_ = create_unix_socket_server(unix_path);
341 48 : if (!socket_server_) {
342 0 : LOG_ERROR("Failed to create Unix socket server");
343 0 : return false;
344 : }
345 :
346 : // Configure socket server
347 48 : socket_server_->set_max_listening_attempts(max_listening_attempts_);
348 :
349 : // Setup connection handler
350 48 : setup_connection_handler();
351 :
352 : // Start listening
353 48 : return socket_server_->start();
354 : }
355 :
356 0 : bool http_server_base::start(uint16_t port) {
357 0 : return start("0.0.0.0", port);
358 : }
359 :
360 0 : bool http_server_base::start(uint16_t port, const std::function<void()> &on_listening) {
361 0 : return start("0.0.0.0", port, on_listening);
362 : }
363 :
364 0 : bool http_server_base::start(const std::string& host, uint16_t port) {
365 0 : return start(host, port, nullptr);
366 : }
367 :
368 54 : bool http_server_base::start(const std::string& host, uint16_t port, const std::function<void()> &on_listening) {
369 54 : if (!listen(host, port)) {
370 0 : return false;
371 : }
372 54 : if (on_listening) {
373 54 : on_listening();
374 : }
375 54 : wait();
376 54 : return true;
377 : }
378 :
379 0 : bool http_server_base::start_unix(const std::string& unix_path) {
380 0 : return start_unix(unix_path, nullptr);
381 : }
382 :
383 0 : bool http_server_base::start_unix(const std::string& unix_path, const std::function<void()> &on_listening) {
384 0 : if (!listen_unix(unix_path)) {
385 0 : return false;
386 : }
387 0 : if (on_listening) {
388 0 : on_listening();
389 : }
390 0 : wait();
391 0 : return true;
392 : }
393 :
394 798 : bool http_server_base::stop() {
395 798 : if (socket_server_) {
396 750 : bool result = socket_server_->stop();
397 750 : socket_server_.reset();
398 750 : return result;
399 : }
400 48 : return false;
401 : }
402 :
403 78 : bool http_server_base::is_listening() const {
404 78 : return socket_server_ != nullptr && socket_server_->is_running();
405 : }
406 :
407 677 : uint16_t http_server_base::local_port() const {
408 677 : return socket_server_ ? socket_server_->local_port() : 0;
409 : }
410 :
411 :
412 : // Private methods
413 756 : void http_server_base::setup_connection_handler() {
414 756 : socket_server_->set_handler([this](std::shared_ptr<asio::socket> socket) {
415 : // Create HTTP connection
416 992 : auto connection = std::make_shared<server_connection>(socket);
417 :
418 : // Set request handler — awaitable coroutine with three-way dispatch
419 2044 : connection->set_handler([this](std::shared_ptr<request> req) -> awaitable<void> {
420 : auto http_connection = req->get_http_connection();
421 : auto stream = req->get_http_stream();
422 : auto http_request = req->get_http_request();
423 :
424 : if (!http_connection || !stream) {
425 : LOG_ERROR("Invalid connection or stream");
426 : co_return;
427 : }
428 :
429 : // 1. Match route
430 : auto* matched_route = router_.find_route(req);
431 :
432 : // 2. Run middlewares (synchronous)
433 : bool passed = false;
434 1038 : execute_middlewares(*req, stream, 0, [&passed]() { passed = true; });
435 : if (!passed) co_return;
436 :
437 : // 3. Three-way dispatch
438 : response res(http_connection, stream, http_request, cors_enabled_);
439 :
440 : if (!matched_route) {
441 : // No route matched → fallback / 404
442 : router_.handle_unmatched(req);
443 : } else if (matched_route->is_deferred_body()) {
444 : // DEFERRED: handler reads body at its discretion
445 : co_await matched_route->handle_request_coro(*req, res);
446 : } else if (http_request->has_pending_body()) {
447 : // PENDING BODY: check size limit, read, then dispatch
448 : if (!http_request->is_chunked_transfer() && req->content_length() > max_body_size_) {
449 : res.error(http_response::status::payload_too_large, "Payload Too Large");
450 : co_return;
451 : }
452 : req->set_max_body_size(max_body_size_);
453 : bool ok = co_await req->read_body();
454 : if (!ok) {
455 : res.error(http_response::status::payload_too_large, "Payload Too Large");
456 : co_return;
457 : }
458 : matched_route->handle_request(*req, res);
459 : } else {
460 : // NO BODY: dispatch directly
461 : matched_route->handle_request(*req, res);
462 : }
463 :
464 : co_return;
465 2104 : });
466 :
467 : // Start handling the connection with configured timeout
468 992 : connection->start(connection_timeout_);
469 992 : });
470 756 : }
471 :
472 1070 : void http_server_base::execute_middlewares(request& req, std::shared_ptr<http_stream> stream,
473 : size_t index, std::function<void()> final_handler) {
474 1070 : if (index >= middlewares_.size()) {
475 : // All middlewares executed, call final handler
476 1038 : final_handler();
477 1038 : return;
478 : }
479 :
480 : // Create response object for middleware
481 32 : auto connection = req.get_http_connection();
482 32 : auto http_request = req.get_http_request();
483 :
484 32 : if (connection) {
485 32 : response res(connection, stream, http_request, cors_enabled_);
486 :
487 : // Execute current middleware
488 32 : middlewares_[index](req, res, [this, &req, stream, index, final_handler]() {
489 : // Middleware called next(), execute next middleware
490 18 : execute_middlewares(req, stream, index + 1, final_handler);
491 18 : });
492 32 : }
493 32 : }
494 :
495 : } // namespace thinger::http
|