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