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