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}