GCC Code Coverage Report


Directory: libs/http_proto/
File: boost/http_proto/serializer.hpp
Date: 2024-07-25 21:11:05
Exec Total Coverage
Lines: 38 38 100.0%
Functions: 17 17 100.0%
Branches: 4 5 80.0%

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