include/boost/burl/response.hpp

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