LCOV - code coverage report
Current view: top level - http/client - response_factory.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 100.0 % 19 19
Test Date: 2026-11-10 10:38:07 Functions: 100.0 % 14 14

            Line data    Source code
       1              : #ifndef THINGER_HTTP_RESPONSE_FACTORY_HPP
       2              : #define THINGER_HTTP_RESPONSE_FACTORY_HPP
       3              : 
       4              : #include <cstddef>
       5              : #include <functional>
       6              : #include <iterator>
       7              : #include <memory>
       8              : #include <string>
       9              : #include <string_view>
      10              : #include <type_traits>
      11              : #include <boost/logic/tribool.hpp>
      12              : #include "client_error.hpp"
      13              : #include "../common/chunked_decoder.hpp"
      14              : #include "../common/http_request.hpp"
      15              : 
      16              : namespace thinger::http {
      17              : 
      18              :     class http_response;
      19              : 
      20              :     /**
      21              :      * Strict parser of HTTP/1.x responses (RFC 9112), fed with what is read from the
      22              :      * connection after sending a request. The head is parsed line by line, and the body
      23              :      * delimited as RFC 9112 section 6.3 says, from the request method and the status:
      24              :      *  - none for responses to HEAD, 1xx, 204 and 304, whatever their headers, nor for 101 or
      25              :      *    a 2xx to CONNECT (what follows is another protocol);
      26              :      *  - "Transfer-Encoding: chunked": decoded strictly (chunked_decoder), trailers dropped;
      27              :      *  - Content-Length;
      28              :      *  - neither: the body ends with the connection (end_of_stream()).
      29              :      * Interim responses (1xx but 101) are skipped. Anything else fails the parsing, with
      30              :      * error() telling why: there is no recovery, as where the response ends, and so where
      31              :      * the next one on the connection starts, would be a guess.
      32              :      */
      33              :     class response_factory {
      34              :     public:
      35              :         /// Maximum response size when buffered (streaming bypasses it)
      36              :         static constexpr size_t DEFAULT_MAX_CONTENT_SIZE = 8 * 1048576;
      37              : 
      38              :         /// Limits of the head of each response (status line and header lines), as for requests
      39              :         static constexpr size_t max_header_lines = 100;
      40              :         static constexpr size_t max_header_section_size = 16 * 1024;
      41              : 
      42              :         /// How the body of a response is delimited
      43              :         enum class body_framing { none, content_length, chunked, until_close };
      44              : 
      45        17011 :         response_factory() = default;
      46              : 
      47              :         /// Method of the request the response answers: responses to HEAD and CONNECT have no
      48              :         /// body. Set it after reset()
      49         9869 :         void set_request_method(method request_method) { request_method_ = request_method; }
      50              : 
      51              :         /// Stop after the head of each response, interim ones included: the caller reads the
      52              :         /// body (as get_body_framing() says)
      53          642 :         void set_headers_only(bool headers_only) { headers_only_ = headers_only; }
      54              : 
      55              :         /// Parse some data. True when the response is complete (its head in headers-only
      56              :         /// mode), false on error (see error()), indeterminate when more data is required.
      57              :         /// `begin` is left where parsing stopped: past the end of the response when it is
      58              :         /// complete, so what follows it is in [begin, end).
      59              :         template<typename InputIterator>
      60       190983 :         boost::tribool parse_some(InputIterator& begin, std::type_identity_t<InputIterator> end) {
      61              :             static_assert(std::contiguous_iterator<InputIterator>, "the input must be contiguous bytes");
      62       190983 :             auto size = static_cast<size_t>(end - begin);
      63       190983 :             const char* data = size ? reinterpret_cast<const char*>(std::to_address(begin)) : nullptr;
      64       190983 :             size_t consumed = 0;
      65       190983 :             boost::tribool result = parse_buffer(data, size, consumed);
      66       190983 :             begin += static_cast<std::iter_difference_t<InputIterator>>(consumed);
      67       190983 :             return result;
      68              :         }
      69              : 
      70              :         /// Same as parse_some(), when what follows the response does not matter
      71              :         template<typename InputIterator>
      72           18 :         boost::tribool parse(InputIterator begin, InputIterator end) {
      73           18 :             return parse_some(begin, end);
      74              :         }
      75              : 
      76              :         /// The connection ended (the server closed it): true if that completes the response
      77              :         /// (a body delimited by the end of the connection), false otherwise
      78              :         bool end_of_stream();
      79              : 
      80              :         /// Why parsing failed
      81          942 :         client_error error() const { return error_; }
      82              : 
      83              :         /// Framing of the body of the response, once its head is parsed
      84         1227 :         body_framing get_body_framing() const { return framing_; }
      85              : 
      86              :         /// Whether the connection can carry another response after this one (once it is
      87              :         /// complete): framed by its length or chunked, kept alive, and not switching protocols
      88              :         bool keep_alive() const;
      89              : 
      90              :         std::shared_ptr<http_response> consume_response();
      91              : 
      92              :         /// Ready for a new response, with no request method nor callbacks
      93              :         void reset();
      94              : 
      95              :         /// HTTP status code (once the status line is parsed), or 0
      96              :         int get_status_code() const;
      97              : 
      98              :         /// The response being parsed (do not modify it)
      99           86 :         std::shared_ptr<http_response> get_response() const { return resp_; }
     100              : 
     101              :         /// Called with 0 at the start of a chunked body, 1 and the data of each chunk, and 2
     102              :         /// at its end. The data is not kept in the response.
     103              :         void setOnChunked(const std::function<void(int, const std::string&)>& onChunked);
     104              : 
     105              :         /**
     106              :          * Set streaming callback for every response body.
     107              :          * @param callback Function called with (data, downloaded_so_far, total_size).
     108              :          *                 total_size is 0 unless the body has a Content-Length.
     109              :          *                 Return false to abort download.
     110              :          */
     111              :         void setOnStreaming(std::function<bool(const std::string_view&, size_t, size_t)> callback);
     112              : 
     113              :         /**
     114              :          * Check if streaming mode is enabled.
     115              :          */
     116         6992 :         bool is_streaming() const { return on_streaming_ != nullptr; }
     117              : 
     118              :         /**
     119              :          * Set the maximum response size when buffering the body. Responses
     120              :          * larger than this are rejected. In streaming mode the limit does
     121              :          * not apply because the body is handed off chunk-by-chunk.
     122              :          */
     123         8528 :         void set_max_content_size(size_t size) { max_content_size_ = size; }
     124         6927 :         size_t get_max_content_size() const { return max_content_size_; }
     125              : 
     126              :         // How the responses are logged as they are consumed (see body_logging)
     127         8504 :         void set_body_logging(const body_logging& options) { body_logging_ = options; }
     128              : 
     129              :     private:
     130              :         enum class state {
     131              :             status_line,
     132              :             header_lines,
     133              :             body_length,
     134              :             body_chunked,
     135              :             body_until_close,
     136              :             done,
     137              :             error
     138              :         };
     139              : 
     140              :         boost::tribool parse_buffer(const char* data, size_t size, size_t& consumed);
     141              : 
     142              :         // Each returns the bytes of `data` it consumed
     143              :         size_t read_head(const char* data, size_t size);
     144              :         size_t read_length(const char* data, size_t size);
     145              :         size_t read_chunked(const char* data, size_t size);
     146              : 
     147              :         void process_line(std::string_view line);
     148              :         void process_status_line(std::string_view line);
     149              :         void process_header_line(std::string_view line);
     150              :         void process_head();
     151              :         void start_response();
     152              :         void start_body();
     153              :         bool switches_protocols(int status) const;
     154              :         body_framing body_framing_of(int status) const;
     155              : 
     156              :         /// Hand body data to the streaming callback, or keep it in the response
     157              :         bool add_body(const char* data, size_t size);
     158              : 
     159              :         void fail(client_error error);
     160              : 
     161              :         std::shared_ptr<http_response> resp_;
     162              :         method request_method_ = method::GET;
     163              :         bool headers_only_ = false;
     164              :         state state_ = state::status_line;
     165              :         client_error error_ = client_error::none;
     166              :         body_framing framing_ = body_framing::none;
     167              :         bool keep_alive_ = false;
     168              : 
     169              :         std::string line_;              // line being read
     170              :         size_t header_section_size_ = 0;
     171              :         size_t header_lines_ = 0;
     172              :         size_t remaining_ = 0;          // unread bytes of a Content-Length body
     173              :         chunked_decoder decoder_;
     174              : 
     175              :         std::function<void(int, const std::string&)> on_chunked_;
     176              :         std::function<bool(const std::string_view&, size_t, size_t)> on_streaming_;
     177              :         size_t streaming_downloaded_ = 0;
     178              :         size_t max_content_size_ = DEFAULT_MAX_CONTENT_SIZE;
     179              :         body_logging body_logging_;
     180              :     };
     181              : 
     182              : }
     183              : 
     184              : #endif
        

Generated by: LCOV version 2.0-1