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