include/boost/burl/response.hpp

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