feat(http2): add HTTP/2 support for Client and Server

This commit is contained in:
Sean McArthur
2018-04-13 13:20:47 -07:00
parent fe1578acf6
commit c119097fd0
25 changed files with 2014 additions and 363 deletions

144
src/proto/h2/client.rs Normal file
View File

@@ -0,0 +1,144 @@
use bytes::IntoBuf;
use futures::{Async, Future, Poll, Stream};
use futures::future::{self, Either};
use futures::sync::oneshot;
use h2::client::{Builder, Handshake, SendRequest};
use tokio_io::{AsyncRead, AsyncWrite};
use body::Payload;
use ::common::{Exec, Never};
use super::{PipeToSendStream, SendBuf};
use ::{Body, Request, Response};
type ClientRx<B> = ::client::dispatch::Receiver<Request<B>, Response<Body>>;
pub struct Client<T, B>
where
B: Payload,
{
executor: Exec,
rx: ClientRx<B>,
state: State<T, SendBuf<B::Data>>,
}
enum State<T, B> where B: IntoBuf {
Handshaking(Handshake<T, B>),
Ready(SendRequest<B>, oneshot::Sender<Never>),
}
impl<T, B> Client<T, B>
where
T: AsyncRead + AsyncWrite + Send + 'static,
B: Payload,
{
pub(crate) fn new(io: T, rx: ClientRx<B>, exec: Exec) -> Client<T, B> {
let handshake = Builder::new()
// we don't expose PUSH promises yet
.enable_push(false)
.handshake(io);
Client {
executor: exec,
rx: rx,
state: State::Handshaking(handshake),
}
}
}
impl<T, B> Future for Client<T, B>
where
T: AsyncRead + AsyncWrite + Send + 'static,
B: Payload + 'static,
{
type Item = ();
type Error = ::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop {
let next = match self.state {
State::Handshaking(ref mut h) => {
let (request_tx, conn) = try_ready!(h.poll().map_err(::Error::new_h2));
// A oneshot channel is used entirely to detect when the
// 'Client' has been dropped. This is to get around a bug
// in h2 where dropping all SendRequests won't notify a
// parked Connection.
let (tx, rx) = oneshot::channel();
let fut = conn
.map_err(|e| debug!("client h2 connection error: {}", e))
.select2(rx)
.then(|res| match res {
Ok(Either::A(((), _))) |
Err(Either::A(((), _))) => {
// conn has finished either way
Either::A(future::ok(()))
},
Err(Either::B((_, conn))) => {
// oneshot has been dropped, hopefully polling
// the connection some more should start shutdown
// and then close
trace!("send_request dropped, starting conn shutdown");
Either::B(conn)
}
Ok(Either::B((never, _))) => match never {},
});
self.executor.execute(fut);
State::Ready(request_tx, tx)
},
State::Ready(ref mut tx, _) => {
try_ready!(tx.poll_ready().map_err(::Error::new_h2));
match self.rx.poll() {
Ok(Async::Ready(Some((req, mut cb)))) => {
// check that future hasn't been canceled already
if let Async::Ready(()) = cb.poll_cancel().expect("poll_cancel cannot error") {
trace!("request canceled");
continue;
}
let (head, body) = req.into_parts();
let mut req = ::http::Request::from_parts(head, ());
super::strip_connection_headers(req.headers_mut());
let eos = body.is_end_stream();
let (fut, body_tx) = match tx.send_request(req, eos) {
Ok(ok) => ok,
Err(err) => {
debug!("client send request error: {}", err);
let _ = cb.send(Err((::Error::new_h2(err), None)));
continue;
}
};
if !eos {
let pipe = PipeToSendStream::new(body, body_tx);
self.executor.execute(pipe.map_err(|e| debug!("client request body error: {}", e)));
}
let fut = fut
.then(move |result| {
match result {
Ok(res) => {
let res = res.map(::Body::h2);
let _ = cb.send(Ok(res));
},
Err(err) => {
debug!("client response error: {}", err);
let _ = cb.send(Err((::Error::new_h2(err), None)));
}
}
Ok(())
});
self.executor.execute(fut);
continue;
},
Ok(Async::NotReady) => return Ok(Async::NotReady),
Ok(Async::Ready(None)) |
Err(_) => {
trace!("client tx dropped");
return Ok(Async::Ready(()));
}
}
},
};
self.state = next;
}
}
}

