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
|