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}