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
9 : //
10 :
11 : #ifndef BOOST_HTTP_SERIALIZER_HPP
12 : #define BOOST_HTTP_SERIALIZER_HPP
13 :
14 : #include <boost/http/detail/config.hpp>
15 : #include <boost/http/detail/workspace.hpp>
16 : #include <boost/http/source.hpp>
17 :
18 : #include <boost/capy/buffers/buffer_pair.hpp>
19 : #include <boost/core/span.hpp>
20 : #include <boost/capy/core/polystore_fwd.hpp>
21 : #include <boost/system/result.hpp>
22 :
23 : #include <type_traits>
24 : #include <utility>
25 :
26 : namespace boost {
27 : namespace http {
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<capy::const_buffer const>;
68 :
69 : /** Destructor
70 : */
71 : BOOST_HTTP_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_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_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_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_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_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 : capy::const_buffer_sequence<ConstBufferSequence>
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 : capy::const_buffer_sequence<ConstBufferSequence>>::type
279 : >
280 : void
281 : start(
282 : message_base const& m,
283 : ConstBufferSequence&& buffers);
284 :
285 : /** Start serializing a message with a @em Source body
286 :
287 : Initializes the serializer with the HTTP start-line and headers from
288 : `m`, and constructs a `Source` object to provide the message body.
289 :
290 : Changing the contents of the message
291 : after calling this function and before
292 : @ref is_done returns `true` results in
293 : undefined behavior.
294 :
295 : The serializer destroys Source object when:
296 : @li `this->is_done() == true`
297 : @li An unrecoverable serialization error occurs
298 : @li The serializer is destroyed
299 :
300 : @par Example
301 : @code
302 : file f("example.zip", file_mode::scan);
303 : response.set_payload_size(f.size());
304 : serializer.start<file_source>(response, std::move(f));
305 : @endcode
306 :
307 : @par Preconditions
308 : @code
309 : this->is_done() == true
310 : @endcode
311 :
312 : @par Postconditions
313 : @code
314 : this->is_done() == false
315 : @endcode
316 :
317 : @par Constraints
318 : @code
319 : is_source<Source>::value == true
320 : @endcode
321 :
322 : @par Exception Safety
323 : Strong guarantee.
324 : Exceptions thrown if there is insufficient
325 : internal buffer space to start the
326 : operation.
327 :
328 : @throw std::length_error if there is
329 : insufficient internal buffer space to
330 : start the operation.
331 :
332 : @param m The message to read the HTTP
333 : start-line and headers from.
334 :
335 : @param args Arguments to be passed to the
336 : `Source` constructor.
337 :
338 : @return A reference to the constructed Source object.
339 :
340 : @see
341 : @ref source,
342 : @ref file_source,
343 : @ref message_base.
344 : */
345 : template<
346 : class Source,
347 : class... Args,
348 : class = typename std::enable_if<
349 : is_source<Source>::value>::type>
350 : Source&
351 : start(
352 : message_base const& m,
353 : Args&&... args);
354 :
355 : /** Prepare the serializer for a new message using a stream interface.
356 :
357 : Initializes the serializer with the HTTP
358 : start-line and headers from `m`, and returns
359 : a @ref stream object for writing the body
360 : data into the serializer's internal buffer.
361 :
362 : Once the serializer is destroyed, @ref reset
363 : is called, or @ref is_done returns true, the
364 : only valid operation on the stream is destruction.
365 :
366 : The stream allows inverted control flow: the
367 : caller supplies body data to the serializer’s
368 : internal buffer while reading from an external
369 : source.
370 :
371 : Changing the contents of the message
372 : after calling this function and before
373 : @ref is_done returns `true` results in
374 : undefined behavior.
375 :
376 : @par Example
377 : @code
378 : serializer::stream st = serializer.start_stream(response);
379 : do
380 : {
381 : if(st.is_open())
382 : {
383 : std::size_t n = source.read_some(st.prepare());
384 :
385 : if(ec == error::eof)
386 : st.close();
387 : else
388 : st.commit(n);
389 : }
390 :
391 : write_some(client, serializer);
392 :
393 : } while(!serializer.is_done());
394 : @endcode
395 :
396 : @par Preconditions
397 : @code
398 : this->is_done() == true
399 : @endcode
400 :
401 : @par Postconditions
402 : @code
403 : this->is_done() == false
404 : @endcode
405 :
406 : @par Exception Safety
407 : Strong guarantee.
408 : Exceptions thrown if there is insufficient
409 : internal buffer space to start the
410 : operation.
411 :
412 : @throw std::length_error if there is
413 : insufficient internal buffer space to
414 : start the operation.
415 :
416 : @param m The message to read the HTTP
417 : start-line and headers from.
418 :
419 : @return A @ref stream object for writing the body
420 : data into the serializer's internal buffer.
421 :
422 : @see
423 : @ref stream,
424 : @ref message_base.
425 : */
426 : BOOST_HTTP_DECL
427 : stream
428 : start_stream(
429 : message_base const& m);
430 :
431 : /** Return the output area.
432 :
433 : This function serializes some or all of
434 : the message and returns the corresponding
435 : output buffers. Afterward, a call to @ref
436 : consume is required to report the number
437 : of bytes used, if any.
438 :
439 : If the message includes an
440 : `Expect: 100-continue` header and the
441 : header section of the message has been
442 : consumed, the returned result will contain
443 : @ref error::expect_100_continue to
444 : indicate that the header part of the
445 : message is complete. The next call to @ref
446 : prepare will produce output.
447 :
448 : When the serializer is used through the
449 : @ref stream interface, the result may
450 : contain @ref error::need_data to indicate
451 : that additional input is required to
452 : produce output.
453 :
454 : If a @ref source object is in use and a
455 : call to @ref source::read returns an
456 : error, the serializer enters a faulted
457 : state and propagates the error to the
458 : caller. This faulted state can only be
459 : cleared by calling @ref reset. This
460 : ensures the caller is explicitly aware
461 : that the previous message was truncated
462 : and that the stream must be terminated.
463 :
464 : @par Preconditions
465 : @code
466 : this->is_done() == false
467 : @endcode
468 : No unrecoverable error reported from previous calls.
469 :
470 : @par Exception Safety
471 : Strong guarantee.
472 : Calls to @ref source::read may throw if in use.
473 :
474 : @throw std::logic_error
475 : `this->is_done() == true`.
476 :
477 : @return A result containing @ref
478 : const_buffers_type that represents the
479 : output area or an error if any occurred.
480 :
481 : @see
482 : @ref consume,
483 : @ref is_done,
484 : @ref const_buffers_type.
485 : */
486 : BOOST_HTTP_DECL
487 : auto
488 : prepare() ->
489 : system::result<
490 : const_buffers_type>;
491 :
492 : /** Consume bytes from the output area.
493 :
494 : This function should be called after one
495 : or more bytes contained in the buffers
496 : provided in the prior call to @ref prepare
497 : have been used.
498 :
499 : After a call to @ref consume, callers
500 : should check the return value of @ref
501 : is_done to determine if the entire message
502 : has been serialized.
503 :
504 : @par Preconditions
505 : @code
506 : this->is_done() == false
507 : @endcode
508 :
509 : @par Exception Safety
510 : Strong guarantee.
511 :
512 : @throw std::logic_error
513 : `this->is_done() == true`.
514 :
515 : @param n The number of bytes to consume.
516 : If `n` is greater than the size of the
517 : buffer returned from @ref prepared the
518 : entire output sequence is consumed and no
519 : error is issued.
520 :
521 : @see
522 : @ref prepare,
523 : @ref is_done,
524 : @ref const_buffers_type.
525 : */
526 : BOOST_HTTP_DECL
527 : void
528 : consume(std::size_t n);
529 :
530 : /** Return true if serialization is complete.
531 : */
532 : BOOST_HTTP_DECL
533 : bool
534 : is_done() const noexcept;
535 :
536 : private:
537 : class impl;
538 : class cbs_gen;
539 : template<class>
540 : class cbs_gen_impl;
541 :
542 : BOOST_HTTP_DECL
543 : detail::workspace&
544 : ws();
545 :
546 : BOOST_HTTP_DECL
547 : void
548 : start_init(
549 : message_base const&);
550 :
551 : BOOST_HTTP_DECL
552 : void
553 : start_buffers(
554 : message_base const&,
555 : cbs_gen&);
556 :
557 : BOOST_HTTP_DECL
558 : void
559 : start_source(
560 : message_base const&,
561 : source&);
562 :
563 : impl* impl_ = nullptr;
564 : };
565 :
566 : /** Serializer configuration settings.
567 :
568 : @see
569 : @ref install_serializer_service,
570 : @ref serializer.
571 : */
572 : struct serializer::config
573 : {
574 : /** Enable Brotli Content-Encoding.
575 :
576 : Requires `boost::capy::brotli::encode_service` to be
577 : installed, otherwise an exception is thrown.
578 : */
579 : bool apply_brotli_encoder = false;
580 :
581 : /** Enable Deflate Content-Encoding.
582 :
583 : Requires `boost::zlib::deflate_service` to be
584 : installed, otherwise an exception is thrown.
585 : */
586 : bool apply_deflate_encoder = false;
587 :
588 : /** Enable Gzip Content-Encoding.
589 :
590 : Requires `boost::zlib::deflate_service` to be
591 : installed, otherwise an exception is thrown.
592 : */
593 : bool apply_gzip_encoder = false;
594 :
595 : /** Brotli compression quality (0–11).
596 :
597 : Higher values yield better but slower compression.
598 : */
599 : std::uint32_t brotli_comp_quality = 5;
600 :
601 : /** Brotli compression window size (10–24).
602 :
603 : Larger windows improve compression but increase
604 : memory usage.
605 : */
606 : std::uint32_t brotli_comp_window = 18;
607 :
608 : /** Zlib compression level (0–9).
609 :
610 : 0 = no compression, 1 = fastest, 9 = best
611 : compression.
612 : */
613 : int zlib_comp_level = 6;
614 :
615 : /** Zlib window bits (9–15).
616 :
617 : Controls the history buffer size. Larger values
618 : improve compression but use more memory.
619 : */
620 : int zlib_window_bits = 15;
621 :
622 : /** Zlib memory level (1–9).
623 :
624 : Higher values use more memory, but offer faster
625 : and more efficient compression.
626 : */
627 : int zlib_mem_level = 8;
628 :
629 : /** Minimum buffer size for payloads (must be > 0). */
630 : std::size_t payload_buffer = 8192;
631 :
632 : /** Reserved space for type-erasure storage.
633 :
634 : Used for:
635 : @li User-defined @ref source objects.
636 : @li User-defined ConstBufferSequence instances.
637 : */
638 : std::size_t max_type_erase = 1024;
639 : };
640 :
641 : /** Install the serializer service.
642 :
643 : @par Example
644 : @code
645 : // default configuration settings
646 : install_serializer_service(ctx, {});
647 :
648 : serializer sr(ctx);
649 : @endcode
650 :
651 : @par Exception Safety
652 : Strong guarantee.
653 :
654 : @throw std::invalid_argument If the service is
655 : already installed on the context.
656 :
657 : @param ctx Reference to the context on which
658 : the service should be installed.
659 :
660 : @param cfg Configuration settings for the
661 : serializer.
662 :
663 : @see
664 : @ref serializer::config,
665 : @ref serializer.
666 : */
667 : BOOST_HTTP_DECL
668 : void
669 : install_serializer_service(
670 : capy::polystore& ctx,
671 : serializer::config const& cfg);
672 :
673 : //------------------------------------------------
674 :
675 : /** Used for streaming body data during serialization.
676 :
677 : Provides an interface for supplying serialized
678 : body content from an external source. This
679 : object is returned by @ref
680 : serializer::start_stream and enables
681 : incremental writing of the message body into
682 : the serializer's internal buffer.
683 :
684 : The stream supports an inverted control flow
685 : model, where the caller pushes body data as
686 : needed.
687 :
688 : Valid operations depend on the state of the
689 : serializer. Once the serializer is destroyed,
690 : reset, or completes, the stream becomes
691 : invalid and must only be destroyed.
692 :
693 : @see
694 : @ref serializer::start_stream
695 : */
696 : class serializer::stream
697 : {
698 : public:
699 : /** The type used to represent a sequence
700 : of mutable buffers.
701 : */
702 : using mutable_buffers_type =
703 : capy::mutable_buffer_pair;
704 :
705 : /** Constructor.
706 :
707 : A default-constructed stream is
708 : considered closed.
709 :
710 : @par Postconditions
711 : @code
712 : this->is_open() == false
713 : @endcode
714 : */
715 : stream() noexcept = default;
716 :
717 : /** Constructor.
718 :
719 : After construction, the moved-from
720 : object is as if default-constructed.
721 :
722 : @par Postconditions
723 : @code
724 : other->is_open() == false
725 : @endcode
726 :
727 : @param other The object to move from.
728 : */
729 : stream(stream&& other) noexcept
730 : : impl_(other.impl_)
731 : {
732 : other.impl_ = nullptr;
733 : }
734 :
735 : /** Move assignment.
736 :
737 : After assignment, the moved-from
738 : object is as if default-constructed.
739 :
740 : @par Postconditions
741 : @code
742 : other->is_open() == false
743 : @endcode
744 :
745 : @param other The object to assign from.
746 : @return A reference to this object.
747 : */
748 : stream&
749 : operator=(stream&& other) noexcept
750 : {
751 : std::swap(impl_, other.impl_);
752 : return *this;
753 : }
754 :
755 : /** Return true if the stream is open.
756 : */
757 : bool
758 5030 : is_open() const noexcept
759 : {
760 5030 : return impl_ != nullptr;
761 : }
762 :
763 : /** Return the available capacity.
764 :
765 : @par Preconditions
766 : @code
767 : this->is_open() == true
768 : @endcode
769 :
770 : @par Exception Safety
771 : Strong guarantee.
772 :
773 : @throw std::logic_error
774 : `this->is_open() == false`.
775 : */
776 : BOOST_HTTP_DECL
777 : std::size_t
778 : capacity() const;
779 :
780 : /** Prepare a buffer for writing.
781 :
782 : Retuns a mutable buffer sequence representing
783 : the writable bytes. Use @ref commit to make the
784 : written data available to the serializer.
785 :
786 : All buffer sequences previously obtained
787 : using @ref prepare are invalidated.
788 :
789 : @par Preconditions
790 : @code
791 : this->is_open() == true && n <= this->capacity()
792 : @endcode
793 :
794 : @par Exception Safety
795 : Strong guarantee.
796 :
797 : @return An instance of @ref mutable_buffers_type
798 : the underlying memory is owned by the serializer.
799 :
800 : @throw std::logic_error
801 : `this->is_open() == false`
802 :
803 : @see
804 : @ref commit,
805 : @ref capacity.
806 : */
807 : BOOST_HTTP_DECL
808 : mutable_buffers_type
809 : prepare();
810 :
811 : /** Commit data to the serializer.
812 :
813 : Makes `n` bytes available to the serializer.
814 :
815 : All buffer sequences previously obtained
816 : using @ref prepare are invalidated.
817 :
818 : @par Preconditions
819 : @code
820 : this->is_open() == true && n <= this->capacity()
821 : @endcode
822 :
823 : @par Exception Safety
824 : Strong guarantee.
825 : Exceptions thrown on invalid input.
826 :
827 : @param n The number of bytes to append.
828 :
829 : @throw std::invalid_argument
830 : `n > this->capacity()`
831 :
832 : @throw std::logic_error
833 : `this->is_open() == false`
834 :
835 : @see
836 : @ref prepare,
837 : @ref capacity.
838 : */
839 : BOOST_HTTP_DECL
840 : void
841 : commit(std::size_t n);
842 :
843 : /** Close the stream if open.
844 :
845 : Closes the stream and
846 : notifies the serializer that the
847 : message body has ended.
848 :
849 : If the stream is already closed this
850 : call has no effect.
851 :
852 : @par Postconditions
853 : @code
854 : this->is_open() == false
855 : @endcode
856 : */
857 : BOOST_HTTP_DECL
858 : void
859 : close() noexcept;
860 :
861 : /** Destructor.
862 :
863 : Closes the stream if open.
864 : */
865 24 : ~stream()
866 : {
867 24 : close();
868 24 : }
869 :
870 : private:
871 : friend class serializer;
872 :
873 : explicit
874 23 : stream(serializer::impl* impl) noexcept
875 23 : : impl_(impl)
876 : {
877 23 : }
878 :
879 : serializer::impl* impl_ = nullptr;
880 : };
881 :
882 : } // http
883 : } // boost
884 :
885 : #include <boost/http/impl/serializer.hpp>
886 :
887 : #endif
|