| use std::fmt; |
| use std::net::SocketAddr; |
| use std::pin::Pin; |
| |
| use bytes::Bytes; |
| use encoding_rs::{Encoding, UTF_8}; |
| use futures_util::stream::StreamExt; |
| use hyper::client::connect::HttpInfo; |
| use hyper::{HeaderMap, StatusCode, Version}; |
| use mime::Mime; |
| #[cfg(feature = "json")] |
| use serde::de::DeserializeOwned; |
| #[cfg(feature = "json")] |
| use serde_json; |
| use tokio::time::Sleep; |
| use url::Url; |
| |
| use super::body::Body; |
| use super::decoder::{Accepts, Decoder}; |
| #[cfg(feature = "cookies")] |
| use crate::cookie; |
| use crate::response::ResponseUrl; |
| |
| /// A Response to a submitted `Request`. |
| pub struct Response { |
| pub(super) res: hyper::Response<Decoder>, |
| // Boxed to save space (11 words to 1 word), and it's not accessed |
| // frequently internally. |
| url: Box<Url>, |
| } |
| |
| impl Response { |
| pub(super) fn new( |
| res: hyper::Response<hyper::Body>, |
| url: Url, |
| accepts: Accepts, |
| timeout: Option<Pin<Box<Sleep>>>, |
| ) -> Response { |
| let (mut parts, body) = res.into_parts(); |
| let decoder = Decoder::detect(&mut parts.headers, Body::response(body, timeout), accepts); |
| let res = hyper::Response::from_parts(parts, decoder); |
| |
| Response { |
| res, |
| url: Box::new(url), |
| } |
| } |
| |
| /// Get the `StatusCode` of this `Response`. |
| #[inline] |
| pub fn status(&self) -> StatusCode { |
| self.res.status() |
| } |
| |
| /// Get the HTTP `Version` of this `Response`. |
| #[inline] |
| pub fn version(&self) -> Version { |
| self.res.version() |
| } |
| |
| /// Get the `Headers` of this `Response`. |
| #[inline] |
| pub fn headers(&self) -> &HeaderMap { |
| self.res.headers() |
| } |
| |
| /// Get a mutable reference to the `Headers` of this `Response`. |
| #[inline] |
| pub fn headers_mut(&mut self) -> &mut HeaderMap { |
| self.res.headers_mut() |
| } |
| |
| /// Get the content-length of this response, if known. |
| /// |
| /// Reasons it may not be known: |
| /// |
| /// - The server didn't send a `content-length` header. |
| /// - The response is compressed and automatically decoded (thus changing |
| /// the actual decoded length). |
| pub fn content_length(&self) -> Option<u64> { |
| use hyper::body::HttpBody; |
| |
| HttpBody::size_hint(self.res.body()).exact() |
| } |
| |
| /// Retrieve the cookies contained in the response. |
| /// |
| /// Note that invalid 'Set-Cookie' headers will be ignored. |
| /// |
| /// # Optional |
| /// |
| /// This requires the optional `cookies` feature to be enabled. |
| #[cfg(feature = "cookies")] |
| #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))] |
| pub fn cookies<'a>(&'a self) -> impl Iterator<Item = cookie::Cookie<'a>> + 'a { |
| cookie::extract_response_cookies(self.res.headers()).filter_map(Result::ok) |
| } |
| |
| /// Get the final `Url` of this `Response`. |
| #[inline] |
| pub fn url(&self) -> &Url { |
| &self.url |
| } |
| |
| /// Get the remote address used to get this `Response`. |
| pub fn remote_addr(&self) -> Option<SocketAddr> { |
| self.res |
| .extensions() |
| .get::<HttpInfo>() |
| .map(|info| info.remote_addr()) |
| } |
| |
| /// Returns a reference to the associated extensions. |
| pub fn extensions(&self) -> &http::Extensions { |
| self.res.extensions() |
| } |
| |
| /// Returns a mutable reference to the associated extensions. |
| pub fn extensions_mut(&mut self) -> &mut http::Extensions { |
| self.res.extensions_mut() |
| } |
| |
| // body methods |
| |
| /// Get the full response text. |
| /// |
| /// This method decodes the response body with BOM sniffing |
| /// and with malformed sequences replaced with the REPLACEMENT CHARACTER. |
| /// Encoding is determined from the `charset` parameter of `Content-Type` header, |
| /// and defaults to `utf-8` if not presented. |
| /// |
| /// Note that the BOM is stripped from the returned String. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// # async fn run() -> Result<(), Box<dyn std::error::Error>> { |
| /// let content = reqwest::get("http://httpbin.org/range/26") |
| /// .await? |
| /// .text() |
| /// .await?; |
| /// |
| /// println!("text: {content:?}"); |
| /// # Ok(()) |
| /// # } |
| /// ``` |
| pub async fn text(self) -> crate::Result<String> { |
| self.text_with_charset("utf-8").await |
| } |
| |
| /// Get the full response text given a specific encoding. |
| /// |
| /// This method decodes the response body with BOM sniffing |
| /// and with malformed sequences replaced with the REPLACEMENT CHARACTER. |
| /// You can provide a default encoding for decoding the raw message, while the |
| /// `charset` parameter of `Content-Type` header is still prioritized. For more information |
| /// about the possible encoding name, please go to [`encoding_rs`] docs. |
| /// |
| /// Note that the BOM is stripped from the returned String. |
| /// |
| /// [`encoding_rs`]: https://docs.rs/encoding_rs/0.8/encoding_rs/#relationship-with-windows-code-pages |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// # async fn run() -> Result<(), Box<dyn std::error::Error>> { |
| /// let content = reqwest::get("http://httpbin.org/range/26") |
| /// .await? |
| /// .text_with_charset("utf-8") |
| /// .await?; |
| /// |
| /// println!("text: {content:?}"); |
| /// # Ok(()) |
| /// # } |
| /// ``` |
| pub async fn text_with_charset(self, default_encoding: &str) -> crate::Result<String> { |
| let content_type = self |
| .headers() |
| .get(crate::header::CONTENT_TYPE) |
| .and_then(|value| value.to_str().ok()) |
| .and_then(|value| value.parse::<Mime>().ok()); |
| let encoding_name = content_type |
| .as_ref() |
| .and_then(|mime| mime.get_param("charset").map(|charset| charset.as_str())) |
| .unwrap_or(default_encoding); |
| let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8); |
| |
| let full = self.bytes().await?; |
| |
| let (text, _, _) = encoding.decode(&full); |
| Ok(text.into_owned()) |
| } |
| |
| /// Try to deserialize the response body as JSON. |
| /// |
| /// # Optional |
| /// |
| /// This requires the optional `json` feature enabled. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # extern crate reqwest; |
| /// # extern crate serde; |
| /// # |
| /// # use reqwest::Error; |
| /// # use serde::Deserialize; |
| /// # |
| /// // This `derive` requires the `serde` dependency. |
| /// #[derive(Deserialize)] |
| /// struct Ip { |
| /// origin: String, |
| /// } |
| /// |
| /// # async fn run() -> Result<(), Error> { |
| /// let ip = reqwest::get("http://httpbin.org/ip") |
| /// .await? |
| /// .json::<Ip>() |
| /// .await?; |
| /// |
| /// println!("ip: {}", ip.origin); |
| /// # Ok(()) |
| /// # } |
| /// # |
| /// # fn main() { } |
| /// ``` |
| /// |
| /// # Errors |
| /// |
| /// This method fails whenever the response body is not in JSON format |
| /// or it cannot be properly deserialized to target type `T`. For more |
| /// details please see [`serde_json::from_reader`]. |
| /// |
| /// [`serde_json::from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html |
| #[cfg(feature = "json")] |
| #[cfg_attr(docsrs, doc(cfg(feature = "json")))] |
| pub async fn json<T: DeserializeOwned>(self) -> crate::Result<T> { |
| let full = self.bytes().await?; |
| |
| serde_json::from_slice(&full).map_err(crate::error::decode) |
| } |
| |
| /// Get the full response body as `Bytes`. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// # async fn run() -> Result<(), Box<dyn std::error::Error>> { |
| /// let bytes = reqwest::get("http://httpbin.org/ip") |
| /// .await? |
| /// .bytes() |
| /// .await?; |
| /// |
| /// println!("bytes: {bytes:?}"); |
| /// # Ok(()) |
| /// # } |
| /// ``` |
| pub async fn bytes(self) -> crate::Result<Bytes> { |
| hyper::body::to_bytes(self.res.into_body()).await |
| } |
| |
| /// Stream a chunk of the response body. |
| /// |
| /// When the response body has been exhausted, this will return `None`. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// # async fn run() -> Result<(), Box<dyn std::error::Error>> { |
| /// let mut res = reqwest::get("https://hyper.rs").await?; |
| /// |
| /// while let Some(chunk) = res.chunk().await? { |
| /// println!("Chunk: {chunk:?}"); |
| /// } |
| /// # Ok(()) |
| /// # } |
| /// ``` |
| pub async fn chunk(&mut self) -> crate::Result<Option<Bytes>> { |
| if let Some(item) = self.res.body_mut().next().await { |
| Ok(Some(item?)) |
| } else { |
| Ok(None) |
| } |
| } |
| |
| /// Convert the response into a `Stream` of `Bytes` from the body. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use futures_util::StreamExt; |
| /// |
| /// # async fn run() -> Result<(), Box<dyn std::error::Error>> { |
| /// let mut stream = reqwest::get("http://httpbin.org/ip") |
| /// .await? |
| /// .bytes_stream(); |
| /// |
| /// while let Some(item) = stream.next().await { |
| /// println!("Chunk: {:?}", item?); |
| /// } |
| /// # Ok(()) |
| /// # } |
| /// ``` |
| /// |
| /// # Optional |
| /// |
| /// This requires the optional `stream` feature to be enabled. |
| #[cfg(feature = "stream")] |
| #[cfg_attr(docsrs, doc(cfg(feature = "stream")))] |
| pub fn bytes_stream(self) -> impl futures_core::Stream<Item = crate::Result<Bytes>> { |
| self.res.into_body() |
| } |
| |
| // util methods |
| |
| /// Turn a response into an error if the server returned an error. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// # use reqwest::Response; |
| /// fn on_response(res: Response) { |
| /// match res.error_for_status() { |
| /// Ok(_res) => (), |
| /// Err(err) => { |
| /// // asserting a 400 as an example |
| /// // it could be any status between 400...599 |
| /// assert_eq!( |
| /// err.status(), |
| /// Some(reqwest::StatusCode::BAD_REQUEST) |
| /// ); |
| /// } |
| /// } |
| /// } |
| /// # fn main() {} |
| /// ``` |
| pub fn error_for_status(self) -> crate::Result<Self> { |
| let status = self.status(); |
| if status.is_client_error() || status.is_server_error() { |
| Err(crate::error::status_code(*self.url, status)) |
| } else { |
| Ok(self) |
| } |
| } |
| |
| /// Turn a reference to a response into an error if the server returned an error. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// # use reqwest::Response; |
| /// fn on_response(res: &Response) { |
| /// match res.error_for_status_ref() { |
| /// Ok(_res) => (), |
| /// Err(err) => { |
| /// // asserting a 400 as an example |
| /// // it could be any status between 400...599 |
| /// assert_eq!( |
| /// err.status(), |
| /// Some(reqwest::StatusCode::BAD_REQUEST) |
| /// ); |
| /// } |
| /// } |
| /// } |
| /// # fn main() {} |
| /// ``` |
| pub fn error_for_status_ref(&self) -> crate::Result<&Self> { |
| let status = self.status(); |
| if status.is_client_error() || status.is_server_error() { |
| Err(crate::error::status_code(*self.url.clone(), status)) |
| } else { |
| Ok(self) |
| } |
| } |
| |
| // private |
| |
| // The Response's body is an implementation detail. |
| // You no longer need to get a reference to it, there are async methods |
| // on the `Response` itself. |
| // |
| // This method is just used by the blocking API. |
| #[cfg(feature = "blocking")] |
| pub(crate) fn body_mut(&mut self) -> &mut Decoder { |
| self.res.body_mut() |
| } |
| } |
| |
| impl fmt::Debug for Response { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| f.debug_struct("Response") |
| .field("url", self.url()) |
| .field("status", &self.status()) |
| .field("headers", self.headers()) |
| .finish() |
| } |
| } |
| |
| impl<T: Into<Body>> From<http::Response<T>> for Response { |
| fn from(r: http::Response<T>) -> Response { |
| let (mut parts, body) = r.into_parts(); |
| let body = body.into(); |
| let decoder = Decoder::detect(&mut parts.headers, body, Accepts::none()); |
| let url = parts |
| .extensions |
| .remove::<ResponseUrl>() |
| .unwrap_or_else(|| ResponseUrl(Url::parse("http://no.url.provided.local").unwrap())); |
| let url = url.0; |
| let res = hyper::Response::from_parts(parts, decoder); |
| Response { |
| res, |
| url: Box::new(url), |
| } |
| } |
| } |
| |
| /// A `Response` can be piped as the `Body` of another request. |
| impl From<Response> for Body { |
| fn from(r: Response) -> Body { |
| Body::stream(r.res.into_body()) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::Response; |
| use crate::ResponseBuilderExt; |
| use http::response::Builder; |
| use url::Url; |
| |
| #[test] |
| fn test_from_http_response() { |
| let url = Url::parse("http://example.com").unwrap(); |
| let response = Builder::new() |
| .status(200) |
| .url(url.clone()) |
| .body("foo") |
| .unwrap(); |
| let response = Response::from(response); |
| |
| assert_eq!(response.status(), 200); |
| assert_eq!(*response.url(), url); |
| } |
| } |