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:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user