View File

@@ -1 +1,121 @@
use bytes::Buf;
use futures::{Async, Future, Poll};
use h2::{Reason, SendStream};
use http::HeaderMap;
use http::header::{CONNECTION, TRANSFER_ENCODING};
use ::body::Payload;
use ::proto::h1::Cursor;
mod client;
mod server;
pub(crate) use self::client::Client;
pub(crate) use self::server::Server;
fn strip_connection_headers(headers: &mut HeaderMap) {
if headers.remove(TRANSFER_ENCODING).is_some() {
trace!("removed illegal Transfer-Encoding header");
}
if headers.contains_key(CONNECTION) {
warn!("Connection header illegal in HTTP/2");
//TODO: actually remove it, after checking the value
//and removing all related headers
}
}
// body adapters used by both Client and Server
struct PipeToSendStream<S>
where
S: Payload,
{
body_tx: SendStream<SendBuf<S::Data>>,
stream: S,
}
impl<S> PipeToSendStream<S>
where
S: Payload,
{
fn new(stream: S, tx: SendStream<SendBuf<S::Data>>) -> PipeToSendStream<S> {
PipeToSendStream {
body_tx: tx,
stream: stream,
}
}
}
impl<S> Future for PipeToSendStream<S>
where
S: Payload,
{
type Item = ();
type Error = ::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop {
// TODO: make use of flow control on SendStream
// If you're looking at this and thinking of trying to fix this TODO,
// you may want to look at:
// https://docs.rs/h2/0.1.*/h2/struct.SendStream.html
//
// With that doc open, we'd want to do these things:
// - check self.body_tx.capacity() to see if we can send *any* data
// - if > 0:
// - poll self.stream
// - reserve chunk.len() more capacity (because its about to be used)?
// - send the chunk
// - else:
// - try reserve a smallish amount of capacity
// - call self.body_tx.poll_capacity(), return if NotReady
match self.stream.poll_data() {
Ok(Async::Ready(Some(chunk))) => {
trace!("send body chunk: {}B", chunk.as_ref().len());
self.body_tx.send_data(SendBuf(Some(Cursor::new(chunk))), false)
.map_err(::Error::new_h2)?;
},
Ok(Async::Ready(None)) => {
trace!("send body eos");
self.body_tx.send_data(SendBuf(None), true)
.map_err(::Error::new_h2)?;
return Ok(Async::Ready(()));
},
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(err) => {
let err = ::Error::new_user_body(err);
trace!("send body user stream error: {}", err);
self.body_tx.send_reset(Reason::INTERNAL_ERROR);
return Err(err);
}
}
}
}
}
struct SendBuf<B>(Option<Cursor<B>>);
impl<B: AsRef<[u8]>> Buf for SendBuf<B> {
#[inline]
fn remaining(&self) -> usize {
self.0
.as_ref()
.map(|b| b.remaining())
.unwrap_or(0)
}
#[inline]
fn bytes(&self) -> &[u8] {
self.0
.as_ref()
.map(|b| b.bytes())
.unwrap_or(&[])
}
#[inline]
fn advance(&mut self, cnt: usize) {
self.0
.as_mut()
.map(|b| b.advance(cnt));
}
}

198
src/proto/h2/server.rs Normal file
View File

