include/boost/burl/response.hpp

97.1% Lines (34/35) 93.3% List of functions (14/16) 76.0% Branches (19/25)
response.hpp
f(x) Functions (16)
Function Calls Lines Branches Blocks
boost::burl::response::response() :100 8x 100.0% 100.0% boost::burl::response::status() const :152 19x 100.0% 100.0% boost::burl::response::status_int() const :160 11x 100.0% 100.0% boost::burl::response::ok() const :168 8x 100.0% 100.0% boost::burl::response::reason() const :177 3x 100.0% 100.0% boost::burl::response::raise_for_status() const :199 4x 100.0% 100.0% 83.0% boost::burl::response::version() const :209 2x 100.0% 100.0% boost::burl::response::url() const :217 2x 100.0% 100.0% boost::burl::response::headers() const :225 8x 100.0% 100.0% boost::burl::response::content_length() const :238 20x 100.0% 100.0% 100.0% <unknown function 328> :328 boost::capy::task<boost::capy::io_result<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > boost::burl::response::try_as<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >>() & :328 16x 100.0% 100.0% 77.0% boost::capy::task<boost::capy::io_result<std::filesystem::__cxx11::path> > boost::burl::response::try_as<std::filesystem::__cxx11::path, std::filesystem::__cxx11::path&>(std::filesystem::__cxx11::path&) & :328 3x 50.0% 16.7% 23.0% boost::burl::response::try_as<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >>() &::{lambda()#1}::operator()[abi:cxx11]() const :334 1x 100.0% 55.6% 44.0% boost::burl::response::try_as<std::filesystem::__cxx11::path, std::filesystem::__cxx11::path&>(std::filesystem::__cxx11::path&) &::{lambda()#1}::operator()[abi:cxx11]() const :334 0 20.0% 16.7% 0.0% boost::capy::task<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > boost::burl::response::as<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >>() & :373 4x 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_RESPONSE_HPP
11 #define BOOST_BURL_RESPONSE_HPP
12
13 #include <boost/burl/conversion.hpp>
14 #include <boost/burl/detail/config.hpp>
15 #include <boost/burl/detail/connection_pool.hpp>
16 #include <boost/burl/error.hpp>
17 #include <boost/burl/test/fwd.hpp>
18 #include <boost/capy/io/any_buffer_source.hpp>
19 #include <boost/capy/io/any_read_source.hpp>
20 #include <boost/capy/io_task.hpp>
21 #include <boost/http/fields_base.hpp>
22 #include <boost/http/metadata.hpp>
23 #include <boost/http/response_parser.hpp>
24 #include <boost/http/status.hpp>
25 #include <boost/http/version.hpp>
26 #include <boost/url/url.hpp>
27 #include <boost/url/url_view.hpp>
28
29 #include <chrono>
30 #include <optional>
31 #include <string_view>
32 #include <system_error>
33 #include <utility>
34
35 namespace boost
36 {
37 namespace burl
38 {
39
40 /** The response to an HTTP request.
41
42 Objects of this type provide access to the
43 status, headers, and body of a response. The
44 status line and headers have already been read
45 when the response is obtained; the body remains
46 unread on the connection and is consumed through
47 the body functions.
48
49 The response owns the connection it was received
50 on. Upon destruction, the connection is returned
51 to the pool for reuse when it can be kept alive
52 and the entire message has arrived.
53
54 A response remains usable after the client which
55 produced it is destroyed; in that case the
56 connection is closed upon destruction instead of
57 being returned to the pool.
58
59 @par Example
60 @code
61 auto [ec, r] = co_await c.get("https://example.com").send();
62
63 if(ec)
64 throw std::system_error(ec);
65
66 std::cout << "status: " << r.status_int() << '\n';
67 std::cout << "headers: " << r.headers() << '\n';
68 std::cout << "body: " << co_await r.as<std::string>() << '\n';
69 @endcode
70
71 @see
72 @ref client::execute,
73 @ref request_builder::send.
74 */
75 class response
76 {
77 friend class client;
78 friend class test::response_factory;
79 using clock = std::chrono::steady_clock;
80
81 urls::url url_;
82 detail::pooled_connection conn_;
83 http::response_parser parser_;
84 std::optional<clock::time_point> deadline_;
85
86 BOOST_BURL_DECL
87 response(
88 urls::url url,
89 detail::pooled_connection conn,
90 http::response_parser parser,
91 std::optional<clock::time_point> deadline);
92
93 public:
94 /** Constructor.
95
96 A default-constructed response is not
97 associated with any request, and is intended
98 only as a target for assignment.
99 */
100 8x response() = default;
101
102 /** Copy constructor (deleted).
103 */
104 response(response const&) = delete;
105
106 /** Copy assignment (deleted).
107 */
108 response&
109 operator=(response const&) = delete;
110
111 /** Move constructor.
112
113 Constructs a response by taking ownership of
114 the contents of another response, including
115 the underlying connection. The moved-from
116 response no longer owns a connection.
117
118 @param other The response to move from.
119 */
120 BOOST_BURL_DECL
121 response(response&& other) noexcept;
122
123 /** Move assignment.
124
125 Takes ownership of the contents of another
126 response, including the underlying
127 connection. The previously owned connection,
128 if any, is returned to the pool or closed,
129 as if by destruction. The moved-from
130 response no longer owns a connection.
131
132 @param other The response to move from.
133
134 @return A reference to this object.
135 */
136 BOOST_BURL_DECL
137 response&
138 operator=(response&& other) noexcept;
139
140 /** Destructor.
141
142 Returns the connection to the pool for reuse
143 when it can be kept alive and the entire
144 message has arrived.
145 */
146 BOOST_BURL_DECL
147 ~response();
148
149 /** Return the status code.
150 */
151 http::status
152 19x status() const noexcept
153 {
154 19x return parser_.get().status();
155 }
156
157 /** Return the status code as an integer.
158 */
159 unsigned short
160 11x status_int() const noexcept
161 {
162 11x return parser_.get().status_int();
163 }
164
165 /** Return true if the status code indicates success.
166 */
167 bool
168 8x ok() const noexcept
169 {
170 8x return http::to_status_class(status()) ==
171 8x http::status_class::successful;
172 }
173
174 /** Return the reason phrase of the status code.
175 */
176 std::string_view
177 3x reason() const noexcept
178 {
179 3x return parser_.get().reason();
180 }
181
182 /** Throw an exception for 4xx and 5xx status codes.
183
184 If the status code is 400 or above, throws
185 an exception whose code value is the status
186 code and whose category is
187 @ref burl_category. Otherwise, this function
188 has no effect.
189
190 @par Example
191 @code
192 r.raise_for_status(); // throws on 4XX and 5XX status codes
193 @endcode
194
195 @throw std::system_error
196 The status code is 400 or above.
197 */
198 void
199 4x raise_for_status() const
200 {
201
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
4x if(status_int() >= 400)
202 throw std::system_error(
203
1/1
✓ Branch 5 taken 2 times.
2x std::error_code(status_int(), burl_category()));
204 2x }
205
206 /** Return the HTTP version of the response.
207 */
208 http::version
209 2x version() const noexcept
210 {
211 2x return parser_.get().version();
212 }
213
214 /** Return the final URL of the response.
215 */
216 urls::url_view
217 2x url() const noexcept
218 {
219 2x return url_;
220 }
221
222 /** Return the response headers.
223 */
224 const http::fields_base&
225 8x headers() const noexcept
226 {
227 8x return parser_.get();
228 }
229
230 /** Return the payload size, if known.
231
232 Returns the size of the message payload when
233 it is determined by the message metadata.
234 Otherwise returns an empty optional, such as
235 for chunked messages.
236 */
237 std::optional<std::uint64_t>
238 20x content_length() const noexcept
239 {
240
2/2
✓ Branch 2 taken 17 times.
✓ Branch 3 taken 3 times.
20x if(parser_.get().payload() == http::payload::size)
241 17x return parser_.get().payload_size();
242 3x return std::nullopt;
243 }
244
245 /** Asynchronously read the entire body in place.
246
247 Reads the remainder of the body into the
248 internal buffer of the parser and returns a
249 view of the complete body. The buffer is
250 sized by
251 @ref client::config::response_inplace_buffer;
252 a body which does not fit fails with
253 `http::error::in_place_overflow`. If the
254 body has already been read to completion,
255 the body is returned without performing I/O.
256
257 The returned view references memory owned by
258 the response, and remains valid until the
259 response is destroyed or moved from.
260
261 The remaining time of the request timeout,
262 when one was set, applies to this operation.
263
264 @par Example
265 @code
266 auto [ec, body] = co_await r.try_as_view();
267 @endcode
268
269 @return An awaitable yielding
270 `(error_code,std::string_view)`.
271
272 @see @ref as_view.
273 */
274 BOOST_BURL_DECL
275 capy::io_task<std::string_view>
276 try_as_view() &;
277
278 /** Asynchronously read the entire body in place.
279
280 Equivalent to @ref try_as_view, except
281 that an exception is thrown upon failure.
282
283 @par Example
284 @code
285 std::cout << co_await r.as_view() << '\n';
286 @endcode
287
288 @throw std::system_error
289 The operation failed.
290
291 @return An awaitable yielding a view of the
292 body.
293 */
294 BOOST_BURL_DECL
295 capy::task<std::string_view>
296 as_view() &;
297
298 /** Asynchronously convert the body.
299
300 Reads the body and converts it to `T` by
301 calling `tag_invoke` with @ref body_to_tag.
302
303 The remaining time of the request timeout,
304 when one was set, applies to this operation.
305
306 @par Example
307 @code
308 auto [ec, v] = co_await r.try_as<json::value>();
309 @endcode
310
311 @tparam T The type to convert the body to.
312
313 @param args Additional arguments forwarded
314 to the conversion.
315
316 @return An awaitable yielding
317 `(error_code,T)`.
318
319 @see
320 @ref as,
321 @ref body_to_tag.
322 */
323 template<class T, class... Args>
324 requires requires(response& resp, Args&&... args) {
325 tag_invoke(body_to_tag<T>{}, resp, std::forward<Args>(args)...);
326 }
327 capy::io_task<T>
328 36x try_as(Args&&... args) &
329 {
330
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 17 times.
36x if(deadline_)
331 {
332
1/1
✓ Branch 3 taken 2 times.
2x auto dur = *deadline_ - clock::now();
333
3/3
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 1 time.
2x if(dur <= clock::duration::zero())
334
2/6
✓ Branch 3 taken 1 time.
✓ Branch 6 taken 1 time.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
2x return []() -> capy::io_task<T>
335 {
336 co_return { capy::error::timeout, {} };
337
1/1
✓ Branch 1 taken 1 time.
1x }();
338 return capy::timeout(
339 tag_invoke(
340 body_to_tag<T>{}, *this, std::forward<Args>(args)...),
341
2/4
✓ Branch 1 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 7 not taken.
1x dur);
342 }
343
2/2
✓ Branch 2 taken 3 times.
✓ Branch 5 taken 3 times.
34x return tag_invoke(body_to_tag<T>{}, *this, std::forward<Args>(args)...);
344 }
345
346 /** Asynchronously convert the body.
347
348 Equivalent to @ref try_as, except that an
349 exception is thrown upon failure.
350
351 @par Example
352 @code
353 auto v = co_await r.as<json::value>();
354 @endcode
355
356 @throw std::system_error
357 The operation failed.
358
359 @tparam T The type to convert the body to.
360
361 @param args Additional arguments forwarded
362 to the conversion.
363
364 @return An awaitable yielding the converted
365 body.
366
367 @see
368 @ref try_as,
369 @ref body_to_tag.
370 */
371 template<class T, class... Args>
372 capy::task<T>
373
1/1
✓ Branch 1 taken 4 times.
4x as(Args... args) &
374 {
375 auto [ec, body] = co_await try_as<T>(std::move(args)...);
376
377 if(ec)
378 throw std::system_error(ec);
379
380 co_return std::move(body);
381 8x }
382
383 /** Return a buffer source for reading the body.
384
385 The returned source pulls the body
386 incrementally, exposing the internal buffers
387 of the parser directly instead of buffering
388 the whole body in memory. The response must
389 remain valid until the source is no longer
390 used.
391
392 @par Example
393 @code
394 auto source = r.as_buffer_source();
395 for(;;)
396 {
397 capy::const_buffer arr[8];
398 auto [ec, bufs] = co_await source.pull(arr);
399 if(ec == capy::cond::eof)
400 break;
401 if(ec)
402 throw std::system_error(ec);
403 for(auto const& buf : bufs)
404 {
405 consume_data(buf.data(), buf.size());
406 source.consume(buf.size());
407 }
408 }
409 @endcode
410
411 @return A buffer source for the body.
412
413 @see @ref as_read_source.
414 */
415 BOOST_BURL_DECL
416 capy::any_buffer_source
417 as_buffer_source() &;
418
419 /** Return a read source for reading the body.
420
421 The returned source reads the body
422 incrementally into caller-provided buffers.
423 The response must remain valid until the
424 source is no longer used.
425
426 @return A read source for the body.
427
428 @see @ref as_buffer_source.
429 */
430 BOOST_BURL_DECL
431 capy::any_read_source
432 as_read_source() &;
433 };
434
435 } // namespace burl
436 } // namespace boost
437
438 #endif
439