Skip to content

Commit 612f26e

Browse files
committed
Create body_write_stream (initial wip)
1 parent b64bc65 commit 612f26e

3 files changed

Lines changed: 751 additions & 0 deletions

File tree

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
//
2+
// Copyright (c) 2025 Mungo Gill
3+
//
4+
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5+
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
//
7+
// Official repository: https://github.com/cppalliance/beast2
8+
//
9+
10+
#ifndef BOOST_BEAST2_BODY_WRITE_STREAM_HPP
11+
#define BOOST_BEAST2_BODY_WRITE_STREAM_HPP
12+
13+
#include <boost/asio/async_result.hpp>
14+
#include <boost/http_proto/serializer.hpp>
15+
#include <boost/system/error_code.hpp>
16+
17+
namespace boost {
18+
namespace beast2 {
19+
20+
/** A body writer for HTTP/1 messages.
21+
22+
This type meets the requirements of asio's AsyncWriteStream, and is
23+
constructed with a reference to an underlying AsyncWriteStream.
24+
25+
Any call to `async_write_some` initially triggers writes from the underlying
26+
stream until all of the HTTP headers and at least one byte of the body have
27+
been write and processed. Thereafter, each subsequent call to
28+
`async_write_some` processes at least one byte of the body, triggering, where
29+
required, calls to the underlying stream's `async_write_some` method. The
30+
resulting body data is stored in the referenced MutableBufferSequence.
31+
32+
All processing depends on a beast2::parser object owned by the caller and
33+
referenced in the construction of this object.
34+
35+
@see
36+
@ref http_proto::parser.
37+
*/
38+
template<class AsyncWriteStream>
39+
class body_write_stream
40+
{
41+
42+
public:
43+
/** The type of the executor associated with the stream.
44+
45+
This will be the type of executor used to invoke completion handlers
46+
which do not have an explicit associated executor.
47+
*/
48+
typedef typename AsyncWriteStream::executor_type executor_type;
49+
50+
/** Get the executor associated with the object.
51+
52+
This function may be used to obtain the executor object that the stream
53+
uses to dispatch completion handlers without an assocaited executor.
54+
55+
@return A copy of the executor that stream will use to dispatch
56+
handlers.
57+
*/
58+
executor_type
59+
get_executor()
60+
{
61+
return stream_.get_executor();
62+
}
63+
64+
/** Constructor
65+
66+
This constructor creates the stream by forwarding all arguments to the
67+
underlying socket. The socket then needs to be open and connected or
68+
accepted before data can be sent or received on it.
69+
70+
@param s The underlying stream from which the HTTP message is write.
71+
This object's executor is initialized to that of the
72+
underlying stream.
73+
74+
@param pr A http_proto::parser object which will perform the parsing of
75+
the HTTP message and extraction of the body. This must be
76+
initialized by the caller and ownership of the parser is
77+
retained by the caller, which must guarantee that it remains
78+
valid until the handler is called.
79+
*/
80+
explicit body_write_stream(AsyncWriteStream& s, http_proto::serializer& sr);
81+
82+
/** Write some data asynchronously.
83+
84+
This function is used to asynchronously write data from the stream.
85+
86+
This call always returns immediately. The asynchronous operation will
87+
continue until one of the following conditions is true:
88+
89+
@li The HTTP headers are write in full from the underlying stream and one
90+
or more bytes of the body are write from the stream and stored in the
91+
buffer `mb`.
92+
93+
@li An error occurs.
94+
95+
The algorithm, known as a <em>composed asynchronous operation</em>, is
96+
implemented in terms of calls to the underlying stream's
97+
`async_write_some` function. The program must ensure that no other calls
98+
implemented using the underlying stream's `async_write_some` are
99+
performed until this operation completes.
100+
101+
@param mb The buffers into which the body data will be write. If the
102+
size of the buffers is zero bytes, the operation always completes
103+
immediately with no error. Although the buffers object may be copied as
104+
necessary, ownership of the underlying memory blocks is retained by the
105+
caller, which must guarantee that they remain valid until the handler is
106+
called. Where the mb buffer is not of sufficient size to hold the write
107+
data, the remainder may be write by subsequent calls to this function.
108+
109+
@param handler The completion handler to invoke when the operation
110+
completes. The implementation takes ownership of the handler by
111+
performing a decay-copy. The equivalent function signature of the
112+
handler must be:
113+
@code
114+
void handler(
115+
error_code const& error, // result of operation
116+
std::size_t bytes_transferred // the number of bytes consumed by the parser
117+
);
118+
@endcode
119+
Regardless of whether the asynchronous operation
120+
completes immediately or not, the completion handler will not be invoked
121+
from within this function. On immediate completion, invocation of the
122+
handler will be performed in a manner equivalent to using
123+
`asio::async_immediate`.
124+
125+
@note The `async_write_some` operation may not receive all of the
126+
requested number of bytes. Consider using the function
127+
`asio::async_write` if you need to ensure that the requested amount of
128+
data is write before the asynchronous operation completes.
129+
130+
@par Per-Operation Cancellation
131+
132+
This asynchronous operation supports cancellation for the following
133+
net::cancellation_type values:
134+
135+
@li @c net::cancellation_type::terminal
136+
@li @c net::cancellation_type::partial
137+
@li @c net::cancellation_type::total
138+
139+
if they are also supported by the underlying stream's @c async_write_some
140+
operation.
141+
*/
142+
template<
143+
class ConstBufferSequence,
144+
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(system::error_code, std::size_t))
145+
CompletionToken>
146+
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
147+
CompletionToken,
148+
void(system::error_code, std::size_t))
149+
async_write_some(ConstBufferSequence mb, CompletionToken&& handler);
150+
151+
private:
152+
AsyncWriteStream& stream_;
153+
http_proto::serializer& sr_;
154+
};
155+
156+
} // beast2
157+
} // boost
158+
159+
#include <boost/beast2/impl/body_write_stream.hpp>
160+
161+
#endif // BOOST_BEAST2_BODY_WRITE_STREAM_HPP
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
//
2+
// Copyright (c) 2025 Mungo Gill
3+
//
4+
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5+
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
//
7+
// Official repository: https://github.com/cppalliance/beast2
8+
//
9+
10+
#ifndef BOOST_BEAST2_IMPL_BODY_WRITE_STREAM_HPP
11+
#define BOOST_BEAST2_IMPL_BODY_WRITE_STREAM_HPP
12+
13+
#include <boost/beast2/detail/config.hpp>
14+
#include <boost/beast2/write.hpp>
15+
16+
#include <boost/asio/buffer.hpp>
17+
#include <boost/asio/buffers_iterator.hpp>
18+
#include <boost/asio/coroutine.hpp>
19+
#include <boost/core/ignore_unused.hpp>
20+
21+
#include <iostream>
22+
23+
namespace boost {
24+
namespace beast2 {
25+
26+
namespace detail {
27+
28+
template<class ConstBufferSequence, class AsyncWriteStream>
29+
class body_write_stream_op : public asio::coroutine
30+
{
31+
32+
AsyncWriteStream& stream_;
33+
ConstBufferSequence cb_;
34+
http_proto::serializer& sr_;
35+
36+
public:
37+
body_write_stream_op(
38+
AsyncWriteStream& s,
39+
ConstBufferSequence&& cb,
40+
http_proto::serializer& pr) noexcept
41+
: stream_(s)
42+
, cb_(std::move(cb))
43+
, sr_(pr)
44+
{
45+
}
46+
47+
template<class Self>
48+
void
49+
operator()(
50+
Self& self,
51+
system::error_code ec = {},
52+
std::size_t bytes_transferred = 0)
53+
{
54+
boost::ignore_unused(bytes_transferred);
55+
56+
BOOST_ASIO_CORO_REENTER(*this)
57+
{
58+
self.reset_cancellation_state(asio::enable_total_cancellation());
59+
60+
if(buffers::size(cb_) == 0)
61+
{
62+
BOOST_ASIO_CORO_YIELD
63+
{
64+
BOOST_ASIO_HANDLER_LOCATION(
65+
(__FILE__, __LINE__, "immediate"));
66+
auto io_ex = self.get_io_executor();
67+
asio::async_immediate(
68+
io_ex,
69+
asio::append(
70+
std::move(self), system::error_code{}));
71+
}
72+
goto upcall;
73+
}
74+
75+
if(!sr_.got_header())
76+
{
77+
BOOST_ASIO_CORO_YIELD
78+
{
79+
BOOST_ASIO_HANDLER_LOCATION(
80+
(__FILE__, __LINE__, "async_write_header"));
81+
beast2::async_write_header<AsyncWriteStream, Self>(
82+
stream_, sr_, std::move(self));
83+
}
84+
if(ec.failed())
85+
goto upcall;
86+
}
87+
88+
if(!!self.cancelled())
89+
{
90+
ec = asio::error::operation_aborted;
91+
goto upcall;
92+
}
93+
94+
BOOST_ASIO_CORO_YIELD
95+
{
96+
BOOST_ASIO_HANDLER_LOCATION(
97+
(__FILE__, __LINE__, "async_write_some"));
98+
beast2::async_write_some(stream_, sr_, std::move(self));
99+
}
100+
101+
upcall:
102+
std::size_t n = 0;
103+
104+
if(!ec.failed())
105+
{
106+
auto dbs = asio::buffer_size(cb_);
107+
108+
if(dbs > 0)
109+
{
110+
auto source_buf = sr_.pull_body();
111+
112+
n = asio::buffer_copy(cb_, source_buf);
113+
114+
sr_.consume_body(n);
115+
116+
ec = (n != 0) ? system::error_code{}
117+
: asio::stream_errc::eof;
118+
}
119+
}
120+
121+
self.complete(ec, n);
122+
}
123+
}
124+
};
125+
126+
} // detail
127+
128+
//------------------------------------------------
129+
130+
// TODO: copy in Beast's stream traits to check if AsyncWriteStream
131+
// is an AsyncWriteStream, and also static_assert that body_write_stream is too.
132+
133+
template<class AsyncWriteStream>
134+
body_write_stream<AsyncWriteStream>::body_write_stream(
135+
AsyncWriteStream& s,
136+
http_proto::serializer& pr)
137+
: stream_(s)
138+
, sr_(pr)
139+
{
140+
}
141+
142+
template<class AsyncWriteStream>
143+
template<
144+
class ConstBufferSequence,
145+
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(system::error_code, std::size_t))
146+
CompletionToken>
147+
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
148+
CompletionToken,
149+
void(system::error_code, std::size_t))
150+
body_write_stream<AsyncWriteStream>::async_write_some(
151+
ConstBufferSequence cb,
152+
CompletionToken&& token)
153+
{
154+
return asio::
155+
async_compose<CompletionToken, void(system::error_code, std::size_t)>(
156+
detail::body_write_stream_op<ConstBufferSequence, AsyncWriteStream>{
157+
stream_, std::move(cb), sr_ },
158+
token,
159+
stream_);
160+
}
161+
162+
} // beast2
163+
} // boost
164+
165+
#endif // BOOST_BEAST2_IMPL_BODY_WRITE_STREAM_HPP

0 commit comments

Comments
 (0)