From c5f2bf6c3216103b5b83fa6694fecfaa14db0a6a Mon Sep 17 00:00:00 2001 From: messense Date: Sun, 23 Jun 2019 14:39:41 +0800 Subject: [PATCH] Add Response::text() to async Client --- src/async_impl/response.rs | 59 ++++++++++++++++++++++++++++++++++++++ tests/async.rs | 36 +++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/src/async_impl/response.rs b/src/async_impl/response.rs index 9a375f9..fc5ea8c 100644 --- a/src/async_impl/response.rs +++ b/src/async_impl/response.rs @@ -2,13 +2,16 @@ use std::fmt; use std::mem; use std::marker::PhantomData; use std::net::SocketAddr; +use std::borrow::Cow; +use encoding_rs::{Encoding, UTF_8}; use futures::{Async, Future, Poll, Stream}; use futures::stream::Concat2; use http; use hyper::{HeaderMap, StatusCode, Version}; use hyper::client::connect::HttpInfo; use hyper::header::{CONTENT_LENGTH}; +use mime::Mime; use tokio::timer::Delay; use serde::de::DeserializeOwned; use serde_json; @@ -135,6 +138,35 @@ impl Response { self.version } + /// Get the response text + pub fn text(&mut self) -> impl Future { + self.text_with_charset("utf-8") + } + + /// Get the response text given a specific encoding + pub fn text_with_charset(&mut self, default_encoding: &str) -> impl Future { + let body = mem::replace(&mut self.body, Decoder::empty()); + let content_type = self.headers.get(::header::CONTENT_TYPE) + .and_then(|value| { + value.to_str().ok() + }) + .and_then(|value| { + value.parse::().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); + Text { + concat: body.concat2(), + encoding + } + } /// Try to deserialize the response body as JSON using `serde`. #[inline] @@ -261,6 +293,33 @@ impl fmt::Debug for Json { } } +#[derive(Debug)] +pub struct Text { + concat: Concat2, + encoding: &'static Encoding, +} + +impl Future for Text { + type Item = String; + type Error = ::Error; + fn poll(&mut self) -> Poll { + let bytes = try_ready!(self.concat.poll()); + // a block because of borrow checker + { + let (text, _, _) = self.encoding.decode(&bytes); + match text { + Cow::Owned(s) => return Ok(Async::Ready(s)), + _ => (), + } + } + unsafe { + // decoding returned Cow::Borrowed, meaning these bytes + // are already valid utf8 + Ok(Async::Ready(String::from_utf8_unchecked(bytes.to_vec()))) + } + } +} + #[derive(Debug, Clone, PartialEq)] struct ResponseUrl(Url); diff --git a/tests/async.rs b/tests/async.rs index ecbd681..9a783bb 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -29,6 +29,42 @@ fn gzip_single_byte_chunks() { gzip_case(10, 1); } +#[test] +fn response_text() { + let _ = env_logger::try_init(); + + let server = server! { + request: b"\ + GET /text HTTP/1.1\r\n\ + user-agent: $USERAGENT\r\n\ + accept: */*\r\n\ + accept-encoding: gzip\r\n\ + host: $HOST\r\n\ + \r\n\ + ", + response: b"\ + HTTP/1.1 200 OK\r\n\ + Content-Length: 5\r\n\ + \r\n\ + Hello\ + " + }; + + let mut rt = Runtime::new().expect("new rt"); + + let client = Client::new(); + + let res_future = client.get(&format!("http://{}/text", server.addr())) + .send() + .and_then(|mut res| res.text()) + .and_then(|text| { + assert_eq!("Hello", text); + Ok(()) + }); + + rt.block_on(res_future).unwrap(); +} + #[test] fn multipart() { let _ = env_logger::try_init();