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 /// Reasons it may not be known:
194 ///
195 /// - The server didn't send a `content-length` header.
196 /// - The response is gzipped and automatically decoded (thus changing
197 /// the actual decoded length).
198 pub fn content_length(&self) -> Option<u64> {
199 self.inner.content_length()
200 }
201
202 /// Try and deserialize the response body as JSON using `serde`.
203 ///
204 /// # Optional
205 ///
206 /// This requires the optional `json` feature enabled.
207 ///
208 /// # Examples
209 ///
210 /// ```rust
211 /// # extern crate reqwest;
212 /// # extern crate serde;
213 /// #
214 /// # use reqwest::Error;
215 /// # use serde::Deserialize;
216 /// #
217 /// // This `derive` requires the `serde` dependency.
218 /// #[derive(Deserialize)]
219 /// struct Ip {
220 /// origin: String,
221 /// }
222 ///
223 /// # fn run() -> Result<(), Error> {
224 /// let json: Ip = reqwest::blocking::get("http://httpbin.org/ip")?.json()?;
225 /// # Ok(())
226 /// # }
227 /// #
228 /// # fn main() { }
229 /// ```
230 ///
231 /// # Errors
232 ///
233 /// This method fails whenever the response body is not in JSON format,
234 /// or it cannot be properly deserialized to target type `T`. For more
235 /// details please see [`serde_json::from_reader`].
236 ///
237 /// [`serde_json::from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html
238 #[cfg(feature = "json")]
239 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
240 pub fn json<T: DeserializeOwned>(self) -> crate::Result<T> {
241 wait::timeout(self.inner.json(), self.timeout).map_err(|e| match e {
242 wait::Waited::TimedOut(e) => crate::error::decode(e),
243 wait::Waited::Inner(e) => e,
244 })
245 }
246
247 /// Get the full response body as `Bytes`.
248 ///
249 /// # Example
250 ///
251 /// ```
252 /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
253 /// let bytes = reqwest::blocking::get("http://httpbin.org/ip")?.bytes()?;
254 ///
255 /// println!("bytes: {bytes:?}");
256 /// # Ok(())
257 /// # }
258 /// ```
259 pub fn bytes(self) -> crate::Result<Bytes> {
260 wait::timeout(self.inner.bytes(), self.timeout).map_err(|e| match e {
261 wait::Waited::TimedOut(e) => crate::error::decode(e),
262 wait::Waited::Inner(e) => e,
263 })
264 }
265
266 /// Get the response text.
267 ///
268 /// This method decodes the response body with BOM sniffing
269 /// and with malformed sequences replaced with the REPLACEMENT CHARACTER.
270 /// Encoding is determined from the `charset` parameter of `Content-Type` header,
271 /// and defaults to `utf-8` if not presented.
272 ///
273 /// # Note
274 ///
275 /// If the `charset` feature is disabled the method will only attempt to decode the
276 /// response as UTF-8, regardless of the given `Content-Type`
277 ///
278 /// # Example
279 ///
280 /// ```rust
281 /// # extern crate reqwest;
282 /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
283 /// let content = reqwest::blocking::get("http://httpbin.org/range/26")?.text()?;
284 /// # Ok(())
285 /// # }
286 /// ```
287 pub fn text(self) -> crate::Result<String> {
288 wait::timeout(self.inner.text(), self.timeout).map_err(|e| match e {
289 wait::Waited::TimedOut(e) => crate::error::decode(e),
290 wait::Waited::Inner(e) => e,
291 })
292 }
293
294 /// Get the response text given a specific encoding.
295 ///
296 /// This method decodes the response body with BOM sniffing
297 /// and with malformed sequences replaced with the REPLACEMENT CHARACTER.
298 /// You can provide a default encoding for decoding the raw message, while the
299 /// `charset` parameter of `Content-Type` header is still prioritized. For more information
300 /// about the possible encoding name, please go to [`encoding_rs`] docs.
301 ///
302 /// [`encoding_rs`]: https://docs.rs/encoding_rs/0.8/encoding_rs/#relationship-with-windows-code-pages
303 ///
304 /// # Optional
305 ///
306 /// This requires the optional `charset` feature enabled.
307 ///
308 /// # Example
309 ///
310 /// ```rust
311 /// # extern crate reqwest;
312 /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
313 /// let content = reqwest::blocking::get("http://httpbin.org/range/26")?
314 /// .text_with_charset("utf-8")?;
315 /// # Ok(())
316 /// # }
317 /// ```
318 #[cfg(feature = "charset")]
319 #[cfg_attr(docsrs, doc(cfg(feature = "charset")))]
320 pub fn text_with_charset(self, default_encoding: &str) -> crate::Result<String> {
321 wait::timeout(self.inner.text_with_charset(default_encoding), self.timeout).map_err(|e| {
322 match e {
323 wait::Waited::TimedOut(e) => crate::error::decode(e),
324 wait::Waited::Inner(e) => e,
325 }
326 })
327 }
328
329 /// Copy the response body into a writer.
330 ///
331 /// This function internally uses [`std::io::copy`] and hence will continuously read data from
332 /// the body and then write it into writer in a streaming fashion until EOF is met.
333 ///
334 /// On success, the total number of bytes that were copied to `writer` is returned.
335 ///
336 /// [`std::io::copy`]: https://doc.rust-lang.org/std/io/fn.copy.html
337 ///
338 /// # Example
339 ///
340 /// ```rust
341 /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
342 /// let mut resp = reqwest::blocking::get("http://httpbin.org/range/5")?;
343 /// let mut buf: Vec<u8> = vec![];
344 /// resp.copy_to(&mut buf)?;
345 /// assert_eq!(b"abcde", buf.as_slice());
346 /// # Ok(())
347 /// # }
348 /// ```
349 pub fn copy_to<W: ?Sized>(&mut self, w: &mut W) -> crate::Result<u64>
350 where
351 W: io::Write,
352 {
353 io::copy(self, w).map_err(crate::error::decode_io)
354 }
355
356 /// Turn a response into an error if the server returned an error.
357 ///
358 /// # Example
359 ///
360 /// ```rust,no_run
361 /// # extern crate reqwest;
362 /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
363 /// let res = reqwest::blocking::get("http://httpbin.org/status/400")?
364 /// .error_for_status();
365 /// if let Err(err) = res {
366 /// assert_eq!(err.status(), Some(reqwest::StatusCode::BAD_REQUEST));
367 /// }
368 /// # Ok(())
369 /// # }
370 /// # fn main() {}
371 /// ```
372 pub fn error_for_status(self) -> crate::Result<Self> {
373 let Response {
374 body,
375 inner,
376 timeout,
377 _thread_handle,
378 } = self;
379 inner.error_for_status().map(move |inner| Response {
380 inner,
381 body,
382 timeout,
383 _thread_handle,
384 })
385 }
386
387 /// Turn a reference to a response into an error if the server returned an error.
388 ///
389 /// # Example
390 ///
391 /// ```rust,no_run
392 /// # extern crate reqwest;
393 /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
394 /// let res = reqwest::blocking::get("http://httpbin.org/status/400")?;
395 /// let res = res.error_for_status_ref();
396 /// if let Err(err) = res {
397 /// assert_eq!(err.status(), Some(reqwest::StatusCode::BAD_REQUEST));
398 /// }
399 /// # Ok(())
400 /// # }
401 /// # fn main() {}
402 /// ```
403 pub fn error_for_status_ref(&self) -> crate::Result<&Self> {
404 self.inner.error_for_status_ref().and_then(|_| Ok(self))
405 }
406
407 // private
408
409 fn body_mut(&mut self) -> Pin<&mut dyn futures_util::io::AsyncRead> {
410 use futures_util::TryStreamExt;
411 if self.body.is_none() {
412 let body = mem::replace(self.inner.body_mut(), async_impl::Decoder::empty());
413
414 let body = body.into_stream().into_async_read();
415
416 self.body = Some(Box::pin(body));
417 }
418 self.body.as_mut().expect("body was init").as_mut()
419 }
420}
421
422impl Read for Response {
423 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
424 use futures_util::io::AsyncReadExt;
425
426 let timeout = self.timeout;
427 wait::timeout(self.body_mut().read(buf), timeout).map_err(|e| match e {
428 wait::Waited::TimedOut(e) => crate::error::decode(e).into_io(),
429 wait::Waited::Inner(e) => e,
430 })
431 }
432}
433
434impl<T: Into<async_impl::body::Body>> From<http::Response<T>> for Response {
435 fn from(r: http::Response<T>) -> Response {
436 let response = async_impl::Response::from(r);
437 Response::new(response, None, KeepCoreThreadAlive::empty())
438 }
439}