GCC Code Coverage Report


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