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/config.hpp>
15 : #include <boost/http/detail/workspace.hpp>
16 : #include <boost/http/error.hpp>
17 :
18 : #include <boost/capy/buffers.hpp>
19 : #include <boost/capy/buffers/buffer_pair.hpp>
20 : #include <boost/capy/concept/buffer_sink.hpp>
21 : #include <boost/capy/concept/write_stream.hpp>
22 : #include <boost/capy/io_task.hpp>
23 : #include <boost/core/span.hpp>
24 : #include <boost/system/result.hpp>
25 :
26 : #include <cstddef>
27 : #include <type_traits>
28 : #include <utility>
29 :
30 : namespace boost {
31 : namespace http {
32 :
33 : // Forward declaration
34 : class message_base;
35 :
36 : //------------------------------------------------
37 :
38 : /** A serializer for HTTP/1 messages
39 :
40 : This is used to serialize one or more complete
41 : HTTP/1 messages. Each message consists of a
42 : required header followed by an optional body.
43 :
44 : Objects of this type operate using an "input area" and an
45 : "output area". Callers provide data to the input area
46 : using one of the @ref start or @ref start_stream member
47 : functions. After input is provided, serialized data
48 : becomes available in the serializer's output area in the
49 : form of a constant buffer sequence.
50 :
51 : Callers alternate between filling the input area and
52 : consuming the output area until all the input has been
53 : provided and all the output data has been consumed, or
54 : an error occurs.
55 :
56 : After calling @ref start, the caller must ensure that the
57 : contents of the associated message are not changed or
58 : destroyed until @ref is_done returns true, @ref reset is
59 : called, or the serializer is destroyed, otherwise the
60 : behavior is undefined.
61 : */
62 : class serializer
63 : {
64 : public:
65 : template<capy::WriteStream Stream>
66 : class sink;
67 :
68 : /** The type used to represent a sequence
69 : of mutable buffers for streaming.
70 : */
71 : using mutable_buffers_type =
72 : capy::mutable_buffer_pair;
73 :
74 : /** The type used to represent a sequence of
75 : constant buffers that refers to the output
76 : area.
77 : */
78 : using const_buffers_type =
79 : boost::span<capy::const_buffer const>;
80 :
81 : /** Destructor
82 : */
83 : BOOST_HTTP_DECL
84 : ~serializer();
85 :
86 : /** Default constructor.
87 :
88 : Constructs a serializer with no allocated state.
89 : The serializer must be assigned from a valid
90 : serializer before use.
91 :
92 : @par Postconditions
93 : The serializer has no allocated state.
94 : */
95 2 : serializer() = default;
96 :
97 : /** Constructor.
98 :
99 : Constructs a serializer with the provided configuration.
100 :
101 : @par Postconditions
102 : @code
103 : this->is_done() == true
104 : @endcode
105 :
106 : @param cfg Shared pointer to serializer configuration.
107 :
108 : @see @ref make_serializer_config, @ref serializer_config.
109 : */
110 : BOOST_HTTP_DECL
111 : explicit
112 : serializer(
113 : std::shared_ptr<serializer_config_impl const> cfg);
114 :
115 : /** Constructor with associated message.
116 :
117 : Constructs a serializer with the provided configuration
118 : and associates a message for the serializer's lifetime.
119 : The message must remain valid until the serializer is
120 : destroyed.
121 :
122 : @par Postconditions
123 : @code
124 : this->is_done() == true
125 : @endcode
126 :
127 : @param cfg Shared pointer to serializer configuration.
128 :
129 : @param m The message to associate.
130 :
131 : @see @ref make_serializer_config, @ref serializer_config.
132 : */
133 : BOOST_HTTP_DECL
134 : serializer(
135 : std::shared_ptr<serializer_config_impl const> cfg,
136 : message_base const& m);
137 :
138 : /** Constructor.
139 :
140 : The states of `other` are transferred
141 : to the newly constructed object,
142 : which includes the allocated buffer.
143 : After construction, the only valid
144 : operations on the moved-from object
145 : are destruction and assignment.
146 :
147 : Buffer sequences previously obtained
148 : using @ref prepare remain valid.
149 :
150 : @par Postconditions
151 : @code
152 : other.is_done() == true
153 : @endcode
154 :
155 : @par Complexity
156 : Constant.
157 :
158 : @param other The serializer to move from.
159 : */
160 : BOOST_HTTP_DECL
161 : serializer(
162 : serializer&& other) noexcept;
163 :
164 : /** Assignment.
165 : The states of `other` are transferred
166 : to this object, which includes the
167 : allocated buffer. After assignment,
168 : the only valid operations on the
169 : moved-from object are destruction and
170 : assignment.
171 : Buffer sequences previously obtained
172 : using @ref prepare remain valid.
173 : @par Complexity
174 : Constant.
175 : @param other The serializer to move from.
176 : @return A reference to this object.
177 : */
178 : BOOST_HTTP_DECL
179 : serializer&
180 : operator=(serializer&& other) noexcept;
181 :
182 : /** Reset the serializer for a new message.
183 :
184 : Aborts any ongoing serialization and
185 : prepares the serializer to start
186 : serialization of a new message.
187 : */
188 : BOOST_HTTP_DECL
189 : void
190 : reset() noexcept;
191 :
192 : /** Set the message to serialize.
193 :
194 : Associates a message with the serializer for subsequent
195 : streaming operations. The message is not copied; the caller
196 : must ensure it remains valid until serialization completes.
197 :
198 : @param m The message to associate.
199 : */
200 : BOOST_HTTP_DECL
201 : void
202 : set_message(message_base const& m) noexcept;
203 :
204 : /** Start serializing a message with an empty body
205 :
206 : This function prepares the serializer to create a message which
207 : has an empty body.
208 : Ownership of the specified message is not transferred; the caller is
209 : responsible for ensuring the lifetime of the object extends until the
210 : serializer is done.
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 Exception Safety
223 : Strong guarantee.
224 : Exceptions thrown if there is insufficient internal buffer space
225 : to start the operation.
226 :
227 : @throw std::logic_error `this->is_done() == true`.
228 :
229 : @throw std::length_error if there is insufficient internal buffer
230 : space to start the operation.
231 :
232 : @param m The request or response headers to serialize.
233 :
234 : @see
235 : @ref message_base.
236 : */
237 : void
238 : BOOST_HTTP_DECL
239 : start(message_base const& m);
240 :
241 : /** Start serializing the associated message with an empty body.
242 :
243 : Uses the message associated at construction time.
244 :
245 : @par Preconditions
246 : A message was associated at construction.
247 : @code
248 : this->is_done() == true
249 : @endcode
250 :
251 : @par Postconditions
252 : @code
253 : this->is_done() == false
254 : @endcode
255 :
256 : @par Exception Safety
257 : Strong guarantee.
258 :
259 : @throw std::logic_error if no message is associated or
260 : `this->is_done() == false`.
261 :
262 : @throw std::length_error if there is insufficient internal buffer
263 : space to start the operation.
264 : */
265 : void
266 : BOOST_HTTP_DECL
267 : start();
268 :
269 : /** Start serializing a message with a buffer sequence body
270 :
271 : Initializes the serializer with the HTTP start-line and headers from `m`,
272 : and the provided `buffers` for reading the message body from.
273 :
274 : Changing the contents of the message after calling this function and
275 : before @ref is_done returns `true` results in undefined behavior.
276 :
277 : At least one copy of the specified buffer sequence is maintained until
278 : the serializer is done, gets reset, or ios destroyed, after which all
279 : of its copies are destroyed. Ownership of the underlying memory is not
280 : transferred; the caller must ensure the memory remains valid until the
281 : serializer’s copies are destroyed.
282 :
283 : @par Preconditions
284 : @code
285 : this->is_done() == true
286 : @endcode
287 :
288 : @par Postconditions
289 : @code
290 : this->is_done() == false
291 : @endcode
292 :
293 : @par Constraints
294 : @code
295 : capy::ConstBufferSequence<ConstBufferSequence>
296 : @endcode
297 :
298 : @par Exception Safety
299 : Strong guarantee.
300 : Exceptions thrown if there is insufficient internal buffer space
301 : to start the operation.
302 :
303 : @throw std::logic_error `this->is_done() == true`.
304 :
305 : @throw std::length_error If there is insufficient internal buffer
306 : space to start the operation.
307 :
308 : @param m The message to read the HTTP start-line and headers from.
309 :
310 : @param buffers A buffer sequence containing the message body.
311 :
312 : containing the message body data. While
313 : the buffers object is copied, ownership of
314 : the underlying memory remains with the
315 : caller, who must ensure it stays valid
316 : until @ref is_done returns `true`.
317 :
318 : @see
319 : @ref message_base.
320 : */
321 : template<
322 : class ConstBufferSequence,
323 : class = typename std::enable_if<
324 : capy::ConstBufferSequence<ConstBufferSequence>>::type
325 : >
326 : void
327 : start(
328 : message_base const& m,
329 : ConstBufferSequence&& buffers);
330 :
331 : /** Prepare the serializer for streaming body data.
332 :
333 : Initializes the serializer with the HTTP
334 : start-line and headers from `m` for streaming
335 : mode. After calling this function, use
336 : @ref stream_prepare, @ref stream_commit, and
337 : @ref stream_close to write body data.
338 :
339 : Changing the contents of the message
340 : after calling this function and before
341 : @ref is_done returns `true` results in
342 : undefined behavior.
343 :
344 : @par Example
345 : @code
346 : auto sink = sr.sink_for(socket);
347 : sr.start_stream(response);
348 :
349 : capy::mutable_buffer arr[16];
350 : auto bufs = sink.prepare(arr);
351 : std::memcpy(bufs[0].data(), "Hello", 5);
352 : co_await sink.commit(5, true);
353 : @endcode
354 :
355 : @par Preconditions
356 : @code
357 : this->is_done() == true
358 : @endcode
359 :
360 : @par Postconditions
361 : @code
362 : this->is_done() == false
363 : @endcode
364 :
365 : @par Exception Safety
366 : Strong guarantee.
367 : Exceptions thrown if there is insufficient
368 : internal buffer space to start the
369 : operation.
370 :
371 : @throw std::length_error if there is
372 : insufficient internal buffer space to
373 : start the operation.
374 :
375 : @param m The message to read the HTTP
376 : start-line and headers from.
377 :
378 : @see
379 : @ref stream_prepare,
380 : @ref stream_commit,
381 : @ref stream_close,
382 : @ref message_base.
383 : */
384 : BOOST_HTTP_DECL
385 : void
386 : start_stream(
387 : message_base const& m);
388 :
389 : /** Start streaming the associated message.
390 :
391 : Uses the message associated at construction time.
392 :
393 : @par Preconditions
394 : A message was associated at construction.
395 : @code
396 : this->is_done() == true
397 : @endcode
398 :
399 : @par Postconditions
400 : @code
401 : this->is_done() == false
402 : @endcode
403 :
404 : @par Exception Safety
405 : Strong guarantee.
406 :
407 : @throw std::logic_error if no message is associated or
408 : `this->is_done() == false`.
409 :
410 : @throw std::length_error if there is insufficient internal buffer
411 : space to start the operation.
412 :
413 : @see
414 : @ref stream_prepare,
415 : @ref stream_commit,
416 : @ref stream_close.
417 : */
418 : BOOST_HTTP_DECL
419 : void
420 : start_stream();
421 :
422 : /** Get a sink wrapper for writing body data.
423 :
424 : Returns a @ref sink object that can be used to write body
425 : data to the provided stream. This function does not call
426 : @ref start_stream. The caller must call @ref start_stream
427 : before using the sink.
428 :
429 : This allows the sink to be obtained early (e.g., at
430 : construction time) and stored, with streaming started
431 : later when the message is ready.
432 :
433 : @par Example
434 : @code
435 : http::serializer sr(cfg, res);
436 : auto sink = sr.sink_for(socket);
437 : // ... later ...
438 : sr.start_stream(); // Configure for streaming
439 :
440 : capy::mutable_buffer arr[16];
441 : auto bufs = sink.prepare(arr);
442 : std::memcpy(bufs[0].data(), "Hello", 5);
443 : co_await sink.commit(5, true);
444 : @endcode
445 :
446 : @tparam Stream The output stream type satisfying
447 : @ref capy::WriteStream.
448 :
449 : @param ws The output stream to write serialized data to.
450 :
451 : @return A @ref sink object for writing body data.
452 :
453 : @see @ref sink, @ref start_stream.
454 : */
455 : template<capy::WriteStream Stream>
456 : sink<Stream>
457 : sink_for(Stream& ws) noexcept;
458 :
459 : /** Return the output area.
460 :
461 : This function serializes some or all of
462 : the message and returns the corresponding
463 : output buffers. Afterward, a call to @ref
464 : consume is required to report the number
465 : of bytes used, if any.
466 :
467 : If the message includes an
468 : `Expect: 100-continue` header and the
469 : header section of the message has been
470 : consumed, the returned result will contain
471 : @ref error::expect_100_continue to
472 : indicate that the header part of the
473 : message is complete. The next call to @ref
474 : prepare will produce output.
475 :
476 : When the serializer is in streaming mode,
477 : the result may contain @ref error::need_data
478 : to indicate that additional input is required
479 : to produce output.
480 :
481 : If a @ref source object is in use and a
482 : call to @ref source::read returns an
483 : error, the serializer enters a faulted
484 : state and propagates the error to the
485 : caller. This faulted state can only be
486 : cleared by calling @ref reset. This
487 : ensures the caller is explicitly aware
488 : that the previous message was truncated
489 : and that the stream must be terminated.
490 :
491 : @par Preconditions
492 : @code
493 : this->is_done() == false
494 : @endcode
495 : No unrecoverable error reported from previous calls.
496 :
497 : @par Exception Safety
498 : Strong guarantee.
499 : Calls to @ref source::read may throw if in use.
500 :
501 : @throw std::logic_error
502 : `this->is_done() == true`.
503 :
504 : @return A result containing @ref
505 : const_buffers_type that represents the
506 : output area or an error if any occurred.
507 :
508 : @see
509 : @ref consume,
510 : @ref is_done,
511 : @ref const_buffers_type.
512 : */
513 : BOOST_HTTP_DECL
514 : auto
515 : prepare() ->
516 : system::result<
517 : const_buffers_type>;
518 :
519 : /** Consume bytes from the output area.
520 :
521 : This function should be called after one
522 : or more bytes contained in the buffers
523 : provided in the prior call to @ref prepare
524 : have been used.
525 :
526 : After a call to @ref consume, callers
527 : should check the return value of @ref
528 : is_done to determine if the entire message
529 : has been serialized.
530 :
531 : @par Preconditions
532 : @code
533 : this->is_done() == false
534 : @endcode
535 :
536 : @par Exception Safety
537 : Strong guarantee.
538 :
539 : @throw std::logic_error
540 : `this->is_done() == true`.
541 :
542 : @param n The number of bytes to consume.
543 : If `n` is greater than the size of the
544 : buffer returned from @ref prepared the
545 : entire output sequence is consumed and no
546 : error is issued.
547 :
548 : @see
549 : @ref prepare,
550 : @ref is_done,
551 : @ref const_buffers_type.
552 : */
553 : BOOST_HTTP_DECL
554 : void
555 : consume(std::size_t n);
556 :
557 : /** Return true if serialization is complete.
558 : */
559 : BOOST_HTTP_DECL
560 : bool
561 : is_done() const noexcept;
562 :
563 : /** Return the available capacity for streaming.
564 :
565 : Returns the number of bytes that can be written
566 : to the serializer's internal buffer.
567 :
568 : @par Preconditions
569 : The serializer is in streaming mode (after calling
570 : @ref start_stream).
571 :
572 : @par Exception Safety
573 : Strong guarantee.
574 :
575 : @throw std::logic_error if not in streaming mode.
576 : */
577 : BOOST_HTTP_DECL
578 : std::size_t
579 : stream_capacity() const;
580 :
581 : /** Prepare a buffer for writing stream data.
582 :
583 : Returns a mutable buffer sequence representing
584 : the writable bytes. Use @ref stream_commit to make the
585 : written data available to the serializer.
586 :
587 : All buffer sequences previously obtained
588 : using @ref stream_prepare are invalidated.
589 :
590 : @par Preconditions
591 : The serializer is in streaming mode.
592 :
593 : @par Exception Safety
594 : Strong guarantee.
595 :
596 : @return An instance of @ref mutable_buffers_type.
597 : The underlying memory is owned by the serializer.
598 :
599 : @throw std::logic_error if not in streaming mode.
600 :
601 : @see
602 : @ref stream_commit,
603 : @ref stream_capacity.
604 : */
605 : BOOST_HTTP_DECL
606 : mutable_buffers_type
607 : stream_prepare();
608 :
609 : /** Commit data to the serializer stream.
610 :
611 : Makes `n` bytes available to the serializer.
612 :
613 : All buffer sequences previously obtained
614 : using @ref stream_prepare are invalidated.
615 :
616 : @par Preconditions
617 : The serializer is in streaming mode and
618 : `n <= stream_capacity()`.
619 :
620 : @par Exception Safety
621 : Strong guarantee.
622 : Exceptions thrown on invalid input.
623 :
624 : @param n The number of bytes to commit.
625 :
626 : @throw std::invalid_argument if `n > stream_capacity()`.
627 :
628 : @throw std::logic_error if not in streaming mode.
629 :
630 : @see
631 : @ref stream_prepare,
632 : @ref stream_capacity.
633 : */
634 : BOOST_HTTP_DECL
635 : void
636 : stream_commit(std::size_t n);
637 :
638 : /** Close the stream.
639 :
640 : Notifies the serializer that the message body
641 : has ended. After calling this function, no more
642 : data can be written to the stream.
643 :
644 : @par Preconditions
645 : The serializer is in streaming mode.
646 :
647 : @par Postconditions
648 : The stream is closed.
649 : */
650 : BOOST_HTTP_DECL
651 : void
652 : stream_close() noexcept;
653 :
654 : private:
655 : class impl;
656 : class cbs_gen;
657 : template<class>
658 : class cbs_gen_impl;
659 :
660 : BOOST_HTTP_DECL
661 : detail::workspace&
662 : ws();
663 :
664 : BOOST_HTTP_DECL
665 : void
666 : start_init(
667 : message_base const&);
668 :
669 : BOOST_HTTP_DECL
670 : void
671 : start_buffers(
672 : message_base const&,
673 : cbs_gen&);
674 :
675 : impl* impl_ = nullptr;
676 : };
677 :
678 : //------------------------------------------------
679 :
680 : /** A sink adapter for writing HTTP message bodies.
681 :
682 : This class wraps an underlying @ref capy::WriteStream and a
683 : @ref serializer to provide a @ref capy::BufferSink interface
684 : for writing message body data. The caller writes directly into
685 : the serializer's internal buffer (zero-copy); the serializer
686 : automatically handles:
687 :
688 : @li Chunked transfer-encoding (chunk framing added automatically)
689 : @li Content-Encoding compression (gzip, deflate, brotli if configured)
690 : @li Content-Length validation (if specified in headers)
691 :
692 : For @ref capy::WriteSink semantics (caller owns buffers), wrap
693 : this sink with @ref capy::any_buffer_sink which provides both
694 : interfaces.
695 :
696 : @tparam Stream The underlying stream type satisfying
697 : @ref capy::WriteStream.
698 :
699 : @par Thread Safety
700 : Distinct objects: Safe.
701 : Shared objects: Unsafe.
702 :
703 : @par Example
704 : @code
705 : capy::task<void>
706 : send_response(capy::WriteStream auto& socket)
707 : {
708 : http::response res;
709 : res.set_chunked(true);
710 : http::serializer sr(cfg, res);
711 :
712 : auto sink = sr.sink_for(socket);
713 : sr.start_stream();
714 :
715 : // Zero-copy write using BufferSink interface
716 : capy::mutable_buffer arr[16];
717 : auto bufs = sink.prepare(arr);
718 : std::memcpy(bufs[0].data(), "Hello", 5);
719 : co_await sink.commit(5, true);
720 : }
721 : @endcode
722 :
723 : @see capy::BufferSink, capy::any_buffer_sink, serializer
724 : */
725 : template<capy::WriteStream Stream>
726 : class serializer::sink
727 : {
728 : Stream* stream_ = nullptr;
729 : serializer* sr_ = nullptr;
730 :
731 : public:
732 : /** Constructor.
733 :
734 : A default-constructed sink is in an empty state.
735 : */
736 : sink() noexcept = default;
737 :
738 : /** Constructor.
739 :
740 : @param stream The underlying stream to write serialized data to.
741 : @param sr The serializer performing HTTP framing.
742 : */
743 68 : sink(
744 : Stream& stream,
745 : serializer& sr) noexcept
746 68 : : stream_(&stream)
747 68 : , sr_(&sr)
748 : {
749 68 : }
750 :
751 : /** Prepare writable buffers.
752 :
753 : Fills the provided span with mutable buffer descriptors
754 : pointing to the serializer's internal storage. This
755 : operation is synchronous.
756 :
757 : @param dest Span of mutable_buffer to fill.
758 :
759 : @return A span of filled buffers.
760 : */
761 : std::span<capy::mutable_buffer>
762 62 : prepare(std::span<capy::mutable_buffer> dest)
763 : {
764 62 : auto bufs = sr_->stream_prepare();
765 62 : std::size_t count = 0;
766 124 : for(auto const& b : bufs)
767 : {
768 124 : if(count >= dest.size() || b.size() == 0)
769 62 : break;
770 62 : dest[count++] = b;
771 : }
772 124 : return dest.first(count);
773 : }
774 :
775 : /** Commit bytes written to the prepared buffers.
776 :
777 : Commits `n` bytes written to the buffers returned by the
778 : most recent call to @ref prepare. The operation flushes
779 : serialized output to the underlying stream.
780 :
781 : @param n The number of bytes to commit.
782 :
783 : @return An awaitable yielding `(error_code)`.
784 : */
785 : auto
786 50 : commit(std::size_t n)
787 : -> capy::io_task<>
788 : {
789 50 : return commit(n, false);
790 : }
791 :
792 : /** Commit bytes written with optional end-of-stream.
793 :
794 : Commits `n` bytes written to the buffers returned by the
795 : most recent call to @ref prepare. If `eof` is true, also
796 : signals end-of-stream.
797 :
798 : @param n The number of bytes to commit.
799 : @param eof If true, signals end-of-stream after committing.
800 :
801 : @return An awaitable yielding `(error_code)`.
802 : */
803 : auto
804 68 : commit(std::size_t n, bool eof)
805 : -> capy::io_task<>
806 : {
807 : sr_->stream_commit(n);
808 :
809 : if(eof)
810 : sr_->stream_close();
811 :
812 : while(!sr_->is_done())
813 : {
814 : auto cbs = sr_->prepare();
815 : if(cbs.has_error())
816 : {
817 : if(cbs.error() == error::need_data && !eof)
818 : break;
819 : if(cbs.error() == error::need_data)
820 : continue;
821 : co_return {cbs.error()};
822 : }
823 :
824 : if(capy::buffer_empty(*cbs))
825 : {
826 : sr_->consume(0);
827 : continue;
828 : }
829 :
830 : auto [ec, written] = co_await stream_->write_some(*cbs);
831 : sr_->consume(written);
832 :
833 : if(ec)
834 : co_return {ec};
835 : }
836 :
837 : co_return {};
838 136 : }
839 :
840 : /** Signal end-of-stream.
841 :
842 : Closes the body stream and flushes any remaining serializer
843 : output to the underlying stream. For chunked encoding, this
844 : writes the final zero-length chunk.
845 :
846 : @return An awaitable yielding `(error_code)`.
847 :
848 : @post The serializer's `is_done()` returns `true` on success.
849 : */
850 : auto
851 28 : commit_eof()
852 : -> capy::io_task<>
853 : {
854 : sr_->stream_close();
855 :
856 : while(!sr_->is_done())
857 : {
858 : auto cbs = sr_->prepare();
859 : if(cbs.has_error())
860 : {
861 : if(cbs.error() == error::need_data)
862 : continue;
863 : co_return {cbs.error()};
864 : }
865 :
866 : if(capy::buffer_empty(*cbs))
867 : {
868 : sr_->consume(0);
869 : continue;
870 : }
871 :
872 : auto [ec, written] = co_await stream_->write_some(*cbs);
873 : sr_->consume(written);
874 :
875 : if(ec)
876 : co_return {ec};
877 : }
878 :
879 : co_return {};
880 56 : }
881 : };
882 :
883 : //------------------------------------------------
884 :
885 : template<capy::WriteStream Stream>
886 : serializer::sink<Stream>
887 68 : serializer::sink_for(Stream& ws) noexcept
888 : {
889 68 : return sink<Stream>(ws, *this);
890 : }
891 :
892 : } // http
893 : } // boost
894 :
895 : #include <boost/http/impl/serializer.hpp>
896 :
897 : #endif
|