include/boost/burl/message_reader.hpp

100.0% Lines (31/31) 100.0% List of functions (24/24) 80.0% Branches (8/10)
message_reader.hpp
f(x) Functions (24)
Function Calls Lines Branches Blocks
boost::burl::message_reader<boost::burl::detail::pooled_connection>::message_reader(boost::burl::detail::pooled_connection*, boost::burl::parser*) :101 180x 100.0% 50.0% 60.0% boost::burl::message_reader<boost::capy::any_stream>::message_reader(boost::capy::any_stream*, boost::burl::parser*) :101 11x 100.0% 50.0% 60.0% boost::burl::message_reader<boost::capy::test::stream>::message_reader(boost::capy::test::stream*, boost::burl::parser*) :101 33x 100.0% 50.0% 60.0% boost::burl::message_reader<boost::burl::detail::pooled_connection>::read_header() :118 105x 100.0% – 100.0% boost::burl::message_reader<boost::capy::any_stream>::read_header() :118 11x 100.0% – 100.0% boost::burl::message_reader<boost::capy::test::stream>::read_header() :118 19x 100.0% – 100.0% boost::burl::message_reader<boost::burl::detail::pooled_connection>::read_body() :137 31x 100.0% – 100.0% boost::capy::task<std::tuple<std::error_code, unsigned long> > boost::burl::message_reader<boost::burl::detail::pooled_connection>::read_some<std::span<boost::capy::mutable_buffer const, 18446744073709551615ul> >(std::span<boost::capy::mutable_buffer const, 18446744073709551615ul>) :158 7x 100.0% – 100.0% boost::capy::task<std::tuple<std::error_code, unsigned long> > boost::burl::message_reader<boost::burl::detail::pooled_connection>::read<std::span<boost::capy::mutable_buffer const, 18446744073709551615ul> >(std::span<boost::capy::mutable_buffer const, 18446744073709551615ul>) :177 14x 100.0% – 100.0% boost::burl::message_reader<boost::burl::detail::pooled_connection>::pull(std::span<boost::capy::const_buffer, 18446744073709551615ul>) :197 41x 100.0% – 100.0% boost::burl::message_reader<boost::capy::test::stream>::pull(std::span<boost::capy::const_buffer, 18446744073709551615ul>) :197 58x 100.0% – 100.0% boost::burl::message_reader<boost::burl::detail::pooled_connection>::consume(unsigned long) :209 21x 100.0% – 100.0% boost::burl::message_reader<boost::burl::detail::pooled_connection>::refill_(boost::burl::detail::pooled_connection&, boost::burl::parser&) :249 164x 100.0% 100.0% 44.0% boost::burl::message_reader<boost::capy::any_stream>::refill_(boost::capy::any_stream&, boost::burl::parser&) :249 11x 100.0% 100.0% 44.0% boost::burl::message_reader<boost::capy::test::stream>::refill_(boost::capy::test::stream&, boost::burl::parser&) :249 507x 100.0% 100.0% 44.0% boost::burl::message_reader<boost::burl::detail::pooled_connection>::read_header_(boost::burl::detail::pooled_connection&, boost::burl::parser&) :266 105x 100.0% 100.0% 44.0% boost::burl::message_reader<boost::capy::any_stream>::read_header_(boost::capy::any_stream&, boost::burl::parser&) :266 11x 100.0% 100.0% 44.0% boost::burl::message_reader<boost::capy::test::stream>::read_header_(boost::capy::test::stream&, boost::burl::parser&) :266 19x 100.0% 100.0% 44.0% boost::burl::message_reader<boost::burl::detail::pooled_connection>::read_body_(boost::burl::detail::pooled_connection&, boost::burl::parser&) :283 31x 100.0% 100.0% 44.0% boost::capy::task<std::tuple<std::error_code, unsigned long> > boost::burl::message_reader<boost::burl::detail::pooled_connection>::read_some_<boost::capy::detail::slice_of<std::span<boost::capy::mutable_buffer const, 18446744073709551615ul> > >(boost::burl::detail::pooled_connection&, boost::burl::parser&, boost::capy::detail::slice_of<std::span<boost::capy::mutable_buffer const, 18446744073709551615ul> >) :301 25x 100.0% 100.0% 44.0% boost::capy::task<std::tuple<std::error_code, unsigned long> > boost::burl::message_reader<boost::burl::detail::pooled_connection>::read_some_<std::span<boost::capy::mutable_buffer const, 18446744073709551615ul> >(boost::burl::detail::pooled_connection&, boost::burl::parser&, std::span<boost::capy::mutable_buffer const, 18446744073709551615ul>) :301 7x 100.0% 100.0% 44.0% boost::capy::task<std::tuple<std::error_code, unsigned long> > boost::burl::message_reader<boost::burl::detail::pooled_connection>::read_<std::span<boost::capy::mutable_buffer const, 18446744073709551615ul> >(boost::burl::detail::pooled_connection&, boost::burl::parser&, std::span<boost::capy::mutable_buffer const, 18446744073709551615ul>) :337 14x 100.0% 100.0% 44.0% boost::burl::message_reader<boost::burl::detail::pooled_connection>::pull_(boost::burl::detail::pooled_connection&, boost::burl::parser&, std::span<boost::capy::const_buffer, 18446744073709551615ul>) :361 41x 100.0% 100.0% 44.0% boost::burl::message_reader<boost::capy::test::stream>::pull_(boost::capy::test::stream&, boost::burl::parser&, std::span<boost::capy::const_buffer, 18446744073709551615ul>) :361 58x 100.0% 100.0% 44.0%
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Mohammad Nejati
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/burl
8 //
9
10 #ifndef BOOST_BURL_MESSAGE_READER_HPP
11 #define BOOST_BURL_MESSAGE_READER_HPP
12
13 #include <boost/burl/parser.hpp>
14
15 #include <boost/assert.hpp>
16 #include <boost/capy/buffers/buffer_slice.hpp>
17 #include <boost/capy/buffers/consuming_buffers.hpp>
18 #include <boost/capy/concept/read_stream.hpp>
19 #include <boost/capy/cond.hpp>
20 #include <boost/capy/error.hpp>
21 #include <boost/capy/io_task.hpp>
22 #include <boost/http/error.hpp>
23
24 #include <cstddef>
25 #include <span>
26 #include <string_view>
27 #include <utility>
28
29 namespace boost
30 {
31 namespace burl
32 {
33
34 /** Drives a @ref parser over a stream.
35
36 A reader binds a stream to a parser. It holds
37 only pointers to both, which must outlive it.
38
39 @par Example
40 @code
41 response_parser pr( cfg );
42 message_reader reader( &sock, &pr );
43
44 pr.start();
45
46 if(auto [ec] = co_await reader.read_header(); ec)
47 co_return { ec };
48
49 auto const& head = pr.get();
50 std::cout
51 << head.status_int() << " " << head.reason() << "\n"
52 << head.value_or( http::field::content_type, "" ) << "\n";
53
54 capy::const_buffer bufs[ 8 ];
55 for(;;)
56 {
57 auto [ec, data] = co_await reader.pull( bufs );
58 if(ec == capy::cond::eof)
59 break;
60 if(ec)
61 co_return { ec };
62 write_to_file( data );
63 reader.consume( capy::buffer_size( data ));
64 }
65 @endcode
66
67 The loop above never copies the body: @ref pull
68 hands out descriptors into the parser's own
69 buffer. Use @ref read_some instead when the
70 octets have to land in memory of your choosing.
71
72 Every operation parses the header first, so
73 reading a body without having read the header
74 explicitly works as expected.
75
76 This type satisfies @ref capy::ReadStream, @ref
77 http::ReadSource, and @ref http::BufferSource,
78 all over the octets of the message body.
79
80 @tparam S A type satisfying @ref capy::ReadStream.
81
82 @see @ref parser.
83 */
84 template<capy::ReadStream S>
85 class message_reader
86 {
87 S* s_;
88 parser* p_;
89
90 public:
91 /** Constructor.
92
93 @par Preconditions
94 Neither pointer is null, and both objects
95 outlive the reader.
96
97 @param stream The stream to read from.
98
99 @param pr The parser to drive.
100 */
101 224x message_reader(S* stream, parser* pr) noexcept
102 224x : s_(stream)
103 224x , p_(pr)
104 {
105
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 224 times.
224x BOOST_ASSERT(s_ != nullptr);
106
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 224 times.
224x BOOST_ASSERT(p_ != nullptr);
107 224x }
108
109 /** Asynchronously parse the message header.
110
111 Reads from the stream until the header is
112 complete or an error occurs. Has no effect
113 once @ref parser::got_header returns true.
114
115 @return An awaitable yielding `(error_code)`.
116 */
117 capy::io_task<>
118 135x read_header()
119 {
120 135x return read_header_(*s_, *p_);
121 }
122
123 /** Asynchronously read the complete body in place.
124
125 Reads the remainder of the body into the
126 parser's buffer and returns a view of the
127 whole body, without copying. Fails with
128 @ref http::error::in_place_overflow if the
129 body does not fit.
130
131 @return An awaitable yielding
132 `(error_code,std::string_view)`.
133
134 @see @ref parser::flatten_body.
135 */
136 capy::io_task<std::string_view>
137 31x read_body()
138 {
139 31x return read_body_(*s_, *p_);
140 }
141
142 /** Asynchronously read body octets.
143
144 Copies into `buffers`, or lets an installed
145 decoder write into them directly. Yields
146 `capy::error::eof` once the body is
147 complete.
148
149 @param buffers The destination.
150
151 @return An awaitable yielding
152 `(error_code,std::size_t)`.
153
154 @see @ref parser::read_some.
155 */
156 template<capy::MutableBufferSequence MB>
157 capy::io_task<std::size_t>
158 7x read_some(MB buffers)
159 {
160 7x return read_some_(*s_, *p_, std::move(buffers));
161 }
162
163 /** Asynchronously fill a buffer sequence with body octets.
164
165 Reads until `buffers` is full, the body is
166 complete, or an error occurs. A body shorter
167 than `buffers` yields `capy::error::eof`
168 alongside the octets transferred.
169
170 @param buffers The destination.
171
172 @return An awaitable yielding
173 `(error_code,std::size_t)`.
174 */
175 template<capy::MutableBufferSequence MB>
176 capy::io_task<std::size_t>
177 14x read(MB buffers)
178 {
179 14x return read_(*s_, *p_, std::move(buffers));
180 }
181
182 /** Asynchronously borrow body octets.
183
184 Fills `dest` with descriptors referring to
185 the parser's own buffers, which @ref consume
186 then releases. Yields `capy::error::eof`
187 once the body is complete.
188
189 @param dest The descriptors to fill.
190
191 @return An awaitable yielding
192 `(error_code,std::span<capy::const_buffer>)`.
193
194 @see @ref consume, @ref parser::pull.
195 */
196 capy::io_task<std::span<capy::const_buffer>>
197 99x pull(std::span<capy::const_buffer> dest)
198 {
199 99x return pull_(*s_, *p_, dest);
200 }
201
202 /** Release body octets returned by @ref pull.
203
204 @param n The number of octets to release.
205
206 @see @ref pull.
207 */
208 void
209 21x consume(std::size_t n) noexcept
210 {
211 21x p_->consume(n);
212 21x }
213
214 private:
215 static capy::io_task<>
216 refill_(S& stream, parser& pr);
217
218 static capy::io_task<>
219 read_header_(S& stream, parser& pr);
220
221 static capy::io_task<std::string_view>
222 read_body_(S& stream, parser& pr);
223
224 static capy::io_task<std::span<capy::const_buffer>>
225 pull_(
226 S& stream,
227 parser& pr,
228 std::span<capy::const_buffer> dest);
229
230 template<capy::MutableBufferSequence MB>
231 static capy::io_task<std::size_t>
232 read_some_(
233 S& stream,
234 parser& pr,
235 MB buffers);
236
237 template<capy::MutableBufferSequence MB>
238 static capy::io_task<std::size_t>
239 read_(
240 S& stream,
241 parser& pr,
242 MB buffers);
243 };
244
245 //------------------------------------------------
246
247 template<capy::ReadStream S>
248 capy::io_task<>
249
1/1
✓ Branch 1 taken 682 times.
682x message_reader<S>::
250 refill_(S& stream, parser& pr)
251 {
252 auto [ec, n] = co_await stream.read_some(pr.prepare());
253 pr.commit(n);
254 if(ec == capy::cond::eof)
255 {
256 pr.commit_eof();
257 co_return {};
258 }
259 if(ec)
260 co_return { ec };
261 co_return {};
262 1364x }
263
264 template<capy::ReadStream S>
265 capy::io_task<>
266
1/1
✓ Branch 1 taken 135 times.
135x message_reader<S>::
267 read_header_(S& stream, parser& pr)
268 {
269 for(;;)
270 {
271 auto const rv = pr.parse_header();
272 if(rv.has_value())
273 co_return {};
274 if(rv.error() != http::error::need_data)
275 co_return { rv.error() };
276 if(auto [rec] = co_await refill_(stream, pr); rec)
277 co_return { rec };
278 }
279 270x }
280
281 template<capy::ReadStream S>
282 capy::io_task<std::string_view>
283
1/1
✓ Branch 1 taken 31 times.
31x message_reader<S>::
284 read_body_(S& stream, parser& pr)
285 {
286 for(;;)
287 {
288 auto const r = pr.flatten_body();
289 if(r.has_value())
290 co_return { std::error_code(), *r };
291 if(r.error() != http::error::need_data)
292 co_return { r.error(), {} };
293 if(auto [rec] = co_await refill_(stream, pr); rec)
294 co_return { rec, {} };
295 }
296 62x }
297
298 template<capy::ReadStream S>
299 template<capy::MutableBufferSequence MB>
300 capy::io_task<std::size_t>
301
1/1
✓ Branch 1 taken 32 times.
32x message_reader<S>::
302 read_some_(
303 S& stream,
304 parser& pr,
305 MB buffers)
306 {
307 for(;;)
308 {
309 auto const r = pr.read_some(buffers);
310 if(r.has_value())
311 co_return { std::error_code(), *r };
312 if(r.error() != http::error::need_data)
313 co_return { r.error(), 0 };
314
315 if(auto const lim = pr.direct_capacity(); lim != 0)
316 {
317 auto [rec, rn] = co_await stream.read_some(
318 capy::buffer_slice(buffers, 0, lim));
319 pr.commit_direct(rn);
320 if(rec == capy::cond::eof)
321 pr.commit_eof();
322 else if(rec)
323 co_return { rec, rn };
324 if(rn != 0)
325 co_return { std::error_code(), rn };
326 continue;
327 }
328
329 if(auto [rec] = co_await refill_(stream, pr); rec)
330 co_return { rec, 0 };
331 }
332 64x }
333
334 template<capy::ReadStream S>
335 template<capy::MutableBufferSequence MB>
336 capy::io_task<std::size_t>
337
1/1
✓ Branch 1 taken 14 times.
14x message_reader<S>::
338 read_(
339 S& stream,
340 parser& pr,
341 MB buffers)
342 {
343 auto const total_size = capy::buffer_size(buffers);
344 capy::consuming_buffers dest(buffers);
345 std::size_t total = 0;
346
347 while(total < total_size)
348 {
349 auto [ec, n] = co_await read_some_(stream, pr, dest.data());
350 dest.consume(n);
351 total += n;
352 if(ec && total < total_size)
353 co_return { ec, total };
354 }
355
356 co_return { std::error_code(), total };
357 28x }
358
359 template<capy::ReadStream S>
360 capy::io_task<std::span<capy::const_buffer>>
361
1/1
✓ Branch 1 taken 99 times.
99x message_reader<S>::
362 pull_(
363 S& stream,
364 parser& pr,
365 std::span<capy::const_buffer> dest)
366 {
367 for(;;)
368 {
369 auto const r = pr.pull(dest);
370 if(r.has_value())
371 co_return { std::error_code(), *r };
372 if(r.error() != http::error::need_data)
373 co_return { r.error(), {} };
374 if(auto [rec] = co_await refill_(stream, pr); rec)
375 co_return { rec, {} };
376 }
377 198x }
378
379 } // namespace burl
380 } // namespace boost
381
382 #endif
383