GCC Code Coverage Report


Directory: libs/http_proto/
File: include/boost/http_proto/serializer.hpp
Date: 2025-10-12 23:51:57
Exec Total Coverage
Lines: 8 8 100.0%
Functions: 3 3 100.0%
Branches: 0 0 -%

Line Branch Exec Source
1 //
2 // Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
3 // Copyright (c) 2025 Mohammad Nejati
4 //
5 // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 //
8 // Official repository: https://github.com/cppalliance/http_proto
9 //
10
11 #ifndef BOOST_HTTP_PROTO_SERIALIZER_HPP
12 #define BOOST_HTTP_PROTO_SERIALIZER_HPP
13
14 #include <boost/http_proto/detail/config.hpp>
15 #include <boost/http_proto/detail/workspace.hpp>
16 #include <boost/http_proto/source.hpp>
17
18 #include <boost/buffers/buffer_pair.hpp>
19 #include <boost/core/span.hpp>
20 #include <boost/rts/context_fwd.hpp>
21 #include <boost/system/result.hpp>
22
23 #include <type_traits>
24 #include <utility>
25
26 namespace boost {
27 namespace http_proto {
28
29 // Forward declaration
30 class message_base;
31
32 /** A serializer for HTTP/1 messages
33
34 This is used to serialize one or more complete
35 HTTP/1 messages. Each message consists of a
36 required header followed by an optional body.
37
38 Objects of this type operate using an "input area" and an
39 "output area". Callers provide data to the input area
40 using one of the @ref start or @ref start_stream member
41 functions. After input is provided, serialized data
42 becomes available in the serializer's output area in the
43 form of a constant buffer sequence.
44
45 Callers alternate between filling the input area and
46 consuming the output area until all the input has been
47 provided and all the output data has been consumed, or
48 an error occurs.
49
50 After calling @ref start, the caller must ensure that the
51 contents of the associated message are not changed or
52 destroyed until @ref is_done returns true, @ref reset is
53 called, or the serializer is destroyed, otherwise the
54 behavior is undefined.
55 */
56 class serializer
57 {
58 public:
59 class stream;
60 struct config;
61
62 /** The type used to represent a sequence of
63 constant buffers that refers to the output
64 area.
65 */
66 using const_buffers_type =
67 boost::span<buffers::const_buffer const>;
68
69 /** Constructor.
70
71 Constructs a serializer that uses the @ref
72 config parameters installed on the
73 provided `ctx`.
74
75 The serializer will attempt to allocate
76 the required space on startup, with the
77 amount depending on the @ref config
78 parameters, and will not perform any
79 further allocations, except for Brotli
80 encoder instances, if enabled.
81
82 Depending on which compression algorithms
83 are enabled in the @ref config, the
84 serializer will attempt to access the
85 corresponding encoder services on the same
86 `ctx`.
87
88 @par Example
89 @code
90 serializer sr(ctx);
91 @endcode
92
93 @par Postconditions
94 @code
95 this->is_done() == true
96 @endcode
97
98 @par Complexity
99 Constant.
100
101 @par Exception Safety
102 Calls to allocate may throw.
103
104 @param ctx Context from which the
105 serializer will access registered
106 services. The caller is responsible for
107 ensuring that the provided ctx remains
108 valid for the lifetime of the serializer.
109
110 @see
111 @ref install_serializer_service,
112 @ref config.
113 */
114 BOOST_HTTP_PROTO_DECL
115 explicit
116 serializer(
117 const rts::context& ctx);
118
119 /** Constructor.
120
121 The states of `other` are transferred
122 to the newly constructed object,
123 which includes the allocated buffer.
124 After construction, the only valid
125 operations on the moved-from object
126 are destruction and assignment.
127
128 Buffer sequences previously obtained
129 using @ref prepare or @ref stream::prepare
130 remain valid.
131
132 @par Postconditions
133 @code
134 other.is_done() == true
135 @endcode
136
137 @par Complexity
138 Constant.
139
140 @param other The serializer to move from.
141 */
142 BOOST_HTTP_PROTO_DECL
143 serializer(
144 serializer&& other) noexcept;
145
146 /** Destructor
147 */
148 BOOST_HTTP_PROTO_DECL
149 ~serializer();
150
151 /** Reset the serializer for a new message.
152
153 Aborts any ongoing serialization and
154 prepares the serializer to start
155 serialization of a new message.
156 */
157 BOOST_HTTP_PROTO_DECL
158 void
159 reset() noexcept;
160
161 /** Start serializing a message with an empty body
162
163 This function prepares the serializer to create a message which
164 has an empty body.
165 Ownership of the specified message is not transferred; the caller is
166 responsible for ensuring the lifetime of the object extends until the
167 serializer is done.
168
169 @par Preconditions
170 @code
171 this->is_done() == true
172 @endcode
173
174 @par Postconditions
175 @code
176 this->is_done() == false
177 @endcode
178
179 @par Exception Safety
180 Strong guarantee.
181 Exceptions thrown if there is insufficient internal buffer space
182 to start the operation.
183
184 @throw std::logic_error `this->is_done() == true`.
185
186 @throw std::length_error if there is insufficient internal buffer
187 space to start the operation.
188
189 @param m The request or response headers to serialize.
190
191 @see
192 @ref message_base.
193 */
194 void
195 BOOST_HTTP_PROTO_DECL
196 start(message_base const& m);
197
198 /** Start serializing a message with a buffer sequence body
199
200 Initializes the serializer with the HTTP start-line and headers from `m`,
201 and the provided `buffers` for reading the message body from.
202
203 Changing the contents of the message after calling this function and
204 before @ref is_done returns `true` results in undefined behavior.
205
206 At least one copy of the specified buffer sequence is maintained until
207 the serializer is done, gets reset, or ios destroyed, after which all
208 of its copies are destroyed. Ownership of the underlying memory is not
209 transferred; the caller must ensure the memory remains valid until the
210 serializer’s copies are destroyed.
211
212 @par Preconditions
213 @code
214 this->is_done() == true
215 @endcode
216
217 @par Postconditions
218 @code
219 this->is_done() == false
220 @endcode
221
222 @par Constraints
223 @code
224 buffers::is_const_buffer_sequence_v<ConstBufferSequence> == true
225 @endcode
226
227 @par Exception Safety
228 Strong guarantee.
229 Exceptions thrown if there is insufficient internal buffer space
230 to start the operation.
231
232 @throw std::logic_error `this->is_done() == true`.
233
234 @throw std::length_error If there is insufficient internal buffer
235 space to start the operation.
236
237 @param m The message to read the HTTP start-line and headers from.
238
239 @param buffers A buffer sequence containing the message body.
240
241 containing the message body data. While
242 the buffers object is copied, ownership of
243 the underlying memory remains with the
244 caller, who must ensure it stays valid
245 until @ref is_done returns `true`.
246
247 @see
248 @ref message_base.
249 */
250 template<
251 class ConstBufferSequence,
252 class = typename std::enable_if<
253 buffers::is_const_buffer_sequence<
254 ConstBufferSequence>::value>::type
255 >
256 void
257 start(
258 message_base const& m,
259 ConstBufferSequence&& buffers);
260
261 /** Start serializing a message with a @em Source body
262
263 Initializes the serializer with the HTTP start-line and headers from
264 `m`, and constructs a `Source` object to provide the message body.
265
266 Changing the contents of the message
267 after calling this function and before
268 @ref is_done returns `true` results in
269 undefined behavior.
270
271 The serializer destroys Source object when:
272 @li `this->is_done() == true`
273 @li An unrecoverable serialization error occurs
274 @li The serializer is destroyed
275
276 @par Example
277 @code
278 file f("example.zip", file_mode::scan);
279 response.set_payload_size(f.size());
280 serializer.start<file_source>(response, std::move(f));
281 @endcode
282
283 @par Preconditions
284 @code
285 this->is_done() == true
286 @endcode
287
288 @par Postconditions
289 @code
290 this->is_done() == false
291 @endcode
292
293 @par Constraints
294 @code
295 is_source<Source>::value == true
296 @endcode
297
298 @par Exception Safety
299 Strong guarantee.
300 Exceptions thrown if there is insufficient
301 internal buffer space to start the
302 operation.
303
304 @throw std::length_error if there is
305 insufficient internal buffer space to
306 start the operation.
307
308 @param m The message to read the HTTP
309 start-line and headers from.
310
311 @param args Arguments to be passed to the
312 `Source` constructor.
313
314 @return A reference to the constructed Source object.
315
316 @see
317 @ref source,
318 @ref file_source,
319 @ref message_base.
320 */
321 template<
322 class Source,
323 class... Args,
324 class = typename std::enable_if<
325 is_source<Source>::value>::type>
326 Source&
327 start(
328 message_base const& m,
329 Args&&... args);
330
331 /** Prepare the serializer for a new message using a stream interface.
332
333 Initializes the serializer with the HTTP
334 start-line and headers from `m`, and returns
335 a @ref stream object for reading the body
336 from an external source.
337
338 Once the serializer is destroyed, @ref reset
339 is called, or @ref is_done returns true, the
340 only valid operation on the stream is destruction.
341
342 The stream allows inverted control flow: the
343 caller supplies body data via the serializer’s
344 internal buffer while reading from an external
345 source.
346
347 Changing the contents of the message
348 after calling this function and before
349 @ref is_done returns `true` results in
350 undefined behavior.
351
352 @par Example
353 @code
354 serializer::stream strm = serializer.start_stream(response);
355 do
356 {
357 if(strm.is_open())
358 {
359 std::size_t n = source.read_some(strm.prepare());
360
361 if(ec == error::eof)
362 strm.close();
363 else
364 strm.commit(n);
365 }
366
367 write_some(client, serializer);
368
369 } while(!serializer.is_done());
370 @endcode
371
372 @par Preconditions
373 @code
374 this->is_done() == true
375 @endcode
376
377 @par Postconditions
378 @code
379 this->is_done() == false
380 @endcode
381
382 @par Exception Safety
383 Strong guarantee.
384 Exceptions thrown if there is insufficient
385 internal buffer space to start the
386 operation.
387
388 @throw std::length_error if there is
389 insufficient internal buffer space to
390 start the operation.
391
392 @param m The message to read the HTTP
393 start-line and headers from.
394
395 @return A @ref stream object for reading body
396 content into the serializer's buffer.
397
398 @see
399 @ref stream,
400 @ref message_base.
401 */
402 BOOST_HTTP_PROTO_DECL
403 stream
404 start_stream(
405 message_base const& m);
406
407 /** Return the output area.
408
409 This function serializes some or all of
410 the message and returns the corresponding
411 output buffers. Afterward, a call to @ref
412 consume is required to report the number
413 of bytes used, if any.
414
415 If the message includes an
416 `Expect: 100-continue` header and the
417 header section of the message has been
418 consumed, the returned result will contain
419 @ref error::expect_100_continue to
420 indicate that the header part of the
421 message is complete. The next call to @ref
422 prepare will produce output.
423
424 When the serializer is used through the
425 @ref stream interface, the result may
426 contain @ref error::need_data to indicate
427 that additional input is required to
428 produce output.
429
430 If a @ref source object is in use and a
431 call to @ref source::read returns an
432 error, the serializer enters a faulted
433 state and propagates the error to the
434 caller. This faulted state can only be
435 cleared by calling @ref reset. This
436 ensures the caller is explicitly aware
437 that the previous message was truncated
438 and that the stream must be terminated.
439
440 @par Preconditions
441 @code
442 this->is_done() == false
443 @endcode
444 No unrecoverable error reported from previous calls.
445
446 @par Exception Safety
447 Strong guarantee.
448 Calls to @ref source::read may throw if in use.
449
450 @throw std::logic_error
451 `this->is_done() == true`.
452
453 @return A result containing @ref
454 const_buffers_type that represents the
455 output area or an error if any occurred.
456
457 @see
458 @ref consume,
459 @ref is_done,
460 @ref const_buffers_type.
461 */
462 BOOST_HTTP_PROTO_DECL
463 auto
464 prepare() ->
465 system::result<
466 const_buffers_type>;
467
468 /** Consume bytes from the output area.
469
470 This function should be called after one
471 or more bytes contained in the buffers
472 provided in the prior call to @ref prepare
473 have been used.
474
475 After a call to @ref consume, callers
476 should check the return value of @ref
477 is_done to determine if the entire message
478 has been serialized.
479
480 @par Preconditions
481 @code
482 this->is_done() == false
483 @endcode
484
485 @par Exception Safety
486 Strong guarantee.
487
488 @throw std::logic_error
489 `this->is_done() == true`.
490
491 @param n The number of bytes to consume.
492 If `n` is greater than the size of the
493 buffer returned from @ref prepared the
494 entire output sequence is consumed and no
495 error is issued.
496
497 @see
498 @ref prepare,
499 @ref is_done,
500 @ref const_buffers_type.
501 */
502 BOOST_HTTP_PROTO_DECL
503 void
504 consume(std::size_t n);
505
506 /** Return true if serialization is complete.
507 */
508 BOOST_HTTP_PROTO_DECL
509 bool
510 is_done() const noexcept;
511
512 private:
513 class impl;
514 class cbs_gen;
515 template<class>
516 class cbs_gen_impl;
517
518 BOOST_HTTP_PROTO_DECL
519 detail::workspace&
520 ws();
521
522 BOOST_HTTP_PROTO_DECL
523 void
524 start_init(
525 message_base const&);
526
527 BOOST_HTTP_PROTO_DECL
528 void
529 start_buffers(
530 message_base const&,
531 cbs_gen&);
532
533 BOOST_HTTP_PROTO_DECL
534 void
535 start_source(
536 message_base const&,
537 source&);
538
539 impl* impl_;
540 };
541
542 /** Serializer configuration settings.
543
544 @see
545 @ref install_serializer_service,
546 @ref serializer.
547 */
548 struct serializer::config
549 {
550 /** Enable Brotli Content-Encoding.
551
552 Requires `boost::rts::brotli::encode_service` to be
553 installed, otherwise an exception is thrown.
554 */
555 bool apply_brotli_encoder = false;
556
557 /** Enable Deflate Content-Encoding.
558
559 Requires `boost::zlib::deflate_service` to be
560 installed, otherwise an exception is thrown.
561 */
562 bool apply_deflate_encoder = false;
563
564 /** Enable Gzip Content-Encoding.
565
566 Requires `boost::zlib::deflate_service` to be
567 installed, otherwise an exception is thrown.
568 */
569 bool apply_gzip_encoder = false;
570
571 /** Brotli compression quality (0–11).
572
573 Higher values yield better but slower compression.
574 */
575 std::uint32_t brotli_comp_quality = 5;
576
577 /** Brotli compression window size (10–24).
578
579 Larger windows improve compression but increase
580 memory usage.
581 */
582 std::uint32_t brotli_comp_window = 18;
583
584 /** Zlib compression level (0–9).
585
586 0 = no compression, 1 = fastest, 9 = best
587 compression.
588 */
589 int zlib_comp_level = 6;
590
591 /** Zlib window bits (9–15).
592
593 Controls the history buffer size. Larger values
594 improve compression but use more memory.
595 */
596 int zlib_window_bits = 15;
597
598 /** Zlib memory level (1–9).
599
600 Higher values use more memory, but offer faster
601 and more efficient compression.
602 */
603 int zlib_mem_level = 8;
604
605 /** Minimum buffer size for payloads (must be > 0). */
606 std::size_t payload_buffer = 8192;
607
608 /** Reserved space for type-erasure storage.
609
610 Used for:
611 @li User-defined @ref source objects.
612 @li User-defined ConstBufferSequence instances.
613 */
614 std::size_t max_type_erase = 1024;
615 };
616
617 /** Install the serializer service.
618
619 @par Example
620 @code
621 // default configuration settings
622 install_serializer_service(ctx, {});
623
624 serializer sr(ctx);
625 @endcode
626
627 @par Exception Safety
628 Strong guarantee.
629
630 @throw std::invalid_argument If the service is
631 already installed on the context.
632
633 @param ctx Reference to the context on which
634 the service should be installed.
635
636 @param cfg Configuration settings for the
637 serializer.
638
639 @see
640 @ref serializer::config,
641 @ref serializer.
642 */
643 BOOST_HTTP_PROTO_DECL
644 void
645 install_serializer_service(
646 rts::context& ctx,
647 serializer::config const& cfg);
648
649 //------------------------------------------------
650
651 /** Used for streaming body data during serialization.
652
653 Provides an interface for supplying serialized
654 body content from an external source. This
655 object is returned by @ref
656 serializer::start_stream and enables
657 incremental writing of the message body into
658 the serializer's internal buffer.
659
660 The stream supports an inverted control flow
661 model, where the caller pushes body data as
662 needed.
663
664 Valid operations depend on the state of the
665 serializer. Once the serializer is destroyed,
666 reset, or completes, the stream becomes
667 invalid and must only be destroyed.
668
669 @see
670 @ref serializer::start_stream
671 */
672 class serializer::stream
673 {
674 public:
675 /** The type used to represent a sequence
676 of mutable buffers.
677 */
678 using mutable_buffers_type =
679 buffers::mutable_buffer_pair;
680
681 /** Constructor.
682
683 A default-constructed stream is
684 considered closed.
685
686 @par Postconditions
687 @code
688 this->is_open() == false
689 @endcode
690 */
691 stream() noexcept = default;
692
693 /** Constructor.
694
695 After construction, the moved-from
696 object is as if default-constructed.
697
698 @par Postconditions
699 @code
700 other->is_open() == false
701 @endcode
702
703 @param other The object to move from.
704 */
705 stream(stream&& other) noexcept
706 : impl_(other.impl_)
707 {
708 other.impl_ = nullptr;
709 }
710
711 /** Move assignment.
712
713 After assignment, the moved-from
714 object is as if default-constructed.
715
716 @par Postconditions
717 @code
718 other->is_open() == false
719 @endcode
720
721 @param other The object to assign from.
722 @return A reference to this object.
723 */
724 stream&
725 operator=(stream&& other) noexcept
726 {
727 std::swap(impl_, other.impl_);
728 return *this;
729 }
730
731 /** Return true if the stream is open.
732 */
733 bool
734 5030 is_open() const noexcept
735 {
736 5030 return impl_ != nullptr;
737 }
738
739 /** Return the available capacity.
740
741 @par Preconditions
742 @code
743 this->is_open() == true
744 @endcode
745
746 @par Exception Safety
747 Strong guarantee.
748
749 @throw std::logic_error
750 `this->is_open() == false`.
751 */
752 BOOST_HTTP_PROTO_DECL
753 std::size_t
754 capacity() const;
755
756 /** Prepare a buffer for writing.
757
758 Retuns a mutable buffer sequence representing
759 the writable bytes. Use @ref commit to make the
760 written data available to the serializer.
761
762 All buffer sequences previously obtained
763 using @ref prepare are invalidated.
764
765 @par Preconditions
766 @code
767 this->is_open() == true && n <= this->capacity()
768 @endcode
769
770 @par Exception Safety
771 Strong guarantee.
772
773 @return An instance of @ref mutable_buffers_type
774 the underlying memory is owned by the serializer.
775
776 @throw std::logic_error
777 `this->is_open() == false`
778
779 @see
780 @ref commit,
781 @ref capacity.
782 */
783 BOOST_HTTP_PROTO_DECL
784 mutable_buffers_type
785 prepare();
786
787 /** Commit data to the serializer.
788
789 Makes `n` bytes available to the serializer.
790
791 All buffer sequences previously obtained
792 using @ref prepare are invalidated.
793
794 @par Preconditions
795 @code
796 this->is_open() == true && n <= this->capacity()
797 @endcode
798
799 @par Exception Safety
800 Strong guarantee.
801 Exceptions thrown on invalid input.
802
803 @param n The number of bytes to append.
804
805 @throw std::invalid_argument
806 `n > this->capacity()`
807
808 @throw std::logic_error
809 `this->is_open() == false`
810
811 @see
812 @ref prepare,
813 @ref capacity.
814 */
815 BOOST_HTTP_PROTO_DECL
816 void
817 commit(std::size_t n);
818
819 /** Close the stream if open.
820
821 Closes the stream and
822 notifies the serializer that the
823 message body has ended.
824
825 If the stream is already closed this
826 call has no effect.
827
828 @par Postconditions
829 @code
830 this->is_open() == false
831 @endcode
832 */
833 BOOST_HTTP_PROTO_DECL
834 void
835 close() noexcept;
836
837 /** Destructor.
838
839 Closes the stream if open.
840 */
841 24 ~stream()
842 {
843 24 close();
844 24 }
845
846 private:
847 friend class serializer;
848
849 explicit
850 23 stream(serializer::impl* impl) noexcept
851 23 : impl_(impl)
852 {
853 23 }
854
855 serializer::impl* impl_ = nullptr;
856 };
857
858 } // http_proto
859 } // boost
860
861 #include <boost/http_proto/impl/serializer.hpp>
862
863 #endif
864