@@ -0,0 +1,198 @@
use futures::{Async, Future, Poll, Stream};
use h2::Reason;
use h2::server::{Builder, Connection, Handshake, SendResponse};
use tokio_io::{AsyncRead, AsyncWrite};
use ::body::Payload;
use ::common::Exec;
use ::server::Service;
use super::{PipeToSendStream, SendBuf};
use ::{Body, Request, Response};
pub(crate) struct Server<T, S, B>
where
S: Service,
B: Payload,
{
exec: Exec,
service: S,
state: State<T, B>,
}
enum State<T, B>
where
B: Payload,
{
Handshaking(Handshake<T, SendBuf<B::Data>>),
Serving(Serving<T, B>),
}
struct Serving<T, B>
where
B: Payload,
{
conn: Connection<T, SendBuf<B::Data>>,
}
impl<T, S, B> Server<T, S, B>
where
T: AsyncRead + AsyncWrite,
S: Service<Request=Request<Body>, Response=Response<B>>,
S::Error: Into<Box<::std::error::Error + Send + Sync>>,
S::Future: Send + 'static,
B: Payload,
{
pub(crate) fn new(io: T, service: S, exec: Exec) -> Server<T, S, B> {
let handshake = Builder::new()
.handshake(io);
Server {
exec,
state: State::Handshaking(handshake),
service,
}
}
pub fn graceful_shutdown(&mut self) {
unimplemented!("h2 server graceful shutdown");
}
}
impl<T, S, B> Future for Server<T, S, B>
where
T: AsyncRead + AsyncWrite,
S: Service<Request=Request<Body>, Response=Response<B>>,
S::Error: Into<Box<::std::error::Error + Send + Sync>>,
S::Future: Send + 'static,
B: Payload,
{
type Item = ();
type Error = ::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop {
let next = match self.state {
State::Handshaking(ref mut h) => {
let conn = try_ready!(h.poll().map_err(::Error::new_h2));
State::Serving(Serving {
conn: conn,
})
},
State::Serving(ref mut srv) => {
return srv.poll_server(&mut self.service, &self.exec);
}
};
self.state = next;
}
}
}
impl<T, B> Serving<T, B>
where
T: AsyncRead + AsyncWrite,
B: Payload,
{
fn poll_server<S>(&mut self, service: &mut S, exec: &Exec) -> Poll<(), ::Error>
where
S: Service<
Request=Request<Body>,
Response=Response<B>,
>,
S::Error: Into<Box<::std::error::Error + Send + Sync>>,
S::Future: Send + 'static,
{
while let Some((req, respond)) = try_ready!(self.conn.poll().map_err(::Error::new_h2)) {
trace!("incoming request");
let req = req.map(::Body::h2);
let fut = H2Stream::new(service.call(req), respond);
exec.execute(fut);
}
// no more incoming streams...
trace!("incoming connection complete");
Ok(Async::Ready(()))
}
}
struct H2Stream<F, B>
where
B: Payload,
{
reply: SendResponse<SendBuf<B::Data>>,
state: H2StreamState<F, B>,
}
enum H2StreamState<F, B>
where
B: Payload,
{
Service(F),
Body(PipeToSendStream<B>),
}
impl<F, B> H2Stream<F, B>
where
F: Future<Item=Response<B>>,
F::Error: Into<Box<::std::error::Error + Send + Sync>>,
B: Payload,
{
fn new(fut: F, respond: SendResponse<SendBuf<B::Data>>) -> H2Stream<F, B> {
H2Stream {
reply: respond,
state: H2StreamState::Service(fut),
}
}
fn poll2(&mut self) -> Poll<(), ::Error> {
loop {
let next = match self.state {
H2StreamState::Service(ref mut h) => {
let res = try_ready!(h.poll().map_err(::Error::new_user_service));
let (head, body) = res.into_parts();
let mut res = ::http::Response::from_parts(head, ());
super::strip_connection_headers(res.headers_mut());
macro_rules! reply {
($eos:expr) => ({
match self.reply.send_response(res, $eos) {
Ok(tx) => tx,
Err(e) => {
trace!("send response error: {}", e);
self.reply.send_reset(Reason::INTERNAL_ERROR);
return Err(::Error::new_h2(e));
}
}
})
}
if !body.is_end_stream() {
let body_tx = reply!(false);
H2StreamState::Body(PipeToSendStream::new(body, body_tx))
} else {
reply!(true);
return Ok(Async::Ready(()));
}
},
H2StreamState::Body(ref mut pipe) => {
return pipe.poll();
}
};
self.state = next;
}
}
}
impl<F, B> Future for H2Stream<F, B>
where
F: Future<Item=Response<B>>,
F::Error: Into<Box<::std::error::Error + Send + Sync>>,
B: Payload,
{
type Item = ();
type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
self.poll2()
.map_err(|e| debug!("stream error: {}", e))
}
}