Rewrite tests with a hyper server instead of raw TCP

This makes the tests much less brittle, by not depending on the exact
order of the HTTP headers, nor always requiring to check for every
single header.
This commit is contained in:
Sean McArthur
2019-09-23 11:33:04 -07:00
parent 3cf8ede960
commit f4100e4148
10 changed files with 881 additions and 1806 deletions

View File

@@ -1,35 +1,15 @@
#[macro_use]
mod support; mod support;
use support::*;
#[test] #[test]
fn test_response_text() { fn test_response_text() {
let server = server! { let server = server::http(move |_req| async { http::Response::new("Hello".into()) });
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\
Server: test\r\n\
Content-Length: 5\r\n\
\r\n\
Hello\
"
};
let url = format!("http://{}/text", server.addr()); let url = format!("http://{}/text", server.addr());
let res = reqwest::blocking::get(&url).unwrap(); let res = reqwest::blocking::get(&url).unwrap();
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); assert_eq!(res.content_length(), Some(5));
assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"5"
);
let body = res.text().unwrap(); let body = res.text().unwrap();
assert_eq!(b"Hello", body.as_bytes()); assert_eq!(b"Hello", body.as_bytes());
@@ -37,34 +17,20 @@ fn test_response_text() {
#[test] #[test]
fn test_response_non_utf_8_text() { fn test_response_non_utf_8_text() {
let server = server! { let server = server::http(move |_req| {
request: b"\ async {
GET /text HTTP/1.1\r\n\ http::Response::builder()
user-agent: $USERAGENT\r\n\ .header("content-type", "text/plain; charset=gbk")
accept: */*\r\n\ .body(b"\xc4\xe3\xba\xc3"[..].into())
accept-encoding: gzip\r\n\ .unwrap()
host: $HOST\r\n\ }
\r\n\ });
",
response: b"\
HTTP/1.1 200 OK\r\n\
Server: test\r\n\
Content-Length: 4\r\n\
Content-Type: text/plain; charset=gbk\r\n\
\r\n\
\xc4\xe3\xba\xc3\
"
};
let url = format!("http://{}/text", server.addr()); let url = format!("http://{}/text", server.addr());
let res = reqwest::blocking::get(&url).unwrap(); let res = reqwest::blocking::get(&url).unwrap();
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); assert_eq!(res.content_length(), Some(4));
assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"4"
);
let body = res.text().unwrap(); let body = res.text().unwrap();
assert_eq!("你好", &body); assert_eq!("你好", &body);
@@ -73,33 +39,13 @@ fn test_response_non_utf_8_text() {
#[test] #[test]
fn test_response_json() { fn test_response_json() {
let server = server! { let server = server::http(move |_req| async { http::Response::new("\"Hello\"".into()) });
request: b"\
GET /json 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\
Server: test\r\n\
Content-Length: 7\r\n\
\r\n\
\"Hello\"\
"
};
let url = format!("http://{}/json", server.addr()); let url = format!("http://{}/json", server.addr());
let res = reqwest::blocking::get(&url).unwrap(); let res = reqwest::blocking::get(&url).unwrap();
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); assert_eq!(res.content_length(), Some(7));
assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"7"
);
let body = res.json::<String>().unwrap(); let body = res.json::<String>().unwrap();
assert_eq!("Hello", body); assert_eq!("Hello", body);
@@ -107,66 +53,27 @@ fn test_response_json() {
#[test] #[test]
fn test_response_copy_to() { fn test_response_copy_to() {
let server = server! { let server = server::http(move |_req| async { http::Response::new("Hello".into()) });
request: b"\
GET /1 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\
Server: test\r\n\
Content-Length: 5\r\n\
\r\n\
Hello\
"
};
let url = format!("http://{}/1", server.addr()); let url = format!("http://{}/1", server.addr());
let res = reqwest::blocking::get(&url).unwrap(); let mut res = reqwest::blocking::get(&url).unwrap();
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test");
assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"5"
);
assert_eq!("Hello".to_owned(), res.text().unwrap()); let mut dst = Vec::new();
res.copy_to(&mut dst).unwrap();
assert_eq!(dst, b"Hello");
} }
#[test] #[test]
fn test_get() { fn test_get() {
let server = server! { let server = server::http(move |_req| async { http::Response::default() });
request: b"\
GET /1 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\
Server: test\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/1", server.addr()); let url = format!("http://{}/1", server.addr());
let res = reqwest::blocking::get(&url).unwrap(); let res = reqwest::blocking::get(&url).unwrap();
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test");
assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"0"
);
assert_eq!(res.remote_addr(), Some(server.addr())); assert_eq!(res.remote_addr(), Some(server.addr()));
assert_eq!(res.text().unwrap().len(), 0) assert_eq!(res.text().unwrap().len(), 0)
@@ -174,24 +81,17 @@ fn test_get() {
#[test] #[test]
fn test_post() { fn test_post() {
let server = server! { let server = server::http(move |mut req| {
request: b"\ async move {
POST /2 HTTP/1.1\r\n\ assert_eq!(req.method(), "POST");
content-length: 5\r\n\ assert_eq!(req.headers()["content-length"], "5");
user-agent: $USERAGENT\r\n\
accept: */*\r\n\ let data = req.body_mut().next().await.unwrap().unwrap();
accept-encoding: gzip\r\n\ assert_eq!(&*data, b"Hello");
host: $HOST\r\n\
\r\n\ http::Response::default()
Hello\ }
", });
response: b"\
HTTP/1.1 200 OK\r\n\
Server: post\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/2", server.addr()); let url = format!("http://{}/2", server.addr());
let res = reqwest::blocking::Client::new() let res = reqwest::blocking::Client::new()
@@ -202,36 +102,25 @@ fn test_post() {
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"post");
assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"0"
);
assert_eq!(res.text().unwrap().len(), 0)
} }
#[test] #[test]
fn test_post_form() { fn test_post_form() {
let server = server! { let server = server::http(move |mut req| {
request: b"\ async move {
POST /form HTTP/1.1\r\n\ assert_eq!(req.method(), "POST");
content-type: application/x-www-form-urlencoded\r\n\ assert_eq!(req.headers()["content-length"], "24");
content-length: 24\r\n\ assert_eq!(
user-agent: $USERAGENT\r\n\ req.headers()["content-type"],
accept: */*\r\n\ "application/x-www-form-urlencoded"
accept-encoding: gzip\r\n\ );
host: $HOST\r\n\
\r\n\ let data = req.body_mut().next().await.unwrap().unwrap();
hello=world&sean=monstar\ assert_eq!(&*data, b"hello=world&sean=monstar");
",
response: b"\ http::Response::default()
HTTP/1.1 200 OK\r\n\ }
Server: post-form\r\n\ });
Content-Length: 0\r\n\
\r\n\
"
};
let form = &[("hello", "world"), ("sean", "monstar")]; let form = &[("hello", "world"), ("sean", "monstar")];
@@ -250,22 +139,14 @@ fn test_post_form() {
/// returns a error. /// returns a error.
#[test] #[test]
fn test_error_for_status_4xx() { fn test_error_for_status_4xx() {
let server = server! { let server = server::http(move |_req| {
request: b"\ async {
GET /1 HTTP/1.1\r\n\ http::Response::builder()
user-agent: $USERAGENT\r\n\ .status(400)
accept: */*\r\n\ .body(Default::default())
accept-encoding: gzip\r\n\ .unwrap()
host: $HOST\r\n\ }
\r\n\ });
",
response: b"\
HTTP/1.1 400 OK\r\n\
Server: test\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/1", server.addr()); let url = format!("http://{}/1", server.addr());
let res = reqwest::blocking::get(&url).unwrap(); let res = reqwest::blocking::get(&url).unwrap();
@@ -279,22 +160,14 @@ fn test_error_for_status_4xx() {
/// returns a error. /// returns a error.
#[test] #[test]
fn test_error_for_status_5xx() { fn test_error_for_status_5xx() {
let server = server! { let server = server::http(move |_req| {
request: b"\ async {
GET /1 HTTP/1.1\r\n\ http::Response::builder()
user-agent: $USERAGENT\r\n\ .status(500)
accept: */*\r\n\ .body(Default::default())
accept-encoding: gzip\r\n\ .unwrap()
host: $HOST\r\n\ }
\r\n\ });
",
response: b"\
HTTP/1.1 500 OK\r\n\
Server: test\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/1", server.addr()); let url = format!("http://{}/1", server.addr());
let res = reqwest::blocking::get(&url).unwrap(); let res = reqwest::blocking::get(&url).unwrap();
@@ -309,144 +182,75 @@ fn test_error_for_status_5xx() {
#[test] #[test]
fn test_default_headers() { fn test_default_headers() {
use reqwest::header; let server = server::http(move |req| {
let mut headers = header::HeaderMap::with_capacity(1); async move {
headers.insert(header::COOKIE, header::HeaderValue::from_static("a=b;c=d")); assert_eq!(req.headers()["reqwest-test"], "orly");
http::Response::default()
}
});
let mut headers = http::HeaderMap::with_capacity(1);
headers.insert("reqwest-test", "orly".parse().unwrap());
let client = reqwest::blocking::Client::builder() let client = reqwest::blocking::Client::builder()
.default_headers(headers) .default_headers(headers)
.build() .build()
.unwrap(); .unwrap();
let server = server! {
request: b"\
GET /1 HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
cookie: a=b;c=d\r\n\
accept-encoding: gzip\r\n\
host: $HOST\r\n\
\r\n\
",
response: b"\
HTTP/1.1 200 OK\r\n\
Server: test\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/1", server.addr()); let url = format!("http://{}/1", server.addr());
let res = client.get(&url).send().unwrap(); let res = client.get(&url).send().unwrap();
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test");
assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"0"
);
let server = server! {
request: b"\
GET /2 HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
cookie: a=b;c=d\r\n\
accept-encoding: gzip\r\n\
host: $HOST\r\n\
\r\n\
",
response: b"\
HTTP/1.1 200 OK\r\n\
Server: test\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/2", server.addr());
let res = client.get(&url).send().unwrap();
assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test");
assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"0"
);
} }
#[test] #[test]
fn test_override_default_headers() { fn test_override_default_headers() {
use reqwest::header; let server = server::http(move |req| {
let mut headers = header::HeaderMap::with_capacity(1); async move {
// not 'iamatoken'
assert_eq!(req.headers()[&http::header::AUTHORIZATION], "secret");
http::Response::default()
}
});
let mut headers = http::HeaderMap::with_capacity(1);
headers.insert( headers.insert(
header::AUTHORIZATION, http::header::AUTHORIZATION,
header::HeaderValue::from_static("iamatoken"), http::header::HeaderValue::from_static("iamatoken"),
); );
let client = reqwest::blocking::Client::builder() let client = reqwest::blocking::Client::builder()
.default_headers(headers) .default_headers(headers)
.build() .build()
.unwrap(); .unwrap();
let server = server! {
request: b"\
GET /3 HTTP/1.1\r\n\
authorization: secret\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\
Server: test\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/3", server.addr()); let url = format!("http://{}/3", server.addr());
let res = client let res = client
.get(&url) .get(&url)
.header( .header(
header::AUTHORIZATION, http::header::AUTHORIZATION,
header::HeaderValue::from_static("secret"), http::header::HeaderValue::from_static("secret"),
) )
.send() .send()
.unwrap(); .unwrap();
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test");
assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"0"
);
} }
#[test] #[test]
fn test_appended_headers_not_overwritten() { fn test_appended_headers_not_overwritten() {
let client = reqwest::blocking::Client::new(); let server = server::http(move |req| {
async move {
let mut accepts = req.headers().get_all("accept").into_iter();
assert_eq!(accepts.next().unwrap(), "application/json");
assert_eq!(accepts.next().unwrap(), "application/json+hal");
assert_eq!(accepts.next(), None);
let server = server! { http::Response::default()
request: b"\ }
GET /4 HTTP/1.1\r\n\ });
accept: application/json\r\n\
accept: application/json+hal\r\n\ let client = reqwest::blocking::Client::new();
user-agent: $USERAGENT\r\n\
accept-encoding: gzip\r\n\
host: $HOST\r\n\
\r\n\
",
response: b"\
HTTP/1.1 200 OK\r\n\
Server: test\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/4", server.addr()); let url = format!("http://{}/4", server.addr());
let res = client let res = client
@@ -458,11 +262,6 @@ fn test_appended_headers_not_overwritten() {
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test");
assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"0"
);
// make sure this also works with default headers // make sure this also works with default headers
use reqwest::header; use reqwest::header;
@@ -476,24 +275,6 @@ fn test_appended_headers_not_overwritten() {
.build() .build()
.unwrap(); .unwrap();
let server = server! {
request: b"\
GET /4 HTTP/1.1\r\n\
accept: application/json\r\n\
accept: application/json+hal\r\n\
user-agent: $USERAGENT\r\n\
accept-encoding: gzip\r\n\
host: $HOST\r\n\
\r\n\
",
response: b"\
HTTP/1.1 200 OK\r\n\
Server: test\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/4", server.addr()); let url = format!("http://{}/4", server.addr());
let res = client let res = client
.get(&url) .get(&url)
@@ -504,9 +285,4 @@ fn test_appended_headers_not_overwritten() {
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test");
assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"0"
);
} }

View File

@@ -1,44 +1,35 @@
#[macro_use]
mod support; mod support;
use support::*;
use std::io::Write; use reqwest::Client;
use std::time::Duration;
use reqwest::multipart::{Form, Part};
use reqwest::{Body, Client};
use bytes::Bytes;
#[tokio::test] #[tokio::test]
async fn gzip_response() { async fn auto_headers() {
gzip_case(10_000, 4096).await; let server = server::http(move |req| {
} async move {
assert_eq!(req.method(), "GET");
#[tokio::test] assert_eq!(req.headers()["accept"], "*/*");
async fn gzip_single_byte_chunks() { assert_eq!(req.headers()["user-agent"], DEFAULT_USER_AGENT);
gzip_case(10, 1).await; assert_eq!(req.headers()["accept-encoding"], "gzip");
http::Response::default()
}
});
let url = format!("http://{}/1", server.addr());
let res = reqwest::get(&url).await.unwrap();
assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.remote_addr(), Some(server.addr()));
} }
#[tokio::test] #[tokio::test]
async fn response_text() { async fn response_text() {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let server = server! { let server = server::http(move |_req| async { http::Response::new("Hello".into()) });
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 client = Client::new(); let client = Client::new();
@@ -55,22 +46,7 @@ async fn response_text() {
async fn response_json() { async fn response_json() {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let server = server! { let server = server::http(move |_req| async { http::Response::new("\"Hello\"".into()) });
request: b"\
GET /json 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: 7\r\n\
\r\n\
\"Hello\"\
"
};
let client = Client::new(); let client = Client::new();
@@ -83,287 +59,29 @@ async fn response_json() {
assert_eq!("Hello", text); assert_eq!("Hello", text);
} }
#[tokio::test]
async fn multipart() {
use futures_util::{future, stream};
let _ = env_logger::try_init();
let stream = reqwest::Body::wrap_stream(stream::once(future::ready(Ok::<_, reqwest::Error>(
"part1 part2".to_owned(),
))));
let part = Part::stream(stream);
let form = Form::new().text("foo", "bar").part("part_stream", part);
let expected_body = format!(
"\
24\r\n\
--{0}\r\n\r\n\
2E\r\n\
Content-Disposition: form-data; name=\"foo\"\r\n\r\n\r\n\
3\r\n\
bar\r\n\
2\r\n\
\r\n\r\n\
24\r\n\
--{0}\r\n\r\n\
36\r\n\
Content-Disposition: form-data; name=\"part_stream\"\r\n\r\n\r\n\
B\r\n\
part1 part2\r\n\
2\r\n\
\r\n\r\n\
26\r\n\
--{0}--\r\n\r\n\
0\r\n\r\n\
",
form.boundary()
);
let server = server! {
request: format!("\
POST /multipart/1 HTTP/1.1\r\n\
content-type: multipart/form-data; boundary={}\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
accept-encoding: gzip\r\n\
host: $HOST\r\n\
transfer-encoding: chunked\r\n\
\r\n\
{}\
", form.boundary(), expected_body),
response: b"\
HTTP/1.1 200 OK\r\n\
Server: multipart\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/multipart/1", server.addr());
let client = Client::new();
let res = client
.post(&url)
.multipart(form)
.send()
.await
.expect("Failed to post multipart");
assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK);
}
#[tokio::test]
async fn request_timeout() {
let _ = env_logger::try_init();
let server = server! {
request: b"\
GET /slow 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\
",
read_timeout: Duration::from_secs(2)
};
let client = Client::builder()
.timeout(Duration::from_millis(500))
.build()
.unwrap();
let url = format!("http://{}/slow", server.addr());
let res = client.get(&url).send().await;
let err = res.unwrap_err();
assert!(err.is_timeout());
assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str()));
}
#[tokio::test]
async fn response_timeout() {
let _ = env_logger::try_init();
let server = server! {
request: b"\
GET /slow 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\
",
write_timeout: Duration::from_secs(2)
};
let client = Client::builder()
.timeout(Duration::from_millis(500))
.build()
.unwrap();
let url = format!("http://{}/slow", server.addr());
let res = client.get(&url).send().await.expect("Failed to get");
let body = res.text().await;
let err = body.unwrap_err();
assert!(err.is_timeout());
}
async fn gzip_case(response_size: usize, chunk_size: usize) {
let content: String = (0..response_size)
.into_iter()
.map(|i| format!("test {}", i))
.collect();
let mut encoder = libflate::gzip::Encoder::new(Vec::new()).unwrap();
match encoder.write(content.as_bytes()) {
Ok(n) => assert!(n > 0, "Failed to write to encoder."),
_ => panic!("Failed to gzip encode string."),
};
let gzipped_content = encoder.finish().into_result().unwrap();
let mut response = format!(
"\
HTTP/1.1 200 OK\r\n\
Server: test-accept\r\n\
Content-Encoding: gzip\r\n\
Content-Length: {}\r\n\
\r\n",
&gzipped_content.len()
)
.into_bytes();
response.extend(&gzipped_content);
let server = server! {
request: b"\
GET /gzip HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
accept-encoding: gzip\r\n\
host: $HOST\r\n\
\r\n\
",
chunk_size: chunk_size,
write_timeout: Duration::from_millis(10),
response: response
};
let client = Client::new();
let res = client
.get(&format!("http://{}/gzip", server.addr()))
.send()
.await
.expect("response");
let body = res.text().await.expect("text");
assert_eq!(body, content);
}
#[tokio::test]
async fn body_stream() {
let _ = env_logger::try_init();
let source = futures_util::stream::iter::<Vec<Result<Bytes, std::io::Error>>>(vec![
Ok(Bytes::from_static(b"123")),
Ok(Bytes::from_static(b"4567")),
]);
let expected_body = "3\r\n123\r\n4\r\n4567\r\n0\r\n\r\n";
let server = server! {
request: format!("\
POST /post HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
accept-encoding: gzip\r\n\
host: $HOST\r\n\
transfer-encoding: chunked\r\n\
\r\n\
{}\
", expected_body),
response: b"\
HTTP/1.1 200 OK\r\n\
Server: post\r\n\
Content-Length: 7\r\n\
\r\n\
"
};
let url = format!("http://{}/post", server.addr());
let client = Client::new();
let res = client
.post(&url)
.body(Body::wrap_stream(source))
.send()
.await
.expect("Failed to post");
assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK);
}
#[tokio::test] #[tokio::test]
async fn body_pipe_response() { async fn body_pipe_response() {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let server = server! { let server = server::http(move |mut req| {
request: b"\ async move {
GET /get HTTP/1.1\r\n\ if req.uri() == "/get" {
user-agent: $USERAGENT\r\n\ http::Response::new("pipe me".into())
accept: */*\r\n\ } else {
accept-encoding: gzip\r\n\ assert_eq!(req.uri(), "/pipe");
host: $HOST\r\n\ assert_eq!(req.headers()["transfer-encoding"], "chunked");
\r\n\
",
response: b"\
HTTP/1.1 200 OK\r\n\
Server: pipe\r\n\
Content-Length: 7\r\n\
\r\n\
pipe me\
";
request: b"\ let mut full: Vec<u8> = Vec::new();
POST /pipe HTTP/1.1\r\n\ while let Some(item) = req.body_mut().next().await {
user-agent: $USERAGENT\r\n\ full.extend(&*item.unwrap());
accept: */*\r\n\ }
accept-encoding: gzip\r\n\
host: $HOST\r\n\ assert_eq!(full, b"pipe me");
transfer-encoding: chunked\r\n\
\r\n\ http::Response::default()
7\r\n\ }
pipe me\r\n\ }
0\r\n\r\n\ });
",
response: b"\
HTTP/1.1 200 OK\r\n\
Server: pipe\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let client = Client::new(); let client = Client::new();

View File

@@ -1,38 +1,32 @@
#[macro_use]
mod support; mod support;
use support::*;
#[test] #[tokio::test]
fn cookie_response_accessor() { async fn cookie_response_accessor() {
let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); let server = server::http(move |_req| {
let client = reqwest::r#async::Client::new(); async move {
http::Response::builder()
.header("Set-Cookie", "key=val")
.header(
"Set-Cookie",
"expires=1; Expires=Wed, 21 Oct 2015 07:28:00 GMT",
)
.header("Set-Cookie", "path=1; Path=/the-path")
.header("Set-Cookie", "maxage=1; Max-Age=100")
.header("Set-Cookie", "domain=1; Domain=mydomain")
.header("Set-Cookie", "secure=1; Secure")
.header("Set-Cookie", "httponly=1; HttpOnly")
.header("Set-Cookie", "samesitelax=1; SameSite=Lax")
.header("Set-Cookie", "samesitestrict=1; SameSite=Strict")
.body(Default::default())
.unwrap()
}
});
let server = server! { let client = reqwest::Client::new();
request: b"\
GET / 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\
Set-Cookie: key=val\r\n\
Set-Cookie: expires=1; Expires=Wed, 21 Oct 2015 07:28:00 GMT\r\n\
Set-Cookie: path=1; Path=/the-path\r\n\
Set-Cookie: maxage=1; Max-Age=100\r\n\
Set-Cookie: domain=1; Domain=mydomain\r\n\
Set-Cookie: secure=1; Secure\r\n\
Set-Cookie: httponly=1; HttpOnly\r\n\
Set-Cookie: samesitelax=1; SameSite=Lax\r\n\
Set-Cookie: samesitestrict=1; SameSite=Strict\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/", server.addr()); let url = format!("http://{}/", server.addr());
let res = rt.block_on(client.get(&url).send()).unwrap(); let res = client.get(&url).send().await.unwrap();
let cookies = res.cookies().collect::<Vec<_>>(); let cookies = res.cookies().collect::<Vec<_>>();
@@ -79,273 +73,143 @@ fn cookie_response_accessor() {
assert!(cookies[8].same_site_strict()); assert!(cookies[8].same_site_strict());
} }
#[test] #[tokio::test]
fn cookie_store_simple() { async fn cookie_store_simple() {
let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); let server = server::http(move |req| {
let client = reqwest::r#async::Client::builder() async move {
if req.uri() == "/2" {
assert_eq!(req.headers()["cookie"], "key=val");
}
http::Response::builder()
.header("Set-Cookie", "key=val; HttpOnly")
.body(Default::default())
.unwrap()
}
});
let client = reqwest::Client::builder()
.cookie_store(true) .cookie_store(true)
.build() .build()
.unwrap(); .unwrap();
let server = server! {
request: b"\
GET / 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\
Set-Cookie: key=val; HttpOnly\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/", server.addr()); let url = format!("http://{}/", server.addr());
rt.block_on(client.get(&url).send()).unwrap(); client.get(&url).send().await.unwrap();
let server = server! { let url = format!("http://{}/2", server.addr());
request: b"\ client.get(&url).send().await.unwrap();
GET / HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
cookie: key=val\r\n\
accept-encoding: gzip\r\n\
host: $HOST\r\n\
\r\n\
",
response: b"\
HTTP/1.1 200 OK\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/", server.addr());
rt.block_on(client.get(&url).send()).unwrap();
} }
#[test] #[tokio::test]
fn cookie_store_overwrite_existing() { async fn cookie_store_overwrite_existing() {
let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); let server = server::http(move |req| {
let client = reqwest::r#async::Client::builder() async move {
if req.uri() == "/" {
http::Response::builder()
.header("Set-Cookie", "key=val")
.body(Default::default())
.unwrap()
} else if req.uri() == "/2" {
assert_eq!(req.headers()["cookie"], "key=val");
http::Response::builder()
.header("Set-Cookie", "key=val2")
.body(Default::default())
.unwrap()
} else {
assert_eq!(req.uri(), "/3");
assert_eq!(req.headers()["cookie"], "key=val2");
http::Response::default()
}
}
});
let client = reqwest::Client::builder()
.cookie_store(true) .cookie_store(true)
.build() .build()
.unwrap(); .unwrap();
let server = server! {
request: b"\
GET / 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\
Set-Cookie: key=val\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/", server.addr()); let url = format!("http://{}/", server.addr());
rt.block_on(client.get(&url).send()).unwrap(); client.get(&url).send().await.unwrap();
let server = server! { let url = format!("http://{}/2", server.addr());
request: b"\ client.get(&url).send().await.unwrap();
GET / HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
cookie: key=val\r\n\
accept-encoding: gzip\r\n\
host: $HOST\r\n\
\r\n\
",
response: b"\
HTTP/1.1 200 OK\r\n\
Set-Cookie: key=val2\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/", server.addr());
rt.block_on(client.get(&url).send()).unwrap();
let server = server! { let url = format!("http://{}/3", server.addr());
request: b"\ client.get(&url).send().await.unwrap();
GET / HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
cookie: key=val2\r\n\
accept-encoding: gzip\r\n\
host: $HOST\r\n\
\r\n\
",
response: b"\
HTTP/1.1 200 OK\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/", server.addr());
rt.block_on(client.get(&url).send()).unwrap();
} }
#[test] #[tokio::test]
fn cookie_store_max_age() { async fn cookie_store_max_age() {
let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); let server = server::http(move |req| {
let client = reqwest::r#async::Client::builder() async move {
assert_eq!(req.headers().get("cookie"), None);
http::Response::builder()
.header("Set-Cookie", "key=val; Max-Age=0")
.body(Default::default())
.unwrap()
}
});
let client = reqwest::Client::builder()
.cookie_store(true) .cookie_store(true)
.build() .build()
.unwrap(); .unwrap();
let server = server! {
request: b"\
GET / 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\
Set-Cookie: key=val; Max-Age=0\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/", server.addr()); let url = format!("http://{}/", server.addr());
rt.block_on(client.get(&url).send()).unwrap(); client.get(&url).send().await.unwrap();
client.get(&url).send().await.unwrap();
let server = server! {
request: b"\
GET / 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: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/", server.addr());
rt.block_on(client.get(&url).send()).unwrap();
} }
#[test] #[tokio::test]
fn cookie_store_expires() { async fn cookie_store_expires() {
let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); let server = server::http(move |req| {
async move {
assert_eq!(req.headers().get("cookie"), None);
http::Response::builder()
.header(
"Set-Cookie",
"key=val; Expires=Wed, 21 Oct 2015 07:28:00 GMT",
)
.body(Default::default())
.unwrap()
}
});
let client = reqwest::r#async::Client::builder() let client = reqwest::r#async::Client::builder()
.cookie_store(true) .cookie_store(true)
.build() .build()
.unwrap(); .unwrap();
let server = server! {
request: b"\
GET / 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\
Set-Cookie: key=val; Expires=Wed, 21 Oct 2015 07:28:00 GMT\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/", server.addr()); let url = format!("http://{}/", server.addr());
rt.block_on(client.get(&url).send()).unwrap(); client.get(&url).send().await.unwrap();
client.get(&url).send().await.unwrap();
let server = server! {
request: b"\
GET / 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: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/", server.addr());
rt.block_on(client.get(&url).send()).unwrap();
} }
#[test] #[tokio::test]
fn cookie_store_path() { async fn cookie_store_path() {
let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); let server = server::http(move |req| {
async move {
if req.uri() == "/" {
assert_eq!(req.headers().get("cookie"), None);
http::Response::builder()
.header("Set-Cookie", "key=val; Path=/subpath")
.body(Default::default())
.unwrap()
} else {
assert_eq!(req.uri(), "/subpath");
assert_eq!(req.headers()["cookie"], "key=val");
http::Response::default()
}
}
});
let client = reqwest::r#async::Client::builder() let client = reqwest::r#async::Client::builder()
.cookie_store(true) .cookie_store(true)
.build() .build()
.unwrap(); .unwrap();
let server = server! {
request: b"\
GET / 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\
Set-Cookie: key=val; Path=/subpath\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/", server.addr()); let url = format!("http://{}/", server.addr());
rt.block_on(client.get(&url).send()).unwrap(); client.get(&url).send().await.unwrap();
client.get(&url).send().await.unwrap();
let server = server! {
request: b"\
GET / 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: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/", server.addr());
rt.block_on(client.get(&url).send()).unwrap();
let server = server! {
request: b"\
GET /subpath HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
cookie: key=val\r\n\
accept-encoding: gzip\r\n\
host: $HOST\r\n\
\r\n\
",
response: b"\
HTTP/1.1 200 OK\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/subpath", server.addr()); let url = format!("http://{}/subpath", server.addr());
rt.block_on(client.get(&url).send()).unwrap(); client.get(&url).send().await.unwrap();
} }

