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