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

View File

@@ -1,44 +1,35 @@
#[macro_use]
mod support;
use support::*;
use std::io::Write;
use std::time::Duration;
use reqwest::multipart::{Form, Part};
use reqwest::{Body, Client};
use bytes::Bytes;
use reqwest::Client;
#[tokio::test]
async fn gzip_response() {
gzip_case(10_000, 4096).await;
}
async fn auto_headers() {
let server = server::http(move |req| {
async move {
assert_eq!(req.method(), "GET");
#[tokio::test]
async fn gzip_single_byte_chunks() {
gzip_case(10, 1).await;
assert_eq!(req.headers()["accept"], "*/*");
assert_eq!(req.headers()["user-agent"], DEFAULT_USER_AGENT);
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]
async fn response_text() {
let _ = env_logger::try_init();
let server = server! {
request: b"\
GET /text HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
accept-encoding: gzip\r\n\
host: $HOST\r\n\
\r\n\
",
response: b"\
HTTP/1.1 200 OK\r\n\
Content-Length: 5\r\n\
\r\n\
Hello\
"
};
let server = server::http(move |_req| async { http::Response::new("Hello".into()) });
let client = Client::new();
@@ -55,22 +46,7 @@ async fn response_text() {
async fn response_json() {
let _ = env_logger::try_init();
let server = server! {
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 server = server::http(move |_req| async { http::Response::new("\"Hello\"".into()) });
let client = Client::new();
@@ -83,287 +59,29 @@ async fn response_json() {
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]
async fn body_pipe_response() {
let _ = env_logger::try_init();
let server = server! {
request: b"\
GET /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\
Server: pipe\r\n\
Content-Length: 7\r\n\
\r\n\
pipe me\
";
let server = server::http(move |mut req| {
async move {
if req.uri() == "/get" {
http::Response::new("pipe me".into())
} else {
assert_eq!(req.uri(), "/pipe");
assert_eq!(req.headers()["transfer-encoding"], "chunked");
request: b"\
POST /pipe 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\
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 mut full: Vec<u8> = Vec::new();
while let Some(item) = req.body_mut().next().await {
full.extend(&*item.unwrap());
}
assert_eq!(full, b"pipe me");
http::Response::default()
}
}
});
let client = Client::new();

View File

@@ -1,38 +1,32 @@
#[macro_use]
mod support;
use support::*;
#[test]
fn cookie_response_accessor() {
let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt");
let client = reqwest::r#async::Client::new();
#[tokio::test]
async fn cookie_response_accessor() {
let server = server::http(move |_req| {
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! {
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 client = reqwest::Client::new();
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<_>>();
@@ -79,273 +73,143 @@ fn cookie_response_accessor() {
assert!(cookies[8].same_site_strict());
}
#[test]
fn cookie_store_simple() {
let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt");
let client = reqwest::r#async::Client::builder()
#[tokio::test]
async fn cookie_store_simple() {
let server = server::http(move |req| {
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)
.build()
.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());
rt.block_on(client.get(&url).send()).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\
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();
let url = format!("http://{}/2", server.addr());
client.get(&url).send().await.unwrap();
}
#[test]
fn cookie_store_overwrite_existing() {
let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt");
let client = reqwest::r#async::Client::builder()
#[tokio::test]
async fn cookie_store_overwrite_existing() {
let server = server::http(move |req| {
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)
.build()
.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());
rt.block_on(client.get(&url).send()).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\
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 url = format!("http://{}/2", server.addr());
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\
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();
let url = format!("http://{}/3", server.addr());
client.get(&url).send().await.unwrap();
}
#[test]
fn cookie_store_max_age() {
let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt");
let client = reqwest::r#async::Client::builder()
#[tokio::test]
async fn cookie_store_max_age() {
let server = server::http(move |req| {
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)
.build()
.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());
rt.block_on(client.get(&url).send()).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();
client.get(&url).send().await.unwrap();
client.get(&url).send().await.unwrap();
}
#[test]
fn cookie_store_expires() {
let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt");
#[tokio::test]
async fn cookie_store_expires() {
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()
.cookie_store(true)
.build()
.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());
rt.block_on(client.get(&url).send()).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();
client.get(&url).send().await.unwrap();
client.get(&url).send().await.unwrap();
}
#[test]
fn cookie_store_path() {
let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt");
#[tokio::test]
async fn cookie_store_path() {
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()
.cookie_store(true)
.build()
.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());
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());
rt.block_on(client.get(&url).send()).unwrap();
client.get(&url).send().await.unwrap();
}

View File

@@ -1,13 +1,101 @@
#[macro_use]
mod support;
use support::*;
use std::io::Write;
use std::time::Duration;
#[tokio::test]
async fn test_gzip_response() {
let content: String = (0..50).into_iter().map(|i| format!("test {}", i)).collect();
let chunk_size = content.len() / 3;
async fn gzip_response() {
gzip_case(10_000, 4096).await;
}
#[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();
match encoder.write(content.as_bytes()) {
Ok(n) => assert!(n > 0, "Failed to write to encoder."),
@@ -28,147 +116,38 @@ async fn test_gzip_response() {
.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 url = format!("http://{}/gzip", server.addr());
let res = reqwest::get(&url).await.unwrap();
let server = server::http(move |req| {
assert_eq!(req.headers()["accept-encoding"], "gzip");
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);
}
#[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;
use support::*;
#[tokio::test]
async fn text_part() {
@@ -17,25 +17,26 @@ async fn text_part() {
form.boundary()
);
let server = server! {
request: format!("\
POST /multipart/1 HTTP/1.1\r\n\
content-type: multipart/form-data; boundary={}\r\n\
content-length: 125\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
accept-encoding: gzip\r\n\
host: $HOST\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 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()["content-length"], "125");
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());
@@ -50,9 +51,74 @@ async fn text_part() {
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")]
#[test]
fn file() {
fn blocking_file_part() {
let _ = env_logger::try_init();
let form = reqwest::blocking::multipart::Form::new()
@@ -73,25 +139,30 @@ fn file() {
fcontents
);
let server = server! {
request: format!("\
POST /multipart/2 HTTP/1.1\r\n\
content-type: multipart/form-data; boundary={}\r\n\
content-length: {}\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
accept-encoding: gzip\r\n\
host: $HOST\r\n\
\r\n\
{}\
", form.boundary(), expected_body.len(), expected_body),
response: b"\
HTTP/1.1 200 OK\r\n\
Server: multipart\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
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);
// files know their exact size
assert_eq!(
req.headers()["content-length"],
expected_body.len().to_string()
);
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/2", server.addr());

View File

@@ -1,30 +1,21 @@
#[macro_use]
mod support;
use support::*;
use std::env;
#[tokio::test]
async fn http_proxy() {
let server = server! {
request: b"\
GET http://hyper.rs/prox HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
accept-encoding: gzip\r\n\
host: hyper.rs\r\n\
\r\n\
",
response: b"\
HTTP/1.1 200 OK\r\n\
Server: proxied\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
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 url = "http://hyper.rs/prox";
let res = reqwest::Client::builder()
.proxy(reqwest::Proxy::http(&proxy).unwrap())
.build()
@@ -36,35 +27,25 @@ async fn http_proxy() {
assert_eq!(res.url().as_str(), url);
assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"proxied"
);
}
#[tokio::test]
async fn http_proxy_basic_auth() {
let server = server! {
request: b"\
GET http://hyper.rs/prox HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
accept-encoding: gzip\r\n\
proxy-authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\r\n\
host: hyper.rs\r\n\
\r\n\
",
response: b"\
HTTP/1.1 200 OK\r\n\
Server: proxied\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
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");
assert_eq!(
req.headers()["proxy-authorization"],
"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
);
async { http::Response::default() }
});
let proxy = format!("http://{}", server.addr());
let url = "http://hyper.rs/prox";
let res = reqwest::Client::builder()
.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.status(), reqwest::StatusCode::OK);
assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"proxied"
);
}
#[tokio::test]
async fn http_proxy_basic_auth_parsed() {
let server = server! {
request: b"\
GET http://hyper.rs/prox HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
accept-encoding: gzip\r\n\
proxy-authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\r\n\
host: hyper.rs\r\n\
\r\n\
",
response: b"\
HTTP/1.1 200 OK\r\n\
Server: proxied\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
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");
assert_eq!(
req.headers()["proxy-authorization"],
"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
);
async { http::Response::default() }
});
let proxy = format!("http://Aladdin:open sesame@{}", server.addr());
let url = "http://hyper.rs/prox";
let res = reqwest::Client::builder()
.proxy(reqwest::Proxy::http(&proxy).unwrap())
.build()
@@ -120,30 +91,16 @@ async fn http_proxy_basic_auth_parsed() {
assert_eq!(res.url().as_str(), url);
assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"proxied"
);
}
#[tokio::test]
async fn test_no_proxy() {
let server = server! {
request: b"\
GET /4 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 server = server::http(move |req| {
assert_eq!(req.method(), "GET");
assert_eq!(req.uri(), "/4");
async { http::Response::default() }
});
let proxy = format!("http://{}", server.addr());
let url = format!("http://{}/4", server.addr());
@@ -164,28 +121,20 @@ async fn test_no_proxy() {
#[tokio::test]
async fn test_using_system_proxy() {
let server = server! {
request: b"\
GET http://hyper.rs/prox HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
accept-encoding: gzip\r\n\
host: hyper.rs\r\n\
\r\n\
",
response: b"\
HTTP/1.1 200 OK\r\n\
Server: proxied\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
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() }
});
// save system setting first.
let system_proxy = env::var("http_proxy");
// set-up http proxy.
env::set_var("http_proxy", format!("http://{}", server.addr()));
let url = "http://hyper.rs/prox";
let res = reqwest::Client::builder()
.use_sys_proxy()
.build()
@@ -197,10 +146,6 @@ async fn test_using_system_proxy() {
assert_eq!(res.url().as_str(), url);
assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"proxied"
);
// reset user setting.
match system_proxy {
@@ -208,3 +153,30 @@ async fn test_using_system_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;
use support::*;
#[tokio::test]
async fn test_redirect_301_and_302_and_303_changes_post_to_get() {
let client = reqwest::Client::new();
let codes = [301, 302, 303];
let codes = [301u16, 302, 303];
for code in codes.iter() {
let redirect = server! {
request: format!("\
POST /{} 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\
\r\n\
", code)
;
for &code in codes.iter() {
let redirect = server::http(move |req| {
async move {
if req.method() == "POST" {
assert_eq!(req.uri(), &*format!("/{}", code));
http::Response::builder()
.status(code)
.header("location", "/dst")
.header("server", "test-redirect")
.body(Default::default())
.unwrap()
} else {
assert_eq!(req.method(), "GET");
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\
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\
"
};
http::Response::builder()
.header("server", "test-dst")
.body(Default::default())
.unwrap()
}
}
});
let url = format!("http://{}/{}", redirect.addr(), code);
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]
async fn test_redirect_307_and_308_tries_to_get_again() {
let client = reqwest::Client::new();
let codes = [307, 308];
for code in codes.iter() {
let redirect = 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\
\r\n\
", code)
;
let codes = [307u16, 308];
for &code in codes.iter() {
let redirect = server::http(move |req| {
async move {
assert_eq!(req.method(), "GET");
if req.uri() == &*format!("/{}", code) {
http::Response::builder()
.status(code)
.header("location", "/dst")
.header("server", "test-redirect")
.body(Default::default())
.unwrap()
} else {
assert_eq!(req.uri(), "/dst");
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\
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\
"
};
http::Response::builder()
.header("server", "test-dst")
.body(Default::default())
.unwrap()
}
}
});
let url = format!("http://{}/{}", redirect.addr(), code);
let dst = format!("http://{}/{}", redirect.addr(), "dst");
@@ -111,47 +81,32 @@ async fn test_redirect_307_and_308_tries_to_get_again() {
#[tokio::test]
async fn test_redirect_307_and_308_tries_to_post_again() {
let client = reqwest::Client::new();
let codes = [307, 308];
for code in codes.iter() {
let redirect = server! {
request: format!("\
POST /{} HTTP/1.1\r\n\
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)
;
let codes = [307u16, 308];
for &code in codes.iter() {
let redirect = server::http(move |mut req| {
async move {
assert_eq!(req.method(), "POST");
request: format!("\
POST /dst HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
accept-encoding: gzip\r\n\
referer: http://$HOST/{}\r\n\
host: $HOST\r\n\
content-length: 5\r\n\
\r\n\
Hello\
", code),
response: b"\
HTTP/1.1 200 OK\r\n\
Server: test-dst\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let data = req.body_mut().next().await.unwrap().unwrap();
assert_eq!(&*data, b"Hello");
if req.uri() == &*format!("/{}", code) {
http::Response::builder()
.status(code)
.header("location", "/dst")
.header("server", "test-redirect")
.body(Default::default())
.unwrap()
} else {
assert_eq!(req.uri(), "/dst");
http::Response::builder()
.header("server", "test-dst")
.body(Default::default())
.unwrap()
}
}
});
let url = format!("http://{}/{}", redirect.addr(), code);
let dst = format!("http://{}/{}", redirect.addr(), "dst");
@@ -169,30 +124,25 @@ async fn test_redirect_307_and_308_tries_to_post_again() {
#[test]
fn test_redirect_307_does_not_try_if_reader_cannot_reset() {
let client = reqwest::blocking::Client::new();
let codes = [307, 308];
let codes = [307u16, 308];
for &code in codes.iter() {
let redirect = server! {
request: format!("\
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\
5\r\n\
Hello\r\n\
0\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\
\r\n\
", code)
};
let redirect = server::http(move |mut req| {
async move {
assert_eq!(req.method(), "POST");
assert_eq!(req.uri(), &*format!("/{}", code));
assert_eq!(req.headers()["transfer-encoding"], "chunked");
let data = req.body_mut().next().await.unwrap().unwrap();
assert_eq!(&*data, b"Hello");
http::Response::builder()
.status(code)
.header("location", "/dst")
.header("server", "test-redirect")
.body(Default::default())
.unwrap()
}
});
let url = format!("http://{}/{}", redirect.addr(), code);
let res = client
@@ -201,50 +151,46 @@ fn test_redirect_307_does_not_try_if_reader_cannot_reset() {
.send()
.unwrap();
assert_eq!(res.url().as_str(), url);
assert_eq!(res.status(), reqwest::StatusCode::from_u16(code).unwrap());
assert_eq!(res.status(), code);
}
}
#[tokio::test]
async fn test_redirect_removes_sensitive_headers() {
let end_server = server! {
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\
"
};
use tokio::sync::watch;
let mid_server = server! {
request: b"\
GET /sensitive HTTP/1.1\r\n\
cookie: foo=bar\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
accept-encoding: gzip\r\n\
host: $HOST\r\n\
\r\n\
",
response: format!("\
HTTP/1.1 302 Found\r\n\
Server: test\r\n\
Location: http://{}/otherhost\r\n\
Content-Length: 0\r\n\
\r\n\
", end_server.addr())
};
let (tx, rx) = watch::channel(None);
let end_server = server::http(move |req| {
let mut rx = rx.clone();
async move {
assert_eq!(req.headers().get("cookie"), None);
let mid_addr = rx.recv().await.unwrap().unwrap();
assert_eq!(
req.headers()["referer"],
format!("http://{}/sensitive", mid_addr)
);
http::Response::default()
}
});
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()
.referer(false)
.build()
.unwrap()
.get(&format!("http://{}/sensitive", mid_server.addr()))
@@ -259,23 +205,17 @@ async fn test_redirect_removes_sensitive_headers() {
#[tokio::test]
async fn test_redirect_policy_can_return_errors() {
let server = server! {
request: b"\
GET /loop 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 302 Found\r\n\
Server: test\r\n\
Location: /loop\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let server = server::http(move |req| {
async move {
assert_eq!(req.uri(), "/loop");
http::Response::builder()
.status(302)
.header("location", "/loop")
.body(Default::default())
.unwrap()
}
});
let url = format!("http://{}/loop", server.addr());
let err = reqwest::get(&url).await.unwrap_err();
assert!(err.is_redirect());
@@ -283,23 +223,16 @@ async fn test_redirect_policy_can_return_errors() {
#[tokio::test]
async fn test_redirect_policy_can_stop_redirects_without_an_error() {
let server = server! {
request: b"\
GET /no-redirect 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 302 Found\r\n\
Server: test-dont\r\n\
Location: /dont\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let server = server::http(move |req| {
async move {
assert_eq!(req.uri(), "/no-redirect");
http::Response::builder()
.status(302)
.header("location", "/dont")
.body(Default::default())
.unwrap()
}
});
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.status(), reqwest::StatusCode::FOUND);
assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"test-dont"
);
}
#[tokio::test]
async fn test_referer_is_not_set_if_disabled() {
let server = server! {
request: b"\
GET /no-refer 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 302 Found\r\n\
Server: test-no-referer\r\n\
Content-Length: 0\r\n\
Location: /dst\r\n\
Connection: close\r\n\
\r\n\
"
;
let server = server::http(move |req| {
async move {
if req.uri() == "/no-refer" {
http::Response::builder()
.status(302)
.header("location", "/dst")
.body(Default::default())
.unwrap()
} else {
assert_eq!(req.uri(), "/dst");
assert_eq!(req.headers().get("referer"), None);
http::Response::default()
}
}
});
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()
.referer(false)
.build()
@@ -368,23 +280,15 @@ async fn test_referer_is_not_set_if_disabled() {
#[tokio::test]
async fn test_invalid_location_stops_redirect_gh484() {
let server = server! {
request: b"\
GET /yikes 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 302 Found\r\n\
Server: test-yikes\r\n\
Location: http://www.yikes{KABOOM}\r\n\
Content-Length: 0\r\n\
\r\n\
"
};
let server = server::http(move |_req| {
async move {
http::Response::builder()
.status(302)
.header("location", "http://www.yikes{KABOOM}")
.body(Default::default())
.unwrap()
}
});
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.status(), reqwest::StatusCode::FOUND);
assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"test-yikes"
);
}
#[cfg(feature = "cookies")]
#[tokio::test]
async fn test_redirect_302_with_set_cookies() {
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()
.cookie_store(true)
.build()
.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();
assert_eq!(res.url().as_str(), dst);
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;
// 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::io::{Read, Write};
use std::convert::Infallible;
use std::future::Future;
use std::net;
use std::sync::mpsc;
use std::sync::mpsc as std_mpsc;
use std::thread;
use std::time::Duration;
use tokio::sync::oneshot;
pub use http::Response;
pub struct Server {
addr: net::SocketAddr,
panic_rx: mpsc::Receiver<()>,
panic_rx: std_mpsc::Receiver<()>,
shutdown_tx: Option<oneshot::Sender<()>>,
}
impl Server {
@@ -19,7 +23,11 @@ impl Server {
impl Drop for Server {
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
.recv_timeout(Duration::from_secs(3))
.expect("test server should not panic");
@@ -27,197 +35,46 @@ impl Drop for Server {
}
}
#[derive(Debug, Default)]
pub struct Txn {
pub request: Vec<u8>,
pub response: Vec<u8>,
pub fn http<F, Fut>(func: F) -> Server
where
F: Fn(http::Request<hyper::Body>) -> Fut + Clone + Send + 'static,
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>,
pub read_closes: bool,
pub response_timeout: Option<Duration>,
pub write_timeout: Option<Duration>,
pub chunk_size: Option<usize>,
}
let addr = srv.local_addr();
let (shutdown_tx, shutdown_rx) = oneshot::channel();
let srv = srv.with_graceful_shutdown(async move {
let _ = shutdown_rx.await;
});
static DEFAULT_USER_AGENT: &'static str =
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 (panic_tx, panic_rx) = std_mpsc::channel();
let tname = format!(
"test({})-support-server",
thread::current().name().unwrap_or("<unknown>")
);
thread::Builder::new().name(tname).spawn(move || {
'txns: for txn in txns {
let mut expected = txn.request;
let reply = txn.response;
let (mut socket, _addr) = listener.accept().unwrap();
socket.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
replace_expected_vars(&mut expected, addr.to_string().as_ref(), DEFAULT_USER_AGENT.as_ref());
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();
}
}
thread::Builder::new()
.name(tname)
.spawn(move || {
let mut rt = tokio::runtime::current_thread::Runtime::new().expect("rt new");
rt.block_on(srv).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)
})
}
.expect("thread spawn");
#[macro_export]
macro_rules! __internal__txn {
($($field:ident: $val:expr,)+) => (
crate::support::server::Txn {
$( $field: __internal__prop!($field: $val), )+
.. Default::default()
Server {
addr,
panic_rx,
shutdown_tx: Some(shutdown_tx),
}
)
}
#[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;
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
/// is canceled.
@@ -17,24 +74,13 @@ fn timeout_closes_connection() {
.build()
.unwrap();
let server = server! {
request: b"\
GET /closes 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),
read_closes: true
};
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 url = format!("http://{}/closes", server.addr());
let err = client.get(&url).send().unwrap_err();
@@ -47,7 +93,7 @@ fn timeout_closes_connection() {
#[test]
fn write_timeout_large_body() {
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;
// Make Client drop *after* the Server, so the background doesn't
@@ -57,28 +103,15 @@ fn write_timeout_large_body() {
.build()
.unwrap();
let server = server! {
request: format!("\
POST /write-timeout HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\
accept: */*\r\n\
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 server = server::http(move |_req| {
async {
// delay returning the response
tokio::timer::delay(Instant::now() + Duration::from_secs(2)).await;
http::Response::default()
}
});
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 err = client
.post(&url)
@@ -89,78 +122,3 @@ fn write_timeout_large_body() {
assert!(err.is_timeout());
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());
}