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
@@ -41,7 +41,7 @@ members = [
|
|||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
tokio-io = "0.1.4"
|
tokio-io = "0.1.4"
|
||||||
bytes = "0.4.7"
|
bytes = "0.4.7"
|
||||||
http = "0.1.3"
|
http = "0.1.8"
|
||||||
byteorder = "1.0"
|
byteorder = "1.0"
|
||||||
log = "0.4.1"
|
log = "0.4.1"
|
||||||
fnv = "1.0.5"
|
fnv = "1.0.5"
|
||||||
|
|||||||
117
src/client.rs
117
src/client.rs
@@ -162,8 +162,8 @@ use frame::{Headers, Pseudo, Reason, Settings, StreamId};
|
|||||||
use proto;
|
use proto;
|
||||||
|
|
||||||
use bytes::{Bytes, IntoBuf};
|
use bytes::{Bytes, IntoBuf};
|
||||||
use futures::{Async, Future, Poll};
|
use futures::{Async, Future, Poll, Stream};
|
||||||
use http::{uri, Request, Response, Method, Version};
|
use http::{uri, HeaderMap, Request, Response, Method, Version};
|
||||||
use tokio_io::{AsyncRead, AsyncWrite};
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
use tokio_io::io::WriteAll;
|
use tokio_io::io::WriteAll;
|
||||||
|
|
||||||
@@ -294,6 +294,54 @@ pub struct Connection<T, B: IntoBuf = Bytes> {
|
|||||||
#[must_use = "futures do nothing unless polled"]
|
#[must_use = "futures do nothing unless polled"]
|
||||||
pub struct ResponseFuture {
|
pub struct ResponseFuture {
|
||||||
inner: proto::OpaqueStreamRef,
|
inner: proto::OpaqueStreamRef,
|
||||||
|
push_promise_consumed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A future of a pushed HTTP response.
|
||||||
|
///
|
||||||
|
/// We have to differentiate between pushed and non pushed because of the spec
|
||||||
|
/// <https://httpwg.org/specs/rfc7540.html#PUSH_PROMISE>
|
||||||
|
/// > PUSH_PROMISE frames MUST only be sent on a peer-initiated stream
|
||||||
|
/// > that is in either the "open" or "half-closed (remote)" state.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[must_use = "futures do nothing unless polled"]
|
||||||
|
pub struct PushedResponseFuture {
|
||||||
|
inner: ResponseFuture,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A pushed response and corresponding request headers
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PushPromise {
|
||||||
|
/// The request headers
|
||||||
|
request: Request<()>,
|
||||||
|
|
||||||
|
/// The pushed response
|
||||||
|
response: PushedResponseFuture,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// A stream of pushed responses and corresponding promised requests
|
||||||
|
pub struct PushPromises {
|
||||||
|
inner: proto::OpaqueStreamRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stream for PushPromises {
|
||||||
|
type Item = PushPromise;
|
||||||
|
type Error = ::Error;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||||
|
match try_ready!(self.inner.poll_pushed()) {
|
||||||
|
Some((request, response)) => {
|
||||||
|
let response = PushedResponseFuture {
|
||||||
|
inner: ResponseFuture {
|
||||||
|
inner: response, push_promise_consumed: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Async::Ready(Some(PushPromise{request, response})))
|
||||||
|
}
|
||||||
|
None => Ok(Async::Ready(None)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds client connections with custom configuration values.
|
/// Builds client connections with custom configuration values.
|
||||||
@@ -516,7 +564,7 @@ where
|
|||||||
/// .body(())
|
/// .body(())
|
||||||
/// .unwrap();
|
/// .unwrap();
|
||||||
///
|
///
|
||||||
/// // Send the request to the server. Since we are not sending a
|
/// // Send the request to the server. If we are not sending a
|
||||||
/// // body or trailers, we can drop the `SendStream` instance.
|
/// // body or trailers, we can drop the `SendStream` instance.
|
||||||
/// let (response, mut send_stream) = send_request
|
/// let (response, mut send_stream) = send_request
|
||||||
/// .send_request(request, false).unwrap();
|
/// .send_request(request, false).unwrap();
|
||||||
@@ -567,6 +615,7 @@ where
|
|||||||
|
|
||||||
let response = ResponseFuture {
|
let response = ResponseFuture {
|
||||||
inner: stream.clone_to_opaque(),
|
inner: stream.clone_to_opaque(),
|
||||||
|
push_promise_consumed: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let stream = SendStream::new(stream);
|
let stream = SendStream::new(stream);
|
||||||
@@ -1352,6 +1401,61 @@ impl ResponseFuture {
|
|||||||
pub fn stream_id(&self) -> ::StreamId {
|
pub fn stream_id(&self) -> ::StreamId {
|
||||||
::StreamId::from_internal(self.inner.stream_id())
|
::StreamId::from_internal(self.inner.stream_id())
|
||||||
}
|
}
|
||||||
|
/// Returns a stream of PushPromises
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If this method has been called before
|
||||||
|
/// or the stream was itself was pushed
|
||||||
|
pub fn push_promises(&mut self) -> PushPromises {
|
||||||
|
if self.push_promise_consumed {
|
||||||
|
panic!("Reference to push promises stream taken!");
|
||||||
|
}
|
||||||
|
self.push_promise_consumed = true;
|
||||||
|
PushPromises { inner: self.inner.clone() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl PushPromise =====
|
||||||
|
|
||||||
|
impl PushPromise {
|
||||||
|
/// Returns a reference to the push promise's request headers.
|
||||||
|
pub fn request(&self) -> &Request<()> {
|
||||||
|
&self.request
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to the push promise's request headers.
|
||||||
|
pub fn request_mut(&mut self) -> &mut Request<()> {
|
||||||
|
&mut self.request
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes `self`, returning the push promise's request headers and
|
||||||
|
/// response future.
|
||||||
|
pub fn into_parts(self) -> (Request<()>, PushedResponseFuture) {
|
||||||
|
(self.request, self.response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl PushedResponseFuture =====
|
||||||
|
|
||||||
|
impl Future for PushedResponseFuture {
|
||||||
|
type Item = Response<RecvStream>;
|
||||||
|
type Error = ::Error;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
self.inner.poll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PushedResponseFuture {
|
||||||
|
/// Returns the stream ID of the response stream.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If the lock on the stream store has been poisoned.
|
||||||
|
pub fn stream_id(&self) -> ::StreamId {
|
||||||
|
self.inner.stream_id()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== impl Peer =====
|
// ===== impl Peer =====
|
||||||
@@ -1431,12 +1535,11 @@ impl proto::Peer for Peer {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_poll_message(headers: Headers) -> Result<Self::Poll, RecvError> {
|
fn convert_poll_message(
|
||||||
|
pseudo: Pseudo, fields: HeaderMap, stream_id: StreamId
|
||||||
|
) -> Result<Self::Poll, RecvError> {
|
||||||
let mut b = Response::builder();
|
let mut b = Response::builder();
|
||||||
|
|
||||||
let stream_id = headers.stream_id();
|
|
||||||
let (pseudo, fields) = headers.into_parts();
|
|
||||||
|
|
||||||
b.version(Version::HTTP_2);
|
b.version(Version::HTTP_2);
|
||||||
|
|
||||||
if let Some(status) = pseudo.status {
|
if let Some(status) = pseudo.status {
|
||||||
|
|||||||
@@ -380,6 +380,13 @@ impl PushPromise {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PushPromise {
|
||||||
|
/// Consume `self`, returning the parts of the frame
|
||||||
|
pub fn into_parts(self) -> (Pseudo, HeaderMap) {
|
||||||
|
(self.header_block.pseudo, self.header_block.fields)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "unstable")]
|
#[cfg(feature = "unstable")]
|
||||||
impl PushPromise {
|
impl PushPromise {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
@@ -400,10 +407,6 @@ impl PushPromise {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_parts(self) -> (Pseudo, HeaderMap) {
|
|
||||||
(self.header_block.pseudo, self.header_block.fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fields(&self) -> &HeaderMap {
|
pub fn fields(&self) -> &HeaderMap {
|
||||||
&self.header_block.fields
|
&self.header_block.fields
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use codec::RecvError;
|
use codec::RecvError;
|
||||||
use error::Reason;
|
use error::Reason;
|
||||||
use frame::{Headers, StreamId};
|
use frame::{Pseudo, StreamId};
|
||||||
use proto::Open;
|
use proto::Open;
|
||||||
|
|
||||||
use http::{Request, Response};
|
use http::{HeaderMap, Request, Response};
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
@@ -16,7 +16,9 @@ pub(crate) trait Peer {
|
|||||||
|
|
||||||
fn is_server() -> bool;
|
fn is_server() -> bool;
|
||||||
|
|
||||||
fn convert_poll_message(headers: Headers) -> Result<Self::Poll, RecvError>;
|
fn convert_poll_message(
|
||||||
|
pseudo: Pseudo, fields: HeaderMap, stream_id: StreamId
|
||||||
|
) -> Result<Self::Poll, RecvError>;
|
||||||
|
|
||||||
fn is_local_init(id: StreamId) -> bool {
|
fn is_local_init(id: StreamId) -> bool {
|
||||||
assert!(!id.is_zero());
|
assert!(!id.is_zero());
|
||||||
@@ -51,12 +53,14 @@ impl Dyn {
|
|||||||
self.is_server() == id.is_server_initiated()
|
self.is_server() == id.is_server_initiated()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn convert_poll_message(&self, headers: Headers) -> Result<PollMessage, RecvError> {
|
pub fn convert_poll_message(
|
||||||
|
&self, pseudo: Pseudo, fields: HeaderMap, stream_id: StreamId
|
||||||
|
) -> Result<PollMessage, RecvError> {
|
||||||
if self.is_server() {
|
if self.is_server() {
|
||||||
::server::Peer::convert_poll_message(headers)
|
::server::Peer::convert_poll_message(pseudo, fields, stream_id)
|
||||||
.map(PollMessage::Server)
|
.map(PollMessage::Server)
|
||||||
} else {
|
} else {
|
||||||
::client::Peer::convert_poll_message(headers)
|
::client::Peer::convert_poll_message(pseudo, fields, stream_id)
|
||||||
.map(PollMessage::Client)
|
.map(PollMessage::Client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ use frame::{StreamId, StreamIdOverflow};
|
|||||||
use proto::*;
|
use proto::*;
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use http::{Request, Response};
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use {frame, proto};
|
|||||||
use codec::{RecvError, UserError};
|
use codec::{RecvError, UserError};
|
||||||
use frame::{Reason, DEFAULT_INITIAL_WINDOW_SIZE};
|
use frame::{Reason, DEFAULT_INITIAL_WINDOW_SIZE};
|
||||||
|
|
||||||
use http::HeaderMap;
|
use http::{HeaderMap, Response, Request, Method};
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
@@ -216,7 +216,9 @@ impl Recv {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let message = counts.peer().convert_poll_message(frame)?;
|
let stream_id = frame.stream_id();
|
||||||
|
let (pseudo, fields) = frame.into_parts();
|
||||||
|
let message = counts.peer().convert_poll_message(pseudo, fields, stream_id)?;
|
||||||
|
|
||||||
// Push the frame onto the stream's recv buffer
|
// Push the frame onto the stream's recv buffer
|
||||||
stream
|
stream
|
||||||
@@ -247,6 +249,37 @@ impl Recv {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called by the client to get pushed response
|
||||||
|
pub fn poll_pushed(
|
||||||
|
&mut self, stream: &mut store::Ptr
|
||||||
|
) -> Poll<Option<(Request<()>, store::Key)>, proto::Error> {
|
||||||
|
use super::peer::PollMessage::*;
|
||||||
|
|
||||||
|
let mut ppp = stream.pending_push_promises.take();
|
||||||
|
let pushed = ppp.pop(stream.store_mut()).map(
|
||||||
|
|mut pushed| match pushed.pending_recv.pop_front(&mut self.buffer) {
|
||||||
|
Some(Event::Headers(Server(headers))) =>
|
||||||
|
Async::Ready(Some((headers, pushed.key()))),
|
||||||
|
// When frames are pushed into the queue, it is verified that
|
||||||
|
// the first frame is a HEADERS frame.
|
||||||
|
_ => panic!("Headers not set on pushed stream")
|
||||||
|
}
|
||||||
|
);
|
||||||
|
stream.pending_push_promises = ppp;
|
||||||
|
if let Some(p) = pushed {
|
||||||
|
Ok(p)
|
||||||
|
} else {
|
||||||
|
let is_open = stream.state.ensure_recv_open()?;
|
||||||
|
|
||||||
|
if is_open {
|
||||||
|
stream.recv_task = Some(task::current());
|
||||||
|
Ok(Async::NotReady)
|
||||||
|
} else {
|
||||||
|
Ok(Async::Ready(None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Called by the client to get the response
|
/// Called by the client to get the response
|
||||||
pub fn poll_response(
|
pub fn poll_response(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -538,13 +571,7 @@ impl Recv {
|
|||||||
frame: frame::PushPromise,
|
frame: frame::PushPromise,
|
||||||
stream: &mut store::Ptr,
|
stream: &mut store::Ptr,
|
||||||
) -> Result<(), RecvError> {
|
) -> Result<(), RecvError> {
|
||||||
|
|
||||||
// TODO: Streams in the reserved states do not count towards the concurrency
|
|
||||||
// limit. However, it seems like there should be a cap otherwise this
|
|
||||||
// could grow in memory indefinitely.
|
|
||||||
|
|
||||||
stream.state.reserve_remote()?;
|
stream.state.reserve_remote()?;
|
||||||
|
|
||||||
if frame.is_over_size() {
|
if frame.is_over_size() {
|
||||||
// A frame is over size if the decoded header block was bigger than
|
// A frame is over size if the decoded header block was bigger than
|
||||||
// SETTINGS_MAX_HEADER_LIST_SIZE.
|
// SETTINGS_MAX_HEADER_LIST_SIZE.
|
||||||
@@ -564,9 +591,46 @@ impl Recv {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let promised_id = frame.promised_id();
|
||||||
|
use http::header;
|
||||||
|
let (pseudo, fields) = frame.into_parts();
|
||||||
|
let req = ::server::Peer::convert_poll_message(pseudo, fields, promised_id)?;
|
||||||
|
// The spec has some requirements for promised request headers
|
||||||
|
// [https://httpwg.org/specs/rfc7540.html#PushRequests]
|
||||||
|
|
||||||
|
// A promised request "that indicates the presence of a request body
|
||||||
|
// MUST reset the promised stream with a stream error"
|
||||||
|
if let Some(content_length) = req.headers().get(header::CONTENT_LENGTH) {
|
||||||
|
match parse_u64(content_length.as_bytes()) {
|
||||||
|
Ok(0) => {},
|
||||||
|
_ => {
|
||||||
|
return Err(RecvError::Stream {
|
||||||
|
id: promised_id,
|
||||||
|
reason: Reason::PROTOCOL_ERROR,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// "The server MUST include a method in the :method pseudo-header field
|
||||||
|
// that is safe and cacheable"
|
||||||
|
if !Self::safe_and_cacheable(req.method()) {
|
||||||
|
return Err(RecvError::Stream {
|
||||||
|
id: promised_id,
|
||||||
|
reason: Reason::PROTOCOL_ERROR,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
use super::peer::PollMessage::*;
|
||||||
|
stream.pending_recv.push_back(&mut self.buffer, Event::Headers(Server(req)));
|
||||||
|
stream.notify_recv();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn safe_and_cacheable(method: &Method) -> bool {
|
||||||
|
// Cacheable: https://httpwg.org/specs/rfc7231.html#cacheable.methods
|
||||||
|
// Safe: https://httpwg.org/specs/rfc7231.html#safe.methods
|
||||||
|
return method == Method::GET || method == Method::HEAD;
|
||||||
|
}
|
||||||
|
|
||||||
/// Ensures that `id` is not in the `Idle` state.
|
/// Ensures that `id` is not in the `Idle` state.
|
||||||
pub fn ensure_not_idle(&self, id: StreamId) -> Result<(), Reason> {
|
pub fn ensure_not_idle(&self, id: StreamId) -> Result<(), Reason> {
|
||||||
if let Ok(next) = self.next_stream_id {
|
if let Ok(next) = self.next_stream_id {
|
||||||
|
|||||||
@@ -153,10 +153,7 @@ impl State {
|
|||||||
if eos {
|
if eos {
|
||||||
Closed(Cause::EndStream)
|
Closed(Cause::EndStream)
|
||||||
} else {
|
} else {
|
||||||
Open {
|
HalfClosedLocal(Streaming)
|
||||||
local: AwaitingHeaders,
|
|
||||||
remote,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Open {
|
Open {
|
||||||
|
|||||||
@@ -245,7 +245,6 @@ where
|
|||||||
if let Err(RecvError::Stream { .. }) = res {
|
if let Err(RecvError::Stream { .. }) = res {
|
||||||
actions.recv.release_connection_capacity(sz as WindowSize, &mut None);
|
actions.recv.release_connection_capacity(sz as WindowSize, &mut None);
|
||||||
}
|
}
|
||||||
|
|
||||||
actions.reset_on_recv_stream_err(send_buffer, stream, counts, res)
|
actions.reset_on_recv_stream_err(send_buffer, stream, counts, res)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -426,6 +425,10 @@ where
|
|||||||
None => return Err(RecvError::Connection(Reason::PROTOCOL_ERROR)),
|
None => return Err(RecvError::Connection(Reason::PROTOCOL_ERROR)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Streams in the reserved states do not count towards the concurrency
|
||||||
|
// limit. However, it seems like there should be a cap otherwise this
|
||||||
|
// could grow in memory indefinitely.
|
||||||
|
|
||||||
// Ensure that we can reserve streams
|
// Ensure that we can reserve streams
|
||||||
me.actions.recv.ensure_can_reserve()?;
|
me.actions.recv.ensure_can_reserve()?;
|
||||||
|
|
||||||
@@ -437,8 +440,9 @@ where
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a scope
|
// Try to handle the frame and create a corresponding key for the pushed stream
|
||||||
let child_key = {
|
// this requires a bit of indirection to make the borrow checker happy.
|
||||||
|
let child_key: Option<store::Key> = {
|
||||||
// Create state for the stream
|
// Create state for the stream
|
||||||
let stream = me.store.insert(promised_id, {
|
let stream = me.store.insert(promised_id, {
|
||||||
Stream::new(
|
Stream::new(
|
||||||
@@ -450,23 +454,29 @@ where
|
|||||||
let actions = &mut me.actions;
|
let actions = &mut me.actions;
|
||||||
|
|
||||||
me.counts.transition(stream, |counts, stream| {
|
me.counts.transition(stream, |counts, stream| {
|
||||||
let res = actions.recv.recv_push_promise(frame, stream);
|
let stream_valid =
|
||||||
|
actions.recv.recv_push_promise(frame, stream);
|
||||||
|
|
||||||
|
match stream_valid {
|
||||||
|
Ok(()) =>
|
||||||
|
Ok(Some(stream.key())),
|
||||||
|
_ => {
|
||||||
let mut send_buffer = self.send_buffer.inner.lock().unwrap();
|
let mut send_buffer = self.send_buffer.inner.lock().unwrap();
|
||||||
actions.reset_on_recv_stream_err(&mut *send_buffer, stream, counts, res)
|
actions.reset_on_recv_stream_err(&mut *send_buffer, stream, counts, stream_valid)
|
||||||
.map(|_| stream.key())
|
.map(|()| None)
|
||||||
|
}
|
||||||
|
}
|
||||||
})?
|
})?
|
||||||
};
|
};
|
||||||
|
// If we're successful, push the headers and stream...
|
||||||
// Push the stream... this requires a bit of indirection to make
|
if let Some(child) = child_key {
|
||||||
// the borrow checker happy.
|
|
||||||
let mut ppp = me.store[parent_key].pending_push_promises.take();
|
let mut ppp = me.store[parent_key].pending_push_promises.take();
|
||||||
ppp.push(&mut me.store.resolve(child_key));
|
ppp.push(&mut me.store.resolve(child));
|
||||||
|
|
||||||
let parent = &mut me.store[parent_key];
|
|
||||||
|
|
||||||
|
let parent = &mut me.store.resolve(parent_key);
|
||||||
parent.pending_push_promises = ppp;
|
parent.pending_push_promises = ppp;
|
||||||
parent.notify_recv();
|
parent.notify_recv();
|
||||||
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -972,6 +982,26 @@ impl OpaqueStreamRef {
|
|||||||
|
|
||||||
me.actions.recv.poll_response(&mut stream)
|
me.actions.recv.poll_response(&mut stream)
|
||||||
}
|
}
|
||||||
|
/// Called by a client to check for a pushed request.
|
||||||
|
pub fn poll_pushed(
|
||||||
|
&mut self
|
||||||
|
) -> Poll<Option<(Request<()>, OpaqueStreamRef)>, proto::Error> {
|
||||||
|
let mut me = self.inner.lock().unwrap();
|
||||||
|
let me = &mut *me;
|
||||||
|
|
||||||
|
let res = {
|
||||||
|
let mut stream = me.store.resolve(self.key);
|
||||||
|
try_ready!(me.actions.recv.poll_pushed(&mut stream))
|
||||||
|
};
|
||||||
|
Ok(Async::Ready(res.map(|(h, key)| {
|
||||||
|
me.store.resolve(key).ref_inc();
|
||||||
|
let opaque_ref =
|
||||||
|
OpaqueStreamRef {
|
||||||
|
inner: self.inner.clone(), key,
|
||||||
|
};
|
||||||
|
(h, opaque_ref)
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn body_is_empty(&self) -> bool {
|
pub fn body_is_empty(&self) -> bool {
|
||||||
let mut me = self.inner.lock().unwrap();
|
let mut me = self.inner.lock().unwrap();
|
||||||
@@ -1102,6 +1132,7 @@ fn drop_stream_ref(inner: &Mutex<Inner>, key: store::Key) {
|
|||||||
maybe_cancel(stream, actions, counts);
|
maybe_cancel(stream, actions, counts);
|
||||||
|
|
||||||
if stream.ref_count == 0 {
|
if stream.ref_count == 0 {
|
||||||
|
// We won't be able to reach our push promises anymore
|
||||||
let mut ppp = stream.pending_push_promises.take();
|
let mut ppp = stream.pending_push_promises.take();
|
||||||
while let Some(promise) = ppp.pop(stream.store_mut()) {
|
while let Some(promise) = ppp.pop(stream.store_mut()) {
|
||||||
counts.transition(promise, |counts, stream| {
|
counts.transition(promise, |counts, stream| {
|
||||||
|
|||||||
@@ -131,12 +131,12 @@
|
|||||||
|
|
||||||
use {SendStream, RecvStream, ReleaseCapacity};
|
use {SendStream, RecvStream, ReleaseCapacity};
|
||||||
use codec::{Codec, RecvError};
|
use codec::{Codec, RecvError};
|
||||||
use frame::{self, Reason, Settings, StreamId};
|
use frame::{self, Pseudo, Reason, Settings, StreamId};
|
||||||
use proto::{self, Config, Prioritized};
|
use proto::{self, Config, Prioritized};
|
||||||
|
|
||||||
use bytes::{Buf, Bytes, IntoBuf};
|
use bytes::{Buf, Bytes, IntoBuf};
|
||||||
use futures::{self, Async, Future, Poll};
|
use futures::{self, Async, Future, Poll};
|
||||||
use http::{Request, Response};
|
use http::{HeaderMap, Request, Response};
|
||||||
use std::{convert, fmt, io, mem};
|
use std::{convert, fmt, io, mem};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio_io::{AsyncRead, AsyncWrite};
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
@@ -1168,7 +1168,7 @@ impl Peer {
|
|||||||
|
|
||||||
// Build the set pseudo header set. All requests will include `method`
|
// Build the set pseudo header set. All requests will include `method`
|
||||||
// and `path`.
|
// and `path`.
|
||||||
let pseudo = frame::Pseudo::response(status);
|
let pseudo = Pseudo::response(status);
|
||||||
|
|
||||||
// Create the HEADERS frame
|
// Create the HEADERS frame
|
||||||
let mut frame = frame::Headers::new(id, pseudo, headers);
|
let mut frame = frame::Headers::new(id, pseudo, headers);
|
||||||
@@ -1192,14 +1192,13 @@ impl proto::Peer for Peer {
|
|||||||
proto::DynPeer::Server
|
proto::DynPeer::Server
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_poll_message(headers: frame::Headers) -> Result<Self::Poll, RecvError> {
|
fn convert_poll_message(
|
||||||
|
pseudo: Pseudo, fields: HeaderMap, stream_id: StreamId
|
||||||
|
) -> Result<Self::Poll, RecvError> {
|
||||||
use http::{uri, Version};
|
use http::{uri, Version};
|
||||||
|
|
||||||
let mut b = Request::builder();
|
let mut b = Request::builder();
|
||||||
|
|
||||||
let stream_id = headers.stream_id();
|
|
||||||
let (pseudo, fields) = headers.into_parts();
|
|
||||||
|
|
||||||
macro_rules! malformed {
|
macro_rules! malformed {
|
||||||
($($arg:tt)*) => {{
|
($($arg:tt)*) => {{
|
||||||
debug!($($arg)*);
|
debug!($($arg)*);
|
||||||
|
|||||||
@@ -220,6 +220,18 @@ impl Mock<frame::PushPromise> {
|
|||||||
Mock(frame)
|
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) {
|
fn into_parts(self) -> (StreamId, StreamId, frame::Pseudo, HeaderMap) {
|
||||||
assert!(self.0.is_end_headers(), "unset eoh will be lost");
|
assert!(self.0.is_end_headers(), "unset eoh will be lost");
|
||||||
let id = self.0.stream_id();
|
let id = self.0.stream_id();
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ use h2_support::prelude::*;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn recv_push_works() {
|
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 _ = ::env_logger::try_init();
|
||||||
|
|
||||||
let (io, srv) = mock::new();
|
let (io, srv) = mock::new();
|
||||||
@@ -17,9 +15,11 @@ fn recv_push_works() {
|
|||||||
.request("GET", "https://http2.akamai.com/")
|
.request("GET", "https://http2.akamai.com/")
|
||||||
.eos(),
|
.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, 2).request("GET", "https://http2.akamai.com/style.css"))
|
||||||
.send_frame(frames::headers(1).response(200).eos())
|
.send_frame(frames::data(1, "").eos())
|
||||||
.send_frame(frames::headers(2).response(200).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 h2 = client::handshake(io).unwrap().and_then(|(mut client, h2)| {
|
||||||
let request = Request::builder()
|
let request = Request::builder()
|
||||||
@@ -27,16 +27,82 @@ fn recv_push_works() {
|
|||||||
.uri("https://http2.akamai.com/")
|
.uri("https://http2.akamai.com/")
|
||||||
.body(())
|
.body(())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let req = client
|
let (mut resp, _) = client
|
||||||
.send_request(request, true)
|
.send_request(request, true)
|
||||||
.unwrap()
|
.unwrap();
|
||||||
.0.unwrap()
|
let pushed = resp.push_promises();
|
||||||
.and_then(|resp| {
|
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);
|
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();
|
h2.join(mock).wait().unwrap();
|
||||||
@@ -189,9 +255,58 @@ fn recv_push_promise_over_max_header_list_size() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
fn recv_invalid_push_promise_headers_is_stream_protocol_error() {
|
||||||
fn recv_push_promise_with_unsafe_method_is_stream_error() {
|
// Unsafe method or content length is stream protocol error
|
||||||
// for instance, when :method = POST
|
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]
|
#[test]
|
||||||
@@ -202,8 +317,6 @@ fn recv_push_promise_with_wrong_authority_is_stream_error() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn recv_push_promise_skipped_stream_id() {
|
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 _ = ::env_logger::try_init();
|
||||||
|
|
||||||
let (io, srv) = mock::new();
|
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, 4).request("GET", "https://http2.akamai.com/style.css"))
|
||||||
.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())
|
.recv_frame(frames::go_away(0).protocol_error())
|
||||||
.close()
|
.close();
|
||||||
;
|
|
||||||
|
|
||||||
let h2 = client::handshake(io).unwrap().and_then(|(mut client, h2)| {
|
let h2 = client::handshake(io).unwrap().and_then(|(mut client, h2)| {
|
||||||
let request = Request::builder()
|
let request = Request::builder()
|
||||||
@@ -255,8 +367,6 @@ fn recv_push_promise_skipped_stream_id() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn recv_push_promise_dup_stream_id() {
|
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 _ = ::env_logger::try_init();
|
||||||
|
|
||||||
let (io, srv) = mock::new();
|
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"))
|
||||||
.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())
|
.recv_frame(frames::go_away(0).protocol_error())
|
||||||
.close()
|
.close();
|
||||||
;
|
|
||||||
|
|
||||||
let h2 = client::handshake(io).unwrap().and_then(|(mut client, h2)| {
|
let h2 = client::handshake(io).unwrap().and_then(|(mut client, h2)| {
|
||||||
let request = Request::builder()
|
let request = Request::builder()
|
||||||
|
|||||||
Reference in New Issue
Block a user