GCC Code Coverage Report


Directory: libs/http_proto/
File: include/boost/http_proto/serializer.hpp
Date: 2025-06-06 17:40:42
Exec Total Coverage
Lines: 30 30 100.0%
Functions: 16 16 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/context.hpp>
14 #include <boost/http_proto/detail/array_of_buffers.hpp>
15 #include <boost/http_proto/detail/config.hpp>
16 #include <boost/http_proto/detail/except.hpp>
17 #include <boost/http_proto/detail/header.hpp>
18 #include <boost/http_proto/detail/workspace.hpp>
19 #include <boost/http_proto/source.hpp>
20 #include <boost/buffers/circular_buffer.hpp>
21 #include <boost/buffers/const_buffer_span.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 namespace detail {
40 class filter;
41 } // detail
42 #endif
43
44 /** A serializer for HTTP/1 messages
45
46 This is used to serialize one or more complete
47 HTTP/1 messages. Each message consists of a
48 required header followed by an optional body.
49
50 Objects of this type operate using an "input area" and an
51 "output area". Callers provide data to the input area
52 using one of the @ref start or @ref start_stream member
53 functions. After input is provided, serialized data
54 becomes available in the serializer's output area in the
55 form of a constant buffer sequence.
56
57 Callers alternate between filling the input area and
58 consuming the output area until all the input has been
59 provided and all the output data has been consumed, or
60 an error occurs.
61
62 After calling @ref start, the caller must ensure that the
63 contents of the associated message are not changed or
64 destroyed until @ref is_done returns true, @ref reset is
65 called, or the serializer is destroyed, otherwise the
66 behavior is undefined.
67 */
68 class serializer
69 {
70 public:
71 using const_buffers_type = buffers::const_buffer_span;
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 1603 is_done() const noexcept
195 {
196 1603 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 34 construct_source(Args&&... args)
262 {
263 34 return ws_.emplace<Source>(
264 34 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 detail::workspace&,
274 Args...>::value>::type* = nullptr>
275 Source&
276 construct_source(Args&&... args)
277 {
278 return ws_.emplace<Source>(
279 ws_, std::forward<Args>(args)...);
280 }
281
282 BOOST_HTTP_PROTO_DECL void start_init(message_view_base const&);
283 BOOST_HTTP_PROTO_DECL void start_empty(message_view_base const&);
284 BOOST_HTTP_PROTO_DECL void start_buffers(message_view_base const&);
285 BOOST_HTTP_PROTO_DECL void start_source(message_view_base const&, source*);
286
287 enum class style
288 {
289 empty,
290 buffers,
291 source,
292 stream
293 };
294
295 // chunked-body = *chunk
296 // last-chunk
297 // trailer-section
298 // CRLF
299
300 static
301 constexpr
302 std::size_t
303 crlf_len_ = 2;
304
305 // chunk = chunk-size [ chunk-ext ] CRLF
306 // chunk-data CRLF
307 static
308 constexpr
309 std::size_t
310 chunk_header_len_ =
311 16 + // 16 hex digits => 64 bit number
312 crlf_len_;
313
314 // last-chunk = 1*("0") [ chunk-ext ] CRLF
315 static
316 constexpr
317 std::size_t
318 last_chunk_len_ =
319 1 + // "0"
320 crlf_len_ +
321 crlf_len_; // chunked-body termination requires an extra CRLF
322
323 static
324 constexpr
325 std::size_t
326 chunked_overhead_ =
327 chunk_header_len_ +
328 crlf_len_ + // closing chunk data
329 last_chunk_len_;
330
331 detail::workspace ws_;
332 detail::array_of_const_buffers buf_;
333 detail::filter* filter_ = nullptr;
334 source* src_;
335 context& ctx_;
336 buffers::circular_buffer tmp0_;
337 buffers::circular_buffer tmp1_;
338 detail::array_of_const_buffers prepped_;
339
340 buffers::mutable_buffer chunk_header_;
341 buffers::mutable_buffer chunk_close_;
342 buffers::mutable_buffer last_chunk_;
343
344 buffers::circular_buffer* in_ = nullptr;
345 buffers::circular_buffer* out_ = nullptr;
346
347 buffers::const_buffer* hp_; // header
348
349 style st_;
350 bool more_;
351 bool is_done_;
352 bool is_header_done_;
353 bool is_chunked_;
354 bool is_expect_continue_;
355 bool is_compressed_ = false;
356 bool filter_done_ = false;
357 };
358
359 //------------------------------------------------
360
361 /** The type used for caller-provided body data during
362 serialization.
363
364 @code{.cpp}
365 http_proto::serializer sr(128);
366
367 http_proto::request req;
368 auto stream = sr.start_stream(req);
369
370 std::string_view msg = "Hello, world!";
371 auto n = buffers::copy(
372 stream.prepare(),
373 buffers::make_buffer(
374 msg.data(), msg.size()));
375
376 stream.commit(n);
377
378 auto cbs = sr.prepare().value();
379 (void)cbs;
380 @endcode
381 */
382 struct serializer::stream
383 {
384 /** Constructor.
385
386 The only valid operations on default constructed
387 streams are assignment and destruction.
388 */
389 stream() = default;
390
391 /** Constructor.
392
393 The constructed stream will share the same
394 serializer as `other`.
395 */
396 stream(stream const& other) = default;
397
398 /** Assignment.
399
400 The current stream will share the same serializer
401 as `other`.
402 */
403 stream& operator= (
404 stream const& other) = default;
405
406 /** A MutableBufferSequence consisting of a buffer pair.
407 */
408 using buffers_type =
409 buffers::mutable_buffer_pair;
410
411 /** Returns the remaining available capacity.
412
413 The returned value represents the available free
414 space in the backing fixed-sized buffers used by the
415 serializer associated with this stream.
416
417 The capacity is absolute and does not do any
418 accounting for any octets required by a chunked
419 transfer encoding.
420 */
421 BOOST_HTTP_PROTO_DECL
422 std::size_t
423 capacity() const noexcept;
424
425 /** Returns the number of octets serialized by this
426 stream.
427
428 The associated serializer stores stream output in its
429 internal buffers. The stream returns the size of this
430 output.
431 */
432 BOOST_HTTP_PROTO_DECL
433 std::size_t
434 size() const noexcept;
435
436 /** Return true if the stream cannot currently hold
437 additional output data.
438
439 The fixed-sized buffers maintained by the associated
440 serializer can be sufficiently full from previous
441 calls to @ref stream::commit.
442
443 This function can be called to determine if the caller
444 should drain the serializer via @ref serializer::consume calls
445 before attempting to fill the buffer sequence
446 returned from @ref stream::prepare.
447 */
448 BOOST_HTTP_PROTO_DECL
449 bool
450 is_full() const noexcept;
451
452 /** Returns a MutableBufferSequence for storing
453 serializer input. If `n` bytes are written to the
454 buffer sequence, @ref stream::commit must be called
455 with `n` to update the backing serializer's buffers.
456
457 The returned buffer sequence is as wide as is
458 possible.
459
460 @exception std::length_error Thrown if the stream
461 has insufficient capacity and a chunked transfer
462 encoding is being used
463 */
464 BOOST_HTTP_PROTO_DECL
465 buffers_type
466 prepare() const;
467
468 /** Make `n` bytes available to the serializer.
469
470 Once the buffer sequence returned from @ref stream::prepare
471 has been filled, the input can be marked as ready
472 for serialization by using this function.
473
474 @exception std::logic_error Thrown if `commit` is
475 called with 0.
476 */
477 BOOST_HTTP_PROTO_DECL
478 void
479 commit(std::size_t n) const;
480
481 /** Indicate that no more data is coming and that the
482 body should be treated as complete.
483
484 @excpeption std::logic_error Thrown if the stream
485 has been previously closed.
486 */
487 BOOST_HTTP_PROTO_DECL
488 void
489 close() const;
490
491 private:
492 friend class serializer;
493
494 explicit
495 22 stream(
496 serializer& sr) noexcept
497 22 : sr_(&sr)
498 {
499 22 }
500
501 serializer* sr_ = nullptr;
502 };
503
504 //---------------------------------------------------------
505
506 template<
507 class ConstBufferSequence,
508 class>
509 void
510 48 serializer::
511 start(
512 message_view_base const& m,
513 ConstBufferSequence&& body)
514 {
515 48 start_init(m);
516 auto const& bs =
517 48 ws_.emplace<ConstBufferSequence>(
518 std::forward<ConstBufferSequence>(body));
519
520 48 std::size_t n = std::distance(
521 buffers::begin(bs),
522 buffers::end(bs));
523
524 48 buf_ = make_array(n);
525 48 auto p = buf_.data();
526
3/3
✓ Branch 3 taken 390 times.
✓ Branch 4 taken 24 times.
✓ Branch 5 taken 2 times.
832 for(buffers::const_buffer b : buffers::range(bs))
527 784 *p++ = b;
528
529 48 start_buffers(m);
530 48 }
531
532 template<
533 class Source,
534 class... Args,
535 class>
536 Source&
537 34 serializer::
538 start(
539 message_view_base const& m,
540 Args&&... args)
541 {
542 static_assert(
543 !std::is_abstract<Source>::value, "");
544 static_assert(
545 std::is_constructible<Source, Args...>::value ||
546 std::is_constructible<Source, detail::workspace&, Args...>::value,
547 "The Source cannot be constructed with the given arguments");
548
549 34 start_init(m);
550 34 auto& src = construct_source<Source>(
551 std::forward<Args>(args)...);
552 34 start_source(m, std::addressof(src));
553 34 return src;
554 }
555
556 //------------------------------------------------
557
558 inline
559 auto
560 99 serializer::
561 make_array(std::size_t n) ->
562 detail::array_of_const_buffers
563 {
564 return {
565 99 ws_.push_array(n,
566 99 buffers::const_buffer{}),
567
1/2
✓ Branch 2 taken 99 times.
✗ Branch 3 not taken.
198 n };
568 }
569
570 } // http_proto
571 } // boost
572
573 #endif
574