LCOV - code coverage report
Current view: top level - http/server - http_server_base.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 69.1 % 230 159
Test Date: 2026-02-20 15:38:22 Functions: 70.7 % 58 41

            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
        

Generated by: LCOV version 2.0-1