GCC Code Coverage Report


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