1 | use std::fmt; |
2 | use std::net::SocketAddr; |
3 | use std::pin::Pin; |
4 | |
5 | use bytes::Bytes; |
6 | use encoding_rs::{Encoding, UTF_8}; |
7 | use futures_util::stream::StreamExt; |
8 | use hyper::client::connect::HttpInfo; |
9 | use hyper::{HeaderMap, StatusCode, Version}; |
10 | use mime::Mime; |
11 | #[cfg (feature = "json" )] |
12 | use serde::de::DeserializeOwned; |
13 | #[cfg (feature = "json" )] |
14 | use serde_json; |
15 | use tokio::time::Sleep; |
16 | use url::Url; |
17 | |
18 | use super::body::Body; |
19 | use super::decoder::{Accepts, Decoder}; |
20 | #[cfg (feature = "cookies" )] |
21 | use crate::cookie; |
22 | use crate::response::ResponseUrl; |
23 | |
24 | /// A Response to a submitted `Request`. |
25 | pub struct Response { |
26 | pub(super) res: hyper::Response<Decoder>, |
27 | // Boxed to save space (11 words to 1 word), and it's not accessed |
28 | // frequently internally. |
29 | url: Box<Url>, |
30 | } |
31 | |
32 | impl Response { |
33 | pub(super) fn new( |
34 | res: hyper::Response<hyper::Body>, |
35 | url: Url, |
36 | accepts: Accepts, |
37 | timeout: Option<Pin<Box<Sleep>>>, |
38 | ) -> Response { |
39 | let (mut parts, body) = res.into_parts(); |
40 | let decoder = Decoder::detect(&mut parts.headers, Body::response(body, timeout), accepts); |
41 | let res = hyper::Response::from_parts(parts, decoder); |
42 | |
43 | Response { |
44 | res, |
45 | url: Box::new(url), |
46 | } |
47 | } |
48 | |
49 | /// Get the `StatusCode` of this `Response`. |
50 | #[inline ] |
51 | pub fn status(&self) -> StatusCode { |
52 | self.res.status() |
53 | } |
54 | |
55 | /// Get the HTTP `Version` of this `Response`. |
56 | #[inline ] |
57 | pub fn version(&self) -> Version { |
58 | self.res.version() |
59 | } |
60 | |
61 | /// Get the `Headers` of this `Response`. |
62 | #[inline ] |
63 | pub fn headers(&self) -> &HeaderMap { |
64 | self.res.headers() |
65 | } |
66 | |
67 | /// Get a mutable reference to the `Headers` of this `Response`. |
68 | #[inline ] |
69 | pub fn headers_mut(&mut self) -> &mut HeaderMap { |
70 | self.res.headers_mut() |
71 | } |
72 | |
73 | /// Get the content-length of this response, if known. |
74 | /// |
75 | /// Reasons it may not be known: |
76 | /// |
77 | /// - The server didn't send a `content-length` header. |
78 | /// - The response is compressed and automatically decoded (thus changing |
79 | /// the actual decoded length). |
80 | pub fn content_length(&self) -> Option<u64> { |
81 | use hyper::body::HttpBody; |
82 | |
83 | HttpBody::size_hint(self.res.body()).exact() |
84 | } |
85 | |
86 | /// Retrieve the cookies contained in the response. |
87 | /// |
88 | /// Note that invalid 'Set-Cookie' headers will be ignored. |
89 | /// |
90 | /// # Optional |
91 | /// |
92 | /// This requires the optional `cookies` feature to be enabled. |
93 | #[cfg (feature = "cookies" )] |
94 | #[cfg_attr (docsrs, doc(cfg(feature = "cookies" )))] |
95 | pub fn cookies<'a>(&'a self) -> impl Iterator<Item = cookie::Cookie<'a>> + 'a { |
96 | cookie::extract_response_cookies(self.res.headers()).filter_map(Result::ok) |
97 | } |
98 | |
99 | /// Get the final `Url` of this `Response`. |
100 | #[inline ] |
101 | pub fn url(&self) -> &Url { |
102 | &self.url |
103 | } |
104 | |
105 | /// Get the remote address used to get this `Response`. |
106 | pub fn remote_addr(&self) -> Option<SocketAddr> { |
107 | self.res |
108 | .extensions() |
109 | .get::<HttpInfo>() |
110 | .map(|info| info.remote_addr()) |
111 | } |
112 | |
113 | /// Returns a reference to the associated extensions. |
114 | pub fn extensions(&self) -> &http::Extensions { |
115 | self.res.extensions() |
116 | } |
117 | |
118 | /// Returns a mutable reference to the associated extensions. |
119 | pub fn extensions_mut(&mut self) -> &mut http::Extensions { |
120 | self.res.extensions_mut() |
121 | } |
122 | |
123 | // body methods |
124 | |
125 | /// Get the full response text. |
126 | /// |
127 | /// This method decodes the response body with BOM sniffing |
128 | /// and with malformed sequences replaced with the REPLACEMENT CHARACTER. |
129 | /// Encoding is determined from the `charset` parameter of `Content-Type` header, |
130 | /// and defaults to `utf-8` if not presented. |
131 | /// |
132 | /// Note that the BOM is stripped from the returned String. |
133 | /// |
134 | /// # Example |
135 | /// |
136 | /// ``` |
137 | /// # async fn run() -> Result<(), Box<dyn std::error::Error>> { |
138 | /// let content = reqwest::get("http://httpbin.org/range/26" ) |
139 | /// .await? |
140 | /// .text() |
141 | /// .await?; |
142 | /// |
143 | /// println!("text: {content:?}" ); |
144 | /// # Ok(()) |
145 | /// # } |
146 | /// ``` |
147 | pub async fn text(self) -> crate::Result<String> { |
148 | self.text_with_charset("utf-8" ).await |
149 | } |
150 | |
151 | /// Get the full response text given a specific encoding. |
152 | /// |
153 | /// This method decodes the response body with BOM sniffing |
154 | /// and with malformed sequences replaced with the REPLACEMENT CHARACTER. |
155 | /// You can provide a default encoding for decoding the raw message, while the |
156 | /// `charset` parameter of `Content-Type` header is still prioritized. For more information |
157 | /// about the possible encoding name, please go to [`encoding_rs`] docs. |
158 | /// |
159 | /// Note that the BOM is stripped from the returned String. |
160 | /// |
161 | /// [`encoding_rs`]: https://docs.rs/encoding_rs/0.8/encoding_rs/#relationship-with-windows-code-pages |
162 | /// |
163 | /// # Example |
164 | /// |
165 | /// ``` |
166 | /// # async fn run() -> Result<(), Box<dyn std::error::Error>> { |
167 | /// let content = reqwest::get("http://httpbin.org/range/26" ) |
168 | /// .await? |
169 | /// .text_with_charset("utf-8" ) |
170 | /// .await?; |
171 | /// |
172 | /// println!("text: {content:?}" ); |
173 | /// # Ok(()) |
174 | /// # } |
175 | /// ``` |
176 | pub async fn text_with_charset(self, default_encoding: &str) -> crate::Result<String> { |
177 | let content_type = self |
178 | .headers() |
179 | .get(crate::header::CONTENT_TYPE) |
180 | .and_then(|value| value.to_str().ok()) |
181 | .and_then(|value| value.parse::<Mime>().ok()); |
182 | let encoding_name = content_type |
183 | .as_ref() |
184 | .and_then(|mime| mime.get_param("charset" ).map(|charset| charset.as_str())) |
185 | .unwrap_or(default_encoding); |
186 | let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8); |
187 | |
188 | let full = self.bytes().await?; |
189 | |
190 | let (text, _, _) = encoding.decode(&full); |
191 | Ok(text.into_owned()) |
192 | } |
193 | |
194 | /// Try to deserialize the response body as JSON. |
195 | /// |
196 | /// # Optional |
197 | /// |
198 | /// This requires the optional `json` feature enabled. |
199 | /// |
200 | /// # Examples |
201 | /// |
202 | /// ``` |
203 | /// # extern crate reqwest; |
204 | /// # extern crate serde; |
205 | /// # |
206 | /// # use reqwest::Error; |
207 | /// # use serde::Deserialize; |
208 | /// # |
209 | /// // This `derive` requires the `serde` dependency. |
210 | /// #[derive(Deserialize)] |
211 | /// struct Ip { |
212 | /// origin: String, |
213 | /// } |
214 | /// |
215 | /// # async fn run() -> Result<(), Error> { |
216 | /// let ip = reqwest::get("http://httpbin.org/ip" ) |
217 | /// .await? |
218 | /// .json::<Ip>() |
219 | /// .await?; |
220 | /// |
221 | /// println!("ip: {}" , ip.origin); |
222 | /// # Ok(()) |
223 | /// # } |
224 | /// # |
225 | /// # fn main() { } |
226 | /// ``` |
227 | /// |
228 | /// # Errors |
229 | /// |
230 | /// This method fails whenever the response body is not in JSON format |
231 | /// or it cannot be properly deserialized to target type `T`. For more |
232 | /// details please see [`serde_json::from_reader`]. |
233 | /// |
234 | /// [`serde_json::from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html |
235 | #[cfg (feature = "json" )] |
236 | #[cfg_attr (docsrs, doc(cfg(feature = "json" )))] |
237 | pub async fn json<T: DeserializeOwned>(self) -> crate::Result<T> { |
238 | let full = self.bytes().await?; |
239 | |
240 | serde_json::from_slice(&full).map_err(crate::error::decode) |
241 | } |
242 | |
243 | /// Get the full response body as `Bytes`. |
244 | /// |
245 | /// # Example |
246 | /// |
247 | /// ``` |
248 | /// # async fn run() -> Result<(), Box<dyn std::error::Error>> { |
249 | /// let bytes = reqwest::get("http://httpbin.org/ip" ) |
250 | /// .await? |
251 | /// .bytes() |
252 | /// .await?; |
253 | /// |
254 | /// println!("bytes: {bytes:?}" ); |
255 | /// # Ok(()) |
256 | /// # } |
257 | /// ``` |
258 | pub async fn bytes(self) -> crate::Result<Bytes> { |
259 | hyper::body::to_bytes(self.res.into_body()).await |
260 | } |
261 | |
262 | /// Stream a chunk of the response body. |
263 | /// |
264 | /// When the response body has been exhausted, this will return `None`. |
265 | /// |
266 | /// # Example |
267 | /// |
268 | /// ``` |
269 | /// # async fn run() -> Result<(), Box<dyn std::error::Error>> { |
270 | /// let mut res = reqwest::get("https://hyper.rs" ).await?; |
271 | /// |
272 | /// while let Some(chunk) = res.chunk().await? { |
273 | /// println!("Chunk: {chunk:?}" ); |
274 | /// } |
275 | /// # Ok(()) |
276 | /// # } |
277 | /// ``` |
278 | pub async fn chunk(&mut self) -> crate::Result<Option<Bytes>> { |
279 | if let Some(item) = self.res.body_mut().next().await { |
280 | Ok(Some(item?)) |
281 | } else { |
282 | Ok(None) |
283 | } |
284 | } |
285 | |
286 | /// Convert the response into a `Stream` of `Bytes` from the body. |
287 | /// |
288 | /// # Example |
289 | /// |
290 | /// ``` |
291 | /// use futures_util::StreamExt; |
292 | /// |
293 | /// # async fn run() -> Result<(), Box<dyn std::error::Error>> { |
294 | /// let mut stream = reqwest::get("http://httpbin.org/ip" ) |
295 | /// .await? |
296 | /// .bytes_stream(); |
297 | /// |
298 | /// while let Some(item) = stream.next().await { |
299 | /// println!("Chunk: {:?}" , item?); |
300 | /// } |
301 | /// # Ok(()) |
302 | /// # } |
303 | /// ``` |
304 | /// |
305 | /// # Optional |
306 | /// |
307 | /// This requires the optional `stream` feature to be enabled. |
308 | #[cfg (feature = "stream" )] |
309 | #[cfg_attr (docsrs, doc(cfg(feature = "stream" )))] |
310 | pub fn bytes_stream(self) -> impl futures_core::Stream<Item = crate::Result<Bytes>> { |
311 | self.res.into_body() |
312 | } |
313 | |
314 | // util methods |
315 | |
316 | /// Turn a response into an error if the server returned an error. |
317 | /// |
318 | /// # Example |
319 | /// |
320 | /// ``` |
321 | /// # use reqwest::Response; |
322 | /// fn on_response(res: Response) { |
323 | /// match res.error_for_status() { |
324 | /// Ok(_res) => (), |
325 | /// Err(err) => { |
326 | /// // asserting a 400 as an example |
327 | /// // it could be any status between 400...599 |
328 | /// assert_eq!( |
329 | /// err.status(), |
330 | /// Some(reqwest::StatusCode::BAD_REQUEST) |
331 | /// ); |
332 | /// } |
333 | /// } |
334 | /// } |
335 | /// # fn main() {} |
336 | /// ``` |
337 | pub fn error_for_status(self) -> crate::Result<Self> { |
338 | let status = self.status(); |
339 | if status.is_client_error() || status.is_server_error() { |
340 | Err(crate::error::status_code(*self.url, status)) |
341 | } else { |
342 | Ok(self) |
343 | } |
344 | } |
345 | |
346 | /// Turn a reference to a response into an error if the server returned an error. |
347 | /// |
348 | /// # Example |
349 | /// |
350 | /// ``` |
351 | /// # use reqwest::Response; |
352 | /// fn on_response(res: &Response) { |
353 | /// match res.error_for_status_ref() { |
354 | /// Ok(_res) => (), |
355 | /// Err(err) => { |
356 | /// // asserting a 400 as an example |
357 | /// // it could be any status between 400...599 |
358 | /// assert_eq!( |
359 | /// err.status(), |
360 | /// Some(reqwest::StatusCode::BAD_REQUEST) |
361 | /// ); |
362 | /// } |
363 | /// } |
364 | /// } |
365 | /// # fn main() {} |
366 | /// ``` |
367 | pub fn error_for_status_ref(&self) -> crate::Result<&Self> { |
368 | let status = self.status(); |
369 | if status.is_client_error() || status.is_server_error() { |
370 | Err(crate::error::status_code(*self.url.clone(), status)) |
371 | } else { |
372 | Ok(self) |
373 | } |
374 | } |
375 | |
376 | // private |
377 | |
378 | // The Response's body is an implementation detail. |
379 | // You no longer need to get a reference to it, there are async methods |
380 | // on the `Response` itself. |
381 | // |
382 | // This method is just used by the blocking API. |
383 | #[cfg (feature = "blocking" )] |
384 | pub(crate) fn body_mut(&mut self) -> &mut Decoder { |
385 | self.res.body_mut() |
386 | } |
387 | } |
388 | |
389 | impl fmt::Debug for Response { |
390 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
391 | f&mut DebugStruct<'_, '_>.debug_struct("Response" ) |
392 | .field("url" , self.url()) |
393 | .field("status" , &self.status()) |
394 | .field(name:"headers" , self.headers()) |
395 | .finish() |
396 | } |
397 | } |
398 | |
399 | impl<T: Into<Body>> From<http::Response<T>> for Response { |
400 | fn from(r: http::Response<T>) -> Response { |
401 | let (mut parts: Parts, body: T) = r.into_parts(); |
402 | let body: Body = body.into(); |
403 | let decoder: Decoder = Decoder::detect(&mut parts.headers, body, Accepts::none()); |
404 | let url: ResponseUrl = partsOption |
405 | .extensions |
406 | .remove::<ResponseUrl>() |
407 | .unwrap_or_else(|| ResponseUrl(Url::parse(input:"http://no.url.provided.local" ).unwrap())); |
408 | let url: Url = url.0; |
409 | let res: Response = hyper::Response::from_parts(parts, body:decoder); |
410 | Response { |
411 | res, |
412 | url: Box::new(url), |
413 | } |
414 | } |
415 | } |
416 | |
417 | /// A `Response` can be piped as the `Body` of another request. |
418 | impl From<Response> for Body { |
419 | fn from(r: Response) -> Body { |
420 | Body::stream(r.res.into_body()) |
421 | } |
422 | } |
423 | |
424 | #[cfg (test)] |
425 | mod tests { |
426 | use super::Response; |
427 | use crate::ResponseBuilderExt; |
428 | use http::response::Builder; |
429 | use url::Url; |
430 | |
431 | #[test ] |
432 | fn test_from_http_response() { |
433 | let url = Url::parse("http://example.com" ).unwrap(); |
434 | let response = Builder::new() |
435 | .status(200) |
436 | .url(url.clone()) |
437 | .body("foo" ) |
438 | .unwrap(); |
439 | let response = Response::from(response); |
440 | |
441 | assert_eq!(response.status(), 200); |
442 | assert_eq!(*response.url(), url); |
443 | } |
444 | } |
445 | |