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