LCOV - code coverage report
Current view: top level - http/server - response.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 67.1 % 146 98
Test Date: 2026-02-20 15:38:22 Functions: 77.8 % 9 7

            Line data    Source code
       1              : #include "response.hpp"
       2              : #include "request.hpp"
       3              : #include "mime_types.hpp"
       4              : #include "../../util/logger.hpp"
       5              : #include "../../util/base64.hpp"
       6              : #include "../../util/sha1.hpp"
       7              : #include "../../asio/sockets/websocket.hpp"
       8              : #include <boost/algorithm/string.hpp>
       9              : #include "../common/http_data.hpp"
      10              : #include "../data/out_chunk.hpp"
      11              : #include <fstream>
      12              : #include <sstream>
      13              : 
      14              : namespace thinger::http {
      15              : 
      16              : // Redirect implementation
      17           67 : void response::redirect(const std::string& url, http::http_response::status redirect_type) {
      18           67 :     prepare_response();
      19           67 :     response_->set_status(redirect_type);
      20           67 :     response_->add_header(header::location, url);
      21           67 :     send_prepared_response();
      22           67 : }
      23              : 
      24              : // File sending implementation
      25           12 : void response::send_file(const std::filesystem::path& path, bool force_download) {
      26           16 :     if (!ensure_not_responded()) return;
      27              :     
      28              :     // For now, we'll implement basic file sending here
      29              :     // TODO: Integrate with simple_file_handler when it's updated to work with response
      30              :     
      31           12 :     if (!std::filesystem::exists(path)) {
      32            2 :         error(http_response::status::not_found, "File not found");
      33            2 :         return;
      34              :     }
      35              :     
      36           10 :     if (!std::filesystem::is_regular_file(path)) {
      37            2 :         error(http_response::status::forbidden, "Not a regular file");
      38            2 :         return;
      39              :     }
      40              :     
      41              :     // Read file content
      42            8 :     std::ifstream file(path, std::ios::binary);
      43            8 :     if (!file) {
      44            0 :         error(http_response::status::internal_server_error, "Failed to open file");
      45            0 :         return;
      46              :     }
      47              :     
      48              :     // Get file size
      49            8 :     file.seekg(0, std::ios::end);
      50            8 :     auto file_size = file.tellg();
      51            8 :     file.seekg(0, std::ios::beg);
      52              :     
      53              :     // Read file content
      54            8 :     std::string content(file_size, '\0');
      55            8 :     file.read(&content[0], file_size);
      56              :     
      57              :     // Determine content type using mime_types
      58            8 :     std::string content_type = mime_types::extension_to_type(path.extension().string());
      59              :     
      60              :     // Create response
      61            8 :     prepare_response();
      62            8 :     response_->set_status(http_response::status::ok);
      63            8 :     response_->set_content(content, content_type);
      64              :     
      65              :     // Add Content-Disposition header if force_download is true
      66            8 :     if (force_download) {
      67            6 :         response_->add_header("Content-Disposition", "attachment; filename=\"" + path.filename().string() + "\"");
      68              :     }
      69              :     
      70            8 :     send_prepared_response();
      71            8 : }
      72              : 
      73              : // WebSocket upgrade implementation
      74           38 : void response::upgrade_websocket(std::function<void(std::shared_ptr<websocket_connection>)> handler,
      75              :                                 const std::set<std::string>& supported_protocols) {
      76           38 :     if (!ensure_not_responded()) return;
      77              :     
      78           38 :     auto conn = connection_.lock();
      79           38 :     auto str = stream_.lock();
      80           38 :     if (!conn || !str) {
      81            0 :         error(http_response::status::internal_server_error, "Connection lost");
      82            0 :         return;
      83              :     }
      84              :     
      85              :     // Check if this is a WebSocket upgrade request
      86           38 :     if (!boost::iequals(http_request_->get_header(http::header::upgrade), "websocket")) {
      87            0 :         error(http_response::status::upgrade_required, "This service requires use of WebSockets");
      88            0 :         return;
      89              :     }
      90              :     
      91              :     // Check WebSocket protocol if specified
      92           38 :     auto protocol = http_request_->get_header("Sec-WebSocket-Protocol");
      93           38 :     if (!protocol.empty()) {
      94            8 :         LOG_DEBUG("Received WebSocket protocol: {}", protocol);
      95            8 :         if (!supported_protocols.contains(protocol)) {
      96            0 :             error(http_response::status::bad_request, "Unsupported WebSocket protocol");
      97            0 :             return;
      98              :         }
      99           30 :     } else if (!supported_protocols.empty()) {
     100            0 :         error(http_response::status::bad_request, "This method requires specifying a WebSocket protocol");
     101            0 :         return;
     102              :     }
     103              :     
     104              :     // Get WebSocket key
     105           38 :     auto ws_key = http_request_->get_header("Sec-WebSocket-Key");
     106           38 :     if (ws_key.empty()) {
     107            0 :         error(http_response::status::bad_request, "Missing Sec-WebSocket-Key header");
     108            0 :         return;
     109              :     }
     110              :     
     111              :     // Calculate WebSocket accept key
     112           38 :     std::string accept_key = ws_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
     113           38 :     std::string hash = ::thinger::util::sha1::hash(accept_key);
     114           38 :     accept_key = ::thinger::util::base64::encode(hash);
     115              :     
     116              :     // Create upgrade response
     117           38 :     prepare_response();
     118           38 :     response_->set_status(http_response::status::switching_protocols);
     119           76 :     response_->add_header(header::upgrade, "websocket");
     120           76 :     response_->add_header(header::connection, "Upgrade");
     121          114 :     response_->add_header("Sec-WebSocket-Accept", accept_key);
     122              :     
     123           38 :     if (!protocol.empty()) {
     124           24 :         response_->add_header("Sec-WebSocket-Protocol", protocol);
     125              :     }
     126              :     
     127              :     // Register callback to be executed after response is sent
     128           38 :     str->on_completed([handler = std::move(handler), conn, str]() {
     129              :         // Release the socket from HTTP connection
     130           38 :         auto socket = conn->release_socket();
     131           38 :         if (!socket) {
     132            0 :             LOG_ERROR("Failed to release socket for WebSocket upgrade");
     133            0 :             return;
     134              :         }
     135              :         
     136              :         // Create WebSocket from the socket
     137           38 :         auto websocket = std::make_shared<asio::websocket>(socket, true, true); // binary=true, server=true
     138           38 :         auto ws_connection = std::make_shared<websocket_connection>(websocket);
     139              :         
     140              :         // Call user handler
     141           38 :         handler(ws_connection);
     142              :         
     143              :         // Start the WebSocket connection
     144           38 :         ws_connection->start();
     145           38 :     });
     146              :     
     147              :     // Send the upgrade response
     148           38 :     conn->handle_stream(str, response_);
     149              :     
     150           38 :     responded_ = true;
     151           38 : }
     152              : 
     153              : // Server-Sent Events implementation
     154            0 : void response::start_sse(std::function<void(std::shared_ptr<sse_connection>)> handler) {
     155            0 :     if (!ensure_not_responded()) return;
     156              :     
     157            0 :     auto conn = connection_.lock();
     158            0 :     auto str = stream_.lock();
     159            0 :     if (!conn || !str) {
     160            0 :         error(http_response::status::internal_server_error, "Connection lost");
     161            0 :         return;
     162              :     }
     163              :     
     164              :     // Create SSE response headers
     165            0 :     prepare_response();
     166            0 :     response_->set_status(http_response::status::ok);
     167            0 :     response_->set_content_type("text/event-stream");
     168            0 :     response_->add_header("Cache-Control", "no-cache");
     169            0 :     response_->add_header("Connection", "keep-alive");
     170            0 :     response_->add_header("X-Accel-Buffering", "no"); // Disable nginx buffering
     171              :     
     172              :     // Register callback to be executed after headers are sent
     173            0 :     str->on_completed([handler = std::move(handler), conn]() {
     174              :         // Release the socket from HTTP connection
     175            0 :         auto socket = conn->release_socket();
     176            0 :         if (!socket) {
     177            0 :             LOG_ERROR("Failed to release socket for SSE");
     178            0 :             return;
     179              :         }
     180              :         
     181              :         // Create SSE connection
     182            0 :         auto sse_conn = std::make_shared<sse_connection>(socket);
     183              :         
     184              :         // Start the SSE connection
     185            0 :         sse_conn->start();
     186              :         
     187              :         // Call user handler
     188            0 :         handler(sse_conn);
     189            0 :     });
     190              :     
     191              :     // Send the SSE headers
     192            0 :     conn->handle_stream(str, response_);
     193              :     
     194            0 :     responded_ = true;
     195            0 : }
     196              : 
     197              : // Chunked response support
     198            8 : bool response::start_chunked(const std::string& content_type, http::http_response::status status) {
     199            8 :     if (!ensure_not_responded()) return false;
     200              : 
     201            8 :     auto conn = connection_.lock();
     202            8 :     auto str = stream_.lock();
     203            8 :     if (!conn || !str) {
     204            0 :         LOG_ERROR("Connection lost while starting chunked response");
     205            0 :         return false;
     206              :     }
     207              : 
     208              :     // Create chunked response headers
     209            8 :     prepare_response();
     210            8 :     response_->set_status(status);
     211            8 :     response_->set_content_type(content_type);
     212           32 :     response_->add_header("Transfer-Encoding", "chunked");
     213           32 :     response_->add_header("X-Content-Type-Options", "nosniff");
     214            8 :     response_->set_last_frame(false);
     215              : 
     216              :     // Send headers (stream stays open for subsequent chunks)
     217            8 :     conn->handle_stream(str, response_);
     218              : 
     219            8 :     responded_ = true;
     220            8 :     return true;
     221            8 : }
     222              : 
     223           10 : bool response::write_chunk(const std::string& data) {
     224           10 :     if (!responded_) {
     225            0 :         LOG_ERROR("Must call start_chunked() before writing chunks");
     226            0 :         return false;
     227              :     }
     228              : 
     229           10 :     auto conn = connection_.lock();
     230           10 :     auto str = stream_.lock();
     231           10 :     if (!conn || !str) {
     232            0 :         LOG_ERROR("Connection lost while writing chunk");
     233            0 :         return false;
     234              :     }
     235              : 
     236           10 :     auto chunk = std::make_shared<http_data>(std::make_shared<data::out_chunk>(data));
     237           10 :     chunk->set_last_frame(false);
     238           10 :     conn->handle_stream(str, chunk);
     239           10 :     return true;
     240           10 : }
     241              : 
     242            8 : bool response::end_chunked() {
     243            8 :     if (!responded_) {
     244            0 :         LOG_ERROR("Must call start_chunked() before ending chunks");
     245            0 :         return false;
     246              :     }
     247              : 
     248            8 :     auto conn = connection_.lock();
     249            8 :     auto str = stream_.lock();
     250            8 :     if (!conn || !str) {
     251            0 :         return false;
     252              :     }
     253              : 
     254              :     // Send final zero-length chunk to terminate the response
     255            8 :     auto chunk = std::make_shared<http_data>(std::make_shared<data::out_chunk>());
     256            8 :     chunk->set_last_frame(true);
     257            8 :     conn->handle_stream(str, chunk);
     258            8 :     return true;
     259            8 : }
     260              : 
     261              : } // namespace thinger::http
        

Generated by: LCOV version 2.0-1