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