Add server support for push (#327)

Closes #291, closes #185
This commit is contained in:
Michael Beaumont
2019-09-16 20:30:59 +02:00
committed by Sean McArthur
parent 0527f5b72a
commit fac165e451
10 changed files with 735 additions and 134 deletions

View File

@@ -1,10 +1,10 @@
use super::*;
use crate::codec::{RecvError, UserError};
use crate::frame::{Reason, DEFAULT_INITIAL_WINDOW_SIZE};
use crate::frame::{PushPromiseHeaderError, Reason, DEFAULT_INITIAL_WINDOW_SIZE};
use crate::{frame, proto};
use std::task::Context;
use http::{HeaderMap, Method, Request, Response};
use http::{HeaderMap, Request, Response};
use std::io;
use std::task::{Poll, Waker};
@@ -178,7 +178,7 @@ impl Recv {
use http::header;
if let Some(content_length) = frame.fields().get(header::CONTENT_LENGTH) {
let content_length = match parse_u64(content_length.as_bytes()) {
let content_length = match frame::parse_u64(content_length.as_bytes()) {
Ok(v) => v,
Err(()) => {
proto_err!(stream: "could not parse content-length; stream={:?}", stream.id);
@@ -632,44 +632,31 @@ impl Recv {
}
let promised_id = frame.promised_id();
use http::header;
let (pseudo, fields) = frame.into_parts();
let req = crate::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) => {}
otherwise => {
proto_err!(stream:
"recv_push_promise; promised request has content-length {:?}; promised_id={:?}",
otherwise,
promised_id,
);
return Err(RecvError::Stream {
id: promised_id,
reason: Reason::PROTOCOL_ERROR,
});
}
if let Err(e) = frame::PushPromise::validate_request(&req) {
use PushPromiseHeaderError::*;
match e {
NotSafeAndCacheable => proto_err!(
stream:
"recv_push_promise: method {} is not safe and cacheable; promised_id={:?}",
req.method(),
promised_id,
),
InvalidContentLength(e) => proto_err!(
stream:
"recv_push_promise; promised request has invalid content-length {:?}; promised_id={:?}",
e,
promised_id,
),
}
}
// "The server MUST include a method in the :method pseudo-header field
// that is safe and cacheable"
if !Self::safe_and_cacheable(req.method()) {
proto_err!(
stream:
"recv_push_promise: method {} is not safe and cacheable; promised_id={:?}",
req.method(),
promised_id,
);
return Err(RecvError::Stream {
id: promised_id,
reason: Reason::PROTOCOL_ERROR,
});
}
use super::peer::PollMessage::*;
stream
.pending_recv
@@ -678,12 +665,6 @@ impl Recv {
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
method == Method::GET || method == Method::HEAD
}
/// Ensures that `id` is not in the `Idle` state.
pub fn ensure_not_idle(&self, id: StreamId) -> Result<(), Reason> {
if let Ok(next) = self.next_stream_id {
@@ -1057,25 +1038,3 @@ impl<T> From<RecvError> for RecvHeaderBlockError<T> {
RecvHeaderBlockError::State(err)
}
}
// ===== util =====
fn parse_u64(src: &[u8]) -> Result<u64, ()> {
if src.len() > 19 {
// At danger for overflow...
return Err(());
}
let mut ret = 0;
for &d in src {
if d < b'0' || d > b'9' {
return Err(());
}
ret *= 10;
ret += u64::from(d - b'0');
}
Ok(ret)
}