]>
Commit | Line | Data |
---|---|---|
b32b8144 FG |
1 | // |
2 | // Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) | |
3 | // | |
4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | |
5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |
6 | // | |
7 | // Official repository: https://github.com/boostorg/beast | |
8 | // | |
9 | ||
10 | #ifndef BOOST_BEAST_HTTP_PARSER_HPP | |
11 | #define BOOST_BEAST_HTTP_PARSER_HPP | |
12 | ||
13 | #include <boost/beast/core/detail/config.hpp> | |
14 | #include <boost/beast/http/basic_parser.hpp> | |
15 | #include <boost/beast/http/message.hpp> | |
16 | #include <boost/beast/http/type_traits.hpp> | |
17 | #include <boost/optional.hpp> | |
18 | #include <boost/throw_exception.hpp> | |
19 | #include <functional> | |
20 | #include <memory> | |
21 | #include <type_traits> | |
22 | #include <utility> | |
23 | ||
24 | namespace boost { | |
25 | namespace beast { | |
26 | namespace http { | |
27 | ||
28 | /** An HTTP/1 parser for producing a message. | |
29 | ||
30 | This class uses the basic HTTP/1 wire format parser to convert | |
31 | a series of octets into a @ref message using the @ref basic_fields | |
32 | container to represent the fields. | |
33 | ||
34 | @tparam isRequest Indicates whether a request or response | |
35 | will be parsed. | |
36 | ||
37 | @tparam Body The type used to represent the body. This must | |
38 | meet the requirements of @b Body. | |
39 | ||
40 | @tparam Allocator The type of allocator used with the | |
41 | @ref basic_fields container. | |
42 | ||
43 | @note A new instance of the parser is required for each message. | |
44 | */ | |
45 | template< | |
46 | bool isRequest, | |
47 | class Body, | |
48 | class Allocator = std::allocator<char>> | |
49 | class parser | |
50 | : public basic_parser<isRequest, | |
51 | parser<isRequest, Body, Allocator>> | |
52 | { | |
53 | static_assert(is_body<Body>::value, | |
54 | "Body requirements not met"); | |
55 | ||
56 | static_assert(is_body_reader<Body>::value, | |
57 | "BodyReader requirements not met"); | |
58 | ||
59 | template<bool, class, class> | |
60 | friend class parser; | |
61 | ||
62 | using base_type = basic_parser<isRequest, | |
63 | parser<isRequest, Body, Allocator>>; | |
64 | ||
65 | message<isRequest, Body, basic_fields<Allocator>> m_; | |
66 | typename Body::reader wr_; | |
67 | bool rd_inited_ = false; | |
68 | ||
69 | std::function<void( | |
70 | std::uint64_t, | |
71 | string_view, | |
72 | error_code&)> cb_h_; | |
73 | ||
74 | std::function<std::size_t( | |
75 | std::uint64_t, | |
76 | string_view, | |
77 | error_code&)> cb_b_; | |
78 | ||
79 | public: | |
80 | /// The type of message returned by the parser | |
81 | using value_type = | |
82 | message<isRequest, Body, basic_fields<Allocator>>; | |
83 | ||
84 | /// Destructor | |
85 | ~parser() = default; | |
86 | ||
87 | /// Constructor | |
88 | parser(); | |
89 | ||
90 | /// Constructor | |
91 | parser(parser const&) = delete; | |
92 | ||
93 | /// Assignment | |
94 | parser& operator=(parser const&) = delete; | |
95 | ||
96 | /** Constructor | |
97 | ||
98 | After the move, the only valid operation | |
99 | on the moved-from object is destruction. | |
100 | */ | |
101 | parser(parser&& other) = default; | |
102 | ||
103 | /** Constructor | |
104 | ||
105 | @param args Optional arguments forwarded to the | |
106 | @ref http::header constructor. | |
107 | ||
108 | @note This function participates in overload | |
109 | resolution only if the first argument is not a | |
110 | @ref parser. | |
111 | */ | |
112 | #if BOOST_BEAST_DOXYGEN | |
113 | template<class... Args> | |
114 | explicit | |
115 | parser(Args&&... args); | |
116 | #else | |
117 | template<class Arg1, class... ArgN, | |
118 | class = typename std::enable_if< | |
119 | ! detail::is_parser<typename | |
120 | std::decay<Arg1>::type>::value>::type> | |
121 | explicit | |
122 | parser(Arg1&& arg1, ArgN&&... argn); | |
123 | #endif | |
124 | ||
125 | /** Construct a parser from another parser, changing the Body type. | |
126 | ||
127 | This constructs a new parser by move constructing the | |
128 | header from another parser with a different body type. The | |
129 | constructed-from parser must not have any parsed body octets or | |
130 | initialized @b BodyReader, otherwise an exception is generated. | |
131 | ||
132 | @par Example | |
133 | @code | |
134 | // Deferred body type commitment | |
135 | request_parser<empty_body> req0; | |
136 | ... | |
137 | request_parser<string_body> req{std::move(req0)}; | |
138 | @endcode | |
139 | ||
140 | If an exception is thrown, the state of the constructed-from | |
141 | parser is undefined. | |
142 | ||
143 | @param parser The other parser to construct from. After | |
144 | this call returns, the constructed-from parser may only | |
145 | be destroyed. | |
146 | ||
147 | @param args Optional arguments forwarded to the message | |
148 | constructor. | |
149 | ||
150 | @throws std::invalid_argument Thrown when the constructed-from | |
151 | parser has already initialized a body reader. | |
152 | ||
153 | @note This function participates in overload resolution only | |
154 | if the other parser uses a different body type. | |
155 | */ | |
156 | #if BOOST_BEAST_DOXYGEN | |
157 | template<class OtherBody, class... Args> | |
158 | #else | |
159 | template<class OtherBody, class... Args, | |
160 | class = typename std::enable_if< | |
161 | ! std::is_same<Body, OtherBody>::value>::type> | |
162 | #endif | |
163 | explicit | |
164 | parser(parser<isRequest, OtherBody, | |
165 | Allocator>&& parser, Args&&... args); | |
166 | ||
167 | /** Returns the parsed message. | |
168 | ||
169 | Depending on the parser's progress, | |
170 | parts of this object may be incomplete. | |
171 | */ | |
172 | value_type const& | |
173 | get() const | |
174 | { | |
175 | return m_; | |
176 | } | |
177 | ||
178 | /** Returns the parsed message. | |
179 | ||
180 | Depending on the parser's progress, | |
181 | parts of this object may be incomplete. | |
182 | */ | |
183 | value_type& | |
184 | get() | |
185 | { | |
186 | return m_; | |
187 | } | |
188 | ||
189 | /** Returns ownership of the parsed message. | |
190 | ||
191 | Ownership is transferred to the caller. | |
192 | Depending on the parser's progress, | |
193 | parts of this object may be incomplete. | |
194 | ||
195 | @par Requires | |
196 | ||
197 | @ref value_type is @b MoveConstructible | |
198 | */ | |
199 | value_type | |
200 | release() | |
201 | { | |
202 | static_assert(std::is_move_constructible<decltype(m_)>::value, | |
203 | "MoveConstructible requirements not met"); | |
204 | return std::move(m_); | |
205 | } | |
206 | ||
207 | /** Set a callback to be invoked on each chunk header. | |
208 | ||
209 | The callback will be invoked once for every chunk in the message | |
210 | payload, as well as once for the last chunk. The invocation | |
211 | happens after the chunk header is available but before any body | |
212 | octets have been parsed. | |
213 | ||
214 | The extensions are provided in raw, validated form, use | |
215 | @ref chunk_extensions::parse to parse the extensions into a | |
216 | structured container for easier access. | |
217 | The implementation type-erases the callback without requiring | |
218 | a dynamic allocation. For this reason, the callback object is | |
219 | passed by a non-constant reference. | |
220 | ||
221 | @par Example | |
222 | @code | |
223 | auto callback = | |
224 | [](std::uint64_t size, string_view extensions, error_code& ec) | |
225 | { | |
226 | //... | |
227 | }; | |
228 | parser.on_chunk_header(callback); | |
229 | @endcode | |
230 | ||
231 | @param cb The function to set, which must be invocable with | |
232 | this equivalent signature: | |
233 | @code | |
234 | void | |
235 | on_chunk_header( | |
236 | std::uint64_t size, // Size of the chunk, zero for the last chunk | |
237 | string_view extensions, // The chunk-extensions in raw form | |
238 | error_code& ec); // May be set by the callback to indicate an error | |
239 | @endcode | |
240 | */ | |
241 | template<class Callback> | |
242 | void | |
243 | on_chunk_header(Callback& cb) | |
244 | { | |
245 | // Callback may not be constant, caller is responsible for | |
246 | // managing the lifetime of the callback. Copies are not made. | |
247 | BOOST_STATIC_ASSERT(! std::is_const<Callback>::value); | |
248 | ||
249 | // Can't set the callback after receiving any chunk data! | |
250 | BOOST_ASSERT(! rd_inited_); | |
251 | ||
252 | cb_h_ = std::ref(cb); | |
253 | } | |
254 | ||
255 | /** Set a callback to be invoked on chunk body data | |
256 | ||
257 | The provided function object will be invoked one or more times | |
258 | to provide buffers corresponding to the chunk body for the current | |
259 | chunk. The callback receives the number of octets remaining in this | |
260 | chunk body including the octets in the buffer provided. | |
261 | ||
262 | The callback must return the number of octets actually consumed. | |
263 | Any octets not consumed will be presented again in a subsequent | |
264 | invocation of the callback. | |
265 | The implementation type-erases the callback without requiring | |
266 | a dynamic allocation. For this reason, the callback object is | |
267 | passed by a non-constant reference. | |
268 | ||
269 | @par Example | |
270 | @code | |
271 | auto callback = | |
272 | [](std::uint64_t remain, string_view body, error_code& ec) | |
273 | { | |
274 | //... | |
275 | }; | |
276 | parser.on_chunk_body(callback); | |
277 | @endcode | |
278 | ||
279 | @param cb The function to set, which must be invocable with | |
280 | this equivalent signature: | |
281 | @code | |
282 | std::size_t | |
283 | on_chunk_header( | |
284 | std::uint64_t remain, // Octets remaining in this chunk, includes `body` | |
285 | string_view body, // A buffer holding some or all of the remainder of the chunk body | |
286 | error_code& ec); // May be set by the callback to indicate an error | |
287 | @endcode | |
288 | */ | |
289 | template<class Callback> | |
290 | void | |
291 | on_chunk_body(Callback& cb) | |
292 | { | |
293 | // Callback may not be constant, caller is responsible for | |
294 | // managing the lifetime of the callback. Copies are not made. | |
295 | BOOST_STATIC_ASSERT(! std::is_const<Callback>::value); | |
296 | ||
297 | // Can't set the callback after receiving any chunk data! | |
298 | BOOST_ASSERT(! rd_inited_); | |
299 | ||
300 | cb_b_ = std::ref(cb); | |
301 | } | |
302 | ||
303 | private: | |
304 | friend class basic_parser<isRequest, parser>; | |
305 | ||
306 | void | |
307 | on_request_impl( | |
308 | verb method, | |
309 | string_view method_str, | |
310 | string_view target, | |
311 | int version, | |
312 | error_code& ec) | |
313 | { | |
314 | try | |
315 | { | |
316 | m_.target(target); | |
317 | if(method != verb::unknown) | |
318 | m_.method(method); | |
319 | else | |
320 | m_.method_string(method_str); | |
321 | ec.assign(0, ec.category()); | |
322 | } | |
323 | catch(std::bad_alloc const&) | |
324 | { | |
325 | ec = error::bad_alloc; | |
326 | } | |
327 | m_.version(version); | |
328 | } | |
329 | ||
330 | void | |
331 | on_response_impl( | |
332 | int code, | |
333 | string_view reason, | |
334 | int version, | |
335 | error_code& ec) | |
336 | { | |
337 | m_.result(code); | |
338 | m_.version(version); | |
339 | try | |
340 | { | |
341 | m_.reason(reason); | |
342 | ec.assign(0, ec.category()); | |
343 | } | |
344 | catch(std::bad_alloc const&) | |
345 | { | |
346 | ec = error::bad_alloc; | |
347 | } | |
348 | } | |
349 | ||
350 | void | |
351 | on_field_impl( | |
352 | field name, | |
353 | string_view name_string, | |
354 | string_view value, | |
355 | error_code& ec) | |
356 | { | |
357 | try | |
358 | { | |
359 | m_.insert(name, name_string, value); | |
360 | ec.assign(0, ec.category()); | |
361 | } | |
362 | catch(std::bad_alloc const&) | |
363 | { | |
364 | ec = error::bad_alloc; | |
365 | } | |
366 | } | |
367 | ||
368 | void | |
369 | on_header_impl(error_code& ec) | |
370 | { | |
371 | ec.assign(0, ec.category()); | |
372 | } | |
373 | ||
374 | void | |
375 | on_body_init_impl( | |
376 | boost::optional<std::uint64_t> const& content_length, | |
377 | error_code& ec) | |
378 | { | |
379 | wr_.init(content_length, ec); | |
380 | rd_inited_ = true; | |
381 | } | |
382 | ||
383 | std::size_t | |
384 | on_body_impl( | |
385 | string_view body, | |
386 | error_code& ec) | |
387 | { | |
388 | return wr_.put(boost::asio::buffer( | |
389 | body.data(), body.size()), ec); | |
390 | } | |
391 | ||
392 | void | |
393 | on_chunk_header_impl( | |
394 | std::uint64_t size, | |
395 | string_view extensions, | |
396 | error_code& ec) | |
397 | { | |
398 | if(cb_h_) | |
399 | return cb_h_(size, extensions, ec); | |
400 | ec.assign(0, ec.category()); | |
401 | } | |
402 | ||
403 | std::size_t | |
404 | on_chunk_body_impl( | |
405 | std::uint64_t remain, | |
406 | string_view body, | |
407 | error_code& ec) | |
408 | { | |
409 | if(cb_b_) | |
410 | return cb_b_(remain, body, ec); | |
411 | return wr_.put(boost::asio::buffer( | |
412 | body.data(), body.size()), ec); | |
413 | } | |
414 | ||
415 | void | |
416 | on_finish_impl(error_code& ec) | |
417 | { | |
418 | wr_.finish(ec); | |
419 | } | |
420 | }; | |
421 | ||
422 | /// An HTTP/1 parser for producing a request message. | |
423 | template<class Body, class Allocator = std::allocator<char>> | |
424 | using request_parser = parser<true, Body, Allocator>; | |
425 | ||
426 | /// An HTTP/1 parser for producing a response message. | |
427 | template<class Body, class Allocator = std::allocator<char>> | |
428 | using response_parser = parser<false, Body, Allocator>; | |
429 | ||
430 | } // http | |
431 | } // beast | |
432 | } // boost | |
433 | ||
434 | #include <boost/beast/http/impl/parser.ipp> | |
435 | ||
436 | #endif |