Line data 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_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
|