1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-28 01:32:57 +01:00

add client decompression support

This commit is contained in:
Nikolay Kim 2019-03-26 20:45:00 -07:00
parent 2629699b62
commit 1cca25c276
7 changed files with 775 additions and 67 deletions

View File

@ -74,7 +74,7 @@ where
Err(e) => return Err(e.into()), Err(e) => return Err(e.into()),
} }
} else { } else {
break; return Ok(Async::Ready(Some(chunk)));
} }
} }
Async::Ready(None) => { Async::Ready(None) => {
@ -150,7 +150,7 @@ impl ContentDecoder {
#[allow(unreachable_patterns)] #[allow(unreachable_patterns)]
fn feed_data(&mut self, data: Bytes) -> io::Result<Option<Bytes>> { fn feed_data(&mut self, data: Bytes) -> io::Result<Option<Bytes>> {
match self { match self {
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[cfg(feature = "brotli")]
ContentDecoder::Br(ref mut decoder) => match decoder.write_all(&data) { ContentDecoder::Br(ref mut decoder) => match decoder.write_all(&data) {
Ok(_) => { Ok(_) => {
decoder.flush()?; decoder.flush()?;

View File

@ -30,7 +30,7 @@ ssl = ["openssl", "actix-http/ssl"]
cookies = ["cookie", "actix-http/cookies"] cookies = ["cookie", "actix-http/cookies"]
# brotli encoding, requires c compiler # brotli encoding, requires c compiler
brotli = ["actix-http/brotli2"] brotli = ["actix-http/brotli"]
# miniz-sys backend for flate2 crate # miniz-sys backend for flate2 crate
flate2-zlib = ["actix-http/flate2-zlib"] flate2-zlib = ["actix-http/flate2-zlib"]
@ -53,8 +53,12 @@ cookie = { version="0.11", features=["percent-encode"], optional = true }
openssl = { version="0.10", optional = true } openssl = { version="0.10", optional = true }
[dev-dependencies] [dev-dependencies]
env_logger = "0.6"
mime = "0.3"
actix-rt = "0.2.1" actix-rt = "0.2.1"
actix-web = { path = "..", features=["ssl"] }
actix-http = { path = "../actix-http/", features=["ssl"] } actix-http = { path = "../actix-http/", features=["ssl"] }
actix-http-test = { path = "../test-server/", features=["ssl"] } actix-http-test = { path = "../test-server/", features=["ssl"] }
brotli2 = { version="^0.3.2" }
flate2 = { version="^1.0.2" }
env_logger = "0.6"
mime = "0.3"
rand = "0.6"

View File

@ -2,6 +2,7 @@ use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
pub use actix_http::client::{ConnectError, InvalidUrl, SendRequestError}; pub use actix_http::client::{ConnectError, InvalidUrl, SendRequestError};
pub use actix_http::error::PayloadError;
pub use actix_http::http; pub use actix_http::http;
use actix_http::client::Connector; use actix_http::client::Connector;

View File

@ -13,15 +13,24 @@ use serde_json;
use actix_http::body::{Body, BodyStream}; use actix_http::body::{Body, BodyStream};
use actix_http::client::{InvalidUrl, SendRequestError}; use actix_http::client::{InvalidUrl, SendRequestError};
use actix_http::http::header::{self, Header, IntoHeaderValue}; use actix_http::encoding::Decoder;
use actix_http::http::header::{self, ContentEncoding, Header, IntoHeaderValue};
use actix_http::http::{ use actix_http::http::{
uri, ConnectionType, Error as HttpError, HeaderName, HeaderValue, HttpTryFrom, uri, ConnectionType, Error as HttpError, HeaderName, HeaderValue, HttpTryFrom,
Method, Uri, Version, Method, Uri, Version,
}; };
use actix_http::{Error, Head, RequestHead}; use actix_http::{Error, Head, Payload, RequestHead};
use crate::response::ClientResponse; use crate::response::ClientResponse;
use crate::Connect; use crate::{Connect, PayloadError};
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))]
const HTTPS_ENCODING: &str = "br, gzip, deflate";
#[cfg(all(
any(feature = "flate2-zlib", feature = "flate2-rust"),
not(feature = "brotli")
))]
const HTTPS_ENCODING: &str = "gzip, deflate";
/// An HTTP Client request builder /// An HTTP Client request builder
/// ///
@ -52,6 +61,7 @@ pub struct ClientRequest {
#[cfg(feature = "cookies")] #[cfg(feature = "cookies")]
cookies: Option<CookieJar>, cookies: Option<CookieJar>,
default_headers: bool, default_headers: bool,
response_decompress: bool,
connector: Rc<RefCell<dyn Connect>>, connector: Rc<RefCell<dyn Connect>>,
} }
@ -81,6 +91,7 @@ impl ClientRequest {
#[cfg(feature = "cookies")] #[cfg(feature = "cookies")]
cookies: None, cookies: None,
default_headers: true, default_headers: true,
response_decompress: true,
} }
} }
@ -275,6 +286,12 @@ impl ClientRequest {
self self
} }
/// Disable automatic decompress of response's body
pub fn no_decompress(mut self) -> Self {
self.response_decompress = false;
self
}
/// This method calls provided closure with builder reference if /// This method calls provided closure with builder reference if
/// value is `true`. /// value is `true`.
pub fn if_true<F>(mut self, value: bool, f: F) -> Self pub fn if_true<F>(mut self, value: bool, f: F) -> Self
@ -303,7 +320,10 @@ impl ClientRequest {
pub fn send_body<B>( pub fn send_body<B>(
mut self, mut self,
body: B, body: B,
) -> impl Future<Item = ClientResponse, Error = SendRequestError> ) -> impl Future<
Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
Error = SendRequestError,
>
where where
B: Into<Body>, B: Into<Body>,
{ {
@ -311,42 +331,44 @@ impl ClientRequest {
return Either::A(err(e.into())); return Either::A(err(e.into()));
} }
let mut slf = if self.default_headers { // validate uri
// enable br only for https let uri = &self.head.uri;
let https = self if uri.host().is_none() {
.head return Either::A(err(InvalidUrl::MissingHost.into()));
.uri } else if uri.scheme_part().is_none() {
.scheme_part() return Either::A(err(InvalidUrl::MissingScheme.into()));
.map(|s| s == &uri::Scheme::HTTPS) } else if let Some(scheme) = uri.scheme_part() {
.unwrap_or(true); match scheme.as_str() {
"http" | "ws" | "https" | "wss" => (),
let mut slf = if https { _ => return Either::A(err(InvalidUrl::UnknownScheme.into())),
self.set_header_if_none(header::ACCEPT_ENCODING, "br, gzip, deflate") }
} else { } else {
self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate") return Either::A(err(InvalidUrl::UnknownScheme.into()));
}; }
// set default headers
let slf = if self.default_headers {
// set request host header // set request host header
if let Some(host) = slf.head.uri.host() { if let Some(host) = self.head.uri.host() {
if !slf.head.headers.contains_key(header::HOST) { if !self.head.headers.contains_key(header::HOST) {
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
let _ = match slf.head.uri.port_u16() { let _ = match self.head.uri.port_u16() {
None | Some(80) | Some(443) => write!(wrt, "{}", host), None | Some(80) | Some(443) => write!(wrt, "{}", host),
Some(port) => write!(wrt, "{}:{}", host, port), Some(port) => write!(wrt, "{}:{}", host, port),
}; };
match wrt.get_mut().take().freeze().try_into() { match wrt.get_mut().take().freeze().try_into() {
Ok(value) => { Ok(value) => {
slf.head.headers.insert(header::HOST, value); self.head.headers.insert(header::HOST, value);
} }
Err(e) => slf.err = Some(e.into()), Err(e) => return Either::A(err(HttpError::from(e).into())),
} }
} }
} }
// user agent // user agent
slf.set_header_if_none( self.set_header_if_none(
header::USER_AGENT, header::USER_AGENT,
concat!("actix-http/", env!("CARGO_PKG_VERSION")), concat!("actix-http/", env!("CARGO_PKG_VERSION")),
) )
@ -354,6 +376,32 @@ impl ClientRequest {
self self
}; };
// enable br only for https
let https = slf
.head
.uri
.scheme_part()
.map(|s| s == &uri::Scheme::HTTPS)
.unwrap_or(true);
#[cfg(any(
feature = "brotli",
feature = "flate2-zlib",
feature = "flate2-rust"
))]
let mut slf = {
if https {
slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING)
} else {
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
{
slf.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate")
}
#[cfg(not(any(feature = "flate2-zlib", feature = "flate2-rust")))]
slf
}
};
#[allow(unused_mut)] #[allow(unused_mut)]
let mut head = slf.head; let mut head = slf.head;
@ -378,30 +426,32 @@ impl ClientRequest {
} }
} }
let uri = head.uri.clone(); let response_decompress = slf.response_decompress;
// validate uri let fut = slf
if uri.host().is_none() { .connector
Either::A(err(InvalidUrl::MissingHost.into())) .borrow_mut()
} else if uri.scheme_part().is_none() { .send_request(head, body.into())
Either::A(err(InvalidUrl::MissingScheme.into())) .map(move |res| {
} else if let Some(scheme) = uri.scheme_part() { res.map_body(|head, payload| {
match scheme.as_str() { if response_decompress {
"http" | "ws" | "https" | "wss" => { Payload::Stream(Decoder::from_headers(&head.headers, payload))
Either::B(slf.connector.borrow_mut().send_request(head, body.into())) } else {
} Payload::Stream(Decoder::new(payload, ContentEncoding::Identity))
_ => Either::A(err(InvalidUrl::UnknownScheme.into())), }
} })
} else { });
Either::A(err(InvalidUrl::UnknownScheme.into())) Either::B(fut)
}
} }
/// Set a JSON body and generate `ClientRequest` /// Set a JSON body and generate `ClientRequest`
pub fn send_json<T: Serialize>( pub fn send_json<T: Serialize>(
self, self,
value: T, value: T,
) -> impl Future<Item = ClientResponse, Error = SendRequestError> { ) -> impl Future<
Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
Error = SendRequestError,
> {
let body = match serde_json::to_string(&value) { let body = match serde_json::to_string(&value) {
Ok(body) => body, Ok(body) => body,
Err(e) => return Either::A(err(Error::from(e).into())), Err(e) => return Either::A(err(Error::from(e).into())),
@ -422,7 +472,10 @@ impl ClientRequest {
pub fn send_form<T: Serialize>( pub fn send_form<T: Serialize>(
self, self,
value: T, value: T,
) -> impl Future<Item = ClientResponse, Error = SendRequestError> { ) -> impl Future<
Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
Error = SendRequestError,
> {
let body = match serde_urlencoded::to_string(&value) { let body = match serde_urlencoded::to_string(&value) {
Ok(body) => body, Ok(body) => body,
Err(e) => return Either::A(err(Error::from(e).into())), Err(e) => return Either::A(err(Error::from(e).into())),
@ -441,7 +494,10 @@ impl ClientRequest {
pub fn send_stream<S, E>( pub fn send_stream<S, E>(
self, self,
stream: S, stream: S,
) -> impl Future<Item = ClientResponse, Error = SendRequestError> ) -> impl Future<
Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
Error = SendRequestError,
>
where where
S: Stream<Item = Bytes, Error = E> + 'static, S: Stream<Item = Bytes, Error = E> + 'static,
E: Into<Error> + 'static, E: Into<Error> + 'static,
@ -450,7 +506,12 @@ impl ClientRequest {
} }
/// Set an empty body and generate `ClientRequest`. /// Set an empty body and generate `ClientRequest`.
pub fn send(self) -> impl Future<Item = ClientResponse, Error = SendRequestError> { pub fn send(
self,
) -> impl Future<
Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
Error = SendRequestError,
> {
self.send_body(Body::Empty) self.send_body(Body::Empty)
} }
} }

View File

@ -1,21 +1,22 @@
use std::cell::{Ref, RefMut}; use std::cell::{Ref, RefMut};
use std::fmt; use std::fmt;
use bytes::Bytes; use bytes::{Bytes, BytesMut};
use futures::{Poll, Stream}; use futures::{Future, Poll, Stream};
use actix_http::error::PayloadError; use actix_http::error::PayloadError;
use actix_http::http::header::CONTENT_LENGTH;
use actix_http::http::{HeaderMap, StatusCode, Version}; use actix_http::http::{HeaderMap, StatusCode, Version};
use actix_http::{Extensions, Head, HttpMessage, Payload, PayloadStream, ResponseHead}; use actix_http::{Extensions, Head, HttpMessage, Payload, PayloadStream, ResponseHead};
/// Client Response /// Client Response
pub struct ClientResponse { pub struct ClientResponse<S = PayloadStream> {
pub(crate) head: ResponseHead, pub(crate) head: ResponseHead,
pub(crate) payload: Payload, pub(crate) payload: Payload<S>,
} }
impl HttpMessage for ClientResponse { impl<S> HttpMessage for ClientResponse<S> {
type Stream = PayloadStream; type Stream = S;
fn headers(&self) -> &HeaderMap { fn headers(&self) -> &HeaderMap {
&self.head.headers &self.head.headers
@ -29,14 +30,14 @@ impl HttpMessage for ClientResponse {
self.head.extensions_mut() self.head.extensions_mut()
} }
fn take_payload(&mut self) -> Payload { fn take_payload(&mut self) -> Payload<S> {
std::mem::replace(&mut self.payload, Payload::None) std::mem::replace(&mut self.payload, Payload::None)
} }
} }
impl ClientResponse { impl<S> ClientResponse<S> {
/// Create new Request instance /// Create new Request instance
pub(crate) fn new(head: ResponseHead, payload: Payload) -> ClientResponse { pub(crate) fn new(head: ResponseHead, payload: Payload<S>) -> Self {
ClientResponse { head, payload } ClientResponse { head, payload }
} }
@ -79,9 +80,35 @@ impl ClientResponse {
pub fn keep_alive(&self) -> bool { pub fn keep_alive(&self) -> bool {
self.head().keep_alive() self.head().keep_alive()
} }
/// Set a body and return previous body value
pub fn map_body<F, U>(mut self, f: F) -> ClientResponse<U>
where
F: FnOnce(&mut ResponseHead, Payload<S>) -> Payload<U>,
{
let payload = f(&mut self.head, self.payload);
ClientResponse {
payload,
head: self.head,
}
}
} }
impl Stream for ClientResponse { impl<S> ClientResponse<S>
where
S: Stream<Item = Bytes, Error = PayloadError> + 'static,
{
/// Load http response's body.
pub fn body(self) -> MessageBody<S> {
MessageBody::new(self)
}
}
impl<S> Stream for ClientResponse<S>
where
S: Stream<Item = Bytes, Error = PayloadError>,
{
type Item = Bytes; type Item = Bytes;
type Error = PayloadError; type Error = PayloadError;
@ -90,7 +117,7 @@ impl Stream for ClientResponse {
} }
} }
impl fmt::Debug for ClientResponse { impl<S> fmt::Debug for ClientResponse<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status(),)?; writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status(),)?;
writeln!(f, " headers:")?; writeln!(f, " headers:")?;
@ -100,3 +127,100 @@ impl fmt::Debug for ClientResponse {
Ok(()) Ok(())
} }
} }
/// Future that resolves to a complete http message body.
pub struct MessageBody<S> {
limit: usize,
length: Option<usize>,
stream: Option<ClientResponse<S>>,
err: Option<PayloadError>,
fut: Option<Box<Future<Item = Bytes, Error = PayloadError>>>,
}
impl<S> MessageBody<S>
where
S: Stream<Item = Bytes, Error = PayloadError> + 'static,
{
/// Create `MessageBody` for request.
pub fn new(res: ClientResponse<S>) -> MessageBody<S> {
let mut len = None;
if let Some(l) = res.headers().get(CONTENT_LENGTH) {
if let Ok(s) = l.to_str() {
if let Ok(l) = s.parse::<usize>() {
len = Some(l)
} else {
return Self::err(PayloadError::UnknownLength);
}
} else {
return Self::err(PayloadError::UnknownLength);
}
}
MessageBody {
limit: 262_144,
length: len,
stream: Some(res),
fut: None,
err: None,
}
}
/// Change max size of payload. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
fn err(e: PayloadError) -> Self {
MessageBody {
stream: None,
limit: 262_144,
fut: None,
err: Some(e),
length: None,
}
}
}
impl<S> Future for MessageBody<S>
where
S: Stream<Item = Bytes, Error = PayloadError> + 'static,
{
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut fut) = self.fut {
return fut.poll();
}
if let Some(err) = self.err.take() {
return Err(err);
}
if let Some(len) = self.length.take() {
if len > self.limit {
return Err(PayloadError::Overflow);
}
}
// future
let limit = self.limit;
self.fut = Some(Box::new(
self.stream
.take()
.expect("Can not be used second time")
.from_err()
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(PayloadError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
})
.map(|body| body.freeze()),
));
self.poll()
}
}

508
awc/tests/test_client.rs Normal file
View File

@ -0,0 +1,508 @@
use std::io::{Read, Write};
use std::{net, thread};
use brotli2::write::BrotliEncoder;
use bytes::Bytes;
use flate2::write::{GzEncoder, ZlibEncoder};
use flate2::Compression;
use futures::stream::once;
use futures::Future;
use rand::Rng;
use actix_http::HttpService;
use actix_http_test::TestServer;
use actix_web::{middleware, web, App, HttpRequest, HttpResponse};
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World";
#[test]
fn test_simple() {
let mut srv =
TestServer::new(|| {
HttpService::new(App::new().service(
web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))),
))
});
let request = srv.get().header("x-test", "111").send();
let response = srv.block_on(request).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.block_on(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
let response = srv.block_on(srv.post().send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.block_on(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
// #[test]
// fn test_connection_close() {
// let mut srv =
// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
// let request = srv.get().header("Connection", "close").finish().unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());
// }
// #[test]
// fn test_with_query_parameter() {
// let mut srv = test::TestServer::new(|app| {
// app.handler(|req: &HttpRequest| match req.query().get("qp") {
// Some(_) => HttpResponse::Ok().finish(),
// None => HttpResponse::BadRequest().finish(),
// })
// });
// let request = srv.get().uri(srv.url("/?qp=5").as_str()).finish().unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());
// }
// #[test]
// fn test_no_decompress() {
// let mut srv =
// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
// let request = srv.get().disable_decompress().finish().unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());
// // read response
// let bytes = srv.execute(response.body()).unwrap();
// let mut e = GzDecoder::new(&bytes[..]);
// let mut dec = Vec::new();
// e.read_to_end(&mut dec).unwrap();
// assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
// // POST
// let request = srv.post().disable_decompress().finish().unwrap();
// let response = srv.execute(request.send()).unwrap();
// let bytes = srv.execute(response.body()).unwrap();
// let mut e = GzDecoder::new(&bytes[..]);
// let mut dec = Vec::new();
// e.read_to_end(&mut dec).unwrap();
// assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
// }
#[test]
fn test_client_gzip_encoding() {
let mut srv = TestServer::new(|| {
HttpService::new(App::new().service(web::resource("/").route(web::to(|| {
let mut e = GzEncoder::new(Vec::new(), Compression::default());
e.write_all(STR.as_ref()).unwrap();
let data = e.finish().unwrap();
HttpResponse::Ok()
.header("content-encoding", "gzip")
.body(data)
}))))
});
// client request
let response = srv.block_on(srv.post().send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.block_on(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
#[test]
fn test_client_gzip_encoding_large() {
let mut srv = TestServer::new(|| {
HttpService::new(App::new().service(web::resource("/").route(web::to(|| {
let mut e = GzEncoder::new(Vec::new(), Compression::default());
e.write_all(STR.repeat(10).as_ref()).unwrap();
let data = e.finish().unwrap();
HttpResponse::Ok()
.header("content-encoding", "gzip")
.body(data)
}))))
});
// client request
let response = srv.block_on(srv.post().send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.block_on(response.body()).unwrap();
assert_eq!(bytes, Bytes::from(STR.repeat(10)));
}
#[test]
fn test_client_gzip_encoding_large_random() {
let data = rand::thread_rng()
.sample_iter(&rand::distributions::Alphanumeric)
.take(100_000)
.collect::<String>();
let mut srv = TestServer::new(|| {
HttpService::new(App::new().service(web::resource("/").route(web::to(
|data: Bytes| {
let mut e = GzEncoder::new(Vec::new(), Compression::default());
e.write_all(&data).unwrap();
let data = e.finish().unwrap();
HttpResponse::Ok()
.header("content-encoding", "gzip")
.body(data)
},
))))
});
// client request
let response = srv.block_on(srv.post().send_body(data.clone())).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.block_on(response.body()).unwrap();
assert_eq!(bytes, Bytes::from(data));
}
#[test]
fn test_client_brotli_encoding() {
let mut srv = TestServer::new(|| {
HttpService::new(App::new().service(web::resource("/").route(web::to(
|data: Bytes| {
let mut e = BrotliEncoder::new(Vec::new(), 5);
e.write_all(&data).unwrap();
let data = e.finish().unwrap();
HttpResponse::Ok()
.header("content-encoding", "br")
.body(data)
},
))))
});
// client request
let response = srv.block_on(srv.post().send_body(STR)).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.block_on(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
// #[test]
// fn test_client_brotli_encoding_large_random() {
// let data = rand::thread_rng()
// .sample_iter(&rand::distributions::Alphanumeric)
// .take(70_000)
// .collect::<String>();
// let mut srv = test::TestServer::new(|app| {
// app.handler(|req: &HttpRequest| {
// req.body()
// .and_then(move |bytes: Bytes| {
// Ok(HttpResponse::Ok()
// .content_encoding(http::ContentEncoding::Gzip)
// .body(bytes))
// })
// .responder()
// })
// });
// // client request
// let request = srv
// .client(http::Method::POST, "/")
// .content_encoding(http::ContentEncoding::Br)
// .body(data.clone())
// .unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());
// // read response
// let bytes = srv.execute(response.body()).unwrap();
// assert_eq!(bytes.len(), data.len());
// assert_eq!(bytes, Bytes::from(data));
// }
// #[cfg(feature = "brotli")]
// #[test]
// fn test_client_deflate_encoding() {
// let mut srv = test::TestServer::new(|app| {
// app.handler(|req: &HttpRequest| {
// req.body()
// .and_then(|bytes: Bytes| {
// Ok(HttpResponse::Ok()
// .content_encoding(http::ContentEncoding::Br)
// .body(bytes))
// })
// .responder()
// })
// });
// // client request
// let request = srv
// .post()
// .content_encoding(http::ContentEncoding::Deflate)
// .body(STR)
// .unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());
// // read response
// let bytes = srv.execute(response.body()).unwrap();
// assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
// }
// #[test]
// fn test_client_deflate_encoding_large_random() {
// let data = rand::thread_rng()
// .sample_iter(&rand::distributions::Alphanumeric)
// .take(70_000)
// .collect::<String>();
// let mut srv = test::TestServer::new(|app| {
// app.handler(|req: &HttpRequest| {
// req.body()
// .and_then(|bytes: Bytes| {
// Ok(HttpResponse::Ok()
// .content_encoding(http::ContentEncoding::Br)
// .body(bytes))
// })
// .responder()
// })
// });
// // client request
// let request = srv
// .post()
// .content_encoding(http::ContentEncoding::Deflate)
// .body(data.clone())
// .unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());
// // read response
// let bytes = srv.execute(response.body()).unwrap();
// assert_eq!(bytes, Bytes::from(data));
// }
// #[test]
// fn test_client_streaming_explicit() {
// let mut srv = test::TestServer::new(|app| {
// app.handler(|req: &HttpRequest| {
// req.body()
// .map_err(Error::from)
// .and_then(|body| {
// Ok(HttpResponse::Ok()
// .chunked()
// .content_encoding(http::ContentEncoding::Identity)
// .body(body))
// })
// .responder()
// })
// });
// let body = once(Ok(Bytes::from_static(STR.as_ref())));
// let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());
// // read response
// let bytes = srv.execute(response.body()).unwrap();
// assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
// }
// #[test]
// fn test_body_streaming_implicit() {
// let mut srv = test::TestServer::new(|app| {
// app.handler(|_| {
// let body = once(Ok(Bytes::from_static(STR.as_ref())));
// HttpResponse::Ok()
// .content_encoding(http::ContentEncoding::Gzip)
// .body(Body::Streaming(Box::new(body)))
// })
// });
// let request = srv.get().finish().unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());
// // read response
// let bytes = srv.execute(response.body()).unwrap();
// assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
// }
// #[test]
// fn test_client_cookie_handling() {
// use actix_web::http::Cookie;
// fn err() -> Error {
// use std::io::{Error as IoError, ErrorKind};
// // stub some generic error
// Error::from(IoError::from(ErrorKind::NotFound))
// }
// let cookie1 = Cookie::build("cookie1", "value1").finish();
// let cookie2 = Cookie::build("cookie2", "value2")
// .domain("www.example.org")
// .path("/")
// .secure(true)
// .http_only(true)
// .finish();
// // Q: are all these clones really necessary? A: Yes, possibly
// let cookie1b = cookie1.clone();
// let cookie2b = cookie2.clone();
// let mut srv = test::TestServer::new(move |app| {
// let cookie1 = cookie1b.clone();
// let cookie2 = cookie2b.clone();
// app.handler(move |req: &HttpRequest| {
// // Check cookies were sent correctly
// req.cookie("cookie1")
// .ok_or_else(err)
// .and_then(|c1| {
// if c1.value() == "value1" {
// Ok(())
// } else {
// Err(err())
// }
// })
// .and_then(|()| req.cookie("cookie2").ok_or_else(err))
// .and_then(|c2| {
// if c2.value() == "value2" {
// Ok(())
// } else {
// Err(err())
// }
// })
// // Send some cookies back
// .map(|_| {
// HttpResponse::Ok()
// .cookie(cookie1.clone())
// .cookie(cookie2.clone())
// .finish()
// })
// })
// });
// let request = srv
// .get()
// .cookie(cookie1.clone())
// .cookie(cookie2.clone())
// .finish()
// .unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());
// let c1 = response.cookie("cookie1").expect("Missing cookie1");
// assert_eq!(c1, cookie1);
// let c2 = response.cookie("cookie2").expect("Missing cookie2");
// assert_eq!(c2, cookie2);
// }
// #[test]
// fn test_default_headers() {
// let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
// let request = srv.get().finish().unwrap();
// let repr = format!("{:?}", request);
// assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\""));
// assert!(repr.contains(concat!(
// "\"user-agent\": \"actix-web/",
// env!("CARGO_PKG_VERSION"),
// "\""
// )));
// let request_override = srv
// .get()
// .header("User-Agent", "test")
// .header("Accept-Encoding", "over_test")
// .finish()
// .unwrap();
// let repr_override = format!("{:?}", request_override);
// assert!(repr_override.contains("\"user-agent\": \"test\""));
// assert!(repr_override.contains("\"accept-encoding\": \"over_test\""));
// assert!(!repr_override.contains("\"accept-encoding\": \"gzip, deflate\""));
// assert!(!repr_override.contains(concat!(
// "\"user-agent\": \"Actix-web/",
// env!("CARGO_PKG_VERSION"),
// "\""
// )));
// }
// #[test]
// fn client_read_until_eof() {
// let addr = test::TestServer::unused_addr();
// thread::spawn(move || {
// let lst = net::TcpListener::bind(addr).unwrap();
// for stream in lst.incoming() {
// let mut stream = stream.unwrap();
// let mut b = [0; 1000];
// let _ = stream.read(&mut b).unwrap();
// let _ = stream
// .write_all(b"HTTP/1.1 200 OK\r\nconnection: close\r\n\r\nwelcome!");
// }
// });
// let mut sys = actix::System::new("test");
// // client request
// let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
// .finish()
// .unwrap();
// let response = sys.block_on(req.send()).unwrap();
// assert!(response.status().is_success());
// // read response
// let bytes = sys.block_on(response.body()).unwrap();
// assert_eq!(bytes, Bytes::from_static(b"welcome!"));
// }
// #[test]
// fn client_basic_auth() {
// let mut srv =
// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
// /// set authorization header to Basic <base64 encoded username:password>
// let request = srv
// .get()
// .basic_auth("username", Some("password"))
// .finish()
// .unwrap();
// let repr = format!("{:?}", request);
// assert!(repr.contains("Basic dXNlcm5hbWU6cGFzc3dvcmQ="));
// }
// #[test]
// fn client_bearer_auth() {
// let mut srv =
// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
// /// set authorization header to Bearer <token>
// let request = srv
// .get()
// .bearer_auth("someS3cr3tAutht0k3n")
// .finish()
// .unwrap();
// let repr = format!("{:?}", request);
// assert!(repr.contains("Bearer someS3cr3tAutht0k3n"));
// }

View File

@ -65,7 +65,7 @@ fn test_body_gzip() {
) )
}); });
let mut response = srv.block_on(srv.get().send()).unwrap(); let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
// read response // read response
@ -95,7 +95,7 @@ fn test_body_gzip_large() {
) )
}); });
let mut response = srv.block_on(srv.get().send()).unwrap(); let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
// read response // read response
@ -128,7 +128,7 @@ fn test_body_gzip_large_random() {
) )
}); });
let mut response = srv.block_on(srv.get().send()).unwrap(); let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
// read response // read response
@ -156,7 +156,7 @@ fn test_body_chunked_implicit() {
) )
}); });
let mut response = srv.block_on(srv.get().send()).unwrap(); let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
assert_eq!( assert_eq!(
response.headers().get(TRANSFER_ENCODING).unwrap(), response.headers().get(TRANSFER_ENCODING).unwrap(),
@ -188,7 +188,12 @@ fn test_body_br_streaming() {
}); });
let mut response = srv let mut response = srv
.block_on(srv.get().header(ACCEPT_ENCODING, "br").send()) .block_on(
srv.get()
.header(ACCEPT_ENCODING, "br")
.no_decompress()
.send(),
)
.unwrap(); .unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
@ -258,7 +263,7 @@ fn test_body_deflate() {
}); });
// client request // client request
let mut response = srv.block_on(srv.get().send()).unwrap(); let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
// read response // read response
@ -285,7 +290,12 @@ fn test_body_brotli() {
// client request // client request
let mut response = srv let mut response = srv
.block_on(srv.get().header(ACCEPT_ENCODING, "br").send()) .block_on(
srv.get()
.header(ACCEPT_ENCODING, "br")
.no_decompress()
.send(),
)
.unwrap(); .unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());