View File

@@ -1,13 +1,101 @@
#[macro_use]
mod support; mod support;
use support::*;
use std::io::Write; use std::io::Write;
use std::time::Duration;
#[tokio::test] #[tokio::test]
async fn test_gzip_response() { async fn gzip_response() {
let content: String = (0..50).into_iter().map(|i| format!("test {}", i)).collect(); gzip_case(10_000, 4096).await;
let chunk_size = content.len() / 3; }
#[tokio::test]
async fn gzip_single_byte_chunks() {
gzip_case(10, 1).await;
}
#[tokio::test]
async fn test_gzip_empty_body() {
let server = server::http(move |req| {
async move {
assert_eq!(req.method(), "HEAD");
http::Response::builder()
.header("content-encoding", "gzip")
.header("content-length", 100)
.body(Default::default())
.unwrap()
}
});
let client = reqwest::Client::new();
let res = client
.head(&format!("http://{}/gzip", server.addr()))
.send()
.await
.unwrap();
let body = res.text().await.unwrap();
assert_eq!(body, "");
}
#[tokio::test]
async fn test_accept_header_is_not_changed_if_set() {
let server = server::http(move |req| {
async move {
assert_eq!(req.headers()["accept"], "application/json");
assert_eq!(req.headers()["accept-encoding"], "gzip");
http::Response::default()
}
});
let client = reqwest::Client::new();
let res = client
.get(&format!("http://{}/accept", server.addr()))
.header(
reqwest::header::ACCEPT,
reqwest::header::HeaderValue::from_static("application/json"),
)
.send()
.await
.unwrap();
assert_eq!(res.status(), reqwest::StatusCode::OK);
}
#[tokio::test]
async fn test_accept_encoding_header_is_not_changed_if_set() {
let server = server::http(move |req| {
async move {
assert_eq!(req.headers()["accept"], "*/*");
assert_eq!(req.headers()["accept-encoding"], "identity");
http::Response::default()
}
});
let client = reqwest::Client::new();
let res = client
.get(&format!("http://{}/accept-encoding", server.addr()))
.header(
reqwest::header::ACCEPT_ENCODING,
reqwest::header::HeaderValue::from_static("identity"),
)
.send()
.await
.unwrap();
assert_eq!(res.status(), reqwest::StatusCode::OK);
}
async fn gzip_case(response_size: usize, chunk_size: usize) {
use futures_util::stream::StreamExt;
let content: String = (0..response_size)
.into_iter()
.map(|i| format!("test {}", i))
.collect();
let mut encoder = libflate::gzip::Encoder::new(Vec::new()).unwrap(); let mut encoder = libflate::gzip::Encoder::new(Vec::new()).unwrap();
match encoder.write(content.as_bytes()) { match encoder.write(content.as_bytes()) {
Ok(n) => assert!(n > 0, "Failed to write to encoder."), Ok(n) => assert!(n > 0, "Failed to write to encoder."),
@@ -28,147 +116,38 @@ async fn test_gzip_response() {
.into_bytes(); .into_bytes();
response.extend(&gzipped_content); response.extend(&gzipped_content);
let server = server! { let server = server::http(move |req| {
request: b"\ assert_eq!(req.headers()["accept-encoding"], "gzip");
GET /gzip HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
accept-encoding: gzip\r\n\
host: $HOST\r\n\
\r\n\
",
chunk_size: chunk_size,
write_timeout: Duration::from_millis(10),
response: response
};
let url = format!("http://{}/gzip", server.addr());
let res = reqwest::get(&url).await.unwrap();
let body = res.text().await.unwrap(); let gzipped = gzipped_content.clone();
async move {
let len = gzipped.len();
let stream = futures_util::stream::unfold((gzipped, 0), move |(gzipped, pos)| {
async move {
let chunk = gzipped.chunks(chunk_size).nth(pos)?.to_vec();
Some((chunk, (gzipped, pos + 1)))
}
});
let body = hyper::Body::wrap_stream(stream.map(Ok::<_, std::convert::Infallible>));
http::Response::builder()
.header("content-encoding", "gzip")
.header("content-length", len)
.body(body)
.unwrap()
}
});
let client = reqwest::Client::new();
let res = client
.get(&format!("http://{}/gzip", server.addr()))
.send()
.await
.expect("response");
let body = res.text().await.expect("text");
assert_eq!(body, content); assert_eq!(body, content);
} }
#[tokio::test]
async fn test_gzip_empty_body() {
let server = server! {
request: b"\
HEAD /gzip 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\
Server: test-accept\r\n\
Content-Encoding: gzip\r\n\
Content-Length: 100\r\n\
\r\n"
};
let client = reqwest::Client::new();
let res = client
.head(&format!("http://{}/gzip", server.addr()))
.send()
.await
.unwrap();
let body = res.text().await.unwrap();
assert_eq!(body, "");
}
#[tokio::test]
async fn test_gzip_invalid_body() {
let server = server! {
request: b"\
GET /gzip 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\
Server: test-accept\r\n\
Content-Encoding: gzip\r\n\
Content-Length: 100\r\n\
\r\n\
0"
};
let url = format!("http://{}/gzip", server.addr());
let res = reqwest::get(&url).await.unwrap();
// this tests that the request.send() didn't error, but that the error
// is in reading the body
res.text().await.unwrap_err();
}
#[tokio::test]
async fn test_accept_header_is_not_changed_if_set() {
let server = server! {
request: b"\
GET /accept HTTP/1.1\r\n\
accept: application/json\r\n\
user-agent: $USERAGENT\r\n\
accept-encoding: gzip\r\n\
host: $HOST\r\n\
\r\n\
",
response: b"\
HTTP/1.1 200 OK\r\n\
Server: test-accept\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let client = reqwest::Client::new();
let res = client
.get(&format!("http://{}/accept", server.addr()))
.header(
reqwest::header::ACCEPT,
reqwest::header::HeaderValue::from_static("application/json"),
)
.send()
.await
.unwrap();
assert_eq!(res.status(), reqwest::StatusCode::OK);
}
#[tokio::test]
async fn test_accept_encoding_header_is_not_changed_if_set() {
let server = server! {
request: b"\
GET /accept-encoding HTTP/1.1\r\n\
accept-encoding: identity\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
host: $HOST\r\n\
\r\n\
",
response: b"\
HTTP/1.1 200 OK\r\n\
Server: test-accept-encoding\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let client = reqwest::Client::new();
let res = client
.get(&format!("http://{}/accept-encoding", server.addr()))
.header(
reqwest::header::ACCEPT_ENCODING,
reqwest::header::HeaderValue::from_static("identity"),
)
.send()
.await
.unwrap();
assert_eq!(res.status(), reqwest::StatusCode::OK);
}

View File

@@ -1,5 +1,5 @@
#[macro_use]
mod support; mod support;
use support::*;
#[tokio::test] #[tokio::test]
async fn text_part() { async fn text_part() {
@@ -17,25 +17,26 @@ async fn text_part() {
form.boundary() form.boundary()
); );
let server = server! { let ct = format!("multipart/form-data; boundary={}", form.boundary());
request: format!("\
POST /multipart/1 HTTP/1.1\r\n\ let server = server::http(move |mut req| {
content-type: multipart/form-data; boundary={}\r\n\ let ct = ct.clone();
content-length: 125\r\n\ let expected_body = expected_body.clone();
user-agent: $USERAGENT\r\n\ async move {
accept: */*\r\n\ assert_eq!(req.method(), "POST");
accept-encoding: gzip\r\n\ assert_eq!(req.headers()["content-type"], ct);
host: $HOST\r\n\ assert_eq!(req.headers()["content-length"], "125");
\r\n\
{}\ let mut full: Vec<u8> = Vec::new();
", form.boundary(), expected_body), while let Some(item) = req.body_mut().next().await {
response: b"\ full.extend(&*item.unwrap());
HTTP/1.1 200 OK\r\n\ }
Server: multipart\r\n\
Content-Length: 0\r\n\ assert_eq!(full, expected_body.as_bytes());
\r\n\
" http::Response::default()
}; }
});
let url = format!("http://{}/multipart/1", server.addr()); let url = format!("http://{}/multipart/1", server.addr());
@@ -50,9 +51,74 @@ async fn text_part() {
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
} }
#[tokio::test]
async fn stream_part() {
use futures_util::{future, stream};
let _ = env_logger::try_init();
let stream = reqwest::Body::wrap_stream(stream::once(future::ready(Ok::<_, reqwest::Error>(
"part1 part2".to_owned(),
))));
let part = reqwest::multipart::Part::stream(stream);
let form = reqwest::multipart::Form::new()
.text("foo", "bar")
.part("part_stream", part);
let expected_body = format!(
"\
--{0}\r\n\
Content-Disposition: form-data; name=\"foo\"\r\n\
\r\n\
bar\r\n\
--{0}\r\n\
Content-Disposition: form-data; name=\"part_stream\"\r\n\
\r\n\
part1 part2\r\n\
--{0}--\r\n\
",
form.boundary()
);
let ct = format!("multipart/form-data; boundary={}", form.boundary());
let server = server::http(move |mut req| {
let ct = ct.clone();
let expected_body = expected_body.clone();
async move {
assert_eq!(req.method(), "POST");
assert_eq!(req.headers()["content-type"], ct);
assert_eq!(req.headers()["transfer-encoding"], "chunked");
let mut full: Vec<u8> = Vec::new();
while let Some(item) = req.body_mut().next().await {
full.extend(&*item.unwrap());
}
assert_eq!(full, expected_body.as_bytes());
http::Response::default()
}
});
let url = format!("http://{}/multipart/1", server.addr());
let client = reqwest::Client::new();
let res = client
.post(&url)
.multipart(form)
.send()
.await
.expect("Failed to post multipart");
assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK);
}
#[cfg(feature = "blocking")] #[cfg(feature = "blocking")]
#[test] #[test]
fn file() { fn blocking_file_part() {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let form = reqwest::blocking::multipart::Form::new() let form = reqwest::blocking::multipart::Form::new()
@@ -73,25 +139,30 @@ fn file() {
fcontents fcontents
); );
let server = server! { let ct = format!("multipart/form-data; boundary={}", form.boundary());
request: format!("\
POST /multipart/2 HTTP/1.1\r\n\ let server = server::http(move |mut req| {
content-type: multipart/form-data; boundary={}\r\n\ let ct = ct.clone();
content-length: {}\r\n\ let expected_body = expected_body.clone();
user-agent: $USERAGENT\r\n\ async move {
accept: */*\r\n\ assert_eq!(req.method(), "POST");
accept-encoding: gzip\r\n\ assert_eq!(req.headers()["content-type"], ct);
host: $HOST\r\n\ // files know their exact size
\r\n\ assert_eq!(
{}\ req.headers()["content-length"],
", form.boundary(), expected_body.len(), expected_body), expected_body.len().to_string()
response: b"\ );
HTTP/1.1 200 OK\r\n\
Server: multipart\r\n\ let mut full: Vec<u8> = Vec::new();
Content-Length: 0\r\n\ while let Some(item) = req.body_mut().next().await {
\r\n\ full.extend(&*item.unwrap());
" }
};
assert_eq!(full, expected_body.as_bytes());
http::Response::default()
}
});
let url = format!("http://{}/multipart/2", server.addr()); let url = format!("http://{}/multipart/2", server.addr());

View File

@@ -1,30 +1,21 @@
#[macro_use]
mod support; mod support;
use support::*;
use std::env; use std::env;
#[tokio::test] #[tokio::test]
async fn http_proxy() { async fn http_proxy() {
let server = server! { let url = "http://hyper.rs/prox";
request: b"\ let server = server::http(move |req| {
GET http://hyper.rs/prox HTTP/1.1\r\n\ assert_eq!(req.method(), "GET");
user-agent: $USERAGENT\r\n\ assert_eq!(req.uri(), url);
accept: */*\r\n\ assert_eq!(req.headers()["host"], "hyper.rs");
accept-encoding: gzip\r\n\
host: hyper.rs\r\n\ async { http::Response::default() }
\r\n\ });
",
response: b"\
HTTP/1.1 200 OK\r\n\
Server: proxied\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let proxy = format!("http://{}", server.addr()); let proxy = format!("http://{}", server.addr());
let url = "http://hyper.rs/prox";
let res = reqwest::Client::builder() let res = reqwest::Client::builder()
.proxy(reqwest::Proxy::http(&proxy).unwrap()) .proxy(reqwest::Proxy::http(&proxy).unwrap())
.build() .build()
@@ -36,35 +27,25 @@ async fn http_proxy() {
assert_eq!(res.url().as_str(), url); assert_eq!(res.url().as_str(), url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"proxied"
);
} }
#[tokio::test] #[tokio::test]
async fn http_proxy_basic_auth() { async fn http_proxy_basic_auth() {
let server = server! { let url = "http://hyper.rs/prox";
request: b"\ let server = server::http(move |req| {
GET http://hyper.rs/prox HTTP/1.1\r\n\ assert_eq!(req.method(), "GET");
user-agent: $USERAGENT\r\n\ assert_eq!(req.uri(), url);
accept: */*\r\n\ assert_eq!(req.headers()["host"], "hyper.rs");
accept-encoding: gzip\r\n\ assert_eq!(
proxy-authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\r\n\ req.headers()["proxy-authorization"],
host: hyper.rs\r\n\ "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
\r\n\ );
",
response: b"\ async { http::Response::default() }
HTTP/1.1 200 OK\r\n\ });
Server: proxied\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let proxy = format!("http://{}", server.addr()); let proxy = format!("http://{}", server.addr());
let url = "http://hyper.rs/prox";
let res = reqwest::Client::builder() let res = reqwest::Client::builder()
.proxy( .proxy(
reqwest::Proxy::http(&proxy) reqwest::Proxy::http(&proxy)
@@ -80,35 +61,25 @@ async fn http_proxy_basic_auth() {
assert_eq!(res.url().as_str(), url); assert_eq!(res.url().as_str(), url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"proxied"
);
} }
#[tokio::test] #[tokio::test]
async fn http_proxy_basic_auth_parsed() { async fn http_proxy_basic_auth_parsed() {
let server = server! { let url = "http://hyper.rs/prox";
request: b"\ let server = server::http(move |req| {
GET http://hyper.rs/prox HTTP/1.1\r\n\ assert_eq!(req.method(), "GET");
user-agent: $USERAGENT\r\n\ assert_eq!(req.uri(), url);
accept: */*\r\n\ assert_eq!(req.headers()["host"], "hyper.rs");
accept-encoding: gzip\r\n\ assert_eq!(
proxy-authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\r\n\ req.headers()["proxy-authorization"],
host: hyper.rs\r\n\ "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
\r\n\ );
",
response: b"\ async { http::Response::default() }
HTTP/1.1 200 OK\r\n\ });
Server: proxied\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let proxy = format!("http://Aladdin:open sesame@{}", server.addr()); let proxy = format!("http://Aladdin:open sesame@{}", server.addr());
let url = "http://hyper.rs/prox";
let res = reqwest::Client::builder() let res = reqwest::Client::builder()
.proxy(reqwest::Proxy::http(&proxy).unwrap()) .proxy(reqwest::Proxy::http(&proxy).unwrap())
.build() .build()
@@ -120,30 +91,16 @@ async fn http_proxy_basic_auth_parsed() {
assert_eq!(res.url().as_str(), url); assert_eq!(res.url().as_str(), url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"proxied"
);
} }
#[tokio::test] #[tokio::test]
async fn test_no_proxy() { async fn test_no_proxy() {
let server = server! { let server = server::http(move |req| {
request: b"\ assert_eq!(req.method(), "GET");
GET /4 HTTP/1.1\r\n\ assert_eq!(req.uri(), "/4");
user-agent: $USERAGENT\r\n\
accept: */*\r\n\ async { http::Response::default() }
accept-encoding: gzip\r\n\ });
host: $HOST\r\n\
\r\n\
",
response: b"\
HTTP/1.1 200 OK\r\n\
Server: test\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let proxy = format!("http://{}", server.addr()); let proxy = format!("http://{}", server.addr());
let url = format!("http://{}/4", server.addr()); let url = format!("http://{}/4", server.addr());
@@ -164,28 +121,20 @@ async fn test_no_proxy() {
#[tokio::test] #[tokio::test]
async fn test_using_system_proxy() { async fn test_using_system_proxy() {
let server = server! { let url = "http://hyper.rs/prox";
request: b"\ let server = server::http(move |req| {
GET http://hyper.rs/prox HTTP/1.1\r\n\ assert_eq!(req.method(), "GET");
user-agent: $USERAGENT\r\n\ assert_eq!(req.uri(), url);
accept: */*\r\n\ assert_eq!(req.headers()["host"], "hyper.rs");
accept-encoding: gzip\r\n\
host: hyper.rs\r\n\ async { http::Response::default() }
\r\n\ });
",
response: b"\
HTTP/1.1 200 OK\r\n\
Server: proxied\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
// save system setting first. // save system setting first.
let system_proxy = env::var("http_proxy"); let system_proxy = env::var("http_proxy");
// set-up http proxy. // set-up http proxy.
env::set_var("http_proxy", format!("http://{}", server.addr())); env::set_var("http_proxy", format!("http://{}", server.addr()));
let url = "http://hyper.rs/prox";
let res = reqwest::Client::builder() let res = reqwest::Client::builder()
.use_sys_proxy() .use_sys_proxy()
.build() .build()
@@ -197,10 +146,6 @@ async fn test_using_system_proxy() {
assert_eq!(res.url().as_str(), url); assert_eq!(res.url().as_str(), url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"proxied"
);
// reset user setting. // reset user setting.
match system_proxy { match system_proxy {
@@ -208,3 +153,30 @@ async fn test_using_system_proxy() {
Ok(proxy) => env::set_var("http_proxy", proxy), Ok(proxy) => env::set_var("http_proxy", proxy),
} }
} }
#[tokio::test]
async fn http_over_http() {
let url = "http://hyper.rs/prox";
let server = server::http(move |req| {
assert_eq!(req.method(), "GET");
assert_eq!(req.uri(), url);
assert_eq!(req.headers()["host"], "hyper.rs");
async { http::Response::default() }
});
let proxy = format!("http://{}", server.addr());
let res = reqwest::Client::builder()
.proxy(reqwest::Proxy::http(&proxy).unwrap())
.build()
.unwrap()
.get(url)
.send()
.await
.unwrap();
assert_eq!(res.url().as_str(), url);
assert_eq!(res.status(), reqwest::StatusCode::OK);
}

View File

@@ -1,47 +1,32 @@
#[macro_use]
mod support; mod support;
use support::*;
#[tokio::test] #[tokio::test]
async fn test_redirect_301_and_302_and_303_changes_post_to_get() { async fn test_redirect_301_and_302_and_303_changes_post_to_get() {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let codes = [301, 302, 303]; let codes = [301u16, 302, 303];
for code in codes.iter() { for &code in codes.iter() {
let redirect = server! { let redirect = server::http(move |req| {
request: format!("\ async move {
POST /{} HTTP/1.1\r\n\ if req.method() == "POST" {
user-agent: $USERAGENT\r\n\ assert_eq!(req.uri(), &*format!("/{}", code));
accept: */*\r\n\ http::Response::builder()
accept-encoding: gzip\r\n\ .status(code)
host: $HOST\r\n\ .header("location", "/dst")
\r\n\ .header("server", "test-redirect")
", code), .body(Default::default())
response: format!("\ .unwrap()
HTTP/1.1 {} reason\r\n\ } else {
Server: test-redirect\r\n\ assert_eq!(req.method(), "GET");
Content-Length: 0\r\n\
Location: /dst\r\n\
Connection: close\r\n\
\r\n\
", code)
;
request: format!("\ http::Response::builder()
GET /dst HTTP/1.1\r\n\ .header("server", "test-dst")
user-agent: $USERAGENT\r\n\ .body(Default::default())
accept: */*\r\n\ .unwrap()
accept-encoding: gzip\r\n\ }
referer: http://$HOST/{}\r\n\ }
host: $HOST\r\n\ });
\r\n\
", code),
response: b"\
HTTP/1.1 200 OK\r\n\
Server: test-dst\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/{}", redirect.addr(), code); let url = format!("http://{}/{}", redirect.addr(), code);
let dst = format!("http://{}/{}", redirect.addr(), "dst"); let dst = format!("http://{}/{}", redirect.addr(), "dst");
@@ -58,43 +43,28 @@ async fn test_redirect_301_and_302_and_303_changes_post_to_get() {
#[tokio::test] #[tokio::test]
async fn test_redirect_307_and_308_tries_to_get_again() { async fn test_redirect_307_and_308_tries_to_get_again() {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let codes = [307, 308]; let codes = [307u16, 308];
for code in codes.iter() { for &code in codes.iter() {
let redirect = server! { let redirect = server::http(move |req| {
request: format!("\ async move {
GET /{} HTTP/1.1\r\n\ assert_eq!(req.method(), "GET");
user-agent: $USERAGENT\r\n\ if req.uri() == &*format!("/{}", code) {
accept: */*\r\n\ http::Response::builder()
accept-encoding: gzip\r\n\ .status(code)
host: $HOST\r\n\ .header("location", "/dst")
\r\n\ .header("server", "test-redirect")
", code), .body(Default::default())
response: format!("\ .unwrap()
HTTP/1.1 {} reason\r\n\ } else {
Server: test-redirect\r\n\ assert_eq!(req.uri(), "/dst");
Content-Length: 0\r\n\
Location: /dst\r\n\
Connection: close\r\n\
\r\n\
", code)
;
request: format!("\ http::Response::builder()
GET /dst HTTP/1.1\r\n\ .header("server", "test-dst")
user-agent: $USERAGENT\r\n\ .body(Default::default())
accept: */*\r\n\ .unwrap()
accept-encoding: gzip\r\n\ }
referer: http://$HOST/{}\r\n\ }
host: $HOST\r\n\ });
\r\n\
", code),
response: b"\
HTTP/1.1 200 OK\r\n\
Server: test-dst\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/{}", redirect.addr(), code); let url = format!("http://{}/{}", redirect.addr(), code);
let dst = format!("http://{}/{}", redirect.addr(), "dst"); let dst = format!("http://{}/{}", redirect.addr(), "dst");
@@ -111,47 +81,32 @@ async fn test_redirect_307_and_308_tries_to_get_again() {
#[tokio::test] #[tokio::test]
async fn test_redirect_307_and_308_tries_to_post_again() { async fn test_redirect_307_and_308_tries_to_post_again() {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let codes = [307, 308]; let codes = [307u16, 308];
for code in codes.iter() { for &code in codes.iter() {
let redirect = server! { let redirect = server::http(move |mut req| {
request: format!("\ async move {
POST /{} HTTP/1.1\r\n\ assert_eq!(req.method(), "POST");
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
accept-encoding: gzip\r\n\
host: $HOST\r\n\
content-length: 5\r\n\
\r\n\
Hello\
", code),
response: format!("\
HTTP/1.1 {} reason\r\n\
Server: test-redirect\r\n\
Content-Length: 0\r\n\
Location: /dst\r\n\
Connection: close\r\n\
\r\n\
", code)
;
request: format!("\ let data = req.body_mut().next().await.unwrap().unwrap();
POST /dst HTTP/1.1\r\n\ assert_eq!(&*data, b"Hello");
user-agent: $USERAGENT\r\n\
accept: */*\r\n\ if req.uri() == &*format!("/{}", code) {
accept-encoding: gzip\r\n\ http::Response::builder()
referer: http://$HOST/{}\r\n\ .status(code)
host: $HOST\r\n\ .header("location", "/dst")
content-length: 5\r\n\ .header("server", "test-redirect")
\r\n\ .body(Default::default())
Hello\ .unwrap()
", code), } else {
response: b"\ assert_eq!(req.uri(), "/dst");
HTTP/1.1 200 OK\r\n\
Server: test-dst\r\n\ http::Response::builder()
Content-Length: 0\r\n\ .header("server", "test-dst")
\r\n\ .body(Default::default())
" .unwrap()
}; }
}
});
let url = format!("http://{}/{}", redirect.addr(), code); let url = format!("http://{}/{}", redirect.addr(), code);
let dst = format!("http://{}/{}", redirect.addr(), "dst"); let dst = format!("http://{}/{}", redirect.addr(), "dst");
@@ -169,30 +124,25 @@ async fn test_redirect_307_and_308_tries_to_post_again() {
#[test] #[test]
fn test_redirect_307_does_not_try_if_reader_cannot_reset() { fn test_redirect_307_does_not_try_if_reader_cannot_reset() {
let client = reqwest::blocking::Client::new(); let client = reqwest::blocking::Client::new();
let codes = [307, 308]; let codes = [307u16, 308];
for &code in codes.iter() { for &code in codes.iter() {
let redirect = server! { let redirect = server::http(move |mut req| {
request: format!("\ async move {
POST /{} HTTP/1.1\r\n\ assert_eq!(req.method(), "POST");
user-agent: $USERAGENT\r\n\ assert_eq!(req.uri(), &*format!("/{}", code));
accept: */*\r\n\ assert_eq!(req.headers()["transfer-encoding"], "chunked");
accept-encoding: gzip\r\n\
host: $HOST\r\n\ let data = req.body_mut().next().await.unwrap().unwrap();
transfer-encoding: chunked\r\n\ assert_eq!(&*data, b"Hello");
\r\n\
5\r\n\ http::Response::builder()
Hello\r\n\ .status(code)
0\r\n\r\n\ .header("location", "/dst")
", code), .header("server", "test-redirect")
response: format!("\ .body(Default::default())
HTTP/1.1 {} reason\r\n\ .unwrap()
Server: test-redirect\r\n\ }
Content-Length: 0\r\n\ });
Location: /dst\r\n\
Connection: close\r\n\
\r\n\
", code)
};
let url = format!("http://{}/{}", redirect.addr(), code); let url = format!("http://{}/{}", redirect.addr(), code);
let res = client let res = client
@@ -201,50 +151,46 @@ fn test_redirect_307_does_not_try_if_reader_cannot_reset() {
.send() .send()
.unwrap(); .unwrap();
assert_eq!(res.url().as_str(), url); assert_eq!(res.url().as_str(), url);
assert_eq!(res.status(), reqwest::StatusCode::from_u16(code).unwrap()); assert_eq!(res.status(), code);
} }
} }
#[tokio::test] #[tokio::test]
async fn test_redirect_removes_sensitive_headers() { async fn test_redirect_removes_sensitive_headers() {
let end_server = server! { use tokio::sync::watch;
request: b"\
GET /otherhost HTTP/1.1\r\n\
accept-encoding: gzip\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
host: $HOST\r\n\
\r\n\
",
response: b"\
HTTP/1.1 200 OK\r\n\
Server: test\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let mid_server = server! { let (tx, rx) = watch::channel(None);
request: b"\
GET /sensitive HTTP/1.1\r\n\ let end_server = server::http(move |req| {
cookie: foo=bar\r\n\ let mut rx = rx.clone();
user-agent: $USERAGENT\r\n\ async move {
accept: */*\r\n\ assert_eq!(req.headers().get("cookie"), None);
accept-encoding: gzip\r\n\
host: $HOST\r\n\ let mid_addr = rx.recv().await.unwrap().unwrap();
\r\n\ assert_eq!(
", req.headers()["referer"],
response: format!("\ format!("http://{}/sensitive", mid_addr)
HTTP/1.1 302 Found\r\n\ );
Server: test\r\n\ http::Response::default()
Location: http://{}/otherhost\r\n\ }
Content-Length: 0\r\n\ });
\r\n\
", end_server.addr()) let end_addr = end_server.addr();
};
let mid_server = server::http(move |req| {
async move {
assert_eq!(req.headers()["cookie"], "foo=bar");
http::Response::builder()
.status(302)
.header("location", format!("http://{}/end", end_addr))
.body(Default::default())
.unwrap()
}
});
tx.broadcast(Some(mid_server.addr())).unwrap();
reqwest::Client::builder() reqwest::Client::builder()
.referer(false)
.build() .build()
.unwrap() .unwrap()
.get(&format!("http://{}/sensitive", mid_server.addr())) .get(&format!("http://{}/sensitive", mid_server.addr()))
@@ -259,23 +205,17 @@ async fn test_redirect_removes_sensitive_headers() {
#[tokio::test] #[tokio::test]
async fn test_redirect_policy_can_return_errors() { async fn test_redirect_policy_can_return_errors() {
let server = server! { let server = server::http(move |req| {
request: b"\ async move {
GET /loop HTTP/1.1\r\n\ assert_eq!(req.uri(), "/loop");
user-agent: $USERAGENT\r\n\ http::Response::builder()
accept: */*\r\n\ .status(302)
accept-encoding: gzip\r\n\ .header("location", "/loop")
host: $HOST\r\n\ .body(Default::default())
\r\n\ .unwrap()
", }
response: b"\ });
HTTP/1.1 302 Found\r\n\
Server: test\r\n\
Location: /loop\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/loop", server.addr()); let url = format!("http://{}/loop", server.addr());
let err = reqwest::get(&url).await.unwrap_err(); let err = reqwest::get(&url).await.unwrap_err();
assert!(err.is_redirect()); assert!(err.is_redirect());
@@ -283,23 +223,16 @@ async fn test_redirect_policy_can_return_errors() {
#[tokio::test] #[tokio::test]
async fn test_redirect_policy_can_stop_redirects_without_an_error() { async fn test_redirect_policy_can_stop_redirects_without_an_error() {
let server = server! { let server = server::http(move |req| {
request: b"\ async move {
GET /no-redirect HTTP/1.1\r\n\ assert_eq!(req.uri(), "/no-redirect");
user-agent: $USERAGENT\r\n\ http::Response::builder()
accept: */*\r\n\ .status(302)
accept-encoding: gzip\r\n\ .header("location", "/dont")
host: $HOST\r\n\ .body(Default::default())
\r\n\ .unwrap()
", }
response: b"\ });
HTTP/1.1 302 Found\r\n\
Server: test-dont\r\n\
Location: /dont\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/no-redirect", server.addr()); let url = format!("http://{}/no-redirect", server.addr());
@@ -314,48 +247,27 @@ async fn test_redirect_policy_can_stop_redirects_without_an_error() {
assert_eq!(res.url().as_str(), url); assert_eq!(res.url().as_str(), url);
assert_eq!(res.status(), reqwest::StatusCode::FOUND); assert_eq!(res.status(), reqwest::StatusCode::FOUND);
assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"test-dont"
);
} }
#[tokio::test] #[tokio::test]
async fn test_referer_is_not_set_if_disabled() { async fn test_referer_is_not_set_if_disabled() {
let server = server! { let server = server::http(move |req| {
request: b"\ async move {
GET /no-refer HTTP/1.1\r\n\ if req.uri() == "/no-refer" {
user-agent: $USERAGENT\r\n\ http::Response::builder()
accept: */*\r\n\ .status(302)
accept-encoding: gzip\r\n\ .header("location", "/dst")
host: $HOST\r\n\ .body(Default::default())
\r\n\ .unwrap()
", } else {
response: b"\ assert_eq!(req.uri(), "/dst");
HTTP/1.1 302 Found\r\n\ assert_eq!(req.headers().get("referer"), None);
Server: test-no-referer\r\n\
Content-Length: 0\r\n\ http::Response::default()
Location: /dst\r\n\ }
Connection: close\r\n\ }
\r\n\ });
"
;
request: b"\
GET /dst 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\
Server: test-dst\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
reqwest::Client::builder() reqwest::Client::builder()
.referer(false) .referer(false)
.build() .build()
@@ -368,23 +280,15 @@ async fn test_referer_is_not_set_if_disabled() {
#[tokio::test] #[tokio::test]
async fn test_invalid_location_stops_redirect_gh484() { async fn test_invalid_location_stops_redirect_gh484() {
let server = server! { let server = server::http(move |_req| {
request: b"\ async move {
GET /yikes HTTP/1.1\r\n\ http::Response::builder()
user-agent: $USERAGENT\r\n\ .status(302)
accept: */*\r\n\ .header("location", "http://www.yikes{KABOOM}")
accept-encoding: gzip\r\n\ .body(Default::default())
host: $HOST\r\n\ .unwrap()
\r\n\ }
", });
response: b"\
HTTP/1.1 302 Found\r\n\
Server: test-yikes\r\n\
Location: http://www.yikes{KABOOM}\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/yikes", server.addr()); let url = format!("http://{}/yikes", server.addr());
@@ -392,66 +296,38 @@ async fn test_invalid_location_stops_redirect_gh484() {
assert_eq!(res.url().as_str(), url); assert_eq!(res.url().as_str(), url);
assert_eq!(res.status(), reqwest::StatusCode::FOUND); assert_eq!(res.status(), reqwest::StatusCode::FOUND);
assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"test-yikes"
);
} }
#[cfg(feature = "cookies")] #[cfg(feature = "cookies")]
#[tokio::test] #[tokio::test]
async fn test_redirect_302_with_set_cookies() { async fn test_redirect_302_with_set_cookies() {
let code = 302; let code = 302;
let server = server::http(move |req| {
async move {
if req.uri() == "/302" {
http::Response::builder()
.status(302)
.header("location", "/dst")
.header("set-cookie", "key=value")
.body(Default::default())
.unwrap()
} else {
assert_eq!(req.uri(), "/dst");
assert_eq!(req.headers()["cookie"], "key=value");
http::Response::default()
}
}
});
let url = format!("http://{}/{}", server.addr(), code);
let dst = format!("http://{}/{}", server.addr(), "dst");
let client = reqwest::ClientBuilder::new() let client = reqwest::ClientBuilder::new()
.cookie_store(true) .cookie_store(true)
.build() .build()
.unwrap(); .unwrap();
let server = server! {
request: format!("\
GET /{} HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
accept-encoding: gzip\r\n\
host: $HOST\r\n\
\r\n\
", code),
response: format!("\
HTTP/1.1 {} reason\r\n\
Server: test-redirect\r\n\
Content-Length: 0\r\n\
Location: /dst\r\n\
Connection: close\r\n\
Set-Cookie: key=value\r\n\
\r\n\
", code)
;
request: format!("\
GET /dst HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
accept-encoding: gzip\r\n\
referer: http://$HOST/{}\r\n\
cookie: key=value\r\n\
host: $HOST\r\n\
\r\n\
", code),
response: b"\
HTTP/1.1 200 OK\r\n\
Server: test-dst\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let url = format!("http://{}/{}", server.addr(), code);
let dst = format!("http://{}/{}", server.addr(), "dst");
let res = client.get(&url).send().await.unwrap(); let res = client.get(&url).send().await.unwrap();
assert_eq!(res.url().as_str(), dst); assert_eq!(res.url().as_str(), dst);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"test-dst"
);
} }

View File

@@ -1,2 +1,6 @@
#[macro_use]
pub mod server; pub mod server;
// TODO: remove once done converting to new support server?
#[allow(unused)]
pub static DEFAULT_USER_AGENT: &'static str =
concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));

View File

@@ -1,14 +1,18 @@
//! A server builder helper for the integration tests. use std::convert::Infallible;
use std::future::Future;
use std::io::{Read, Write};
use std::net; use std::net;
use std::sync::mpsc; use std::sync::mpsc as std_mpsc;
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
use tokio::sync::oneshot;
pub use http::Response;
pub struct Server { pub struct Server {
addr: net::SocketAddr, addr: net::SocketAddr,
panic_rx: mpsc::Receiver<()>, panic_rx: std_mpsc::Receiver<()>,
shutdown_tx: Option<oneshot::Sender<()>>,
} }
impl Server { impl Server {
@@ -19,7 +23,11 @@ impl Server {
impl Drop for Server { impl Drop for Server {
fn drop(&mut self) { fn drop(&mut self) {
if !thread::panicking() { if let Some(tx) = self.shutdown_tx.take() {
let _ = tx.send(());
}
if !::std::thread::panicking() {
self.panic_rx self.panic_rx
.recv_timeout(Duration::from_secs(3)) .recv_timeout(Duration::from_secs(3))
.expect("test server should not panic"); .expect("test server should not panic");
@@ -27,197 +35,46 @@ impl Drop for Server {
} }
} }
#[derive(Debug, Default)] pub fn http<F, Fut>(func: F) -> Server
pub struct Txn { where
pub request: Vec<u8>, F: Fn(http::Request<hyper::Body>) -> Fut + Clone + Send + 'static,
pub response: Vec<u8>, Fut: Future<Output = http::Response<hyper::Body>> + Send + 'static,
{
let srv = hyper::Server::bind(&([127, 0, 0, 1], 0).into()).serve(
hyper::service::make_service_fn(move |_| {
let func = func.clone();
async move {
Ok::<_, Infallible>(hyper::service::service_fn(move |req| {
let fut = func(req);
async move { Ok::<_, Infallible>(fut.await) }
}))
}
}),
);
pub read_timeout: Option<Duration>, let addr = srv.local_addr();
pub read_closes: bool, let (shutdown_tx, shutdown_rx) = oneshot::channel();
pub response_timeout: Option<Duration>, let srv = srv.with_graceful_shutdown(async move {
pub write_timeout: Option<Duration>, let _ = shutdown_rx.await;
pub chunk_size: Option<usize>, });
}
static DEFAULT_USER_AGENT: &'static str = let (panic_tx, panic_rx) = std_mpsc::channel();
concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
pub fn spawn(txns: Vec<Txn>) -> Server {
let listener = net::TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let (panic_tx, panic_rx) = mpsc::channel();
let tname = format!( let tname = format!(
"test({})-support-server", "test({})-support-server",
thread::current().name().unwrap_or("<unknown>") thread::current().name().unwrap_or("<unknown>")
); );
thread::Builder::new().name(tname).spawn(move || { thread::Builder::new()
'txns: for txn in txns { .name(tname)
let mut expected = txn.request; .spawn(move || {
let reply = txn.response; let mut rt = tokio::runtime::current_thread::Runtime::new().expect("rt new");
let (mut socket, _addr) = listener.accept().unwrap(); rt.block_on(srv).unwrap();
let _ = panic_tx.send(());
})
.expect("thread spawn");
socket.set_read_timeout(Some(Duration::from_secs(5))).unwrap(); Server {
addr,
replace_expected_vars(&mut expected, addr.to_string().as_ref(), DEFAULT_USER_AGENT.as_ref()); panic_rx,
shutdown_tx: Some(shutdown_tx),
if let Some(dur) = txn.read_timeout {
thread::park_timeout(dur);
}
let mut buf = vec![0; expected.len() + 256];
let mut n = 0;
while n < expected.len() {
match socket.read(&mut buf[n..]) {
Ok(0) => {
if !txn.read_closes {
panic!("server unexpected socket closed");
} else {
continue 'txns;
}
},
Ok(nread) => n += nread,
Err(err) => {
println!("server read error: {}", err);
break;
}
}
}
if txn.read_closes {
socket.set_read_timeout(Some(Duration::from_secs(1))).unwrap();
match socket.read(&mut [0; 256]) {
Ok(0) => {
continue 'txns
},
Ok(_) => {
panic!("server read expected EOF, found more bytes");
},
Err(err) => {
panic!("server read expected EOF, got error: {}", err);
}
}
}
match (std::str::from_utf8(&expected), std::str::from_utf8(&buf[..n])) {
(Ok(expected), Ok(received)) => {
if expected.len() > 300 && std::env::var("REQWEST_TEST_BODY_FULL").is_err() {
assert_eq!(
expected.len(),
received.len(),
"expected len = {}, received len = {}; to skip length check and see exact contents, re-run with REQWEST_TEST_BODY_FULL=1",
expected.len(),
received.len(),
);
}
assert_eq!(expected, received)
},
_ => {
assert_eq!(
expected.len(),
n,
"expected len = {}, received len = {}",
expected.len(),
n,
);
assert_eq!(expected, &buf[..n])
},
}
if let Some(dur) = txn.response_timeout {
thread::park_timeout(dur);
}
if let Some(dur) = txn.write_timeout {
let headers_end = b"\r\n\r\n";
let headers_end = reply.windows(headers_end.len()).position(|w| w == headers_end).unwrap() + 4;
socket.write_all(&reply[..headers_end]).unwrap();
let body = &reply[headers_end..];
if let Some(chunk_size) = txn.chunk_size {
for content in body.chunks(chunk_size) {
thread::park_timeout(dur);
socket.write_all(&content).unwrap();
}
} else {
thread::park_timeout(dur);
socket.write_all(&body).unwrap();
}
} else {
socket.write_all(&reply).unwrap();
}
}
let _ = panic_tx.send(());
}).expect("server thread spawn");
Server { addr, panic_rx }
}
fn replace_expected_vars(bytes: &mut Vec<u8>, host: &[u8], ua: &[u8]) {
// plenty horrible, but these are just tests, and gets the job done
let mut index = 0;
loop {
if index == bytes.len() {
return;
}
for b in (&bytes[index..]).iter() {
index += 1;
if *b == b'$' {
break;
}
}
let has_host = (&bytes[index..]).starts_with(b"HOST");
if has_host {
bytes.drain(index - 1..index + 4);
for (i, b) in host.iter().enumerate() {
bytes.insert(index - 1 + i, *b);
}
} else {
let has_ua = (&bytes[index..]).starts_with(b"USERAGENT");
if has_ua {
bytes.drain(index - 1..index + 9);
for (i, b) in ua.iter().enumerate() {
bytes.insert(index - 1 + i, *b);
}
}
}
} }
} }
#[macro_export]
macro_rules! server {
($($($f:ident: $v:expr),+);*) => ({
let txns = vec![
$(__internal__txn! {
$($f: $v,)+
}),*
];
crate::support::server::spawn(txns)
})
}
#[macro_export]
macro_rules! __internal__txn {
($($field:ident: $val:expr,)+) => (
crate::support::server::Txn {
$( $field: __internal__prop!($field: $val), )+
.. Default::default()
}
)
}
#[macro_export]
macro_rules! __internal__prop {
(request: $val:expr) => {
From::from(&$val[..])
};
(response: $val:expr) => {
From::from(&$val[..])
};
($field:ident: $val:expr) => {
From::from($val)
};
}

View File

@@ -1,7 +1,64 @@
#[macro_use]
mod support; mod support;
use support::*;
use std::time::Duration; use std::time::{Duration, Instant};
#[tokio::test]
async fn request_timeout() {
let _ = env_logger::try_init();
let server = server::http(move |_req| {
async {
// delay returning the response
tokio::timer::delay(Instant::now() + Duration::from_secs(2)).await;
http::Response::default()
}
});
let client = reqwest::Client::builder()
.timeout(Duration::from_millis(500))
.build()
.unwrap();
let url = format!("http://{}/slow", server.addr());
let res = client.get(&url).send().await;
let err = res.unwrap_err();
assert!(err.is_timeout());
assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str()));
}
#[tokio::test]
async fn response_timeout() {
let _ = env_logger::try_init();
let server = server::http(move |_req| {
async {
// immediate response, but delayed body
let body = hyper::Body::wrap_stream(futures_util::stream::once(async {
tokio::timer::delay(Instant::now() + Duration::from_secs(2)).await;
Ok::<_, std::convert::Infallible>("Hello")
}));
http::Response::new(body)
}
});
let client = reqwest::Client::builder()
.timeout(Duration::from_millis(500))
.build()
.unwrap();
let url = format!("http://{}/slow", server.addr());
let res = client.get(&url).send().await.expect("Failed to get");
let body = res.text().await;
let err = body.unwrap_err();
assert!(err.is_timeout());
}
/// Tests that internal client future cancels when the oneshot channel /// Tests that internal client future cancels when the oneshot channel
/// is canceled. /// is canceled.
@@ -17,24 +74,13 @@ fn timeout_closes_connection() {
.build() .build()
.unwrap(); .unwrap();
let server = server! { let server = server::http(move |_req| {
request: b"\ async {
GET /closes HTTP/1.1\r\n\ // delay returning the response
user-agent: $USERAGENT\r\n\ tokio::timer::delay(Instant::now() + Duration::from_secs(2)).await;
accept: */*\r\n\ http::Response::default()
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\
",
read_timeout: Duration::from_secs(2),
read_closes: true
};
let url = format!("http://{}/closes", server.addr()); let url = format!("http://{}/closes", server.addr());
let err = client.get(&url).send().unwrap_err(); let err = client.get(&url).send().unwrap_err();
@@ -47,7 +93,7 @@ fn timeout_closes_connection() {
#[test] #[test]
fn write_timeout_large_body() { fn write_timeout_large_body() {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let body = String::from_utf8(vec![b'x'; 20_000]).unwrap(); let body = vec![b'x'; 20_000];
let len = 8192; let len = 8192;
// Make Client drop *after* the Server, so the background doesn't // Make Client drop *after* the Server, so the background doesn't
@@ -57,28 +103,15 @@ fn write_timeout_large_body() {
.build() .build()
.unwrap(); .unwrap();
let server = server! { let server = server::http(move |_req| {
request: format!("\ async {
POST /write-timeout HTTP/1.1\r\n\ // delay returning the response
user-agent: $USERAGENT\r\n\ tokio::timer::delay(Instant::now() + Duration::from_secs(2)).await;
accept: */*\r\n\ http::Response::default()
content-length: {}\r\n\ }
accept-encoding: gzip\r\n\ });
host: $HOST\r\n\
\r\n\
{}\
", body.len(), body),
response: b"\
HTTP/1.1 200 OK\r\n\
Content-Length: 5\r\n\
\r\n\
Hello\
",
read_timeout: Duration::from_secs(2),
read_closes: true
};
let cursor = std::io::Cursor::new(body.into_bytes()); let cursor = std::io::Cursor::new(body);
let url = format!("http://{}/write-timeout", server.addr()); let url = format!("http://{}/write-timeout", server.addr());
let err = client let err = client
.post(&url) .post(&url)
@@ -89,78 +122,3 @@ fn write_timeout_large_body() {
assert!(err.is_timeout()); assert!(err.is_timeout());
assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str())); assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str()));
} }
#[tokio::test]
async fn test_response_timeout() {
let _ = env_logger::try_init();
let server = server! {
request: b"\
GET /response-timeout 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: 0\r\n\
",
response_timeout: Duration::from_secs(1)
};
let url = format!("http://{}/response-timeout", server.addr());
let err = reqwest::Client::builder()
.timeout(Duration::from_millis(500))
.build()
.unwrap()
.get(&url)
.send()
.await
.unwrap_err();
assert!(err.is_timeout());
assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str()));
}
#[tokio::test]
async fn test_read_timeout() {
let _ = env_logger::try_init();
let server = server! {
request: b"\
GET /read-timeout 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\
",
write_timeout: Duration::from_secs(1)
};
let url = format!("http://{}/read-timeout", server.addr());
let res = reqwest::Client::builder()
.timeout(Duration::from_millis(500))
.build()
.unwrap()
.get(&url)
.send()
.await
.unwrap();
assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"5"
);
let err = res.text().await.unwrap_err();
assert!(err.is_timeout());
}