feat(http2): add HTTP/2 support for Client and Server
This commit is contained in:
146
tests/integration.rs
Normal file
146
tests/integration.rs
Normal file
@@ -0,0 +1,146 @@
|
||||
#[macro_use]
|
||||
mod support;
|
||||
use self::support::*;
|
||||
|
||||
t! {
|
||||
get_1,
|
||||
client:
|
||||
request:
|
||||
uri: "/",
|
||||
;
|
||||
response:
|
||||
status: 200,
|
||||
;
|
||||
server:
|
||||
request:
|
||||
uri: "/",
|
||||
;
|
||||
response:
|
||||
;
|
||||
}
|
||||
|
||||
t! {
|
||||
get_implicit_path,
|
||||
client:
|
||||
request:
|
||||
uri: "",
|
||||
;
|
||||
response:
|
||||
status: 200,
|
||||
;
|
||||
server:
|
||||
request:
|
||||
uri: "/",
|
||||
;
|
||||
response:
|
||||
;
|
||||
}
|
||||
|
||||
t! {
|
||||
get_body,
|
||||
client:
|
||||
request:
|
||||
uri: "/",
|
||||
;
|
||||
response:
|
||||
status: 200,
|
||||
headers: {
|
||||
"content-length" => 11,
|
||||
},
|
||||
body: "hello world",
|
||||
;
|
||||
server:
|
||||
request:
|
||||
uri: "/",
|
||||
;
|
||||
response:
|
||||
headers: {
|
||||
"content-length" => 11,
|
||||
},
|
||||
body: "hello world",
|
||||
;
|
||||
}
|
||||
|
||||
t! {
|
||||
get_body_chunked,
|
||||
client:
|
||||
request:
|
||||
uri: "/",
|
||||
;
|
||||
response:
|
||||
status: 200,
|
||||
headers: {
|
||||
// h2 doesn't actually receive the transfer-encoding header
|
||||
},
|
||||
body: "hello world",
|
||||
;
|
||||
server:
|
||||
request:
|
||||
uri: "/",
|
||||
;
|
||||
response:
|
||||
headers: {
|
||||
// http2 should strip this header
|
||||
"transfer-encoding" => "chunked",
|
||||
},
|
||||
body: "hello world",
|
||||
;
|
||||
}
|
||||
|
||||
t! {
|
||||
post_chunked,
|
||||
client:
|
||||
request:
|
||||
method: "POST",
|
||||
uri: "/post_chunked",
|
||||
headers: {
|
||||
// http2 should strip this header
|
||||
"transfer-encoding" => "chunked",
|
||||
},
|
||||
body: "hello world",
|
||||
;
|
||||
response:
|
||||
;
|
||||
server:
|
||||
request:
|
||||
method: "POST",
|
||||
uri: "/post_chunked",
|
||||
body: "hello world",
|
||||
;
|
||||
response:
|
||||
;
|
||||
}
|
||||
|
||||
t! {
|
||||
get_2,
|
||||
client:
|
||||
request:
|
||||
uri: "/1",
|
||||
;
|
||||
response:
|
||||
status: 200,
|
||||
;
|
||||
request:
|
||||
uri: "/2",
|
||||
;
|
||||
response:
|
||||
status: 200,
|
||||
;
|
||||
server:
|
||||
request:
|
||||
uri: "/1",
|
||||
;
|
||||
response:
|
||||
;
|
||||
request:
|
||||
uri: "/2",
|
||||
;
|
||||
response:
|
||||
;
|
||||
}
|
||||
|
||||
t! {
|
||||
get_parallel_http2,
|
||||
parallel: 0..10
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ fn get_implicitly_empty() {
|
||||
.map_err(|_| unreachable!())
|
||||
.and_then(|(item, _incoming)| {
|
||||
let socket = item.unwrap();
|
||||
Http::<hyper::Chunk>::new().serve_connection(socket, GetImplicitlyEmpty)
|
||||
Http::new().serve_connection(socket, GetImplicitlyEmpty)
|
||||
});
|
||||
|
||||
fut.wait().unwrap();
|
||||
@@ -110,7 +110,6 @@ fn get_implicitly_empty() {
|
||||
|
||||
fn call(&self, req: Request<Body>) -> Self::Future {
|
||||
Box::new(req.into_body()
|
||||
|
||||
.concat2()
|
||||
.map(|buf| {
|
||||
assert!(buf.is_empty());
|
||||
@@ -776,13 +775,13 @@ fn disable_keep_alive_mid_request() {
|
||||
.map_err(|_| unreachable!())
|
||||
.and_then(|(item, _incoming)| {
|
||||
let socket = item.unwrap();
|
||||
Http::<hyper::Chunk>::new().serve_connection(socket, HelloWorld)
|
||||
Http::new().serve_connection(socket, HelloWorld)
|
||||
.select2(rx1)
|
||||
.then(|r| {
|
||||
match r {
|
||||
Ok(Either::A(_)) => panic!("expected rx first"),
|
||||
Ok(Either::B(((), mut conn))) => {
|
||||
conn.disable_keep_alive();
|
||||
conn.graceful_shutdown();
|
||||
tx2.send(()).unwrap();
|
||||
conn
|
||||
}
|
||||
@@ -841,13 +840,13 @@ fn disable_keep_alive_post_request() {
|
||||
stream: socket,
|
||||
_debug: dropped2,
|
||||
};
|
||||
Http::<hyper::Chunk>::new().serve_connection(transport, HelloWorld)
|
||||
Http::new().serve_connection(transport, HelloWorld)
|
||||
.select2(rx1)
|
||||
.then(|r| {
|
||||
match r {
|
||||
Ok(Either::A(_)) => panic!("expected rx first"),
|
||||
Ok(Either::B(((), mut conn))) => {
|
||||
conn.disable_keep_alive();
|
||||
conn.graceful_shutdown();
|
||||
conn
|
||||
}
|
||||
Err(Either::A((e, _))) => panic!("unexpected error {}", e),
|
||||
@@ -883,7 +882,7 @@ fn empty_parse_eof_does_not_return_error() {
|
||||
.map_err(|_| unreachable!())
|
||||
.and_then(|(item, _incoming)| {
|
||||
let socket = item.unwrap();
|
||||
Http::<hyper::Chunk>::new().serve_connection(socket, HelloWorld)
|
||||
Http::new().serve_connection(socket, HelloWorld)
|
||||
});
|
||||
|
||||
fut.wait().unwrap();
|
||||
@@ -905,8 +904,7 @@ fn nonempty_parse_eof_returns_error() {
|
||||
.map_err(|_| unreachable!())
|
||||
.and_then(|(item, _incoming)| {
|
||||
let socket = item.unwrap();
|
||||
Http::<hyper::Chunk>::new().serve_connection(socket, HelloWorld)
|
||||
.map(|_| ())
|
||||
Http::new().serve_connection(socket, HelloWorld)
|
||||
});
|
||||
|
||||
fut.wait().unwrap_err();
|
||||
@@ -933,14 +931,13 @@ fn returning_1xx_response_is_error() {
|
||||
.map_err(|_| unreachable!())
|
||||
.and_then(|(item, _incoming)| {
|
||||
let socket = item.unwrap();
|
||||
Http::<hyper::Chunk>::new()
|
||||
Http::new()
|
||||
.serve_connection(socket, service_fn(|_| {
|
||||
Ok::<_, hyper::Error>(Response::builder()
|
||||
.status(StatusCode::CONTINUE)
|
||||
.body(Body::empty())
|
||||
.unwrap())
|
||||
}))
|
||||
.map(|_| ())
|
||||
});
|
||||
|
||||
fut.wait().unwrap_err();
|
||||
@@ -981,7 +978,7 @@ fn upgrades() {
|
||||
.map_err(|_| -> hyper::Error { unreachable!() })
|
||||
.and_then(|(item, _incoming)| {
|
||||
let socket = item.unwrap();
|
||||
let conn = Http::<hyper::Chunk>::new()
|
||||
let conn = Http::new()
|
||||
.serve_connection(socket, service_fn(|_| {
|
||||
let res = Response::builder()
|
||||
.status(101)
|
||||
@@ -1034,9 +1031,8 @@ fn parse_errors_send_4xx_response() {
|
||||
.map_err(|_| unreachable!())
|
||||
.and_then(|(item, _incoming)| {
|
||||
let socket = item.unwrap();
|
||||
Http::<hyper::Chunk>::new()
|
||||
Http::new()
|
||||
.serve_connection(socket, HelloWorld)
|
||||
.map(|_| ())
|
||||
});
|
||||
|
||||
fut.wait().unwrap_err();
|
||||
@@ -1063,9 +1059,8 @@ fn illegal_request_length_returns_400_response() {
|
||||
.map_err(|_| unreachable!())
|
||||
.and_then(|(item, _incoming)| {
|
||||
let socket = item.unwrap();
|
||||
Http::<hyper::Chunk>::new()
|
||||
Http::new()
|
||||
.serve_connection(socket, HelloWorld)
|
||||
.map(|_| ())
|
||||
});
|
||||
|
||||
fut.wait().unwrap_err();
|
||||
@@ -1096,10 +1091,9 @@ fn max_buf_size() {
|
||||
.map_err(|_| unreachable!())
|
||||
.and_then(|(item, _incoming)| {
|
||||
let socket = item.unwrap();
|
||||
Http::<hyper::Chunk>::new()
|
||||
Http::new()
|
||||
.max_buf_size(MAX)
|
||||
.serve_connection(socket, HelloWorld)
|
||||
.map(|_| ())
|
||||
});
|
||||
|
||||
fut.wait().unwrap_err();
|
||||
@@ -1140,7 +1134,7 @@ fn streaming_body() {
|
||||
.map_err(|_| unreachable!())
|
||||
.and_then(|(item, _incoming)| {
|
||||
let socket = item.unwrap();
|
||||
Http::<hyper::Chunk>::new()
|
||||
Http::new()
|
||||
.keep_alive(false)
|
||||
.serve_connection(socket, service_fn(|_| {
|
||||
static S: &'static [&'static [u8]] = &[&[b'x'; 1_000] as &[u8]; 1_00] as _;
|
||||
@@ -1149,7 +1143,6 @@ fn streaming_body() {
|
||||
let b = hyper::Body::wrap_stream(b);
|
||||
Ok::<_, hyper::Error>(Response::new(b))
|
||||
}))
|
||||
.map(|_| ())
|
||||
});
|
||||
|
||||
fut.join(rx).wait().unwrap();
|
||||
|
||||
328
tests/support/mod.rs
Normal file
328
tests/support/mod.rs
Normal file
@@ -0,0 +1,328 @@
|
||||
pub extern crate futures;
|
||||
pub extern crate hyper;
|
||||
pub extern crate tokio;
|
||||
|
||||
pub use std::net::SocketAddr;
|
||||
pub use self::futures::{future, Future, Stream};
|
||||
pub use self::futures::sync::oneshot;
|
||||
pub use self::hyper::{HeaderMap, StatusCode};
|
||||
pub use self::tokio::runtime::Runtime;
|
||||
|
||||
macro_rules! t {
|
||||
(
|
||||
$name:ident,
|
||||
parallel: $range:expr
|
||||
) => (
|
||||
#[test]
|
||||
fn $name() {
|
||||
|
||||
let mut c = vec![];
|
||||
let mut s = vec![];
|
||||
|
||||
for _i in $range {
|
||||
c.push((
|
||||
__CReq {
|
||||
uri: "/",
|
||||
..Default::default()
|
||||
},
|
||||
__CRes {
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
s.push((
|
||||
__SReq {
|
||||
uri: "/",
|
||||
..Default::default()
|
||||
},
|
||||
__SRes {
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
__run_test(__TestConfig {
|
||||
client_version: 2,
|
||||
client_msgs: c,
|
||||
server_version: 2,
|
||||
server_msgs: s,
|
||||
parallel: true,
|
||||
connections: 1,
|
||||
});
|
||||
|
||||
}
|
||||
);
|
||||
(
|
||||
$name:ident,
|
||||
client: $(
|
||||
request: $(
|
||||
$c_req_prop:ident: $c_req_val:tt,
|
||||
)*;
|
||||
response: $(
|
||||
$c_res_prop:ident: $c_res_val:tt,
|
||||
)*;
|
||||
)*
|
||||
server: $(
|
||||
request: $(
|
||||
$s_req_prop:ident: $s_req_val:tt,
|
||||
)*;
|
||||
response: $(
|
||||
$s_res_prop:ident: $s_res_val:tt,
|
||||
)*;
|
||||
)*
|
||||
) => (
|
||||
#[test]
|
||||
fn $name() {
|
||||
let c = vec![$((
|
||||
__CReq {
|
||||
$($c_req_prop: __internal_req_res_prop!($c_req_prop: $c_req_val),)*
|
||||
..Default::default()
|
||||
},
|
||||
__CRes {
|
||||
$($c_res_prop: __internal_req_res_prop!($c_res_prop: $c_res_val),)*
|
||||
..Default::default()
|
||||
}
|
||||
),)*];
|
||||
let s = vec![$((
|
||||
__SReq {
|
||||
$($s_req_prop: __internal_req_res_prop!($s_req_prop: $s_req_val),)*
|
||||
..Default::default()
|
||||
},
|
||||
__SRes {
|
||||
$($s_res_prop: __internal_req_res_prop!($s_res_prop: $s_res_val),)*
|
||||
..Default::default()
|
||||
}
|
||||
),)*];
|
||||
|
||||
__run_test(__TestConfig {
|
||||
client_version: 1,
|
||||
client_msgs: c.clone(),
|
||||
server_version: 1,
|
||||
server_msgs: s.clone(),
|
||||
parallel: false,
|
||||
connections: 1,
|
||||
});
|
||||
|
||||
__run_test(__TestConfig {
|
||||
client_version: 2,
|
||||
client_msgs: c,
|
||||
server_version: 2,
|
||||
server_msgs: s,
|
||||
parallel: false,
|
||||
connections: 1,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! __internal_req_res_prop {
|
||||
(method: $prop_val:expr) => (
|
||||
$prop_val
|
||||
);
|
||||
(status: $prop_val:expr) => (
|
||||
StatusCode::from_u16($prop_val).expect("status code")
|
||||
);
|
||||
(headers: $map:tt) => ({
|
||||
#[allow(unused_mut)]
|
||||
let mut headers = HeaderMap::new();
|
||||
__internal_headers!(headers, $map);
|
||||
headers
|
||||
});
|
||||
($prop_name:ident: $prop_val:expr) => (
|
||||
From::from($prop_val)
|
||||
)
|
||||
}
|
||||
|
||||
macro_rules! __internal_headers {
|
||||
($headers:ident, { $($name:expr => $val:expr,)* }) => {
|
||||
$(
|
||||
$headers.insert($name, $val.to_string().parse().expect("header value"));
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct __CReq {
|
||||
pub method: &'static str,
|
||||
pub uri: &'static str,
|
||||
pub headers: HeaderMap,
|
||||
pub body: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct __CRes {
|
||||
pub status: hyper::StatusCode,
|
||||
pub body: Vec<u8>,
|
||||
pub headers: HeaderMap,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct __SReq {
|
||||
pub method: &'static str,
|
||||
pub uri: &'static str,
|
||||
pub headers: HeaderMap,
|
||||
pub body: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct __SRes {
|
||||
pub status: hyper::StatusCode,
|
||||
pub body: Vec<u8>,
|
||||
pub headers: HeaderMap,
|
||||
}
|
||||
|
||||
pub struct __TestConfig {
|
||||
pub client_version: usize,
|
||||
pub client_msgs: Vec<(__CReq, __CRes)>,
|
||||
|
||||
pub server_version: usize,
|
||||
pub server_msgs: Vec<(__SReq, __SRes)>,
|
||||
|
||||
pub parallel: bool,
|
||||
pub connections: usize,
|
||||
}
|
||||
|
||||
pub fn __run_test(cfg: __TestConfig) {
|
||||
extern crate pretty_env_logger;
|
||||
use hyper::{Body, Client, Request, Response};
|
||||
use hyper::client::HttpConnector;
|
||||
use std::sync::Arc;
|
||||
let _ = pretty_env_logger::try_init();
|
||||
let rt = Runtime::new().expect("new rt");
|
||||
let handle = rt.reactor().clone();
|
||||
|
||||
let connector = HttpConnector::new_with_handle(1, handle.clone());
|
||||
let client = Client::builder()
|
||||
.http2_only(cfg.client_version == 2)
|
||||
.executor(rt.executor())
|
||||
.build::<_, Body>(connector);
|
||||
|
||||
let serve_handles = ::std::sync::Mutex::new(
|
||||
cfg.server_msgs
|
||||
);
|
||||
let service = hyper::server::service_fn(move |req: Request<Body>| -> Box<Future<Item=Response<Body>, Error=hyper::Error> + Send> {
|
||||
let (sreq, sres) = serve_handles.lock()
|
||||
.unwrap()
|
||||
.remove(0);
|
||||
|
||||
assert_eq!(req.uri().path(), sreq.uri);
|
||||
assert_eq!(req.method(), &sreq.method);
|
||||
for (name, value) in &sreq.headers {
|
||||
assert_eq!(
|
||||
req.headers()[name],
|
||||
value
|
||||
);
|
||||
}
|
||||
let sbody = sreq.body;
|
||||
Box::new(req.into_body()
|
||||
.concat2()
|
||||
.map(move |body| {
|
||||
assert_eq!(body.as_ref(), sbody.as_slice());
|
||||
|
||||
let mut res = Response::builder()
|
||||
.status(sres.status)
|
||||
.body(sres.body.into())
|
||||
.expect("Response::build");
|
||||
*res.headers_mut() = sres.headers;
|
||||
res
|
||||
}))
|
||||
});
|
||||
let new_service = hyper::server::const_service(service);
|
||||
|
||||
let serve = hyper::server::Http::new()
|
||||
.http2_only(cfg.server_version == 2)
|
||||
.executor(rt.executor())
|
||||
.serve_addr_handle(
|
||||
&SocketAddr::from(([127, 0, 0, 1], 0)),
|
||||
&handle,
|
||||
new_service,
|
||||
)
|
||||
.expect("serve_addr_handle");
|
||||
|
||||
let addr = serve.incoming_ref().local_addr();
|
||||
let exe = rt.executor();
|
||||
let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
||||
let (success_tx, success_rx) = oneshot::channel();
|
||||
let expected_connections = cfg.connections;
|
||||
let server = serve
|
||||
.fold(0, move |cnt, conn: hyper::server::Connection<_, _>| {
|
||||
exe.spawn(conn.map_err(|e| panic!("server connection error: {}", e)));
|
||||
Ok::<_, hyper::Error>(cnt + 1)
|
||||
})
|
||||
.map(move |cnt| {
|
||||
assert_eq!(cnt, expected_connections);
|
||||
})
|
||||
.map_err(|e| panic!("serve error: {}", e))
|
||||
.select2(shutdown_rx)
|
||||
.map(move |_| {
|
||||
let _ = success_tx.send(());
|
||||
})
|
||||
.map_err(|_| panic!("shutdown not ok"));
|
||||
|
||||
rt.executor().spawn(server);
|
||||
|
||||
let make_request = Arc::new(move |client: &Client<HttpConnector>, creq: __CReq, cres: __CRes| {
|
||||
let uri = format!("http://{}{}", addr, creq.uri);
|
||||
let mut req = Request::builder()
|
||||
.method(creq.method)
|
||||
.uri(uri)
|
||||
//.headers(creq.headers)
|
||||
.body(creq.body.into())
|
||||
.expect("Request::build");
|
||||
*req.headers_mut() = creq.headers;
|
||||
let cstatus = cres.status;
|
||||
let cheaders = cres.headers;
|
||||
let cbody = cres.body;
|
||||
|
||||
client.request(req)
|
||||
.and_then(move |res| {
|
||||
assert_eq!(res.status(), cstatus);
|
||||
//assert_eq!(res.version(), c_version);
|
||||
for (name, value) in &cheaders {
|
||||
assert_eq!(
|
||||
res.headers()[name],
|
||||
value
|
||||
);
|
||||
}
|
||||
res.into_body().concat2()
|
||||
})
|
||||
.map(move |body| {
|
||||
assert_eq!(body.as_ref(), cbody.as_slice());
|
||||
})
|
||||
.map_err(|e| panic!("client error: {}", e))
|
||||
});
|
||||
|
||||
|
||||
let client_futures: Box<Future<Item=(), Error=()> + Send> = if cfg.parallel {
|
||||
let mut client_futures = vec![];
|
||||
for (creq, cres) in cfg.client_msgs {
|
||||
client_futures.push(make_request(&client, creq, cres));
|
||||
}
|
||||
drop(client);
|
||||
Box::new(future::join_all(client_futures).map(|_| ()))
|
||||
} else {
|
||||
let mut client_futures: Box<Future<Item=Client<HttpConnector>, Error=()> + Send> =
|
||||
Box::new(future::ok(client));
|
||||
for (creq, cres) in cfg.client_msgs {
|
||||
let mk_request = make_request.clone();
|
||||
client_futures = Box::new(
|
||||
client_futures
|
||||
.and_then(move |client| {
|
||||
let fut = mk_request(&client, creq, cres);
|
||||
fut.map(move |()| client)
|
||||
})
|
||||
);
|
||||
}
|
||||
Box::new(client_futures.map(|_| ()))
|
||||
};
|
||||
|
||||
let client_futures = client_futures.map(move |_| {
|
||||
let _ = shutdown_tx.send(());
|
||||
});
|
||||
rt.executor().spawn(client_futures);
|
||||
rt.shutdown_on_idle().wait().expect("rt");
|
||||
success_rx
|
||||
.map_err(|_| "something panicked")
|
||||
.wait()
|
||||
.expect("shutdown succeeded");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user