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