Line | Branch | Exec | Source |
---|---|---|---|
1 | // | ||
2 | // Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.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/cppalliance/http_proto | ||
8 | // | ||
9 | |||
10 | #ifndef BOOST_HTTP_PROTO_SERIALIZER_HPP | ||
11 | #define BOOST_HTTP_PROTO_SERIALIZER_HPP | ||
12 | |||
13 | #include <boost/http_proto/detail/config.hpp> | ||
14 | #include <boost/http_proto/context.hpp> | ||
15 | #include <boost/http_proto/filter.hpp> | ||
16 | #include <boost/http_proto/source.hpp> | ||
17 | #include <boost/http_proto/detail/array_of_buffers.hpp> | ||
18 | #include <boost/http_proto/detail/except.hpp> | ||
19 | #include <boost/http_proto/detail/header.hpp> | ||
20 | #include <boost/http_proto/detail/workspace.hpp> | ||
21 | #include <boost/buffers/circular_buffer.hpp> | ||
22 | #include <boost/buffers/flat_buffer.hpp> | ||
23 | #include <boost/buffers/range.hpp> | ||
24 | #include <boost/buffers/type_traits.hpp> | ||
25 | #include <boost/system/result.hpp> | ||
26 | #include <cstdint> | ||
27 | #include <memory> | ||
28 | #include <type_traits> | ||
29 | #include <utility> | ||
30 | |||
31 | namespace boost { | ||
32 | namespace http_proto { | ||
33 | namespace detail { | ||
34 | // chunked-body = *chunk | ||
35 | // last-chunk | ||
36 | // trailer-section | ||
37 | // CRLF | ||
38 | |||
39 | static | ||
40 | constexpr | ||
41 | std::size_t | ||
42 | crlf_len_ = 2; | ||
43 | |||
44 | // chunk = chunk-size [ chunk-ext ] CRLF | ||
45 | // chunk-data CRLF | ||
46 | static | ||
47 | constexpr | ||
48 | std::size_t | ||
49 | chunk_header_len_ = | ||
50 | 16 + // 16 hex digits => 64 bit number | ||
51 | crlf_len_; | ||
52 | |||
53 | // last-chunk = 1*("0") [ chunk-ext ] CRLF | ||
54 | static | ||
55 | constexpr | ||
56 | std::size_t | ||
57 | last_chunk_len_ = | ||
58 | 1 + // "0" | ||
59 | crlf_len_ + | ||
60 | crlf_len_; // chunked-body termination requires an extra CRLF | ||
61 | |||
62 | static | ||
63 | constexpr | ||
64 | std::size_t | ||
65 | chunked_overhead_ = | ||
66 | chunk_header_len_ + | ||
67 | crlf_len_ + // closing chunk data | ||
68 | last_chunk_len_; | ||
69 | } | ||
70 | |||
71 | |||
72 | #ifndef BOOST_HTTP_PROTO_DOCS | ||
73 | class request; | ||
74 | class response; | ||
75 | class request_view; | ||
76 | class response_view; | ||
77 | class message_view_base; | ||
78 | #endif | ||
79 | |||
80 | /** A serializer for HTTP/1 messages | ||
81 | |||
82 | This is used to serialize one or more complete | ||
83 | HTTP/1 messages. Each message consists of a | ||
84 | required header followed by an optional body. | ||
85 | */ | ||
86 | class BOOST_SYMBOL_VISIBLE | ||
87 | serializer | ||
88 | { | ||
89 | public: | ||
90 | class const_buffers_type; | ||
91 | |||
92 | struct stream; | ||
93 | |||
94 | /** Destructor | ||
95 | */ | ||
96 | BOOST_HTTP_PROTO_DECL | ||
97 | ~serializer(); | ||
98 | |||
99 | /** Constructor | ||
100 | */ | ||
101 | BOOST_HTTP_PROTO_DECL | ||
102 | serializer(); | ||
103 | |||
104 | /** Constructor | ||
105 | */ | ||
106 | BOOST_HTTP_PROTO_DECL | ||
107 | serializer( | ||
108 | serializer&&) noexcept; | ||
109 | |||
110 | /** Constructor | ||
111 | */ | ||
112 | BOOST_HTTP_PROTO_DECL | ||
113 | explicit | ||
114 | serializer( | ||
115 | std::size_t buffer_size); | ||
116 | |||
117 | BOOST_HTTP_PROTO_DECL | ||
118 | serializer( | ||
119 | context& ctx, | ||
120 | std::size_t buffer_size); | ||
121 | |||
122 | //-------------------------------------------- | ||
123 | |||
124 | /** Prepare the serializer for a new stream | ||
125 | */ | ||
126 | BOOST_HTTP_PROTO_DECL | ||
127 | void | ||
128 | reset() noexcept; | ||
129 | |||
130 | /** Prepare the serializer for a new message | ||
131 | |||
132 | The message will not contain a body. | ||
133 | Changing the contents of the message | ||
134 | after calling this function and before | ||
135 | @ref is_done returns `true` results in | ||
136 | undefined behavior. | ||
137 | */ | ||
138 | void | ||
139 | 4 | start( | |
140 | message_view_base const& m) | ||
141 | { | ||
142 | 4 | start_empty(m); | |
143 | 4 | } | |
144 | |||
145 | /** Prepare the serializer for a new message | ||
146 | |||
147 | Changing the contents of the message | ||
148 | after calling this function and before | ||
149 | @ref is_done returns `true` results in | ||
150 | undefined behavior. | ||
151 | |||
152 | @par Constraints | ||
153 | @code | ||
154 | is_const_buffers< ConstBuffers >::value == true | ||
155 | @endcode | ||
156 | */ | ||
157 | template< | ||
158 | class ConstBufferSequence | ||
159 | #ifndef BOOST_HTTP_PROTO_DOCS | ||
160 | ,class = typename | ||
161 | std::enable_if< | ||
162 | buffers::is_const_buffer_sequence< | ||
163 | ConstBufferSequence>::value | ||
164 | >::type | ||
165 | #endif | ||
166 | > | ||
167 | void | ||
168 | start( | ||
169 | message_view_base const& m, | ||
170 | ConstBufferSequence&& body); | ||
171 | |||
172 | /** Prepare the serializer for a new message | ||
173 | |||
174 | Changing the contents of the message | ||
175 | after calling this function and before | ||
176 | @ref is_done returns `true` results in | ||
177 | undefined behavior. | ||
178 | */ | ||
179 | template< | ||
180 | class Source, | ||
181 | class... Args | ||
182 | #ifndef BOOST_HTTP_PROTO_DOCS | ||
183 | ,class = typename std::enable_if< | ||
184 | is_source<Source>::value>::type | ||
185 | #endif | ||
186 | > | ||
187 | Source& | ||
188 | start( | ||
189 | message_view_base const& m, | ||
190 | Args&&... args); | ||
191 | |||
192 | //-------------------------------------------- | ||
193 | |||
194 | /** Create a new stream object associated with the | ||
195 | serializer. | ||
196 | |||
197 | The returned stream must not outlive its backing | ||
198 | serializer object. | ||
199 | |||
200 | Streams permit a user to supply input data to | ||
201 | the serializer using a bounded sequence of mutable | ||
202 | buffers. | ||
203 | |||
204 | \code{.cpp} | ||
205 | std::string msg = "Hello, world!"; | ||
206 | std::size_t buf_size = 16 * 1024; | ||
207 | http_proto::serializer sr(buf_size); | ||
208 | |||
209 | auto stream = sr.start_stream(); | ||
210 | auto bufs = stream.prepare(); | ||
211 | auto s = buffers::buffer_size(bufs); | ||
212 | auto n = buffers::buffer_copy( | ||
213 | bufs, | ||
214 | buffers::make_buffer( | ||
215 | msg.data(), | ||
216 | std::min(s, msg.size()))); | ||
217 | stream.commit(n); | ||
218 | |||
219 | auto cbs = sr.prepare().value(); | ||
220 | // `cbs` contains the serialized octets corresponding | ||
221 | // to our `msg` | ||
222 | \endcode | ||
223 | */ | ||
224 | BOOST_HTTP_PROTO_DECL | ||
225 | stream | ||
226 | start_stream( | ||
227 | message_view_base const& m); | ||
228 | |||
229 | //-------------------------------------------- | ||
230 | |||
231 | /** Return true if serialization is complete. | ||
232 | */ | ||
233 | bool | ||
234 | 78 | is_done() const noexcept | |
235 | { | ||
236 | 78 | return is_done_; | |
237 | } | ||
238 | |||
239 | /** Return the output area. | ||
240 | |||
241 | This function will serialize some or | ||
242 | all of the content and return the | ||
243 | corresponding output buffers. | ||
244 | |||
245 | @par Preconditions | ||
246 | @code | ||
247 | this->is_done() == false | ||
248 | @endcode | ||
249 | */ | ||
250 | BOOST_HTTP_PROTO_DECL | ||
251 | auto | ||
252 | prepare() -> | ||
253 | system::result< | ||
254 | const_buffers_type>; | ||
255 | |||
256 | /** Consume bytes from the output area. | ||
257 | */ | ||
258 | BOOST_HTTP_PROTO_DECL | ||
259 | void | ||
260 | consume(std::size_t n); | ||
261 | |||
262 | private: | ||
263 | static void copy( | ||
264 | buffers::const_buffer*, | ||
265 | buffers::const_buffer const*, | ||
266 | std::size_t n) noexcept; | ||
267 | auto | ||
268 | make_array(std::size_t n) -> | ||
269 | detail::array_of_const_buffers; | ||
270 | |||
271 | auto | ||
272 | make_array(detail::workspace& ws, std::size_t n) -> | ||
273 | detail::array_of_const_buffers; | ||
274 | |||
275 | template< | ||
276 | class Source, | ||
277 | class... Args, | ||
278 | typename std::enable_if< | ||
279 | std::is_constructible< | ||
280 | Source, | ||
281 | Args...>::value>::type* = nullptr> | ||
282 | Source& | ||
283 | 16 | construct_source(Args&&... args) | |
284 | { | ||
285 | 16 | return ws_.emplace<Source>( | |
286 | 16 | std::forward<Args>(args)...); | |
287 | } | ||
288 | |||
289 | template< | ||
290 | class Source, | ||
291 | class... Args, | ||
292 | typename std::enable_if< | ||
293 | std::is_constructible< | ||
294 | Source, | ||
295 | buffered_base::allocator&, | ||
296 | Args...>::value>::type* = nullptr> | ||
297 | Source& | ||
298 | construct_source(Args&&... args) | ||
299 | { | ||
300 | buffered_base::allocator a( | ||
301 | ws_.data(), | ||
302 | (ws_.size() - ws_.space_needed<Source>()) / 2, | ||
303 | false); | ||
304 | auto& src = ws_.emplace<Source>( | ||
305 | a, std::forward<Args>(args)...); | ||
306 | ws_.reserve_front(a.size_used()); | ||
307 | return src; | ||
308 | } | ||
309 | |||
310 | BOOST_HTTP_PROTO_DECL void start_init(message_view_base const&); | ||
311 | BOOST_HTTP_PROTO_DECL void start_empty(message_view_base const&); | ||
312 | BOOST_HTTP_PROTO_DECL void start_buffers(message_view_base const&); | ||
313 | BOOST_HTTP_PROTO_DECL void start_source(message_view_base const&, source*); | ||
314 | |||
315 | enum class style | ||
316 | { | ||
317 | empty, | ||
318 | buffers, | ||
319 | source, | ||
320 | stream | ||
321 | }; | ||
322 | |||
323 | detail::workspace ws_; | ||
324 | detail::workspace filter_ws_; | ||
325 | detail::array_of_const_buffers buf_; | ||
326 | filter* zlib_filter_ = nullptr; | ||
327 | bool filter_done_ = false; | ||
328 | source* src_; | ||
329 | context* ctx_ = nullptr; | ||
330 | buffers::circular_buffer tmp0_; | ||
331 | buffers::circular_buffer tmp1_; | ||
332 | detail::array_of_const_buffers out_; | ||
333 | |||
334 | buffers::const_buffer* hp_; // header | ||
335 | |||
336 | style st_; | ||
337 | bool more_; | ||
338 | bool is_done_; | ||
339 | bool is_chunked_; | ||
340 | bool is_expect_continue_; | ||
341 | bool is_compressed_ = false; | ||
342 | }; | ||
343 | |||
344 | //------------------------------------------------ | ||
345 | |||
346 | /** | ||
347 | A proxy type used to pass bounded input to the | ||
348 | associated serializer. | ||
349 | */ | ||
350 | struct serializer::stream | ||
351 | { | ||
352 | /** Default constructor. | ||
353 | |||
354 | Creates a stream without an associated serializer | ||
355 | object. | ||
356 | */ | ||
357 | stream() = default; | ||
358 | |||
359 | /** Copy constructor. | ||
360 | |||
361 | The constructed stream will share the same | ||
362 | serializer as `other`. | ||
363 | */ | ||
364 | stream(stream const& other) = default; | ||
365 | |||
366 | /** Assignment operator | ||
367 | |||
368 | The current stream will share the same serializer | ||
369 | as `other`. | ||
370 | */ | ||
371 | stream& operator= ( | ||
372 | stream const& other) = default; | ||
373 | |||
374 | /** | ||
375 | A MutableBufferSequence consisting of a buffer pair. | ||
376 | */ | ||
377 | using buffers_type = | ||
378 | buffers::mutable_buffer_pair; | ||
379 | |||
380 | /** | ||
381 | Returns the remaining available capacity. | ||
382 | |||
383 | The returned value represents the available free | ||
384 | space in the backing fixed-sized buffers used by the | ||
385 | serializer associated with this stream. | ||
386 | |||
387 | The capacity is absolute and does not do any | ||
388 | accounting for any octets required by a chunked | ||
389 | transfer encoding. | ||
390 | */ | ||
391 | BOOST_HTTP_PROTO_DECL | ||
392 | std::size_t | ||
393 | capacity() const noexcept; | ||
394 | |||
395 | /** | ||
396 | Returns the number of octets serialized by this | ||
397 | stream. | ||
398 | |||
399 | The associated serializer stores stream output in its | ||
400 | internal buffers. The stream returns the size of this | ||
401 | output. | ||
402 | */ | ||
403 | BOOST_HTTP_PROTO_DECL | ||
404 | std::size_t | ||
405 | size() const noexcept; | ||
406 | |||
407 | /** | ||
408 | Returns a boolean indicating if the stream can | ||
409 | receive more input. | ||
410 | |||
411 | The fixed-sized buffers maintained by the associated | ||
412 | serializer can be sufficiently full from previous | ||
413 | calls to \ref stream::commit. | ||
414 | |||
415 | This function can be called to determine if the user | ||
416 | should drain the serializer via \ref serializer::consume calls | ||
417 | before attempting to fill the buffer sequence | ||
418 | returned from \ref stream::prepare. | ||
419 | */ | ||
420 | BOOST_HTTP_PROTO_DECL | ||
421 | bool | ||
422 | is_full() const noexcept; | ||
423 | |||
424 | /** | ||
425 | Returns a MutableBufferSequence capable of storing | ||
426 | input from the user | ||
427 | |||
428 | The returned buffer sequence is as wide as is | ||
429 | possible. If a non-chunked transfer encoding is | ||
430 | being used than the returned sequence encompasses | ||
431 | the unused area of the serializer's fixed-sized | ||
432 | buffers. | ||
433 | |||
434 | If a chunked transer encoding is used then space is | ||
435 | reserved for the chunk header in addition to the | ||
436 | closing CRLF required for the chunk data. In | ||
437 | addition to this, space is also reserved for the | ||
438 | last-chunk. | ||
439 | |||
440 | This is done so that users can chain calls to | ||
441 | \ref stream::commit and \ref stream::close | ||
442 | without having to drain the serializer via | ||
443 | \ref serializer::consume calls. | ||
444 | |||
445 | \exception std::length_error Thrown if the stream | ||
446 | has insufficient capacity and a chunked transfer | ||
447 | encoding is being used | ||
448 | */ | ||
449 | BOOST_HTTP_PROTO_DECL | ||
450 | buffers_type | ||
451 | prepare() const; | ||
452 | |||
453 | /** | ||
454 | Serialize and commit `n` bytes. | ||
455 | |||
456 | Once the sequence returned from \ref prepare has been | ||
457 | filled, the input can be serialized and committed to the | ||
458 | associated serializer's output area via a call to `commit(n)`. | ||
459 | |||
460 | If a chunked transfer encoding is being used then commit | ||
461 | is responsible for writing the chunk-header and also the | ||
462 | closing CRLF for the chunk-data. `n` denotes the size | ||
463 | of the chunk. | ||
464 | |||
465 | \exception std::logic_error Thrown if commit is | ||
466 | called with 0. Instead, the closing chunk must be | ||
467 | written by a call to \ref stream::close. | ||
468 | */ | ||
469 | BOOST_HTTP_PROTO_DECL | ||
470 | void | ||
471 | commit(std::size_t n) const; | ||
472 | |||
473 | /** | ||
474 | Close the stream. | ||
475 | |||
476 | close() writes the last-chunk to the underlying buffers | ||
477 | of the stream's associated serializer, i.e. `0\r\n\r\n`. | ||
478 | |||
479 | \excpeption std::logic_error Thrown if the stream | ||
480 | has been previously closed. | ||
481 | */ | ||
482 | BOOST_HTTP_PROTO_DECL | ||
483 | void | ||
484 | close() const; | ||
485 | |||
486 | private: | ||
487 | friend class serializer; | ||
488 | |||
489 | explicit | ||
490 | 7 | stream( | |
491 | serializer& sr) noexcept | ||
492 | 7 | : sr_(&sr) | |
493 | { | ||
494 | 7 | } | |
495 | |||
496 | serializer* sr_ = nullptr; | ||
497 | }; | ||
498 | |||
499 | //--------------------------------------------------------- | ||
500 | |||
501 | /** A ConstBufferSequence representing the output | ||
502 | */ | ||
503 | class serializer:: | ||
504 | const_buffers_type | ||
505 | { | ||
506 | std::size_t n_ = 0; | ||
507 | buffers::const_buffer const* p_ = nullptr; | ||
508 | |||
509 | friend class serializer; | ||
510 | |||
511 | 63 | const_buffers_type( | |
512 | buffers::const_buffer const* p, | ||
513 | std::size_t n) noexcept | ||
514 | 63 | : n_(n) | |
515 | 63 | , p_(p) | |
516 | { | ||
517 | 63 | } | |
518 | |||
519 | public: | ||
520 | using iterator = buffers::const_buffer const*; | ||
521 | using const_iterator = iterator; | ||
522 | using value_type = buffers::const_buffer; | ||
523 | using reference = buffers::const_buffer; | ||
524 | using const_reference = buffers::const_buffer; | ||
525 | using size_type = std::size_t; | ||
526 | using difference_type = std::ptrdiff_t; | ||
527 | |||
528 | const_buffers_type() = default; | ||
529 | const_buffers_type( | ||
530 | const_buffers_type const&) = default; | ||
531 | const_buffers_type& operator=( | ||
532 | const_buffers_type const&) = default; | ||
533 | |||
534 | iterator | ||
535 | 126 | begin() const noexcept | |
536 | { | ||
537 | 126 | return p_; | |
538 | } | ||
539 | |||
540 | iterator | ||
541 | 126 | end() const noexcept | |
542 | { | ||
543 | 126 | return p_ + n_; | |
544 | } | ||
545 | }; | ||
546 | |||
547 | //------------------------------------------------ | ||
548 | |||
549 | template< | ||
550 | class ConstBufferSequence, | ||
551 | class> | ||
552 | void | ||
553 | 14 | serializer:: | |
554 | start( | ||
555 | message_view_base const& m, | ||
556 | ConstBufferSequence&& body) | ||
557 | { | ||
558 | 14 | start_init(m); | |
559 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
|
14 | auto& ws = (is_compressed_ ? filter_ws_ : ws_); |
560 | |||
561 | auto const& bs = | ||
562 | 14 | ws.emplace<ConstBufferSequence>( | |
563 | std::forward<ConstBufferSequence>(body)); | ||
564 | |||
565 | 14 | std::size_t n = std::distance( | |
566 | buffers::begin(bs), | ||
567 | buffers::end(bs)); | ||
568 | |||
569 | 14 | buf_ = make_array(ws, n); | |
570 | 14 | auto p = buf_.data(); | |
571 |
3/3✓ Branch 3 taken 5 times.
✓ Branch 4 taken 7 times.
✓ Branch 5 taken 2 times.
|
28 | for(buffers::const_buffer b : buffers::range(bs)) |
572 | 14 | *p++ = b; | |
573 | |||
574 | 14 | start_buffers(m); | |
575 | 14 | } | |
576 | |||
577 | template< | ||
578 | class Source, | ||
579 | class... Args, | ||
580 | class> | ||
581 | Source& | ||
582 | 16 | serializer:: | |
583 | start( | ||
584 | message_view_base const& m, | ||
585 | Args&&... args) | ||
586 | { | ||
587 | static_assert( | ||
588 | !std::is_abstract<Source>::value, | ||
589 | "The Source must be non-abstract, i.e. implements: `auto on_read(buffers::mutable_buffer b) -> http_proto::results;`"); | ||
590 | static_assert( | ||
591 | std::is_constructible<Source, Args...>::value || | ||
592 | std::is_constructible<Source, buffered_base::allocator&, Args...>::value, | ||
593 | "The Source cannot be constructed with the given arguments"); | ||
594 | |||
595 | 16 | start_init(m); | |
596 | 16 | auto& src = construct_source<Source>( | |
597 | std::forward<Args>(args)...); | ||
598 | 16 | start_source(m, std::addressof(src)); | |
599 | 16 | return src; | |
600 | } | ||
601 | |||
602 | //------------------------------------------------ | ||
603 | |||
604 | inline | ||
605 | auto | ||
606 | 26 | serializer:: | |
607 | make_array(std::size_t n) -> | ||
608 | detail::array_of_const_buffers | ||
609 | { | ||
610 | return { | ||
611 | 26 | ws_.push_array(n, | |
612 | 26 | buffers::const_buffer{}), | |
613 |
1/2✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
|
26 | n }; |
614 | } | ||
615 | |||
616 | inline | ||
617 | auto | ||
618 | 7 | serializer:: | |
619 | make_array(detail::workspace& ws, std::size_t n) -> | ||
620 | detail::array_of_const_buffers | ||
621 | { | ||
622 |
1/2✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | return {ws.push_array(n, buffers::const_buffer{}), n}; |
623 | } | ||
624 | |||
625 | } // http_proto | ||
626 | } // boost | ||
627 | |||
628 | #endif | ||
629 |