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}