Add client support for server push (#314)
This patch exposes push promises to the client API. Closes #252
This commit is contained in:
committed by
Carl Lerche
parent
6d8554a23c
commit
6b23542a55
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user