reqwest/blocking/response.rs
1use std::fmt;
2use std::io::{self, Read};
3use std::mem;
4use std::net::SocketAddr;
5use std::pin::Pin;
6use std::time::Duration;
7
8use bytes::Bytes;
9use futures_util::TryStreamExt;
10use http;
11use http_body_util::BodyExt;
12use hyper::header::HeaderMap;
13#[cfg(feature = "json")]
14use serde::de::DeserializeOwned;
15
16use super::client::KeepCoreThreadAlive;
17use super::wait;
18#[cfg(feature = "cookies")]
19use crate::cookie;
20use crate::{async_impl, StatusCode, Url, Version};
21
22/// A Response to a submitted `Request`.
23pub struct Response {
24 inner: async_impl::Response,
25 body: Option<Pin<Box<dyn futures_util::io::AsyncRead + Send + Sync>>>,
26 timeout: Option<Duration>,
27 _thread_handle: KeepCoreThreadAlive,
28}
29
30impl fmt::Debug for Response {
31 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32 fmt::Debug::fmt(&self.inner, f)
33 }
34}
35
36impl Response {
37 pub(crate) fn new(
38 res: async_impl::Response,
39 timeout: Option<Duration>,
40 thread: KeepCoreThreadAlive,
41 ) -> Response {
42 Response {
43 inner: res,
44 body: None,
45 timeout,
46 _thread_handle: thread,
47 }
48 }
49
50 /// Get the `StatusCode` of this `Response`.
51 ///
52 /// # Examples
53 ///
54 /// Checking for general status class:
55 ///
56 /// ```rust
57 /// # #[cfg(feature = "json")]
58 /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
59 /// let resp = reqwest::blocking::get("http://httpbin.org/get")?;
60 /// if resp.status().is_success() {
61 /// println!("success!");
62 /// } else if resp.status().is_server_error() {
63 /// println!("server error!");
64 /// } else {
65 /// println!("Something else happened. Status: {:?}", resp.status());
66 /// }
67 /// # Ok(())
68 /// # }
69 /// ```
70 ///
71 /// Checking for specific status codes:
72 ///
73 /// ```rust
74 /// use reqwest::blocking::Client;
75 /// use reqwest::StatusCode;
76 /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
77 /// let client = Client::new();
78 ///
79 /// let resp = client.post("http://httpbin.org/post")
80 /// .body("possibly too large")
81 /// .send()?;
82 ///
83 /// match resp.status() {
84 /// StatusCode::OK => println!("success!"),
85 /// StatusCode::PAYLOAD_TOO_LARGE => {
86 /// println!("Request payload is too large!");
87 /// }
88 /// s => println!("Received response status: {s:?}"),
89 /// };
90 /// # Ok(())
91 /// # }
92 /// ```
93 #[inline]
94 pub fn status(&self) -> StatusCode {
95 self.inner.status()
96 }
97
98 /// Get the `Headers` of this `Response`.
99 ///
100 /// # Example
101 ///
102 /// Saving an etag when caching a file:
103 ///
104 /// ```
105 /// use reqwest::blocking::Client;
106 /// use reqwest::header::ETAG;
107 ///
108 /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
109 /// let client = Client::new();
110 ///
111 /// let mut resp = client.get("http://httpbin.org/cache").send()?;
112 /// if resp.status().is_success() {
113 /// if let Some(etag) = resp.headers().get(ETAG) {
114 /// std::fs::write("etag", etag.as_bytes());
115 /// }
116 /// let mut file = std::fs::File::create("file")?;
117 /// resp.copy_to(&mut file)?;
118 /// }
119 /// # Ok(())
120 /// # }
121 /// ```
122 #[inline]
123 pub fn headers(&self) -> &HeaderMap {
124 self.inner.headers()
125 }
126
127 /// Get a mutable reference to the `Headers` of this `Response`.
128 #[inline]
129 pub fn headers_mut(&mut self) -> &mut HeaderMap {
130 self.inner.headers_mut()
131 }
132
133 /// Retrieve the cookies contained in the response.
134 ///
135 /// Note that invalid 'Set-Cookie' headers will be ignored.
136 ///
137 /// # Optional
138 ///
139 /// This requires the optional `cookies` feature to be enabled.
140 #[cfg(feature = "cookies")]
141 #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))]
142 pub fn cookies<'a>(&'a self) -> impl Iterator<Item = cookie::Cookie<'a>> + 'a {
143 cookie::extract_response_cookies(self.headers()).filter_map(Result::ok)
144 }
145
146 /// Get the HTTP `Version` of this `Response`.
147 #[inline]
148 pub fn version(&self) -> Version {
149 self.inner.version()
150 }
151
152 /// Get the final `Url` of this `Response`.
153 ///
154 /// # Example
155 ///
156 /// ```rust
157 /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
158 /// let resp = reqwest::blocking::get("http://httpbin.org/redirect/1")?;
159 /// assert_eq!(resp.url().as_str(), "http://httpbin.org/get");
160 /// # Ok(())
161 /// # }
162 /// ```
163 #[inline]
164 pub fn url(&self) -> &Url {
165 self.inner.url()
166 }
167
168 /// Get the remote address used to get this `Response`.
169 ///
170 /// # Example
171 ///
172 /// ```rust
173 /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
174 /// let resp = reqwest::blocking::get("http://httpbin.org/redirect/1")?;
175 /// println!("httpbin.org address: {:?}", resp.remote_addr());
176 /// # Ok(())
177 /// # }
178 /// ```
179 pub fn remote_addr(&self) -> Option<SocketAddr> {
180 self.inner.remote_addr()
181 }
182
183 /// Returns a reference to the associated extensions.
184 pub fn extensions(&self) -> &http::Extensions {
185 self.inner.extensions()
186 }
187
188 /// Returns a mutable reference to the associated extensions.
189 pub fn extensions_mut(&mut self) -> &mut http::Extensions {
190 self.inner.extensions_mut()
191 }
192
193 /// Get the content length of the response, if it is known.
194 ///
195 ///
196 /// This value does not directly represents the value of the `Content-Length`
197 /// header, but rather the size of the response's body. To read the header's
198 /// value, please use the [`Response::headers`] method instead.
199 ///
200 /// Reasons it may not be known:
201 ///
202 /// - The response does not include a body (e.g. it responds to a `HEAD`
203 /// request).
204 /// - The response is gzipped and automatically decoded (thus changing the
205 /// actual decoded length).
206 pub fn content_length(&self) -> Option<u64> {
207 self.inner.content_length()
208 }
209
210 /// Try and deserialize the response body as JSON using `serde`.
211 ///
212 /// # Optional
213 ///
214 /// This requires the optional `json` feature enabled.
215 ///
216 /// # Examples
217 ///
218 /// ```rust
219 /// # extern crate reqwest;
220 /// # extern crate serde;
221 /// #
222 /// # use reqwest::Error;
223 /// # use serde::Deserialize;
224 /// #
225 /// // This `derive` requires the `serde` dependency.
226 /// #[derive(Deserialize)]
227 /// struct Ip {
228 /// origin: String,
229 /// }
230 ///
231 /// # fn run() -> Result<(), Error> {
232 /// let json: Ip = reqwest::blocking::get("http://httpbin.org/ip")?.json()?;
233 /// # Ok(())
234 /// # }
235 /// #
236 /// # fn main() { }
237 /// ```
238 ///
239 /// # Errors
240 ///
241 /// This method fails whenever the response body is not in JSON format,
242 /// or it cannot be properly deserialized to target type `T`. For more
243 /// details please see [`serde_json::from_reader`].
244 ///
245 /// [`serde_json::from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html
246 #[cfg(feature = "json")]
247 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
248 pub fn json<T: DeserializeOwned>(self) -> crate::Result<T> {
249 wait::timeout(self.inner.json(), self.timeout).map_err(|e| match e {
250 wait::Waited::TimedOut(e) => crate::error::decode(e),
251 wait::Waited::Inner(e) => e,
252 })
253 }
254
255 /// Get the full response body as `Bytes`.
256 ///
257 /// # Example
258 ///
259 /// ```
260 /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
261 /// let bytes = reqwest::blocking::get("http://httpbin.org/ip")?.bytes()?;
262 ///
263 /// println!("bytes: {bytes:?}");
264 /// # Ok(())
265 /// # }
266 /// ```
267 pub fn bytes(self) -> crate::Result<Bytes> {
268 wait::timeout(self.inner.bytes(), self.timeout).map_err(|e| match e {
269 wait::Waited::TimedOut(e) => crate::error::decode(e),
270 wait::Waited::Inner(e) => e,
271 })
272 }
273
274 /// Get the response text.
275 ///
276 /// This method decodes the response body with BOM sniffing
277 /// and with malformed sequences replaced with the [`char::REPLACEMENT_CHARACTER`].
278 /// Encoding is determined from the `charset` parameter of `Content-Type` header,
279 /// and defaults to `utf-8` if not presented.
280 ///
281 /// # Note
282 ///
283 /// If the `charset` feature is disabled the method will only attempt to decode the
284 /// response as UTF-8, regardless of the given `Content-Type`
285 ///
286 /// # Example
287 ///
288 /// ```rust
289 /// # extern crate reqwest;
290 /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
291 /// let content = reqwest::blocking::get("http://httpbin.org/range/26")?.text()?;
292 /// # Ok(())
293 /// # }
294 /// ```
295 pub fn text(self) -> crate::Result<String> {
296 wait::timeout(self.inner.text(), self.timeout).map_err(|e| match e {
297 wait::Waited::TimedOut(e) => crate::error::decode(e),
298 wait::Waited::Inner(e) => e,
299 })
300 }
301
302 /// Get the response text given a specific encoding.
303 ///
304 /// This method decodes the response body with BOM sniffing
305 /// and with malformed sequences replaced with the [`char::REPLACEMENT_CHARACTER`].
306 /// You can provide a default encoding for decoding the raw message, while the
307 /// `charset` parameter of `Content-Type` header is still prioritized. For more information
308 /// about the possible encoding name, please go to [`encoding_rs`] docs.
309 ///
310 /// [`encoding_rs`]: https://docs.rs/encoding_rs/0.8/encoding_rs/#relationship-with-windows-code-pages
311 ///
312 /// # Optional
313 ///
314 /// This requires the optional `charset` feature enabled.
315 ///
316 /// # Example
317 ///
318 /// ```rust
319 /// # extern crate reqwest;
320 /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
321 /// let content = reqwest::blocking::get("http://httpbin.org/range/26")?
322 /// .text_with_charset("utf-8")?;
323 /// # Ok(())
324 /// # }
325 /// ```
326 #[cfg(feature = "charset")]
327 #[cfg_attr(docsrs, doc(cfg(feature = "charset")))]
328 pub fn text_with_charset(self, default_encoding: &str) -> crate::Result<String> {
329 wait::timeout(self.inner.text_with_charset(default_encoding), self.timeout).map_err(|e| {
330 match e {
331 wait::Waited::TimedOut(e) => crate::error::decode(e),
332 wait::Waited::Inner(e) => e,
333 }
334 })
335 }
336
337 /// Copy the response body into a writer.
338 ///
339 /// This function internally uses [`std::io::copy`] and hence will continuously read data from
340 /// the body and then write it into writer in a streaming fashion until EOF is met.
341 ///
342 /// On success, the total number of bytes that were copied to `writer` is returned.
343 ///
344 /// [`std::io::copy`]: https://doc.rust-lang.org/std/io/fn.copy.html
345 ///
346 /// # Example
347 ///
348 /// ```rust
349 /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
350 /// let mut resp = reqwest::blocking::get("http://httpbin.org/range/5")?;
351 /// let mut buf: Vec<u8> = vec![];
352 /// resp.copy_to(&mut buf)?;
353 /// assert_eq!(b"abcde", buf.as_slice());
354 /// # Ok(())
355 /// # }
356 /// ```
357 pub fn copy_to<W: ?Sized>(&mut self, w: &mut W) -> crate::Result<u64>
358 where
359 W: io::Write,
360 {
361 io::copy(self, w).map_err(crate::error::decode_io)
362 }
363
364 /// Turn a response into an error if the server returned an error.
365 ///
366 /// # Example
367 ///
368 /// ```rust,no_run
369 /// # extern crate reqwest;
370 /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
371 /// let res = reqwest::blocking::get("http://httpbin.org/status/400")?
372 /// .error_for_status();
373 /// if let Err(err) = res {
374 /// assert_eq!(err.status(), Some(reqwest::StatusCode::BAD_REQUEST));
375 /// }
376 /// # Ok(())
377 /// # }
378 /// # fn main() {}
379 /// ```
380 pub fn error_for_status(self) -> crate::Result<Self> {
381 let Response {
382 body,
383 inner,
384 timeout,
385 _thread_handle,
386 } = self;
387 inner.error_for_status().map(move |inner| Response {
388 inner,
389 body,
390 timeout,
391 _thread_handle,
392 })
393 }
394
395 /// Turn a reference to a response into an error if the server returned an error.
396 ///
397 /// # Example
398 ///
399 /// ```rust,no_run
400 /// # extern crate reqwest;
401 /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
402 /// let res = reqwest::blocking::get("http://httpbin.org/status/400")?;
403 /// let res = res.error_for_status_ref();
404 /// if let Err(err) = res {
405 /// assert_eq!(err.status(), Some(reqwest::StatusCode::BAD_REQUEST));
406 /// }
407 /// # Ok(())
408 /// # }
409 /// # fn main() {}
410 /// ```
411 pub fn error_for_status_ref(&self) -> crate::Result<&Self> {
412 self.inner.error_for_status_ref().and_then(|_| Ok(self))
413 }
414
415 // private
416
417 fn body_mut(&mut self) -> Pin<&mut dyn futures_util::io::AsyncRead> {
418 if self.body.is_none() {
419 let body = mem::replace(
420 self.inner.body_mut(),
421 async_impl::body::boxed(http_body_util::Empty::new()),
422 );
423
424 self.body = Some(Box::pin(
425 async_impl::body::Body::wrap(body)
426 .into_data_stream()
427 .map_err(crate::error::Error::into_io)
428 .into_async_read(),
429 ));
430 }
431 self.body.as_mut().expect("body was init").as_mut()
432 }
433}
434
435impl Read for Response {
436 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
437 use futures_util::io::AsyncReadExt;
438
439 let timeout = self.timeout;
440 wait::timeout(self.body_mut().read(buf), timeout).map_err(|e| match e {
441 wait::Waited::TimedOut(e) => crate::error::decode(e).into_io(),
442 wait::Waited::Inner(e) => e,
443 })
444 }
445}
446
447impl<T: Into<async_impl::body::Body>> From<http::Response<T>> for Response {
448 fn from(r: http::Response<T>) -> Response {
449 let response = async_impl::Response::from(r);
450 Response::new(response, None, KeepCoreThreadAlive::empty())
451 }
452}