feat(http1): Add higher-level HTTP upgrade support to Client and Server (#1563)

- Adds `Body::on_upgrade()` that returns an `OnUpgrade` future.
- Adds `hyper::upgrade` module containing types for dealing with
  upgrades.
- Adds `server::conn::Connection::with_upgrades()` method to enable
  these upgrades when using lower-level API (because of a missing
  `Send` bound on the transport generic).
- Client connections are automatically enabled.
- Optimizes request parsing, to make up for extra work to look for
  upgrade requests.
  - Returns a smaller `DecodedLength` type instead of the fatter
    `Decoder`, which should also allow a couple fewer branches.
  - Removes the `Decode::Ignore` wrapper enum, and instead ignoring
    1xx responses is handled directly in the response parsing code.

Ref #1563 

Closes #1395
This commit is contained in:
Sean McArthur
2018-06-14 13:39:29 -07:00
committed by GitHub
parent 1c3fbfd6bf
commit fea29b29e2
26 changed files with 1271 additions and 574 deletions

View File

@@ -9,6 +9,7 @@
//! higher-level [Client](super) API.
use std::fmt;
use std::marker::PhantomData;
use std::mem;
use bytes::Bytes;
use futures::{Async, Future, Poll};
@@ -17,9 +18,21 @@ use tokio_io::{AsyncRead, AsyncWrite};
use body::Payload;
use common::Exec;
use upgrade::Upgraded;
use proto;
use super::dispatch;
use {Body, Request, Response, StatusCode};
use {Body, Request, Response};
type Http1Dispatcher<T, B, R> = proto::dispatch::Dispatcher<
proto::dispatch::Client<B>,
B,
T,
R,
>;
type ConnEither<T, B> = Either<
Http1Dispatcher<T, B, proto::h1::ClientTransaction>,
proto::h2::Client<T, B>,
>;
/// Returns a `Handshake` future over some IO.
///
@@ -48,15 +61,7 @@ where
T: AsyncRead + AsyncWrite + Send + 'static,
B: Payload + 'static,
{
inner: Either<
proto::dispatch::Dispatcher<
proto::dispatch::Client<B>,
B,
T,
proto::ClientUpgradeTransaction,
>,
proto::h2::Client<T, B>,
>,
inner: Option<ConnEither<T, B>>,
}
@@ -76,7 +81,9 @@ pub struct Builder {
/// If successful, yields a `(SendRequest, Connection)` pair.
#[must_use = "futures do nothing unless polled"]
pub struct Handshake<T, B> {
inner: HandshakeInner<T, B, proto::ClientUpgradeTransaction>,
builder: Builder,
io: Option<T>,
_marker: PhantomData<B>,
}
/// A future returned by `SendRequest::send_request`.
@@ -112,27 +119,18 @@ pub struct Parts<T> {
// ========== internal client api
/// A `Future` for when `SendRequest::poll_ready()` is ready.
#[must_use = "futures do nothing unless polled"]
pub(super) struct WhenReady<B> {
tx: Option<SendRequest<B>>,
}
// A `SendRequest` that can be cloned to send HTTP2 requests.
// private for now, probably not a great idea of a type...
#[must_use = "futures do nothing unless polled"]
pub(super) struct Http2SendRequest<B> {
dispatch: dispatch::UnboundedSender<Request<B>, Response<Body>>,
}
#[must_use = "futures do nothing unless polled"]
pub(super) struct HandshakeNoUpgrades<T, B> {
inner: HandshakeInner<T, B, proto::ClientTransaction>,
}
struct HandshakeInner<T, B, R> {
builder: Builder,
io: Option<T>,
_marker: PhantomData<(B, R)>,
}
// ===== impl SendRequest
impl<B> SendRequest<B>
@@ -354,7 +352,7 @@ where
///
/// Only works for HTTP/1 connections. HTTP/2 connections will panic.
pub fn into_parts(self) -> Parts<T> {
let (io, read_buf, _) = match self.inner {
let (io, read_buf, _) = match self.inner.expect("already upgraded") {
Either::A(h1) => h1.into_inner(),
Either::B(_h2) => {
panic!("http2 cannot into_inner");
@@ -376,12 +374,12 @@ where
/// but it is not desired to actally shutdown the IO object. Instead you
/// would take it back using `into_parts`.
pub fn poll_without_shutdown(&mut self) -> Poll<(), ::Error> {
match self.inner {
Either::A(ref mut h1) => {
match self.inner.as_mut().expect("already upgraded") {
&mut Either::A(ref mut h1) => {
h1.poll_without_shutdown()
},
Either::B(ref mut h2) => {
h2.poll()
&mut Either::B(ref mut h2) => {
h2.poll().map(|x| x.map(|_| ()))
}
}
}
@@ -396,7 +394,22 @@ where
type Error = ::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
self.inner.poll()
match try_ready!(self.inner.poll()) {
Some(proto::Dispatched::Shutdown) |
None => {
Ok(Async::Ready(()))
},
Some(proto::Dispatched::Upgrade(pending)) => {
let h1 = match mem::replace(&mut self.inner, None) {
Some(Either::A(h1)) => h1,
_ => unreachable!("Upgrade expects h1"),
};
let (io, buf, _) = h1.into_inner();
pending.fulfill(Upgraded::new(Box::new(io), buf));
Ok(Async::Ready(()))
}
}
}
}
@@ -456,25 +469,9 @@ impl Builder {
B: Payload + 'static,
{
Handshake {
inner: HandshakeInner {
builder: self.clone(),
io: Some(io),
_marker: PhantomData,
}
}
}
pub(super) fn handshake_no_upgrades<T, B>(&self, io: T) -> HandshakeNoUpgrades<T, B>
where
T: AsyncRead + AsyncWrite + Send + 'static,
B: Payload + 'static,
{
HandshakeNoUpgrades {
inner: HandshakeInner {
builder: self.clone(),
io: Some(io),
_marker: PhantomData,
}
builder: self.clone(),
io: Some(io),
_marker: PhantomData,
}
}
}
@@ -489,64 +486,6 @@ where
type Item = (SendRequest<B>, Connection<T, B>);
type Error = ::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
self.inner.poll()
.map(|async| {
async.map(|(tx, dispatch)| {
(tx, Connection { inner: dispatch })
})
})
}
}
impl<T, B> fmt::Debug for Handshake<T, B> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Handshake")
.finish()
}
}
impl<T, B> Future for HandshakeNoUpgrades<T, B>
where
T: AsyncRead + AsyncWrite + Send + 'static,
B: Payload + 'static,
{
type Item = (SendRequest<B>, Either<
proto::h1::Dispatcher<
proto::h1::dispatch::Client<B>,
B,
T,
proto::ClientTransaction,
>,
proto::h2::Client<T, B>,
>);
type Error = ::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
self.inner.poll()
}
}
impl<T, B, R> Future for HandshakeInner<T, B, R>
where
T: AsyncRead + AsyncWrite + Send + 'static,
B: Payload,
R: proto::h1::Http1Transaction<
Incoming=StatusCode,
Outgoing=proto::RequestLine,
>,
{
type Item = (SendRequest<B>, Either<
proto::h1::Dispatcher<
proto::h1::dispatch::Client<B>,
B,
T,
R,
>,
proto::h2::Client<T, B>,
>);
type Error = ::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let io = self.io.take().expect("polled more than once");
let (tx, rx) = dispatch::channel();
@@ -570,11 +509,20 @@ where
SendRequest {
dispatch: tx,
},
either,
Connection {
inner: Some(either),
},
)))
}
}
impl<T, B> fmt::Debug for Handshake<T, B> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Handshake")
.finish()
}
}
// ===== impl ResponseFuture
impl Future for ResponseFuture {

View File

@@ -268,7 +268,7 @@ where C: Connect + Sync + 'static,
.h1_writev(h1_writev)
.h1_title_case_headers(h1_title_case_headers)
.http2_only(pool_key.1 == Ver::Http2)
.handshake_no_upgrades(io)
.handshake(io)
.and_then(move |(tx, conn)| {
executor.execute(conn.map_err(|e| {
debug!("client connection error: {}", e)