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 : const size_t MAX_CONTENT_SIZE = 8*1048576; // 1MB
20 : const size_t MAX_HEADERS_SIZE = 8*1024; // 8KB
21 :
22 : /// Construct ready to parse the http_request method.
23 767572 : 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 948 : boost::tribool parse(InputIterator begin, InputIterator end, bool head_request = false) {
31 : // iterate over all input chars
32 127394 : while (begin != end) {
33 : // Optimization: batch process content in streaming mode
34 127265 : 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 127263 : boost::tribool result = consume(*begin++, head_request);
59 :
60 : // parse completed/failed
61 127263 : if (result || !result){
62 817 : return result;
63 : }
64 : }
65 : // still not finished
66 129 : 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 : private:
121 : /// Handle the next character of input.
122 : boost::tribool consume(char input, bool head_request=false);
123 :
124 : /// Check if a byte is an HTTP character.
125 : static bool is_char(char c);
126 :
127 : /// Check if a byte is an HTTP control character.
128 : static bool is_ctl(char c);
129 :
130 : /// Check if a byte is defined as an HTTP special character.
131 : static bool is_tspecial(char c);
132 :
133 : /// Check if a byte is a digit.
134 : static bool is_digit(char c);
135 :
136 : /// Check if a byte is hexadecimal digit
137 : bool is_hexadecimal(char c);
138 :
139 : uint8_t get_hex_value(char c);
140 :
141 : std::shared_ptr<http_response> resp;
142 :
143 : std::string tempString1_;
144 : std::string tempString2_;
145 : size_t tempInt_ = 0;
146 : size_t headers_size_ = 0;
147 : bool lastChunk_ = false;
148 : std::function<void(int, const std::string&)> on_chunked_;
149 : std::function<bool(const std::string_view&, size_t, size_t)> on_streaming_;
150 : size_t streaming_downloaded_ = 0;
151 : bool streaming_aborted_ = false;
152 :
153 : enum content_type{
154 : none,
155 : lenght_delimited,
156 : chunked
157 : };
158 :
159 : content_type content_ = content_type::none;
160 :
161 : /// The current state of the parser.
162 : enum state {
163 : http_version_h,
164 : http_version_t_1,
165 : http_version_t_2,
166 : http_version_p,
167 : http_version_slash,
168 : http_version_major_start,
169 : http_version_major,
170 : http_version_minor_start,
171 : http_version_minor,
172 : status_code,
173 : reason_phrase,
174 : expecting_newline_1,
175 : header_line_start,
176 : header_lws,
177 : header_name,
178 : space_before_header_value,
179 : header_value,
180 : expecting_newline_2,
181 : expecting_newline_3,
182 : lenght_delimited_content,
183 : chunked_content_size,
184 : chunked_content_size_expecting_n,
185 : chunked_content,
186 : chunked_content_expecting_n
187 : } state_ = http_version_h;
188 : };
189 :
190 : }
191 :
192 : #endif
|