LCOV - code coverage report
Current view: top level - http/server - response.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 84.5 % 103 87
Test Date: 2026-02-20 15:38:22 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          943 :     bool ensure_not_responded() const {
      33          943 :         if (responded_) {
      34            0 :             LOG_ERROR("Response already sent");
      35            0 :             return false;
      36              :         }
      37          943 :         return true;
      38              :     }
      39              :     
      40         1755 :     void prepare_response() {
      41         1755 :         if (!response_) {
      42          872 :             response_ = std::make_shared<http_response>();
      43          872 :             response_->set_keep_alive(http_request_->keep_alive());
      44              :             
      45              :             // Add CORS headers if enabled
      46          872 :             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         1755 :     }
      54              :     
      55           51 :     static bool is_compressible_content_type(const std::string& content_type) {
      56              :         // Only compress text-based content types
      57           51 :         return content_type.starts_with("text/")
      58           48 :             || content_type.starts_with("application/json")
      59            0 :             || content_type.starts_with("application/xml")
      60            0 :             || content_type.starts_with("application/javascript")
      61            0 :             || content_type.starts_with("application/x-javascript")
      62           99 :             || content_type.starts_with("image/svg+xml");
      63              :     }
      64              : 
      65          828 :     void compress_response_if_needed() {
      66              :         // Only compress if there's a body worth compressing
      67          828 :         const auto& content = response_->get_content();
      68          837 :         if (content.size() < 200) return;
      69              : 
      70              :         // Don't compress if already compressed
      71           51 :         if (response_->has_header("Content-Encoding")) return;
      72              : 
      73              :         // Only compress text-based content types
      74           51 :         const auto& ct = response_->get_content_type();
      75           51 :         if (ct.empty() || !is_compressible_content_type(ct)) return;
      76              : 
      77              :         // Check what the client accepts
      78           51 :         std::string accept_encoding = http_request_->get_header("Accept-Encoding");
      79           51 :         if (accept_encoding.empty()) return;
      80              : 
      81           42 :         if (accept_encoding.find("gzip") != std::string::npos) {
      82           42 :             auto compressed = ::thinger::util::gzip::compress(content);
      83           42 :             if (compressed) {
      84           42 :                 response_->set_content(std::move(*compressed));
      85          210 :                 response_->add_header("Content-Encoding", "gzip");
      86              :             }
      87           42 :         } else if (accept_encoding.find("deflate") != std::string::npos) {
      88            0 :             auto compressed = ::thinger::util::deflate::compress(content);
      89            0 :             if (compressed) {
      90            0 :                 response_->set_content(std::move(*compressed));
      91            0 :                 response_->add_header("Content-Encoding", "deflate");
      92              :             }
      93            0 :         }
      94           51 :     }
      95              : 
      96          826 :     void send_prepared_response() {
      97          826 :         if (!ensure_not_responded()) return;
      98          826 :         prepare_response();
      99          826 :         compress_response_if_needed();
     100              : 
     101          826 :         if (auto conn = connection_.lock()) {
     102          820 :             if (auto str = stream_.lock()) {
     103          820 :                 conn->handle_stream(str, response_);
     104          820 :             }
     105          826 :         }
     106          826 :         responded_ = true;
     107              :     }
     108              : 
     109              : public:
     110          957 :     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          957 :         : connection_(connection), stream_(stream), http_request_(http_request), cors_enabled_(cors_enabled) {}
     115              : 
     116              :     // JSON response
     117          681 :     void json(const nlohmann::json& data, http::http_response::status status = http::http_response::status::ok) {
     118          681 :         prepare_response();
     119          681 :         response_->set_status(status);
     120         1362 :         response_->set_content(data.dump(), "application/json");
     121          681 :         send_prepared_response();
     122          681 :     }
     123              : 
     124              :     // Text response
     125           45 :     void send(const std::string& text, const std::string& content_type = "text/plain") {
     126           45 :         prepare_response();
     127           45 :         response_->set_content(text, content_type);
     128           45 :         send_prepared_response();
     129           45 :     }
     130              : 
     131              :     // HTML response
     132            2 :     void html(const std::string& html) {
     133            2 :         send(html, "text/html");
     134            2 :     }
     135              : 
     136              :     // Error response
     137           25 :     void error(http::http_response::status status, const std::string& message = "") {
     138           25 :         prepare_response();
     139           25 :         response_->set_status(status);
     140           25 :         if (!message.empty()) {
     141           69 :             response_->set_content(message, "text/plain");
     142              :         }
     143           25 :         send_prepared_response();
     144           25 :     }
     145              : 
     146              :     // Set status code (for building custom responses)
     147           34 :     void status(http::http_response::status s) {
     148           34 :         if (!ensure_not_responded()) return;
     149           34 :         prepare_response();
     150           34 :         response_->set_status(s);
     151              :     }
     152              : 
     153              :     // Set header (for building custom responses)
     154           23 :     void header(const std::string& key, const std::string& value) {
     155           23 :         if (!ensure_not_responded()) return;
     156           23 :         prepare_response();
     157           23 :         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