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