feat(body): introduce an Entity trait to represent bodies
This dedicated `Entity` trait replaces the previous `Stream<Item=impl AsRef<[u8]>, Error=hyper::Error>`. This allows for several improvements immediately, and prepares for HTTP2 support. - The `Entity::is_end_stream` makes up for change away from `Option<Body>`, which was previously used to know if the body should be empty. Since `Request` and `Response` now require a body to be set, this method can be used to tell hyper that the body is actually empty. It also provides the possibility of slight optimizations when polling for data, by allowing to check `is_end_stream` before polling again. This can allow a consumer to know that a body stream has ended without polling for `None` afterwards. - The `Entity::content_length` method allows a body to automatically declare a size, in case a user doesn't set a `Content-Length` or `Transfer-Encoding` header. - It's now possible to send and receive trailers, though this will be for HTTP2 connections only. By being a trait owned by hyper, new methods can be added later as new features are wanted (with default implementations). The `hyper::Body` type now implements `Entity` instead of `Stream`, provides a better channel option, and is easier to use with custom streams via `Body::wrap_stream`. BREAKING CHANGE: All code that was assuming the body was a `Stream` must be adjusted to use an `Entity` instead. Using `hyper::Body` as a `Stream` can call `Body::into_stream` to get a stream wrapper. Passing a custom `impl Stream` will need to either implement `Entity`, or as an easier option, switch to `Body::wrap_stream`. `Body::pair` has been replaced with `Body::channel`, which returns a `hyper::body::Sender` instead of a `futures::sync::mpsc::Sender`. Closes #1438
This commit is contained in:
@@ -34,7 +34,6 @@ macro_rules! test {
|
||||
url: $client_url:expr,
|
||||
headers: { $($request_header_name:expr => $request_header_val:expr,)* },
|
||||
body: $request_body:expr,
|
||||
proxy: $request_proxy:expr,
|
||||
|
||||
response:
|
||||
status: $client_status:ident,
|
||||
@@ -53,7 +52,6 @@ macro_rules! test {
|
||||
url: $client_url,
|
||||
headers: { $($request_header_name => $request_header_val,)* },
|
||||
body: $request_body,
|
||||
proxy: $request_proxy,
|
||||
|
||||
response:
|
||||
status: $client_status,
|
||||
@@ -73,7 +71,6 @@ macro_rules! test {
|
||||
url: $client_url:expr,
|
||||
headers: { $($request_header_name:expr => $request_header_val:expr,)* },
|
||||
body: $request_body:expr,
|
||||
proxy: $request_proxy:expr,
|
||||
|
||||
response:
|
||||
status: $client_status:ident,
|
||||
@@ -99,7 +96,6 @@ macro_rules! test {
|
||||
url: $client_url,
|
||||
headers: { $($request_header_name => $request_header_val,)* },
|
||||
body: $request_body,
|
||||
proxy: $request_proxy,
|
||||
}.unwrap();
|
||||
|
||||
|
||||
@@ -108,7 +104,7 @@ macro_rules! test {
|
||||
assert_eq!(res.headers()[$response_header_name], $response_header_val);
|
||||
)*
|
||||
|
||||
let body = core.run(res.into_parts().1.concat2()).unwrap();
|
||||
let body = core.run(res.into_body().into_stream().concat2()).unwrap();
|
||||
|
||||
let expected_res_body = Option::<&[u8]>::from($response_body)
|
||||
.unwrap_or_default();
|
||||
@@ -126,7 +122,6 @@ macro_rules! test {
|
||||
url: $client_url:expr,
|
||||
headers: { $($request_header_name:expr => $request_header_val:expr,)* },
|
||||
body: $request_body:expr,
|
||||
proxy: $request_proxy:expr,
|
||||
|
||||
error: $err:expr,
|
||||
) => (
|
||||
@@ -149,7 +144,6 @@ macro_rules! test {
|
||||
url: $client_url,
|
||||
headers: { $($request_header_name => $request_header_val,)* },
|
||||
body: $request_body,
|
||||
proxy: $request_proxy,
|
||||
}.unwrap_err();
|
||||
if !$err(&err) {
|
||||
panic!("unexpected error: {:?}", err)
|
||||
@@ -171,7 +165,6 @@ macro_rules! test {
|
||||
url: $client_url:expr,
|
||||
headers: { $($request_header_name:expr => $request_header_val:expr,)* },
|
||||
body: $request_body:expr,
|
||||
proxy: $request_proxy:expr,
|
||||
) => ({
|
||||
let server = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let addr = server.local_addr().unwrap();
|
||||
@@ -183,32 +176,21 @@ macro_rules! test {
|
||||
}
|
||||
let client = config.build(&core.handle());
|
||||
|
||||
let mut is_empty = false;
|
||||
let body = if let Some(body) = $request_body {
|
||||
let body: &'static str = body;
|
||||
body.into()
|
||||
} else {
|
||||
is_empty = true;
|
||||
Body::empty()
|
||||
};
|
||||
let mut req = Request::builder();
|
||||
req
|
||||
let req = Request::builder()
|
||||
.method(Method::$client_method)
|
||||
.uri(&*format!($client_url, addr=addr))
|
||||
$(
|
||||
.header($request_header_name, $request_header_val)
|
||||
)*
|
||||
.uri(&*format!($client_url, addr=addr));
|
||||
|
||||
//TODO: remove when client bodies are fixed
|
||||
if is_empty {
|
||||
req.header("content-length", "0");
|
||||
}
|
||||
|
||||
let req = req.body(body)
|
||||
.body(body)
|
||||
.unwrap();
|
||||
|
||||
// req.set_proxy($request_proxy);
|
||||
|
||||
let res = client.request(req);
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
@@ -257,7 +239,6 @@ test! {
|
||||
url: "http://{addr}/",
|
||||
headers: {},
|
||||
body: None,
|
||||
proxy: false,
|
||||
response:
|
||||
status: OK,
|
||||
headers: {
|
||||
@@ -279,7 +260,6 @@ test! {
|
||||
url: "http://{addr}/foo?key=val#dont_send_me",
|
||||
headers: {},
|
||||
body: None,
|
||||
proxy: false,
|
||||
response:
|
||||
status: OK,
|
||||
headers: {
|
||||
@@ -301,7 +281,6 @@ test! {
|
||||
url: "http://{addr}/",
|
||||
headers: {},
|
||||
body: Some(""),
|
||||
proxy: false,
|
||||
response:
|
||||
status: OK,
|
||||
headers: {
|
||||
@@ -331,7 +310,6 @@ test! {
|
||||
"Content-Length" => "7",
|
||||
},
|
||||
body: Some("foo bar"),
|
||||
proxy: false,
|
||||
response:
|
||||
status: OK,
|
||||
headers: {},
|
||||
@@ -361,7 +339,6 @@ test! {
|
||||
"Transfer-Encoding" => "chunked",
|
||||
},
|
||||
body: Some("foo bar baz"),
|
||||
proxy: false,
|
||||
response:
|
||||
status: OK,
|
||||
headers: {},
|
||||
@@ -387,14 +364,14 @@ test! {
|
||||
headers: {
|
||||
"Content-Length" => "0",
|
||||
},
|
||||
body: Some(""),
|
||||
proxy: false,
|
||||
body: None,
|
||||
response:
|
||||
status: OK,
|
||||
headers: {},
|
||||
body: None,
|
||||
}
|
||||
|
||||
/*TODO: when new Connect trait allows stating connection is proxied
|
||||
test! {
|
||||
name: client_http_proxy,
|
||||
|
||||
@@ -407,18 +384,18 @@ test! {
|
||||
reply: REPLY_OK,
|
||||
|
||||
client:
|
||||
proxy: true,
|
||||
request:
|
||||
method: GET,
|
||||
url: "http://{addr}/proxy",
|
||||
headers: {},
|
||||
body: None,
|
||||
proxy: true,
|
||||
response:
|
||||
status: OK,
|
||||
headers: {},
|
||||
body: None,
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
test! {
|
||||
name: client_head_ignores_body,
|
||||
@@ -442,7 +419,6 @@ test! {
|
||||
url: "http://{addr}/head",
|
||||
headers: {},
|
||||
body: None,
|
||||
proxy: false,
|
||||
response:
|
||||
status: OK,
|
||||
headers: {},
|
||||
@@ -473,7 +449,6 @@ test! {
|
||||
url: "http://{addr}/pipe",
|
||||
headers: {},
|
||||
body: None,
|
||||
proxy: false,
|
||||
response:
|
||||
status: OK,
|
||||
headers: {},
|
||||
@@ -500,7 +475,6 @@ test! {
|
||||
url: "http://{addr}/err",
|
||||
headers: {},
|
||||
body: None,
|
||||
proxy: false,
|
||||
error: |err| match err {
|
||||
&hyper::Error::Incomplete => true,
|
||||
_ => false,
|
||||
@@ -527,7 +501,6 @@ test! {
|
||||
url: "http://{addr}/err",
|
||||
headers: {},
|
||||
body: None,
|
||||
proxy: false,
|
||||
error: |err| match err {
|
||||
&hyper::Error::Version => true,
|
||||
_ => false,
|
||||
@@ -562,7 +535,6 @@ test! {
|
||||
"Content-Length" => "7",
|
||||
},
|
||||
body: Some("foo bar"),
|
||||
proxy: false,
|
||||
response:
|
||||
status: OK,
|
||||
headers: {},
|
||||
@@ -592,7 +564,6 @@ test! {
|
||||
url: "http://{addr}/upgrade",
|
||||
headers: {},
|
||||
body: None,
|
||||
proxy: false,
|
||||
error: |err| match err {
|
||||
&hyper::Error::Upgrade => true,
|
||||
_ => false,
|
||||
@@ -618,7 +589,6 @@ test! {
|
||||
url: "http://{addr}/",
|
||||
headers: {},
|
||||
body: None,
|
||||
proxy: false,
|
||||
error: |err| match err {
|
||||
&hyper::Error::Method => true,
|
||||
_ => false,
|
||||
@@ -648,7 +618,6 @@ test! {
|
||||
url: "http://{addr}/no-host/{addr}",
|
||||
headers: {},
|
||||
body: None,
|
||||
proxy: false,
|
||||
response:
|
||||
status: OK,
|
||||
headers: {},
|
||||
@@ -755,7 +724,7 @@ mod dispatch_impl {
|
||||
.unwrap();
|
||||
client.request(req).and_then(move |res| {
|
||||
assert_eq!(res.status(), hyper::StatusCode::OK);
|
||||
res.into_parts().1.concat2()
|
||||
res.into_body().into_stream().concat2()
|
||||
}).and_then(|_| {
|
||||
Timeout::new(Duration::from_secs(1), &handle).unwrap()
|
||||
.from_err()
|
||||
@@ -808,7 +777,7 @@ mod dispatch_impl {
|
||||
.unwrap();
|
||||
let res = client.request(req).and_then(move |res| {
|
||||
assert_eq!(res.status(), hyper::StatusCode::OK);
|
||||
res.into_parts().1.concat2()
|
||||
res.into_body().into_stream().concat2()
|
||||
});
|
||||
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
|
||||
core.run(res.join(rx).map(|r| r.0)).unwrap();
|
||||
@@ -975,7 +944,7 @@ mod dispatch_impl {
|
||||
.unwrap();
|
||||
let res = client.request(req).and_then(move |res| {
|
||||
assert_eq!(res.status(), hyper::StatusCode::OK);
|
||||
res.into_parts().1.concat2()
|
||||
res.into_body().into_stream().concat2()
|
||||
});
|
||||
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
|
||||
core.run(res.join(rx).map(|r| r.0)).unwrap();
|
||||
@@ -1023,7 +992,7 @@ mod dispatch_impl {
|
||||
.unwrap();
|
||||
let res = client.request(req).and_then(move |res| {
|
||||
assert_eq!(res.status(), hyper::StatusCode::OK);
|
||||
res.into_parts().1.concat2()
|
||||
res.into_body().into_stream().concat2()
|
||||
});
|
||||
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
|
||||
core.run(res.join(rx).map(|r| r.0)).unwrap();
|
||||
@@ -1078,7 +1047,7 @@ mod dispatch_impl {
|
||||
.unwrap();
|
||||
let res = client.request(req).and_then(move |res| {
|
||||
assert_eq!(res.status(), hyper::StatusCode::OK);
|
||||
res.into_parts().1.concat2()
|
||||
res.into_body().into_stream().concat2()
|
||||
});
|
||||
|
||||
core.run(res).unwrap();
|
||||
@@ -1130,7 +1099,7 @@ mod dispatch_impl {
|
||||
.unwrap();
|
||||
let res = client.request(req).and_then(move |res| {
|
||||
assert_eq!(res.status(), hyper::StatusCode::OK);
|
||||
res.into_parts().1.concat2()
|
||||
res.into_body().into_stream().concat2()
|
||||
});
|
||||
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
|
||||
|
||||
@@ -1215,8 +1184,6 @@ mod dispatch_impl {
|
||||
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
|
||||
let req = Request::builder()
|
||||
.uri(&*format!("http://{}/a", addr))
|
||||
//TODO: remove this header when auto lengths are fixed
|
||||
.header("content-length", "0")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
let res = client.request(req);
|
||||
@@ -1227,8 +1194,6 @@ mod dispatch_impl {
|
||||
let rx = rx2.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
|
||||
let req = Request::builder()
|
||||
.uri(&*format!("http://{}/b", addr))
|
||||
//TODO: remove this header when auto lengths are fixed
|
||||
.header("content-length", "0")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
let res = client.request(req);
|
||||
@@ -1281,8 +1246,6 @@ mod dispatch_impl {
|
||||
let req = Request::builder()
|
||||
.method("HEAD")
|
||||
.uri(&*format!("http://{}/a", addr))
|
||||
//TODO: remove this header when auto lengths are fixed
|
||||
.header("content-length", "0")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
let res = client.request(req);
|
||||
@@ -1293,8 +1256,6 @@ mod dispatch_impl {
|
||||
let rx = rx2.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
|
||||
let req = Request::builder()
|
||||
.uri(&*format!("http://{}/b", addr))
|
||||
//TODO: remove this header when auto lengths are fixed
|
||||
.header("content-length", "0")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
let res = client.request(req);
|
||||
@@ -1434,7 +1395,7 @@ mod conn {
|
||||
.unwrap();
|
||||
let res = client.send_request(req).and_then(move |res| {
|
||||
assert_eq!(res.status(), hyper::StatusCode::OK);
|
||||
res.into_body().concat2()
|
||||
res.into_body().into_stream().concat2()
|
||||
});
|
||||
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
|
||||
|
||||
@@ -1481,7 +1442,7 @@ mod conn {
|
||||
|
||||
let res = client.send_request(req).and_then(move |res| {
|
||||
assert_eq!(res.status(), hyper::StatusCode::OK);
|
||||
res.into_body().concat2()
|
||||
res.into_body().into_stream().concat2()
|
||||
});
|
||||
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
|
||||
|
||||
@@ -1522,7 +1483,7 @@ mod conn {
|
||||
.unwrap();
|
||||
let res1 = client.send_request(req).and_then(move |res| {
|
||||
assert_eq!(res.status(), hyper::StatusCode::OK);
|
||||
res.into_body().concat2()
|
||||
res.into_body().into_stream().concat2()
|
||||
});
|
||||
|
||||
// pipelined request will hit NotReady, and thus should return an Error::Cancel
|
||||
@@ -1599,7 +1560,7 @@ mod conn {
|
||||
let res = client.send_request(req).and_then(move |res| {
|
||||
assert_eq!(res.status(), hyper::StatusCode::SWITCHING_PROTOCOLS);
|
||||
assert_eq!(res.headers()["Upgrade"], "foobar");
|
||||
res.into_body().concat2()
|
||||
res.into_body().into_stream().concat2()
|
||||
});
|
||||
|
||||
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
|
||||
@@ -1680,7 +1641,7 @@ mod conn {
|
||||
let res = client.send_request(req)
|
||||
.and_then(move |res| {
|
||||
assert_eq!(res.status(), hyper::StatusCode::OK);
|
||||
res.into_body().concat2()
|
||||
res.into_body().into_stream().concat2()
|
||||
})
|
||||
.map(|body| {
|
||||
assert_eq!(body.as_ref(), b"");
|
||||
|
||||
375
tests/server.rs
375
tests/server.rs
@@ -99,8 +99,8 @@ fn get_implicitly_empty() {
|
||||
type Future = Box<Future<Item=Self::Response, Error=Self::Error>>;
|
||||
|
||||
fn call(&self, req: Request<Body>) -> Self::Future {
|
||||
Box::new(req.into_parts()
|
||||
.1
|
||||
Box::new(req.into_body()
|
||||
.into_stream()
|
||||
.concat2()
|
||||
.map(|buf| {
|
||||
assert!(buf.is_empty());
|
||||
@@ -110,112 +110,188 @@ fn get_implicitly_empty() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_fixed_response() {
|
||||
let foo_bar = b"foo bar baz";
|
||||
let server = serve();
|
||||
server.reply()
|
||||
.header("content-length", foo_bar.len().to_string())
|
||||
.body(foo_bar);
|
||||
let mut req = connect(server.addr());
|
||||
req.write_all(b"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: example.domain\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
").unwrap();
|
||||
let mut body = String::new();
|
||||
req.read_to_string(&mut body).unwrap();
|
||||
let n = body.find("\r\n\r\n").unwrap() + 4;
|
||||
mod response_body_lengths {
|
||||
use super::*;
|
||||
|
||||
assert_eq!(&body[n..], "foo bar baz");
|
||||
}
|
||||
struct TestCase {
|
||||
version: usize,
|
||||
headers: &'static [(&'static str, &'static str)],
|
||||
body: Bd,
|
||||
expects_chunked: bool,
|
||||
expects_con_len: bool,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_chunked_response() {
|
||||
let foo_bar = b"foo bar baz";
|
||||
let server = serve();
|
||||
server.reply()
|
||||
.header("transfer-encoding", "chunked")
|
||||
.body(foo_bar);
|
||||
let mut req = connect(server.addr());
|
||||
req.write_all(b"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: example.domain\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
").unwrap();
|
||||
let mut body = String::new();
|
||||
req.read_to_string(&mut body).unwrap();
|
||||
let n = body.find("\r\n\r\n").unwrap() + 4;
|
||||
enum Bd {
|
||||
Known(&'static str),
|
||||
Unknown(&'static str),
|
||||
}
|
||||
|
||||
assert_eq!(&body[n..], "B\r\nfoo bar baz\r\n0\r\n\r\n");
|
||||
}
|
||||
fn run_test(case: TestCase) {
|
||||
assert!(case.version == 0 || case.version == 1, "TestCase.version must 0 or 1");
|
||||
|
||||
#[test]
|
||||
fn get_auto_response() {
|
||||
let foo_bar = b"foo bar baz";
|
||||
let server = serve();
|
||||
server.reply()
|
||||
.body(foo_bar);
|
||||
let mut req = connect(server.addr());
|
||||
req.write_all(b"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: example.domain\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
").unwrap();
|
||||
let mut body = String::new();
|
||||
req.read_to_string(&mut body).unwrap();
|
||||
let server = serve();
|
||||
|
||||
assert!(has_header(&body, "Transfer-Encoding: chunked"));
|
||||
let mut reply = server.reply();
|
||||
for header in case.headers {
|
||||
reply = reply.header(header.0, header.1);
|
||||
}
|
||||
|
||||
let n = body.find("\r\n\r\n").unwrap() + 4;
|
||||
assert_eq!(&body[n..], "B\r\nfoo bar baz\r\n0\r\n\r\n");
|
||||
}
|
||||
let body_str = match case.body {
|
||||
Bd::Known(b) => {
|
||||
reply.body(b);
|
||||
b
|
||||
},
|
||||
Bd::Unknown(b) => {
|
||||
let (mut tx, body) = hyper::Body::channel();
|
||||
tx.send_data(b.into()).expect("send_data");
|
||||
reply.body_stream(body);
|
||||
b
|
||||
},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn http_10_get_auto_response() {
|
||||
let foo_bar = b"foo bar baz";
|
||||
let server = serve();
|
||||
server.reply()
|
||||
.body(foo_bar);
|
||||
let mut req = connect(server.addr());
|
||||
req.write_all(b"\
|
||||
GET / HTTP/1.0\r\n\
|
||||
Host: example.domain\r\n\
|
||||
\r\n\
|
||||
").unwrap();
|
||||
let mut body = String::new();
|
||||
req.read_to_string(&mut body).unwrap();
|
||||
let mut req = connect(server.addr());
|
||||
write!(req, "\
|
||||
GET / HTTP/1.{}\r\n\
|
||||
Host: example.domain\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
", case.version).expect("request write");
|
||||
let mut body = String::new();
|
||||
req.read_to_string(&mut body).unwrap();
|
||||
|
||||
assert!(!has_header(&body, "Transfer-Encoding:"));
|
||||
assert_eq!(
|
||||
case.expects_chunked,
|
||||
has_header(&body, "transfer-encoding:"),
|
||||
"expects_chunked"
|
||||
);
|
||||
assert_eq!(
|
||||
case.expects_con_len,
|
||||
has_header(&body, "content-length:"),
|
||||
"expects_con_len"
|
||||
);
|
||||
|
||||
let n = body.find("\r\n\r\n").unwrap() + 4;
|
||||
assert_eq!(&body[n..], "foo bar baz");
|
||||
}
|
||||
let n = body.find("\r\n\r\n").unwrap() + 4;
|
||||
|
||||
#[test]
|
||||
fn http_10_get_chunked_response() {
|
||||
let foo_bar = b"foo bar baz";
|
||||
let server = serve();
|
||||
server.reply()
|
||||
// this header should actually get removed
|
||||
.header("transfer-encoding", "chunked")
|
||||
.body(foo_bar);
|
||||
let mut req = connect(server.addr());
|
||||
req.write_all(b"\
|
||||
GET / HTTP/1.0\r\n\
|
||||
Host: example.domain\r\n\
|
||||
\r\n\
|
||||
").unwrap();
|
||||
let mut body = String::new();
|
||||
req.read_to_string(&mut body).unwrap();
|
||||
if case.expects_chunked {
|
||||
let len = body.len();
|
||||
assert_eq!(&body[n + 1..n + 3], "\r\n", "expected body chunk size header");
|
||||
assert_eq!(&body[n + 3..len - 7], body_str, "expected body");
|
||||
assert_eq!(&body[len - 7..], "\r\n0\r\n\r\n", "expected body final chunk size header");
|
||||
} else {
|
||||
assert_eq!(&body[n..], body_str, "expected body");
|
||||
}
|
||||
}
|
||||
|
||||
assert!(!has_header(&body, "Transfer-Encoding:"));
|
||||
#[test]
|
||||
fn get_fixed_response_known() {
|
||||
run_test(TestCase {
|
||||
version: 1,
|
||||
headers: &[("content-length", "11")],
|
||||
body: Bd::Known("foo bar baz"),
|
||||
expects_chunked: false,
|
||||
expects_con_len: true,
|
||||
});
|
||||
}
|
||||
|
||||
let n = body.find("\r\n\r\n").unwrap() + 4;
|
||||
assert_eq!(&body[n..], "foo bar baz");
|
||||
#[test]
|
||||
fn get_fixed_response_unknown() {
|
||||
run_test(TestCase {
|
||||
version: 1,
|
||||
headers: &[("content-length", "11")],
|
||||
body: Bd::Unknown("foo bar baz"),
|
||||
expects_chunked: false,
|
||||
expects_con_len: true,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_chunked_response_known() {
|
||||
run_test(TestCase {
|
||||
version: 1,
|
||||
headers: &[("transfer-encoding", "chunked")],
|
||||
// even though we know the length, don't strip user's TE header
|
||||
body: Bd::Known("foo bar baz"),
|
||||
expects_chunked: true,
|
||||
expects_con_len: false,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_chunked_response_unknown() {
|
||||
run_test(TestCase {
|
||||
version: 1,
|
||||
headers: &[("transfer-encoding", "chunked")],
|
||||
body: Bd::Unknown("foo bar baz"),
|
||||
expects_chunked: true,
|
||||
expects_con_len: false,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_chunked_response_trumps_length() {
|
||||
run_test(TestCase {
|
||||
version: 1,
|
||||
headers: &[
|
||||
("transfer-encoding", "chunked"),
|
||||
// both headers means content-length is stripped
|
||||
("content-length", "11"),
|
||||
],
|
||||
body: Bd::Known("foo bar baz"),
|
||||
expects_chunked: true,
|
||||
expects_con_len: false,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_auto_response_with_entity_unknown_length() {
|
||||
run_test(TestCase {
|
||||
version: 1,
|
||||
// no headers means trying to guess from Entity
|
||||
headers: &[],
|
||||
body: Bd::Unknown("foo bar baz"),
|
||||
expects_chunked: true,
|
||||
expects_con_len: false,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_auto_response_with_entity_known_length() {
|
||||
run_test(TestCase {
|
||||
version: 1,
|
||||
// no headers means trying to guess from Entity
|
||||
headers: &[],
|
||||
body: Bd::Known("foo bar baz"),
|
||||
expects_chunked: false,
|
||||
expects_con_len: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn http_10_get_auto_response_with_entity_unknown_length() {
|
||||
run_test(TestCase {
|
||||
version: 0,
|
||||
// no headers means trying to guess from Entity
|
||||
headers: &[],
|
||||
body: Bd::Unknown("foo bar baz"),
|
||||
expects_chunked: false,
|
||||
expects_con_len: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn http_10_get_chunked_response() {
|
||||
run_test(TestCase {
|
||||
version: 0,
|
||||
// http/1.0 should strip this header
|
||||
headers: &[("transfer-encoding", "chunked")],
|
||||
// even when we don't know the length
|
||||
body: Bd::Unknown("foo bar baz"),
|
||||
expects_chunked: false,
|
||||
expects_con_len: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -314,67 +390,6 @@ fn post_with_incomplete_body() {
|
||||
req.read(&mut [0; 256]).expect("read");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_response_chunked() {
|
||||
let server = serve();
|
||||
|
||||
server.reply()
|
||||
.body("");
|
||||
|
||||
let mut req = connect(server.addr());
|
||||
req.write_all(b"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: example.domain\r\n\
|
||||
Content-Length: 0\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
").unwrap();
|
||||
|
||||
|
||||
let mut response = String::new();
|
||||
req.read_to_string(&mut response).unwrap();
|
||||
|
||||
assert!(response.contains("Transfer-Encoding: chunked\r\n"));
|
||||
|
||||
let mut lines = response.lines();
|
||||
assert_eq!(lines.next(), Some("HTTP/1.1 200 OK"));
|
||||
|
||||
let mut lines = lines.skip_while(|line| !line.is_empty());
|
||||
assert_eq!(lines.next(), Some(""));
|
||||
// 0\r\n\r\n
|
||||
assert_eq!(lines.next(), Some("0"));
|
||||
assert_eq!(lines.next(), Some(""));
|
||||
assert_eq!(lines.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_response_chunked_without_body_should_set_content_length() {
|
||||
extern crate pretty_env_logger;
|
||||
let _ = pretty_env_logger::try_init();
|
||||
let server = serve();
|
||||
server.reply()
|
||||
.header("transfer-encoding", "chunked");
|
||||
let mut req = connect(server.addr());
|
||||
req.write_all(b"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: example.domain\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
").unwrap();
|
||||
|
||||
let mut response = String::new();
|
||||
req.read_to_string(&mut response).unwrap();
|
||||
|
||||
assert!(!response.contains("Transfer-Encoding: chunked\r\n"));
|
||||
assert!(response.contains("Content-Length: 0\r\n"));
|
||||
|
||||
let mut lines = response.lines();
|
||||
assert_eq!(lines.next(), Some("HTTP/1.1 200 OK"));
|
||||
|
||||
let mut lines = lines.skip_while(|line| !line.is_empty());
|
||||
assert_eq!(lines.next(), Some(""));
|
||||
assert_eq!(lines.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn head_response_can_send_content_length() {
|
||||
@@ -394,7 +409,7 @@ fn head_response_can_send_content_length() {
|
||||
let mut response = String::new();
|
||||
req.read_to_string(&mut response).unwrap();
|
||||
|
||||
assert!(response.contains("Content-Length: 1024\r\n"));
|
||||
assert!(response.contains("content-length: 1024\r\n"));
|
||||
|
||||
let mut lines = response.lines();
|
||||
assert_eq!(lines.next(), Some("HTTP/1.1 200 OK"));
|
||||
@@ -423,7 +438,7 @@ fn response_does_not_set_chunked_if_body_not_allowed() {
|
||||
let mut response = String::new();
|
||||
req.read_to_string(&mut response).unwrap();
|
||||
|
||||
assert!(!response.contains("Transfer-Encoding"));
|
||||
assert!(!response.contains("transfer-encoding"));
|
||||
|
||||
let mut lines = response.lines();
|
||||
assert_eq!(lines.next(), Some("HTTP/1.1 304 Not Modified"));
|
||||
@@ -691,13 +706,13 @@ fn pipeline_enabled() {
|
||||
{
|
||||
let mut lines = buf.split(|&b| b == b'\n');
|
||||
assert_eq!(s(lines.next().unwrap()), "HTTP/1.1 200 OK\r");
|
||||
assert_eq!(s(lines.next().unwrap()), "Content-Length: 12\r");
|
||||
assert_eq!(s(lines.next().unwrap()), "content-length: 12\r");
|
||||
lines.next().unwrap(); // Date
|
||||
assert_eq!(s(lines.next().unwrap()), "\r");
|
||||
assert_eq!(s(lines.next().unwrap()), "Hello World");
|
||||
|
||||
assert_eq!(s(lines.next().unwrap()), "HTTP/1.1 200 OK\r");
|
||||
assert_eq!(s(lines.next().unwrap()), "Content-Length: 12\r");
|
||||
assert_eq!(s(lines.next().unwrap()), "content-length: 12\r");
|
||||
lines.next().unwrap(); // Date
|
||||
assert_eq!(s(lines.next().unwrap()), "\r");
|
||||
assert_eq!(s(lines.next().unwrap()), "Hello World");
|
||||
@@ -720,7 +735,7 @@ fn http_10_request_receives_http_10_response() {
|
||||
\r\n\
|
||||
").unwrap();
|
||||
|
||||
let expected = "HTTP/1.0 200 OK\r\nContent-Length: 0\r\n";
|
||||
let expected = "HTTP/1.0 200 OK\r\ncontent-length: 0\r\n";
|
||||
let mut buf = [0; 256];
|
||||
let n = req.read(&mut buf).unwrap();
|
||||
assert!(n >= expected.len(), "read: {:?} >= {:?}", n, expected.len());
|
||||
@@ -729,6 +744,7 @@ fn http_10_request_receives_http_10_response() {
|
||||
|
||||
#[test]
|
||||
fn disable_keep_alive_mid_request() {
|
||||
|
||||
let mut core = Core::new().unwrap();
|
||||
let listener = TcpListener::bind(&"127.0.0.1:0".parse().unwrap(), &core.handle()).unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
@@ -773,6 +789,7 @@ fn disable_keep_alive_mid_request() {
|
||||
|
||||
#[test]
|
||||
fn disable_keep_alive_post_request() {
|
||||
let _ = pretty_env_logger::try_init();
|
||||
let mut core = Core::new().unwrap();
|
||||
let listener = TcpListener::bind(&"127.0.0.1:0".parse().unwrap(), &core.handle()).unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
@@ -790,10 +807,11 @@ fn disable_keep_alive_post_request() {
|
||||
let mut buf = [0; 1024 * 8];
|
||||
loop {
|
||||
let n = req.read(&mut buf).expect("reading 1");
|
||||
if n < buf.len() {
|
||||
if &buf[n - HELLO.len()..n] == HELLO.as_bytes() {
|
||||
break;
|
||||
}
|
||||
if &buf[n - HELLO.len()..n] == HELLO.as_bytes() {
|
||||
break;
|
||||
}
|
||||
if n == 0 {
|
||||
panic!("unexpected eof");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1113,16 +1131,14 @@ fn streaming_body() {
|
||||
.map_err(|_| unreachable!())
|
||||
.and_then(|(item, _incoming)| {
|
||||
let (socket, _) = item.unwrap();
|
||||
Http::<& &'static [u8]>::new()
|
||||
Http::<hyper::Chunk>::new()
|
||||
.keep_alive(false)
|
||||
.serve_connection(socket, service_fn(|_| {
|
||||
static S: &'static [&'static [u8]] = &[&[b'x'; 1_000] as &[u8]; 1_00] as _;
|
||||
let b = ::futures::stream::iter_ok(S.iter());
|
||||
let b = ::futures::stream::iter_ok(S.into_iter())
|
||||
.map(|&s| s);
|
||||
let b = hyper::Body::wrap_stream(b);
|
||||
Ok(Response::new(b))
|
||||
/*
|
||||
Ok(Response::<futures::stream::IterOk<::std::slice::Iter<&'static [u8]>, ::hyper::Error>>::new()
|
||||
.with_body(b))
|
||||
*/
|
||||
}))
|
||||
.map(|_| ())
|
||||
});
|
||||
@@ -1195,7 +1211,12 @@ impl<'a> ReplyBuilder<'a> {
|
||||
}
|
||||
|
||||
fn body<T: AsRef<[u8]>>(self, body: T) {
|
||||
self.tx.send(Reply::Body(body.as_ref().into())).unwrap();
|
||||
self.tx.send(Reply::Body(body.as_ref().to_vec().into())).unwrap();
|
||||
}
|
||||
|
||||
fn body_stream(self, body: Body)
|
||||
{
|
||||
self.tx.send(Reply::Body(body)).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1219,11 +1240,11 @@ struct TestService {
|
||||
_timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug)]
|
||||
enum Reply {
|
||||
Status(hyper::StatusCode),
|
||||
Header(HeaderName, HeaderValue),
|
||||
Body(Vec<u8>),
|
||||
Body(hyper::Body),
|
||||
End,
|
||||
}
|
||||
|
||||
@@ -1257,7 +1278,7 @@ impl Service for TestService {
|
||||
let tx2 = self.tx.clone();
|
||||
|
||||
let replies = self.reply.clone();
|
||||
Box::new(req.into_parts().1.for_each(move |chunk| {
|
||||
Box::new(req.into_body().into_stream().for_each(move |chunk| {
|
||||
tx1.lock().unwrap().send(Msg::Chunk(chunk.to_vec())).unwrap();
|
||||
Ok(())
|
||||
}).then(move |result| {
|
||||
@@ -1278,7 +1299,7 @@ impl Service for TestService {
|
||||
res.headers_mut().insert(name, value);
|
||||
},
|
||||
Reply::Body(body) => {
|
||||
*res.body_mut() = body.into();
|
||||
*res.body_mut() = body;
|
||||
},
|
||||
Reply::End => break,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user