Add client support for server push (#314)

This patch exposes push promises to the client API.

Closes #252
This commit is contained in:
Michael Beaumont
2018-10-16 21:51:08 +02:00
committed by Carl Lerche
parent 6d8554a23c
commit 6b23542a55
11 changed files with 397 additions and 76 deletions

View File

@@ -220,6 +220,18 @@ impl Mock<frame::PushPromise> {
Mock(frame)
}
pub fn field<K, V>(self, key: K, value: V) -> Self
where
K: HttpTryInto<http::header::HeaderName>,
V: HttpTryInto<http::header::HeaderValue>,
{
let (id, promised, pseudo, mut fields) = self.into_parts();
fields.insert(key.try_into().unwrap(), value.try_into().unwrap());
let frame = frame::PushPromise::new(id, promised, pseudo, fields);
Mock(frame)
}
fn into_parts(self) -> (StreamId, StreamId, frame::Pseudo, HeaderMap) {
assert!(self.0.is_end_headers(), "unset eoh will be lost");
let id = self.0.stream_id();

View File

@@ -4,8 +4,6 @@ use h2_support::prelude::*;
#[test]
fn recv_push_works() {
// tests that by default, received push promises work
// TODO: once API exists, read the pushed response
let _ = ::env_logger::try_init();
let (io, srv) = mock::new();
@@ -17,9 +15,11 @@ fn recv_push_works() {
.request("GET", "https://http2.akamai.com/")
.eos(),
)
.send_frame(frames::headers(1).response(404))
.send_frame(frames::push_promise(1, 2).request("GET", "https://http2.akamai.com/style.css"))
.send_frame(frames::headers(1).response(200).eos())
.send_frame(frames::headers(2).response(200).eos());
.send_frame(frames::data(1, "").eos())
.send_frame(frames::headers(2).response(200))
.send_frame(frames::data(2, "promised_data").eos());
let h2 = client::handshake(io).unwrap().and_then(|(mut client, h2)| {
let request = Request::builder()
@@ -27,16 +27,82 @@ fn recv_push_works() {
.uri("https://http2.akamai.com/")
.body(())
.unwrap();
let req = client
let (mut resp, _) = client
.send_request(request, true)
.unwrap()
.0.unwrap()
.and_then(|resp| {
.unwrap();
let pushed = resp.push_promises();
let check_resp_status = resp.unwrap().map(|resp| {
assert_eq!(resp.status(), StatusCode::NOT_FOUND)
});
let check_pushed_request = pushed.and_then(|headers| {
let (request, response) = headers.into_parts();
assert_eq!(request.into_parts().0.method, Method::GET);
response
});
let check_pushed_response = check_pushed_request.and_then(
|resp| {
assert_eq!(resp.status(), StatusCode::OK);
Ok(())
});
resp.into_body().concat2().map(|b| assert_eq!(b, "promised_data"))
}
).collect().unwrap().map(|ps| {
assert_eq!(1, ps.len())
});
h2.drive(check_resp_status.join(check_pushed_response))
});
h2.drive(req)
h2.join(mock).wait().unwrap();
}
#[test]
fn pushed_streams_arent_dropped_too_early() {
// tests that by default, received push promises work
let _ = ::env_logger::try_init();
let (io, srv) = mock::new();
let mock = srv.assert_client_handshake()
.unwrap()
.recv_settings()
.recv_frame(
frames::headers(1)
.request("GET", "https://http2.akamai.com/")
.eos(),
)
.send_frame(frames::headers(1).response(404))
.send_frame(frames::push_promise(1, 2).request("GET", "https://http2.akamai.com/style.css"))
.send_frame(frames::push_promise(1, 4).request("GET", "https://http2.akamai.com/style2.css"))
.send_frame(frames::data(1, "").eos())
.idle_ms(10)
.send_frame(frames::headers(2).response(200))
.send_frame(frames::headers(4).response(200).eos())
.send_frame(frames::data(2, "").eos())
.recv_frame(frames::go_away(4));
let h2 = client::handshake(io).unwrap().and_then(|(mut client, h2)| {
let request = Request::builder()
.method(Method::GET)
.uri("https://http2.akamai.com/")
.body(())
.unwrap();
let (mut resp, _) = client
.send_request(request, true)
.unwrap();
let pushed = resp.push_promises();
let check_status = resp.unwrap().and_then(|resp| {
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
Ok(())
});
let check_pushed_headers = pushed.and_then(|headers| {
let (request, response) = headers.into_parts();
assert_eq!(request.into_parts().0.method, Method::GET);
response
});
let check_pushed = check_pushed_headers.map(
|resp| assert_eq!(resp.status(), StatusCode::OK)
).collect().unwrap().and_then(|ps| {
assert_eq!(2, ps.len());
Ok(())
});
h2.drive(check_status.join(check_pushed)).and_then(|(conn, _)| conn.expect("client"))
});
h2.join(mock).wait().unwrap();
@@ -189,9 +255,58 @@ fn recv_push_promise_over_max_header_list_size() {
}
#[test]
#[ignore]
fn recv_push_promise_with_unsafe_method_is_stream_error() {
// for instance, when :method = POST
fn recv_invalid_push_promise_headers_is_stream_protocol_error() {
// Unsafe method or content length is stream protocol error
let _ = ::env_logger::try_init();
let (io, srv) = mock::new();
let mock = srv.assert_client_handshake()
.unwrap()
.recv_settings()
.recv_frame(
frames::headers(1)
.request("GET", "https://http2.akamai.com/")
.eos(),
)
.send_frame(frames::headers(1).response(404))
.send_frame(frames::push_promise(1, 2).request("POST", "https://http2.akamai.com/style.css"))
.send_frame(
frames::push_promise(1, 4)
.request("GET", "https://http2.akamai.com/style.css")
.field(http::header::CONTENT_LENGTH, 1)
)
.send_frame(
frames::push_promise(1, 6)
.request("GET", "https://http2.akamai.com/style.css")
.field(http::header::CONTENT_LENGTH, 0)
)
.send_frame(frames::headers(1).response(404).eos())
.recv_frame(frames::reset(2).protocol_error())
.recv_frame(frames::reset(4).protocol_error())
.send_frame(frames::headers(6).response(200).eos())
.close();
let h2 = client::handshake(io).unwrap().and_then(|(mut client, h2)| {
let request = Request::builder()
.method(Method::GET)
.uri("https://http2.akamai.com/")
.body(())
.unwrap();
let (mut resp, _) = client
.send_request(request, true)
.unwrap();
let check_pushed_request = resp.push_promises().and_then(|headers| {
headers.into_parts().1
});
let check_pushed_response = check_pushed_request
.collect().unwrap().map(|ps| {
// CONTENT_LENGTH = 0 is ok
assert_eq!(1, ps.len())
});
h2.drive(check_pushed_response)
});
h2.join(mock).wait().unwrap();
}
#[test]
@@ -202,8 +317,6 @@ fn recv_push_promise_with_wrong_authority_is_stream_error() {
#[test]
fn recv_push_promise_skipped_stream_id() {
// tests that by default, received push promises work
// TODO: once API exists, read the pushed response
let _ = ::env_logger::try_init();
let (io, srv) = mock::new();
@@ -218,8 +331,7 @@ fn recv_push_promise_skipped_stream_id() {
.send_frame(frames::push_promise(1, 4).request("GET", "https://http2.akamai.com/style.css"))
.send_frame(frames::push_promise(1, 2).request("GET", "https://http2.akamai.com/style.css"))
.recv_frame(frames::go_away(0).protocol_error())
.close()
;
.close();
let h2 = client::handshake(io).unwrap().and_then(|(mut client, h2)| {
let request = Request::builder()
@@ -255,8 +367,6 @@ fn recv_push_promise_skipped_stream_id() {
#[test]
fn recv_push_promise_dup_stream_id() {
// tests that by default, received push promises work
// TODO: once API exists, read the pushed response
let _ = ::env_logger::try_init();
let (io, srv) = mock::new();
@@ -271,8 +381,7 @@ fn recv_push_promise_dup_stream_id() {
.send_frame(frames::push_promise(1, 2).request("GET", "https://http2.akamai.com/style.css"))
.send_frame(frames::push_promise(1, 2).request("GET", "https://http2.akamai.com/style.css"))
.recv_frame(frames::go_away(0).protocol_error())
.close()
;
.close();
let h2 = client::handshake(io).unwrap().and_then(|(mut client, h2)| {
let request = Request::builder()