libs/http/include/boost/http/serializer.hpp

100.0% Lines (21/21) 100.0% Functions (7/7) 81.8% Branches (9/11)
libs/http/include/boost/http/serializer.hpp
Line Branch Hits Source Code
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
9 //
10
11 #ifndef BOOST_HTTP_SERIALIZER_HPP
12 #define BOOST_HTTP_SERIALIZER_HPP
13
14 #include <boost/http/config.hpp>
15 #include <boost/http/detail/workspace.hpp>
16 #include <boost/http/error.hpp>
17
18 #include <boost/capy/buffers.hpp>
19 #include <boost/capy/buffers/buffer_pair.hpp>
20 #include <boost/capy/concept/buffer_sink.hpp>
21 #include <boost/capy/concept/write_stream.hpp>
22 #include <boost/capy/io_task.hpp>
23 #include <boost/core/span.hpp>
24 #include <boost/system/result.hpp>
25
26 #include <cstddef>
27 #include <type_traits>
28 #include <utility>
29
30 namespace boost {
31 namespace http {
32
33 // Forward declaration
34 class message_base;
35
36 //------------------------------------------------
37
38 /** A serializer for HTTP/1 messages
39
40 This is used to serialize one or more complete
41 HTTP/1 messages. Each message consists of a
42 required header followed by an optional body.
43
44 Objects of this type operate using an "input area" and an
45 "output area". Callers provide data to the input area
46 using one of the @ref start or @ref start_stream member
47 functions. After input is provided, serialized data
48 becomes available in the serializer's output area in the
49 form of a constant buffer sequence.
50
51 Callers alternate between filling the input area and
52 consuming the output area until all the input has been
53 provided and all the output data has been consumed, or
54 an error occurs.
55
56 After calling @ref start, the caller must ensure that the
57 contents of the associated message are not changed or
58 destroyed until @ref is_done returns true, @ref reset is
59 called, or the serializer is destroyed, otherwise the
60 behavior is undefined.
61 */
62 class serializer
63 {
64 public:
65 template<capy::WriteStream Stream>
66 class sink;
67
68 /** The type used to represent a sequence
69 of mutable buffers for streaming.
70 */
71 using mutable_buffers_type =
72 capy::mutable_buffer_pair;
73
74 /** The type used to represent a sequence of
75 constant buffers that refers to the output
76 area.
77 */
78 using const_buffers_type =
79 boost::span<capy::const_buffer const>;
80
81 /** Destructor
82 */
83 BOOST_HTTP_DECL
84 ~serializer();
85
86 /** Default constructor.
87
88 Constructs a serializer with no allocated state.
89 The serializer must be assigned from a valid
90 serializer before use.
91
92 @par Postconditions
93 The serializer has no allocated state.
94 */
95 2 serializer() = default;
96
97 /** Constructor.
98
99 Constructs a serializer with the provided configuration.
100
101 @par Postconditions
102 @code
103 this->is_done() == true
104 @endcode
105
106 @param cfg Shared pointer to serializer configuration.
107
108 @see @ref make_serializer_config, @ref serializer_config.
109 */
110 BOOST_HTTP_DECL
111 explicit
112 serializer(
113 std::shared_ptr<serializer_config_impl const> cfg);
114
115 /** Constructor with associated message.
116
117 Constructs a serializer with the provided configuration
118 and associates a message for the serializer's lifetime.
119 The message must remain valid until the serializer is
120 destroyed.
121
122 @par Postconditions
123 @code
124 this->is_done() == true
125 @endcode
126
127 @param cfg Shared pointer to serializer configuration.
128
129 @param m The message to associate.
130
131 @see @ref make_serializer_config, @ref serializer_config.
132 */
133 BOOST_HTTP_DECL
134 serializer(
135 std::shared_ptr<serializer_config_impl const> cfg,
136 message_base const& m);
137
138 /** Constructor.
139
140 The states of `other` are transferred
141 to the newly constructed object,
142 which includes the allocated buffer.
143 After construction, the only valid
144 operations on the moved-from object
145 are destruction and assignment.
146
147 Buffer sequences previously obtained
148 using @ref prepare remain valid.
149
150 @par Postconditions
151 @code
152 other.is_done() == true
153 @endcode
154
155 @par Complexity
156 Constant.
157
158 @param other The serializer to move from.
159 */
160 BOOST_HTTP_DECL
161 serializer(
162 serializer&& other) noexcept;
163
164 /** Assignment.
165 The states of `other` are transferred
166 to this object, which includes the
167 allocated buffer. After assignment,
168 the only valid operations on the
169 moved-from object are destruction and
170 assignment.
171 Buffer sequences previously obtained
172 using @ref prepare remain valid.
173 @par Complexity
174 Constant.
175 @param other The serializer to move from.
176 @return A reference to this object.
177 */
178 BOOST_HTTP_DECL
179 serializer&
180 operator=(serializer&& other) noexcept;
181
182 /** Reset the serializer for a new message.
183
184 Aborts any ongoing serialization and
185 prepares the serializer to start
186 serialization of a new message.
187 */
188 BOOST_HTTP_DECL
189 void
190 reset() noexcept;
191
192 /** Set the message to serialize.
193
194 Associates a message with the serializer for subsequent
195 streaming operations. The message is not copied; the caller
196 must ensure it remains valid until serialization completes.
197
198 @param m The message to associate.
199 */
200 BOOST_HTTP_DECL
201 void
202 set_message(message_base const& m) noexcept;
203
204 /** Start serializing a message with an empty body
205
206 This function prepares the serializer to create a message which
207 has an empty body.
208 Ownership of the specified message is not transferred; the caller is
209 responsible for ensuring the lifetime of the object extends until the
210 serializer is done.
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 Exception Safety
223 Strong guarantee.
224 Exceptions thrown if there is insufficient internal buffer space
225 to start the operation.
226
227 @throw std::logic_error `this->is_done() == true`.
228
229 @throw std::length_error if there is insufficient internal buffer
230 space to start the operation.
231
232 @param m The request or response headers to serialize.
233
234 @see
235 @ref message_base.
236 */
237 void
238 BOOST_HTTP_DECL
239 start(message_base const& m);
240
241 /** Start serializing the associated message with an empty body.
242
243 Uses the message associated at construction time.
244
245 @par Preconditions
246 A message was associated at construction.
247 @code
248 this->is_done() == true
249 @endcode
250
251 @par Postconditions
252 @code
253 this->is_done() == false
254 @endcode
255
256 @par Exception Safety
257 Strong guarantee.
258
259 @throw std::logic_error if no message is associated or
260 `this->is_done() == false`.
261
262 @throw std::length_error if there is insufficient internal buffer
263 space to start the operation.
264 */
265 void
266 BOOST_HTTP_DECL
267 start();
268
269 /** Start serializing a message with a buffer sequence body
270
271 Initializes the serializer with the HTTP start-line and headers from `m`,
272 and the provided `buffers` for reading the message body from.
273
274 Changing the contents of the message after calling this function and
275 before @ref is_done returns `true` results in undefined behavior.
276
277 At least one copy of the specified buffer sequence is maintained until
278 the serializer is done, gets reset, or ios destroyed, after which all
279 of its copies are destroyed. Ownership of the underlying memory is not
280 transferred; the caller must ensure the memory remains valid until the
281 serializer’s copies are destroyed.
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 capy::ConstBufferSequence<ConstBufferSequence>
296 @endcode
297
298 @par Exception Safety
299 Strong guarantee.
300 Exceptions thrown if there is insufficient internal buffer space
301 to start the operation.
302
303 @throw std::logic_error `this->is_done() == true`.
304
305 @throw std::length_error If there is insufficient internal buffer
306 space to start the operation.
307
308 @param m The message to read the HTTP start-line and headers from.
309
310 @param buffers A buffer sequence containing the message body.
311
312 containing the message body data. While
313 the buffers object is copied, ownership of
314 the underlying memory remains with the
315 caller, who must ensure it stays valid
316 until @ref is_done returns `true`.
317
318 @see
319 @ref message_base.
320 */
321 template<
322 class ConstBufferSequence,
323 class = typename std::enable_if<
324 capy::ConstBufferSequence<ConstBufferSequence>>::type
325 >
326 void
327 start(
328 message_base const& m,
329 ConstBufferSequence&& buffers);
330
331 /** Prepare the serializer for streaming body data.
332
333 Initializes the serializer with the HTTP
334 start-line and headers from `m` for streaming
335 mode. After calling this function, use
336 @ref stream_prepare, @ref stream_commit, and
337 @ref stream_close to write body data.
338
339 Changing the contents of the message
340 after calling this function and before
341 @ref is_done returns `true` results in
342 undefined behavior.
343
344 @par Example
345 @code
346 auto sink = sr.sink_for(socket);
347 sr.start_stream(response);
348
349 capy::mutable_buffer arr[16];
350 auto bufs = sink.prepare(arr);
351 std::memcpy(bufs[0].data(), "Hello", 5);
352 co_await sink.commit(5, true);
353 @endcode
354
355 @par Preconditions
356 @code
357 this->is_done() == true
358 @endcode
359
360 @par Postconditions
361 @code
362 this->is_done() == false
363 @endcode
364
365 @par Exception Safety
366 Strong guarantee.
367 Exceptions thrown if there is insufficient
368 internal buffer space to start the
369 operation.
370
371 @throw std::length_error if there is
372 insufficient internal buffer space to
373 start the operation.
374
375 @param m The message to read the HTTP
376 start-line and headers from.
377
378 @see
379 @ref stream_prepare,
380 @ref stream_commit,
381 @ref stream_close,
382 @ref message_base.
383 */
384 BOOST_HTTP_DECL
385 void
386 start_stream(
387 message_base const& m);
388
389 /** Start streaming the associated message.
390
391 Uses the message associated at construction time.
392
393 @par Preconditions
394 A message was associated at construction.
395 @code
396 this->is_done() == true
397 @endcode
398
399 @par Postconditions
400 @code
401 this->is_done() == false
402 @endcode
403
404 @par Exception Safety
405 Strong guarantee.
406
407 @throw std::logic_error if no message is associated or
408 `this->is_done() == false`.
409
410 @throw std::length_error if there is insufficient internal buffer
411 space to start the operation.
412
413 @see
414 @ref stream_prepare,
415 @ref stream_commit,
416 @ref stream_close.
417 */
418 BOOST_HTTP_DECL
419 void
420 start_stream();
421
422 /** Get a sink wrapper for writing body data.
423
424 Returns a @ref sink object that can be used to write body
425 data to the provided stream. This function does not call
426 @ref start_stream. The caller must call @ref start_stream
427 before using the sink.
428
429 This allows the sink to be obtained early (e.g., at
430 construction time) and stored, with streaming started
431 later when the message is ready.
432
433 @par Example
434 @code
435 http::serializer sr(cfg, res);
436 auto sink = sr.sink_for(socket);
437 // ... later ...
438 sr.start_stream(); // Configure for streaming
439
440 capy::mutable_buffer arr[16];
441 auto bufs = sink.prepare(arr);
442 std::memcpy(bufs[0].data(), "Hello", 5);
443 co_await sink.commit(5, true);
444 @endcode
445
446 @tparam Stream The output stream type satisfying
447 @ref capy::WriteStream.
448
449 @param ws The output stream to write serialized data to.
450
451 @return A @ref sink object for writing body data.
452
453 @see @ref sink, @ref start_stream.
454 */
455 template<capy::WriteStream Stream>
456 sink<Stream>
457 sink_for(Stream& ws) noexcept;
458
459 /** Return the output area.
460
461 This function serializes some or all of
462 the message and returns the corresponding
463 output buffers. Afterward, a call to @ref
464 consume is required to report the number
465 of bytes used, if any.
466
467 If the message includes an
468 `Expect: 100-continue` header and the
469 header section of the message has been
470 consumed, the returned result will contain
471 @ref error::expect_100_continue to
472 indicate that the header part of the
473 message is complete. The next call to @ref
474 prepare will produce output.
475
476 When the serializer is in streaming mode,
477 the result may contain @ref error::need_data
478 to indicate that additional input is required
479 to produce output.
480
481 If a @ref source object is in use and a
482 call to @ref source::read returns an
483 error, the serializer enters a faulted
484 state and propagates the error to the
485 caller. This faulted state can only be
486 cleared by calling @ref reset. This
487 ensures the caller is explicitly aware
488 that the previous message was truncated
489 and that the stream must be terminated.
490
491 @par Preconditions
492 @code
493 this->is_done() == false
494 @endcode
495 No unrecoverable error reported from previous calls.
496
497 @par Exception Safety
498 Strong guarantee.
499 Calls to @ref source::read may throw if in use.
500
501 @throw std::logic_error
502 `this->is_done() == true`.
503
504 @return A result containing @ref
505 const_buffers_type that represents the
506 output area or an error if any occurred.
507
508 @see
509 @ref consume,
510 @ref is_done,
511 @ref const_buffers_type.
512 */
513 BOOST_HTTP_DECL
514 auto
515 prepare() ->
516 system::result<
517 const_buffers_type>;
518
519 /** Consume bytes from the output area.
520
521 This function should be called after one
522 or more bytes contained in the buffers
523 provided in the prior call to @ref prepare
524 have been used.
525
526 After a call to @ref consume, callers
527 should check the return value of @ref
528 is_done to determine if the entire message
529 has been serialized.
530
531 @par Preconditions
532 @code
533 this->is_done() == false
534 @endcode
535
536 @par Exception Safety
537 Strong guarantee.
538
539 @throw std::logic_error
540 `this->is_done() == true`.
541
542 @param n The number of bytes to consume.
543 If `n` is greater than the size of the
544 buffer returned from @ref prepared the
545 entire output sequence is consumed and no
546 error is issued.
547
548 @see
549 @ref prepare,
550 @ref is_done,
551 @ref const_buffers_type.
552 */
553 BOOST_HTTP_DECL
554 void
555 consume(std::size_t n);
556
557 /** Return true if serialization is complete.
558 */
559 BOOST_HTTP_DECL
560 bool
561 is_done() const noexcept;
562
563 /** Return the available capacity for streaming.
564
565 Returns the number of bytes that can be written
566 to the serializer's internal buffer.
567
568 @par Preconditions
569 The serializer is in streaming mode (after calling
570 @ref start_stream).
571
572 @par Exception Safety
573 Strong guarantee.
574
575 @throw std::logic_error if not in streaming mode.
576 */
577 BOOST_HTTP_DECL
578 std::size_t
579 stream_capacity() const;
580
581 /** Prepare a buffer for writing stream data.
582
583 Returns a mutable buffer sequence representing
584 the writable bytes. Use @ref stream_commit to make the
585 written data available to the serializer.
586
587 All buffer sequences previously obtained
588 using @ref stream_prepare are invalidated.
589
590 @par Preconditions
591 The serializer is in streaming mode.
592
593 @par Exception Safety
594 Strong guarantee.
595
596 @return An instance of @ref mutable_buffers_type.
597 The underlying memory is owned by the serializer.
598
599 @throw std::logic_error if not in streaming mode.
600
601 @see
602 @ref stream_commit,
603 @ref stream_capacity.
604 */
605 BOOST_HTTP_DECL
606 mutable_buffers_type
607 stream_prepare();
608
609 /** Commit data to the serializer stream.
610
611 Makes `n` bytes available to the serializer.
612
613 All buffer sequences previously obtained
614 using @ref stream_prepare are invalidated.
615
616 @par Preconditions
617 The serializer is in streaming mode and
618 `n <= stream_capacity()`.
619
620 @par Exception Safety
621 Strong guarantee.
622 Exceptions thrown on invalid input.
623
624 @param n The number of bytes to commit.
625
626 @throw std::invalid_argument if `n > stream_capacity()`.
627
628 @throw std::logic_error if not in streaming mode.
629
630 @see
631 @ref stream_prepare,
632 @ref stream_capacity.
633 */
634 BOOST_HTTP_DECL
635 void
636 stream_commit(std::size_t n);
637
638 /** Close the stream.
639
640 Notifies the serializer that the message body
641 has ended. After calling this function, no more
642 data can be written to the stream.
643
644 @par Preconditions
645 The serializer is in streaming mode.
646
647 @par Postconditions
648 The stream is closed.
649 */
650 BOOST_HTTP_DECL
651 void
652 stream_close() noexcept;
653
654 private:
655 class impl;
656 class cbs_gen;
657 template<class>
658 class cbs_gen_impl;
659
660 BOOST_HTTP_DECL
661 detail::workspace&
662 ws();
663
664 BOOST_HTTP_DECL
665 void
666 start_init(
667 message_base const&);
668
669 BOOST_HTTP_DECL
670 void
671 start_buffers(
672 message_base const&,
673 cbs_gen&);
674
675 impl* impl_ = nullptr;
676 };
677
678 //------------------------------------------------
679
680 /** A sink adapter for writing HTTP message bodies.
681
682 This class wraps an underlying @ref capy::WriteStream and a
683 @ref serializer to provide a @ref capy::BufferSink interface
684 for writing message body data. The caller writes directly into
685 the serializer's internal buffer (zero-copy); the serializer
686 automatically handles:
687
688 @li Chunked transfer-encoding (chunk framing added automatically)
689 @li Content-Encoding compression (gzip, deflate, brotli if configured)
690 @li Content-Length validation (if specified in headers)
691
692 For @ref capy::WriteSink semantics (caller owns buffers), wrap
693 this sink with @ref capy::any_buffer_sink which provides both
694 interfaces.
695
696 @tparam Stream The underlying stream type satisfying
697 @ref capy::WriteStream.
698
699 @par Thread Safety
700 Distinct objects: Safe.
701 Shared objects: Unsafe.
702
703 @par Example
704 @code
705 capy::task<void>
706 send_response(capy::WriteStream auto& socket)
707 {
708 http::response res;
709 res.set_chunked(true);
710 http::serializer sr(cfg, res);
711
712 auto sink = sr.sink_for(socket);
713 sr.start_stream();
714
715 // Zero-copy write using BufferSink interface
716 capy::mutable_buffer arr[16];
717 auto bufs = sink.prepare(arr);
718 std::memcpy(bufs[0].data(), "Hello", 5);
719 co_await sink.commit(5, true);
720 }
721 @endcode
722
723 @see capy::BufferSink, capy::any_buffer_sink, serializer
724 */
725 template<capy::WriteStream Stream>
726 class serializer::sink
727 {
728 Stream* stream_ = nullptr;
729 serializer* sr_ = nullptr;
730
731 public:
732 /** Constructor.
733
734 A default-constructed sink is in an empty state.
735 */
736 sink() noexcept = default;
737
738 /** Constructor.
739
740 @param stream The underlying stream to write serialized data to.
741 @param sr The serializer performing HTTP framing.
742 */
743 68 sink(
744 Stream& stream,
745 serializer& sr) noexcept
746 68 : stream_(&stream)
747 68 , sr_(&sr)
748 {
749 68 }
750
751 /** Prepare writable buffers.
752
753 Fills the provided span with mutable buffer descriptors
754 pointing to the serializer's internal storage. This
755 operation is synchronous.
756
757 @param dest Span of mutable_buffer to fill.
758
759 @return A span of filled buffers.
760 */
761 std::span<capy::mutable_buffer>
762 62 prepare(std::span<capy::mutable_buffer> dest)
763 {
764
1/1
✓ Branch 1 taken 62 times.
62 auto bufs = sr_->stream_prepare();
765 62 std::size_t count = 0;
766
1/2
✓ Branch 0 taken 124 times.
✗ Branch 1 not taken.
124 for(auto const& b : bufs)
767 {
768
5/6
✓ Branch 1 taken 124 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 62 times.
✓ Branch 5 taken 62 times.
✓ Branch 6 taken 62 times.
✓ Branch 7 taken 62 times.
124 if(count >= dest.size() || b.size() == 0)
769 62 break;
770 62 dest[count++] = b;
771 }
772 124 return dest.first(count);
773 }
774
775 /** Commit bytes written to the prepared buffers.
776
777 Commits `n` bytes written to the buffers returned by the
778 most recent call to @ref prepare. The operation flushes
779 serialized output to the underlying stream.
780
781 @param n The number of bytes to commit.
782
783 @return An awaitable yielding `(error_code)`.
784 */
785 auto
786 50 commit(std::size_t n)
787 -> capy::io_task<>
788 {
789 50 return commit(n, false);
790 }
791
792 /** Commit bytes written with optional end-of-stream.
793
794 Commits `n` bytes written to the buffers returned by the
795 most recent call to @ref prepare. If `eof` is true, also
796 signals end-of-stream.
797
798 @param n The number of bytes to commit.
799 @param eof If true, signals end-of-stream after committing.
800
801 @return An awaitable yielding `(error_code)`.
802 */
803 auto
804
1/1
✓ Branch 1 taken 68 times.
68 commit(std::size_t n, bool eof)
805 -> capy::io_task<>
806 {
807 sr_->stream_commit(n);
808
809 if(eof)
810 sr_->stream_close();
811
812 while(!sr_->is_done())
813 {
814 auto cbs = sr_->prepare();
815 if(cbs.has_error())
816 {
817 if(cbs.error() == error::need_data && !eof)
818 break;
819 if(cbs.error() == error::need_data)
820 continue;
821 co_return {cbs.error()};
822 }
823
824 if(capy::buffer_empty(*cbs))
825 {
826 sr_->consume(0);
827 continue;
828 }
829
830 auto [ec, written] = co_await stream_->write_some(*cbs);
831 sr_->consume(written);
832
833 if(ec)
834 co_return {ec};
835 }
836
837 co_return {};
838 136 }
839
840 /** Signal end-of-stream.
841
842 Closes the body stream and flushes any remaining serializer
843 output to the underlying stream. For chunked encoding, this
844 writes the final zero-length chunk.
845
846 @return An awaitable yielding `(error_code)`.
847
848 @post The serializer's `is_done()` returns `true` on success.
849 */
850 auto
851
1/1
✓ Branch 1 taken 28 times.
28 commit_eof()
852 -> capy::io_task<>
853 {
854 sr_->stream_close();
855
856 while(!sr_->is_done())
857 {
858 auto cbs = sr_->prepare();
859 if(cbs.has_error())
860 {
861 if(cbs.error() == error::need_data)
862 continue;
863 co_return {cbs.error()};
864 }
865
866 if(capy::buffer_empty(*cbs))
867 {
868 sr_->consume(0);
869 continue;
870 }
871
872 auto [ec, written] = co_await stream_->write_some(*cbs);
873 sr_->consume(written);
874
875 if(ec)
876 co_return {ec};
877 }
878
879 co_return {};
880 56 }
881 };
882
883 //------------------------------------------------
884
885 template<capy::WriteStream Stream>
886 serializer::sink<Stream>
887 68 serializer::sink_for(Stream& ws) noexcept
888 {
889 68 return sink<Stream>(ws, *this);
890 }
891
892 } // http
893 } // boost
894
895 #include <boost/http/impl/serializer.hpp>
896
897 #endif
898