diff options
Diffstat (limited to 'vendor/reqwest/src/async_impl/response.rs')
-rw-r--r-- | vendor/reqwest/src/async_impl/response.rs | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/vendor/reqwest/src/async_impl/response.rs b/vendor/reqwest/src/async_impl/response.rs new file mode 100644 index 000000000..340e54174 --- /dev/null +++ b/vendor/reqwest/src/async_impl/response.rs @@ -0,0 +1,448 @@ +use std::borrow::Cow; +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. + /// + /// # 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. + /// + /// [`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); + if let Cow::Owned(s) = text { + return Ok(s); + } + unsafe { + // decoding returned Cow::Borrowed, meaning these bytes + // are already valid utf8 + Ok(String::from_utf8_unchecked(full.to_vec())) + } + } + + /// 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); + } +} |