LCOV - code coverage report
Current view: top level - http/server - response.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 92.2 % 103 95
Test Date: 2026-04-21 17:49:55 Functions: 93.3 % 15 14

            Line data    Source code
       1              : #ifndef THINGER_HTTP_SERVER_RESPONSE_HPP
       2              : #define THINGER_HTTP_SERVER_RESPONSE_HPP
       3              : 
       4              : #include "../common/http_response.hpp"
       5              : #include "../../util/logger.hpp"
       6              : #include "server_connection.hpp"
       7              : #include "http_stream.hpp"
       8              : #include "websocket_connection.hpp"
       9              : #include "sse_connection.hpp"
      10              : #include "../../util/compression.hpp"
      11              : #include <nlohmann/json.hpp>
      12              : #include <memory>
      13              : #include <functional>
      14              : #include <filesystem>
      15              : #include <set>
      16              : 
      17              : namespace thinger::http {
      18              : 
      19              : // Forward declarations
      20              : class websocket_connection;
      21              : class sse_connection;
      22              : 
      23              : class response {
      24              : private:
      25              :     std::weak_ptr<server_connection> connection_;
      26              :     std::weak_ptr<http_stream> stream_;
      27              :     std::shared_ptr<http::http_request> http_request_;
      28              :     std::shared_ptr<http_response> response_;
      29              :     bool responded_ = false;
      30              :     bool cors_enabled_ = false;
      31              : 
      32         1124 :     bool ensure_not_responded() const {
      33         1124 :         if (responded_) {
      34            0 :             LOG_ERROR("Response already sent");
      35            0 :             return false;
      36              :         }
      37         1124 :         return true;
      38              :     }
      39              :     
      40         2055 :     void prepare_response() {
      41         2055 :         if (!response_) {
      42         1018 :             response_ = std::make_shared<http_response>();
      43         1018 :             response_->set_keep_alive(http_request_->keep_alive());
      44              :             
      45              :             // Add CORS headers if enabled
      46         1018 :             if (cors_enabled_) {
      47            8 :                 response_->add_header("Access-Control-Allow-Origin", "*");
      48            8 :                 response_->add_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH");
      49            8 :                 response_->add_header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
      50           10 :                 response_->add_header("Access-Control-Allow-Credentials", "true");
      51              :             }
      52              :         }
      53         2055 :     }
      54              :     
      55           67 :     static bool is_compressible_content_type(const std::string& content_type) {
      56              :         // Only compress text-based content types
      57           67 :         return content_type.starts_with("text/")
      58           60 :             || content_type.starts_with("application/json")
      59            4 :             || content_type.starts_with("application/xml")
      60            2 :             || content_type.starts_with("application/javascript")
      61            2 :             || content_type.starts_with("application/x-javascript")
      62          127 :             || content_type.starts_with("image/svg+xml");
      63              :     }
      64              : 
      65          953 :     void compress_response_if_needed() {
      66              :         // Only compress if there's a body worth compressing
      67          953 :         const auto& content = response_->get_content();
      68          966 :         if (content.size() < 200) return;
      69              : 
      70              :         // Don't compress if already compressed
      71           67 :         if (response_->has_header("Content-Encoding")) return;
      72              : 
      73              :         // Only compress text-based content types
      74           67 :         const auto& ct = response_->get_content_type();
      75           67 :         if (ct.empty() || !is_compressible_content_type(ct)) return;
      76              : 
      77              :         // Check what the client accepts
      78           65 :         std::string accept_encoding = http_request_->get_header("Accept-Encoding");
      79           65 :         if (accept_encoding.empty()) return;
      80              : 
      81           54 :         if (accept_encoding.find("gzip") != std::string::npos) {
      82           52 :             auto compressed = ::thinger::util::gzip::compress(content);
      83           52 :             if (compressed) {
      84           52 :                 response_->set_content(std::move(*compressed));
      85          260 :                 response_->add_header("Content-Encoding", "gzip");
      86              :             }
      87           54 :         } else if (accept_encoding.find("deflate") != std::string::npos) {
      88            2 :             auto compressed = ::thinger::util::deflate::compress(content);
      89            2 :             if (compressed) {
      90            2 :                 response_->set_content(std::move(*compressed));
      91           10 :                 response_->add_header("Content-Encoding", "deflate");
      92              :             }
      93            2 :         }
      94           65 :     }
      95              : 
      96          951 :     void send_prepared_response() {
      97          951 :         if (!ensure_not_responded()) return;
      98          951 :         prepare_response();
      99          951 :         compress_response_if_needed();
     100              : 
     101          951 :         if (auto conn = connection_.lock()) {
     102          936 :             if (auto str = stream_.lock()) {
     103          936 :                 conn->handle_stream(str, response_);
     104          936 :             }
     105          951 :         }
     106          951 :         responded_ = true;
     107              :     }
     108              : 
     109              : public:
     110         1121 :     response(const std::shared_ptr<server_connection>& connection,
     111              :              const std::shared_ptr<http_stream>& stream, 
     112              :              const std::shared_ptr<http::http_request>& http_request,
     113              :              bool cors_enabled = false)
     114         1121 :         : connection_(connection), stream_(stream), http_request_(http_request), cors_enabled_(cors_enabled) {}
     115              : 
     116              :     // JSON response
     117          763 :     void json(const nlohmann::json& data, http::http_response::status status = http::http_response::status::ok) {
     118          763 :         prepare_response();
     119          763 :         response_->set_status(status);
     120         1526 :         response_->set_content(data.dump(), "application/json");
     121          763 :         send_prepared_response();
     122          763 :     }
     123              : 
     124              :     // Text response
     125           77 :     void send(const std::string& text, const std::string& content_type = "text/plain") {
     126           77 :         prepare_response();
     127           77 :         response_->set_content(text, content_type);
     128           77 :         send_prepared_response();
     129           77 :     }
     130              : 
     131              :     // HTML response
     132            4 :     void html(const std::string& html) {
     133            4 :         send(html, "text/html");
     134            4 :     }
     135              : 
     136              :     // Error response
     137           30 :     void error(http::http_response::status status, const std::string& message = "") {
     138           30 :         prepare_response();
     139           30 :         response_->set_status(status);
     140           30 :         if (!message.empty()) {
     141           84 :             response_->set_content(message, "text/plain");
     142              :         }
     143           30 :         send_prepared_response();
     144           30 :     }
     145              : 
     146              :     // Set status code (for building custom responses)
     147           49 :     void status(http::http_response::status s) {
     148           49 :         if (!ensure_not_responded()) return;
     149           49 :         prepare_response();
     150           49 :         response_->set_status(s);
     151              :     }
     152              : 
     153              :     // Set header (for building custom responses)
     154           37 :     void header(const std::string& key, const std::string& value) {
     155           37 :         if (!ensure_not_responded()) return;
     156           37 :         prepare_response();
     157           37 :         response_->add_header(key, value);
     158              :     }
     159              : 
     160              :     // Send raw http_response object (for advanced use cases)
     161            2 :     void send_response(const std::shared_ptr<http_response>& response) {
     162            2 :         if (!ensure_not_responded()) return;
     163            2 :         response_ = response;
     164              : 
     165              :         // Ensure keep-alive is set properly
     166            2 :         response_->set_keep_alive(http_request_->keep_alive());
     167              : 
     168              :         // Add CORS headers if enabled
     169            2 :         if (cors_enabled_) {
     170            0 :             response_->add_header("Access-Control-Allow-Origin", "*");
     171            0 :             response_->add_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH");
     172            0 :             response_->add_header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
     173            0 :             response_->add_header("Access-Control-Allow-Credentials", "true");
     174              :         }
     175              : 
     176            2 :         compress_response_if_needed();
     177              : 
     178            2 :         if (auto conn = connection_.lock()) {
     179            2 :             if (auto str = stream_.lock()) {
     180            2 :                 conn->handle_stream(str, response_);
     181            2 :             }
     182            2 :         }
     183            2 :         responded_ = true;
     184              :     }
     185              : 
     186              :     // WebSocket upgrade
     187              :     void upgrade_websocket(std::function<void(std::shared_ptr<websocket_connection>)> handler,
     188              :                           const std::set<std::string>& supported_protocols = {});
     189              : 
     190              :     // Server-Sent Events
     191              :     void start_sse(std::function<void(std::shared_ptr<sse_connection>)> handler);
     192              : 
     193              :     // File sending
     194              :     void send_file(const std::filesystem::path& path, bool force_download = false);
     195              :     
     196              :     // Redirect response
     197              :     void redirect(const std::string& url, http::http_response::status redirect_type = http::http_response::status::moved_temporarily);
     198              :     
     199              :     // Chunked response support
     200              :     bool start_chunked(const std::string& content_type, http::http_response::status status = http::http_response::status::ok);
     201              :     bool write_chunk(const std::string& data);
     202              :     bool end_chunked();
     203              : 
     204              :     // Check if response has been sent
     205            0 :     bool has_responded() const {
     206            0 :         return responded_;
     207              :     }
     208              : 
     209              :     // Get the underlying connection (for advanced use cases)
     210            2 :     std::shared_ptr<server_connection> get_connection() const {
     211            2 :         return connection_.lock();
     212              :     }
     213              : };
     214              : 
     215              : } // namespace thinger::http
     216              : 
     217              : #endif // THINGER_HTTP_SERVER_RESPONSE_HPP
        

Generated by: LCOV version 2.0-1