Line data Source code
1 : #ifndef THINGER_HTTP_RESPONSE_FACTORY_HPP
2 : #define THINGER_HTTP_RESPONSE_FACTORY_HPP
3 :
4 : #include <memory>
5 : #include <functional>
6 : #include <string_view>
7 : #include <boost/logic/tribool.hpp>
8 : #include <boost/tuple/tuple.hpp>
9 : #include <boost/lexical_cast.hpp>
10 :
11 : namespace thinger::http {
12 :
13 : class http_response;
14 :
15 : /// Parser for incoming requests.
16 : class response_factory {
17 : public:
18 : /// Specify the maximum response size to be read
19 : static constexpr size_t DEFAULT_MAX_CONTENT_SIZE = 8*1048576; // 8 MB (buffered mode only; streaming bypasses)
20 : const size_t MAX_HEADERS_SIZE = 8*1024; // 8KB
21 :
22 : /// Construct ready to parse the http_request method.
23 767720 : response_factory() = default;
24 :
25 : /// Parse some data. The tribool return value is true when a complete http_request
26 : /// has been parsed, false if the data is invalid, indeterminate when more
27 : /// data is required. The InputIterator return value indicates how much of the
28 : /// input has been consumed.
29 : template<typename InputIterator>
30 1069 : boost::tribool parse(InputIterator begin, InputIterator end, bool head_request = false) {
31 : // iterate over all input chars
32 142545 : while (begin != end) {
33 : // Optimization: batch process content in streaming mode
34 142402 : if (on_streaming_ && state_ == lenght_delimited_content && tempInt_ > 0) {
35 2 : size_t available = static_cast<size_t>(std::distance(begin, end));
36 2 : size_t to_read = std::min(available, tempInt_);
37 :
38 : // Call streaming callback with batch data
39 : // Need to cast to char* since buffer may be uint8_t*
40 2 : std::string_view data(reinterpret_cast<const char*>(&*begin), to_read);
41 2 : streaming_downloaded_ += to_read;
42 :
43 2 : if (!on_streaming_(data, streaming_downloaded_, get_content_length())) {
44 0 : streaming_aborted_ = true;
45 0 : return false;
46 : }
47 :
48 : std::advance(begin, to_read);
49 2 : tempInt_ -= to_read;
50 :
51 2 : if (tempInt_ == 0) {
52 2 : return true; // Done reading content
53 : }
54 0 : continue;
55 0 : }
56 :
57 : // consume character
58 142400 : boost::tribool result = consume(*begin++, head_request);
59 :
60 : // parse completed/failed
61 142400 : if (result || !result){
62 924 : return result;
63 : }
64 : }
65 : // still not finished
66 143 : return boost::indeterminate;
67 : }
68 :
69 : std::shared_ptr<http_response> consume_response();
70 : void reset();
71 :
72 : void on_http_major_version(uint8_t major);
73 :
74 : void on_http_minor_version(uint16_t minor);
75 :
76 : void on_http_status_code(unsigned short status_code);
77 :
78 : void on_http_reason_phrase(const std::string& reason);
79 :
80 : void on_http_header(const std::string& name, const std::string& value);
81 :
82 : void on_content_data(char content);
83 :
84 : bool on_chunk_read(size_t size);
85 :
86 : bool on_length_delimited_content(size_t size);
87 :
88 : size_t get_content_length();
89 :
90 : size_t get_content_read();
91 :
92 : bool empty_headers();
93 :
94 : /**
95 : * Get the HTTP status code (available after headers are parsed).
96 : */
97 : int get_status_code() const;
98 :
99 : /**
100 : * Get the response object without consuming it.
101 : * Note: The response is still being built, so don't modify it.
102 : */
103 : std::shared_ptr<http_response> get_response() const { return resp; }
104 :
105 : void setOnChunked(const std::function<void(int, const std::string&)>& onChunked);
106 :
107 : /**
108 : * Set streaming callback for both chunked and length-delimited responses.
109 : * @param callback Function called with (data, downloaded_so_far, total_size).
110 : * total_size is 0 for chunked responses.
111 : * Return false to abort download.
112 : */
113 : void setOnStreaming(std::function<bool(const std::string_view&, size_t, size_t)> callback);
114 :
115 : /**
116 : * Check if streaming mode is enabled.
117 : */
118 : bool is_streaming() const { return on_streaming_ != nullptr; }
119 :
120 : /**
121 : * Set the maximum response size when buffering the body. Responses
122 : * larger than this are rejected. In streaming mode the limit does
123 : * not apply because the body is handed off chunk-by-chunk.
124 : */
125 940 : void set_max_content_size(size_t size) { max_content_size_ = size; }
126 :
127 : private:
128 : /// Handle the next character of input.
129 : boost::tribool consume(char input, bool head_request=false);
130 :
131 : /// Check if a byte is an HTTP character.
132 : static bool is_char(char c);
133 :
134 : /// Check if a byte is an HTTP control character.
135 : static bool is_ctl(char c);
136 :
137 : /// Check if a byte is defined as an HTTP special character.
138 : static bool is_tspecial(char c);
139 :
140 : /// Check if a byte is a digit.
141 : static bool is_digit(char c);
142 :
143 : /// Check if a byte is hexadecimal digit
144 : bool is_hexadecimal(char c);
145 :
146 : uint8_t get_hex_value(char c);
147 :
148 : std::shared_ptr<http_response> resp;
149 :
150 : std::string tempString1_;
151 : std::string tempString2_;
152 : size_t tempInt_ = 0;
153 : size_t headers_size_ = 0;
154 : bool lastChunk_ = false;
155 : std::function<void(int, const std::string&)> on_chunked_;
156 : std::function<bool(const std::string_view&, size_t, size_t)> on_streaming_;
157 : size_t streaming_downloaded_ = 0;
158 : bool streaming_aborted_ = false;
159 : size_t max_content_size_ = DEFAULT_MAX_CONTENT_SIZE;
160 :
161 : enum content_type{
162 : none,
163 : lenght_delimited,
164 : chunked
165 : };
166 :
167 : content_type content_ = content_type::none;
168 :
169 : /// The current state of the parser.
170 : enum state {
171 : http_version_h,
172 : http_version_t_1,
173 : http_version_t_2,
174 : http_version_p,
175 : http_version_slash,
176 : http_version_major_start,
177 : http_version_major,
178 : http_version_minor_start,
179 : http_version_minor,
180 : status_code,
181 : reason_phrase,
182 : expecting_newline_1,
183 : header_line_start,
184 : header_lws,
185 : header_name,
186 : space_before_header_value,
187 : header_value,
188 : expecting_newline_2,
189 : expecting_newline_3,
190 : lenght_delimited_content,
191 : chunked_content_size,
192 : chunked_content_size_expecting_n,
193 : chunked_content,
194 : chunked_content_expecting_n
195 : } state_ = http_version_h;
196 : };
197 :
198 : }
199 :
200 : #endif
|