Merge pull request #1432 from hyperium/h2
Initial HTTP/2 support for Client and Server
This commit is contained in:
		| @@ -27,6 +27,7 @@ futures-cpupool = "0.1.6" | ||||
| futures-timer = "0.1.0" | ||||
| http = "0.1.5" | ||||
| httparse = "1.0" | ||||
| h2 = "0.1.5" | ||||
| iovec = "0.1" | ||||
| log = "0.4" | ||||
| net2 = "0.2.32" | ||||
| @@ -35,7 +36,7 @@ tokio = "0.1.5" | ||||
| tokio-executor = "0.1.0" | ||||
| tokio-service = "0.1" | ||||
| tokio-io = "0.1" | ||||
| want = "0.0.2" | ||||
| want = "0.0.3" | ||||
|  | ||||
| [dev-dependencies] | ||||
| num_cpus = "1.0" | ||||
|   | ||||
| @@ -51,12 +51,12 @@ fn main() { | ||||
|         println!("Listening on http://{}", srv2.incoming_ref().local_addr()); | ||||
|  | ||||
|         tokio::spawn(srv1.for_each(move |conn| { | ||||
|             tokio::spawn(conn.map(|_| ()).map_err(|err| println!("srv1 error: {:?}", err))); | ||||
|             tokio::spawn(conn.map_err(|err| println!("srv1 error: {:?}", err))); | ||||
|             Ok(()) | ||||
|         }).map_err(|_| ())); | ||||
|  | ||||
|         tokio::spawn(srv2.for_each(move |conn| { | ||||
|             tokio::spawn(conn.map(|_| ()).map_err(|err| println!("srv2 error: {:?}", err))); | ||||
|             tokio::spawn(conn.map_err(|err| println!("srv2 error: {:?}", err))); | ||||
|             Ok(()) | ||||
|         }).map_err(|_| ())); | ||||
|  | ||||
|   | ||||
| @@ -83,7 +83,7 @@ fn main() { | ||||
|         println!("Listening on http://{} with 1 thread.", serve.incoming_ref().local_addr()); | ||||
|  | ||||
|         serve.map_err(|_| ()).for_each(move |conn| { | ||||
|             tokio::spawn(conn.map(|_| ()).map_err(|err| println!("serve error: {:?}", err))) | ||||
|             tokio::spawn(conn.map_err(|err| println!("serve error: {:?}", err))) | ||||
|         }) | ||||
|     })); | ||||
| } | ||||
|   | ||||
							
								
								
									
										30
									
								
								src/body.rs
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								src/body.rs
									
									
									
									
									
								
							| @@ -5,6 +5,7 @@ use std::fmt; | ||||
| use bytes::Bytes; | ||||
| use futures::{Async, Future, Poll, Stream}; | ||||
| use futures::sync::{mpsc, oneshot}; | ||||
| use h2; | ||||
| use http::HeaderMap; | ||||
|  | ||||
| use common::Never; | ||||
| @@ -13,9 +14,9 @@ use super::Chunk; | ||||
| type BodySender = mpsc::Sender<Result<Chunk, ::Error>>; | ||||
|  | ||||
| /// This trait represents a streaming body of a `Request` or `Response`. | ||||
| pub trait Payload { | ||||
| pub trait Payload: Send + 'static { | ||||
|     /// A buffer of bytes representing a single chunk of a body. | ||||
|     type Data: AsRef<[u8]>; | ||||
|     type Data: AsRef<[u8]> + Send; | ||||
|  | ||||
|     /// The error type of this stream. | ||||
|     type Error: Into<Box<::std::error::Error + Send + Sync>>; | ||||
| @@ -107,6 +108,7 @@ enum Kind { | ||||
|         _close_tx: oneshot::Sender<()>, | ||||
|         rx: mpsc::Receiver<Result<Chunk, ::Error>>, | ||||
|     }, | ||||
|     H2(h2::RecvStream), | ||||
|     Wrapped(Box<Stream<Item=Chunk, Error=Box<::std::error::Error + Send + Sync>> + Send>), | ||||
|     Once(Option<Chunk>), | ||||
|     Empty, | ||||
| @@ -219,6 +221,10 @@ impl Body { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn h2(recv: h2::RecvStream) -> Self { | ||||
|         Body::new(Kind::H2(recv)) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn delayed_eof(&mut self, fut: DelayEofUntil) { | ||||
|         self.delayed_eof = Some(DelayEof::NotEof(fut)); | ||||
|     } | ||||
| @@ -269,6 +275,17 @@ impl Body { | ||||
|                 Async::Ready(None) => Ok(Async::Ready(None)), | ||||
|                 Async::NotReady => Ok(Async::NotReady), | ||||
|             }, | ||||
|             Kind::H2(ref mut h2) => { | ||||
|                 h2.poll() | ||||
|                     .map(|async| { | ||||
|                         async.map(|opt| { | ||||
|                             opt.map(|bytes| { | ||||
|                                 Chunk::h2(bytes, h2.release_capacity()) | ||||
|                             }) | ||||
|                         }) | ||||
|                     }) | ||||
|                     .map_err(::Error::new_body) | ||||
|             }, | ||||
|             Kind::Wrapped(ref mut s) => s.poll().map_err(::Error::new_body), | ||||
|             Kind::Once(ref mut val) => Ok(Async::Ready(val.take())), | ||||
|             Kind::Empty => Ok(Async::Ready(None)), | ||||
| @@ -291,9 +308,17 @@ impl Payload for Body { | ||||
|         self.poll_eof() | ||||
|     } | ||||
|  | ||||
|     fn poll_trailers(&mut self) -> Poll<Option<HeaderMap>, Self::Error> { | ||||
|         match self.kind { | ||||
|             Kind::H2(ref mut h2) => h2.poll_trailers().map_err(::Error::new_h2), | ||||
|             _ => Ok(Async::Ready(None)), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn is_end_stream(&self) -> bool { | ||||
|         match self.kind { | ||||
|             Kind::Chan { .. } => false, | ||||
|             Kind::H2(..) => false, | ||||
|             Kind::Wrapped(..) => false, | ||||
|             Kind::Once(ref val) => val.is_none(), | ||||
|             Kind::Empty => true | ||||
| @@ -303,6 +328,7 @@ impl Payload for Body { | ||||
|     fn content_length(&self) -> Option<u64> { | ||||
|         match self.kind { | ||||
|             Kind::Chan { .. } => None, | ||||
|             Kind::H2(..) => None, | ||||
|             Kind::Wrapped(..) => None, | ||||
|             Kind::Once(Some(ref val)) => Some(val.len() as u64), | ||||
|             Kind::Once(None) => None, | ||||
|   | ||||
							
								
								
									
										55
									
								
								src/chunk.rs
									
									
									
									
									
								
							
							
						
						
									
										55
									
								
								src/chunk.rs
									
									
									
									
									
								
							| @@ -1,12 +1,38 @@ | ||||
| use std::fmt; | ||||
|  | ||||
| use bytes::Bytes; | ||||
| use h2::ReleaseCapacity; | ||||
|  | ||||
| /// A piece of a message body. | ||||
| pub struct Chunk(Inner); | ||||
|  | ||||
| enum Inner { | ||||
|     Shared(Bytes), | ||||
| struct Inner { | ||||
|     bytes: Bytes, | ||||
|     _flow_control: Option<AutoRelease>, | ||||
| } | ||||
|  | ||||
| struct AutoRelease { | ||||
|     cap: usize, | ||||
|     release: ReleaseCapacity, | ||||
| } | ||||
|  | ||||
| impl Drop for AutoRelease { | ||||
|     fn drop(&mut self) { | ||||
|         let _ = self.release.release_capacity(self.cap); | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Chunk { | ||||
|     pub(crate) fn h2(bytes: Bytes, rel_cap: &ReleaseCapacity) -> Chunk { | ||||
|         let cap = bytes.len(); | ||||
|         Chunk(Inner { | ||||
|             bytes: bytes, | ||||
|             _flow_control: Some(AutoRelease { | ||||
|                 cap: cap, | ||||
|                 release: rel_cap.clone(), | ||||
|             }), | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Vec<u8>> for Chunk { | ||||
| @@ -39,17 +65,18 @@ impl From<&'static str> for Chunk { | ||||
|  | ||||
| impl From<Bytes> for Chunk { | ||||
|     #[inline] | ||||
|     fn from(mem: Bytes) -> Chunk { | ||||
|         Chunk(Inner::Shared(mem)) | ||||
|     fn from(bytes: Bytes) -> Chunk { | ||||
|         Chunk(Inner { | ||||
|             bytes: bytes, | ||||
|             _flow_control: None, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Chunk> for Bytes { | ||||
|     #[inline] | ||||
|     fn from(chunk: Chunk) -> Bytes { | ||||
|         match chunk.0 { | ||||
|             Inner::Shared(bytes) => bytes, | ||||
|         } | ||||
|         chunk.0.bytes | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -65,9 +92,7 @@ impl ::std::ops::Deref for Chunk { | ||||
| impl AsRef<[u8]> for Chunk { | ||||
|     #[inline] | ||||
|     fn as_ref(&self) -> &[u8] { | ||||
|         match self.0 { | ||||
|             Inner::Shared(ref slice) => slice, | ||||
|         } | ||||
|         &self.0.bytes | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -81,7 +106,7 @@ impl fmt::Debug for Chunk { | ||||
| impl Default for Chunk { | ||||
|     #[inline] | ||||
|     fn default() -> Chunk { | ||||
|         Chunk(Inner::Shared(Bytes::new())) | ||||
|         Chunk::from(Bytes::new()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -91,17 +116,13 @@ impl IntoIterator for Chunk { | ||||
|  | ||||
|     #[inline] | ||||
|     fn into_iter(self) -> Self::IntoIter { | ||||
|         match self.0 { | ||||
|             Inner::Shared(bytes) => bytes.into_iter(), | ||||
|         } | ||||
|         self.0.bytes.into_iter() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Extend<u8> for Chunk { | ||||
|     #[inline] | ||||
|     fn extend<T>(&mut self, iter: T) where T: IntoIterator<Item=u8> { | ||||
|         match self.0 { | ||||
|             Inner::Shared(ref mut bytes) => bytes.extend(iter) | ||||
|         } | ||||
|         self.0.bytes.extend(iter) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -16,6 +16,7 @@ use futures::future::{self, Either}; | ||||
| use tokio_io::{AsyncRead, AsyncWrite}; | ||||
|  | ||||
| use body::Payload; | ||||
| use common::Exec; | ||||
| use proto; | ||||
| use super::dispatch; | ||||
| use {Body, Request, Response, StatusCode}; | ||||
| @@ -25,7 +26,7 @@ use {Body, Request, Response, StatusCode}; | ||||
| /// This is a shortcut for `Builder::new().handshake(io)`. | ||||
| pub fn handshake<T>(io: T) -> Handshake<T, ::Body> | ||||
| where | ||||
|     T: AsyncRead + AsyncWrite, | ||||
|     T: AsyncRead + AsyncWrite + Send + 'static, | ||||
| { | ||||
|     Builder::new() | ||||
|         .handshake(io) | ||||
| @@ -33,10 +34,10 @@ where | ||||
|  | ||||
| /// The sender side of an established connection. | ||||
| pub struct SendRequest<B> { | ||||
|     dispatch: dispatch::Sender<proto::dispatch::ClientMsg<B>, Response<Body>>, | ||||
|  | ||||
|     dispatch: dispatch::Sender<Request<B>, Response<Body>>, | ||||
| } | ||||
|  | ||||
|  | ||||
| /// A future that processes all HTTP state for the IO object. | ||||
| /// | ||||
| /// In most cases, this should just be spawned into an executor, so that it | ||||
| @@ -44,15 +45,17 @@ pub struct SendRequest<B> { | ||||
| #[must_use = "futures do nothing unless polled"] | ||||
| pub struct Connection<T, B> | ||||
| where | ||||
|     T: AsyncRead + AsyncWrite, | ||||
|     T: AsyncRead + AsyncWrite + Send + 'static, | ||||
|     B: Payload + 'static, | ||||
| { | ||||
|     inner: proto::dispatch::Dispatcher< | ||||
|         proto::dispatch::Client<B>, | ||||
|         B, | ||||
|         T, | ||||
|         B::Data, | ||||
|         proto::ClientUpgradeTransaction, | ||||
|     inner: Either< | ||||
|         proto::dispatch::Dispatcher< | ||||
|             proto::dispatch::Client<B>, | ||||
|             B, | ||||
|             T, | ||||
|             proto::ClientUpgradeTransaction, | ||||
|         >, | ||||
|         proto::h2::Client<T, B>, | ||||
|     >, | ||||
| } | ||||
|  | ||||
| @@ -62,7 +65,9 @@ where | ||||
| /// After setting options, the builder is used to create a `Handshake` future. | ||||
| #[derive(Clone, Debug)] | ||||
| pub struct Builder { | ||||
|     exec: Exec, | ||||
|     h1_writev: bool, | ||||
|     http2: bool, | ||||
| } | ||||
|  | ||||
| /// A future setting up HTTP over an IO object. | ||||
| @@ -103,7 +108,18 @@ pub struct Parts<T> { | ||||
|     _inner: (), | ||||
| } | ||||
|  | ||||
| // internal client api | ||||
| // ========== internal client api | ||||
|  | ||||
| /// A `Future` for when `SendRequest::poll_ready()` is ready. | ||||
| 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... | ||||
| 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> { | ||||
| @@ -127,6 +143,12 @@ impl<B> SendRequest<B> | ||||
|         self.dispatch.poll_ready() | ||||
|     } | ||||
|  | ||||
|     pub(super) fn when_ready(self) -> WhenReady<B> { | ||||
|         WhenReady { | ||||
|             tx: Some(self), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(super) fn is_ready(&self) -> bool { | ||||
|         self.dispatch.is_ready() | ||||
|     } | ||||
| @@ -134,6 +156,12 @@ impl<B> SendRequest<B> | ||||
|     pub(super) fn is_closed(&self) -> bool { | ||||
|         self.dispatch.is_closed() | ||||
|     } | ||||
|  | ||||
|     pub(super) fn into_http2(self) -> Http2SendRequest<B> { | ||||
|         Http2SendRequest { | ||||
|             dispatch: self.dispatch.unbound(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<B> SendRequest<B> | ||||
| @@ -257,16 +285,81 @@ impl<B> fmt::Debug for SendRequest<B> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ===== impl Http2SendRequest | ||||
|  | ||||
| impl<B> Http2SendRequest<B> { | ||||
|     pub(super) fn is_ready(&self) -> bool { | ||||
|         self.dispatch.is_ready() | ||||
|     } | ||||
|  | ||||
|     pub(super) fn is_closed(&self) -> bool { | ||||
|         self.dispatch.is_closed() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<B> Http2SendRequest<B> | ||||
| where | ||||
|     B: Payload + 'static, | ||||
| { | ||||
|     //TODO: replace with `impl Future` when stable | ||||
|     pub(super) fn send_request_retryable(&mut self, req: Request<B>) -> Box<Future<Item=Response<Body>, Error=(::Error, Option<Request<B>>)> + Send> | ||||
|     where | ||||
|         B: Send, | ||||
|     { | ||||
|         let inner = match self.dispatch.try_send(req) { | ||||
|             Ok(rx) => { | ||||
|                 Either::A(rx.then(move |res| { | ||||
|                     match res { | ||||
|                         Ok(Ok(res)) => Ok(res), | ||||
|                         Ok(Err(err)) => Err(err), | ||||
|                         // this is definite bug if it happens, but it shouldn't happen! | ||||
|                         Err(_) => panic!("dispatch dropped without returning error"), | ||||
|                     } | ||||
|                 })) | ||||
|             }, | ||||
|             Err(req) => { | ||||
|                 debug!("connection was not ready"); | ||||
|                 let err = ::Error::new_canceled(Some("connection was not ready")); | ||||
|                 Either::B(future::err((err, Some(req)))) | ||||
|             } | ||||
|         }; | ||||
|         Box::new(inner) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<B> fmt::Debug for Http2SendRequest<B> { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         f.debug_struct("Http2SendRequest") | ||||
|             .finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<B> Clone for Http2SendRequest<B> { | ||||
|     fn clone(&self) -> Self { | ||||
|         Http2SendRequest { | ||||
|             dispatch: self.dispatch.clone(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ===== impl Connection | ||||
|  | ||||
| impl<T, B> Connection<T, B> | ||||
| where | ||||
|     T: AsyncRead + AsyncWrite, | ||||
|     T: AsyncRead + AsyncWrite + Send + 'static, | ||||
|     B: Payload + 'static, | ||||
| { | ||||
|     /// Return the inner IO object, and additional information. | ||||
|     /// | ||||
|     /// Only works for HTTP/1 connections. HTTP/2 connections will panic. | ||||
|     pub fn into_parts(self) -> Parts<T> { | ||||
|         let (io, read_buf) = self.inner.into_inner(); | ||||
|         let (io, read_buf) = match self.inner { | ||||
|             Either::A(h1) => h1.into_inner(), | ||||
|             Either::B(_h2) => { | ||||
|                 panic!("http2 cannot into_inner"); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         Parts { | ||||
|             io: io, | ||||
|             read_buf: read_buf, | ||||
| @@ -282,13 +375,20 @@ 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> { | ||||
|         self.inner.poll_without_shutdown() | ||||
|         match self.inner { | ||||
|             Either::A(ref mut h1) => { | ||||
|                 h1.poll_without_shutdown() | ||||
|             }, | ||||
|             Either::B(ref mut h2) => { | ||||
|                 h2.poll() | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T, B> Future for Connection<T, B> | ||||
| where | ||||
|     T: AsyncRead + AsyncWrite, | ||||
|     T: AsyncRead + AsyncWrite + Send + 'static, | ||||
|     B: Payload + 'static, | ||||
| { | ||||
|     type Item = (); | ||||
| @@ -301,7 +401,7 @@ where | ||||
|  | ||||
| impl<T, B> fmt::Debug for Connection<T, B> | ||||
| where | ||||
|     T: AsyncRead + AsyncWrite + fmt::Debug, | ||||
|     T: AsyncRead + AsyncWrite + fmt::Debug + Send + 'static, | ||||
|     B: Payload + 'static, | ||||
| { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
| @@ -317,20 +417,37 @@ impl Builder { | ||||
|     #[inline] | ||||
|     pub fn new() -> Builder { | ||||
|         Builder { | ||||
|             exec: Exec::Default, | ||||
|             h1_writev: true, | ||||
|             http2: false, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|     pub(super) fn exec(&mut self, exec: Exec) -> &mut Builder { | ||||
|         self.exec = exec; | ||||
|         self | ||||
|     } | ||||
|     */ | ||||
|  | ||||
|     pub(super) fn h1_writev(&mut self, enabled: bool) -> &mut Builder { | ||||
|         self.h1_writev = enabled; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sets whether HTTP2 is required. | ||||
|     /// | ||||
|     /// Default is false. | ||||
|     pub fn http2_only(&mut self, enabled: bool) -> &mut Builder { | ||||
|         self.http2 = enabled; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Constructs a connection with the configured options and IO. | ||||
|     #[inline] | ||||
|     pub fn handshake<T, B>(&self, io: T) -> Handshake<T, B> | ||||
|     where | ||||
|         T: AsyncRead + AsyncWrite, | ||||
|         T: AsyncRead + AsyncWrite + Send + 'static, | ||||
|         B: Payload + 'static, | ||||
|     { | ||||
|         Handshake { | ||||
| @@ -344,7 +461,7 @@ impl Builder { | ||||
|  | ||||
|     pub(super) fn handshake_no_upgrades<T, B>(&self, io: T) -> HandshakeNoUpgrades<T, B> | ||||
|     where | ||||
|         T: AsyncRead + AsyncWrite, | ||||
|         T: AsyncRead + AsyncWrite + Send + 'static, | ||||
|         B: Payload + 'static, | ||||
|     { | ||||
|         HandshakeNoUpgrades { | ||||
| @@ -361,7 +478,7 @@ impl Builder { | ||||
|  | ||||
| impl<T, B> Future for Handshake<T, B> | ||||
| where | ||||
|     T: AsyncRead + AsyncWrite, | ||||
|     T: AsyncRead + AsyncWrite + Send + 'static, | ||||
|     B: Payload + 'static, | ||||
| { | ||||
|     type Item = (SendRequest<B>, Connection<T, B>); | ||||
| @@ -386,15 +503,17 @@ impl<T, B> fmt::Debug for Handshake<T, B> { | ||||
|  | ||||
| impl<T, B> Future for HandshakeNoUpgrades<T, B> | ||||
| where | ||||
|     T: AsyncRead + AsyncWrite, | ||||
|     T: AsyncRead + AsyncWrite + Send + 'static, | ||||
|     B: Payload + 'static, | ||||
| { | ||||
|     type Item = (SendRequest<B>, proto::dispatch::Dispatcher< | ||||
|         proto::dispatch::Client<B>, | ||||
|         B, | ||||
|         T, | ||||
|         B::Data, | ||||
|         proto::ClientTransaction, | ||||
|     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; | ||||
|  | ||||
| @@ -405,35 +524,45 @@ where | ||||
|  | ||||
| impl<T, B, R> Future for HandshakeInner<T, B, R> | ||||
| where | ||||
|     T: AsyncRead + AsyncWrite, | ||||
|     B: Payload + 'static, | ||||
|     T: AsyncRead + AsyncWrite + Send + 'static, | ||||
|     B: Payload, | ||||
|     R: proto::Http1Transaction< | ||||
|         Incoming=StatusCode, | ||||
|         Outgoing=proto::RequestLine, | ||||
|     >, | ||||
| { | ||||
|     type Item = (SendRequest<B>, proto::dispatch::Dispatcher< | ||||
|         proto::dispatch::Client<B>, | ||||
|         B, | ||||
|         T, | ||||
|         B::Data, | ||||
|         R, | ||||
|     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(); | ||||
|         let mut conn = proto::Conn::new(io); | ||||
|         if !self.builder.h1_writev { | ||||
|             conn.set_write_strategy_flatten(); | ||||
|         } | ||||
|         let dispatch = proto::dispatch::Dispatcher::new(proto::dispatch::Client::new(rx), conn); | ||||
|         let either = if !self.builder.http2 { | ||||
|             let mut conn = proto::Conn::new(io); | ||||
|             if !self.builder.h1_writev { | ||||
|                 conn.set_write_strategy_flatten(); | ||||
|             } | ||||
|             let cd = proto::h1::dispatch::Client::new(rx); | ||||
|             let dispatch = proto::h1::Dispatcher::new(cd, conn); | ||||
|             Either::A(dispatch) | ||||
|         } else { | ||||
|             let h2 = proto::h2::Client::new(io, rx, self.builder.exec.clone()); | ||||
|             Either::B(h2) | ||||
|         }; | ||||
|  | ||||
|         Ok(Async::Ready(( | ||||
|             SendRequest { | ||||
|                 dispatch: tx, | ||||
|             }, | ||||
|             dispatch, | ||||
|             either, | ||||
|         ))) | ||||
|     } | ||||
| } | ||||
| @@ -457,6 +586,24 @@ impl fmt::Debug for ResponseFuture { | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ===== impl WhenReady | ||||
|  | ||||
| impl<B> Future for WhenReady<B> { | ||||
|     type Item = SendRequest<B>; | ||||
|     type Error = ::Error; | ||||
|  | ||||
|     fn poll(&mut self) -> Poll<Self::Item, Self::Error> { | ||||
|         let mut tx = self.tx.take().expect("polled after complete"); | ||||
|         match tx.poll_ready()? { | ||||
|             Async::Ready(()) => Ok(Async::Ready(tx)), | ||||
|             Async::NotReady => { | ||||
|                 self.tx = Some(tx); | ||||
|                 Ok(Async::NotReady) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| // assert trait markers | ||||
|  | ||||
| trait AssertSend: Send {} | ||||
| @@ -469,7 +616,7 @@ impl<B: Send> AssertSendSync for SendRequest<B> {} | ||||
| #[doc(hidden)] | ||||
| impl<T: Send, B: Send> AssertSend for Connection<T, B> | ||||
| where | ||||
|     T: AsyncRead + AsyncWrite, | ||||
|     T: AsyncRead + AsyncWrite + Send + 'static, | ||||
|     B: Payload + 'static, | ||||
|     B::Data: Send + 'static, | ||||
| {} | ||||
| @@ -477,7 +624,7 @@ where | ||||
| #[doc(hidden)] | ||||
| impl<T: Send + Sync, B: Send + Sync> AssertSendSync for Connection<T, B> | ||||
| where | ||||
|     T: AsyncRead + AsyncWrite, | ||||
|     T: AsyncRead + AsyncWrite + Send + 'static, | ||||
|     B: Payload + 'static, | ||||
|     B::Data: Send + Sync + 'static, | ||||
| {} | ||||
|   | ||||
| @@ -1,17 +1,19 @@ | ||||
| use std::sync::Arc; | ||||
|  | ||||
| use futures::{Async, Poll, Stream}; | ||||
| use futures::sync::{mpsc, oneshot}; | ||||
| use want; | ||||
|  | ||||
| use common::Never; | ||||
|  | ||||
| //pub type Callback<T, U> = oneshot::Sender<Result<U, (::Error, Option<T>)>>; | ||||
| pub type RetryPromise<T, U> = oneshot::Receiver<Result<U, (::Error, Option<T>)>>; | ||||
| pub type Promise<T> = oneshot::Receiver<Result<T, ::Error>>; | ||||
|  | ||||
| pub fn channel<T, U>() -> (Sender<T, U>, Receiver<T, U>) { | ||||
|     let (tx, rx) = mpsc::channel(0); | ||||
|     let (tx, rx) = mpsc::unbounded(); | ||||
|     let (giver, taker) = want::new(); | ||||
|     let tx = Sender { | ||||
|         buffered_once: false, | ||||
|         giver: giver, | ||||
|         inner: tx, | ||||
|     }; | ||||
| @@ -22,28 +24,38 @@ pub fn channel<T, U>() -> (Sender<T, U>, Receiver<T, U>) { | ||||
|     (tx, rx) | ||||
| } | ||||
|  | ||||
| /// A bounded sender of requests and callbacks for when responses are ready. | ||||
| /// | ||||
| /// While the inner sender is unbounded, the Giver is used to determine | ||||
| /// if the Receiver is ready for another request. | ||||
| pub struct Sender<T, U> { | ||||
|     // The Giver helps watch that the the Receiver side has been polled | ||||
|     // when the queue is empty. This helps us know when a request and | ||||
|     // response have been fully processed, and a connection is ready | ||||
|     // for more. | ||||
|     /// One message is always allowed, even if the Receiver hasn't asked | ||||
|     /// for it yet. This boolean keeps track of whether we've sent one | ||||
|     /// without notice. | ||||
|     buffered_once: bool, | ||||
|     /// The Giver helps watch that the the Receiver side has been polled | ||||
|     /// when the queue is empty. This helps us know when a request and | ||||
|     /// response have been fully processed, and a connection is ready | ||||
|     /// for more. | ||||
|     giver: want::Giver, | ||||
|     //inner: mpsc::Sender<(T, Callback<T, U>)>, | ||||
|     inner: mpsc::Sender<Envelope<T, U>>, | ||||
|     /// Actually bounded by the Giver, plus `buffered_once`. | ||||
|     inner: mpsc::UnboundedSender<Envelope<T, U>>, | ||||
| } | ||||
|  | ||||
| /// An unbounded version. | ||||
| /// | ||||
| /// Cannot poll the Giver, but can still use it to determine if the Receiver | ||||
| /// has been dropped. However, this version can be cloned. | ||||
| pub struct UnboundedSender<T, U> { | ||||
|     // Only used for `is_closed`, since mpsc::UnboundedSender cannot be checked. | ||||
|     giver: Arc<want::Giver>, | ||||
|     inner: mpsc::UnboundedSender<Envelope<T, U>>, | ||||
| } | ||||
|  | ||||
| impl<T, U> Sender<T, U> { | ||||
|     pub fn poll_ready(&mut self) -> Poll<(), ::Error> { | ||||
|         match self.inner.poll_ready() { | ||||
|             Ok(Async::Ready(())) => { | ||||
|                 // there's room in the queue, but does the Connection | ||||
|                 // want a message yet? | ||||
|                 self.giver.poll_want() | ||||
|                     .map_err(|_| ::Error::new_closed()) | ||||
|             }, | ||||
|             Ok(Async::NotReady) => Ok(Async::NotReady), | ||||
|             Err(_) => Err(::Error::new_closed()), | ||||
|         } | ||||
|         self.giver.poll_want() | ||||
|             .map_err(|_| ::Error::new_closed()) | ||||
|     } | ||||
|  | ||||
|     pub fn is_ready(&self) -> bool { | ||||
| @@ -54,24 +66,75 @@ impl<T, U> Sender<T, U> { | ||||
|         self.giver.is_canceled() | ||||
|     } | ||||
|  | ||||
|     fn can_send(&mut self) -> bool { | ||||
|         if self.giver.give() || !self.buffered_once { | ||||
|             // If the receiver is ready *now*, then of course we can send. | ||||
|             // | ||||
|             // If the receiver isn't ready yet, but we don't have anything | ||||
|             // in the channel yet, then allow one message. | ||||
|             self.buffered_once = true; | ||||
|             true | ||||
|         } else { | ||||
|             false | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn try_send(&mut self, val: T) -> Result<RetryPromise<T, U>, T> { | ||||
|         if !self.can_send() { | ||||
|             return Err(val); | ||||
|         } | ||||
|         let (tx, rx) = oneshot::channel(); | ||||
|         self.inner.try_send(Envelope(Some((val, Callback::Retry(tx))))) | ||||
|         self.inner.unbounded_send(Envelope(Some((val, Callback::Retry(tx))))) | ||||
|             .map(move |_| rx) | ||||
|             .map_err(|e| e.into_inner().0.take().expect("envelope not dropped").0) | ||||
|     } | ||||
|  | ||||
|     pub fn send(&mut self, val: T) -> Result<Promise<U>, T> { | ||||
|         if !self.can_send() { | ||||
|             return Err(val); | ||||
|         } | ||||
|         let (tx, rx) = oneshot::channel(); | ||||
|         self.inner.try_send(Envelope(Some((val, Callback::NoRetry(tx))))) | ||||
|         self.inner.unbounded_send(Envelope(Some((val, Callback::NoRetry(tx))))) | ||||
|             .map(move |_| rx) | ||||
|             .map_err(|e| e.into_inner().0.take().expect("envelope not dropped").0) | ||||
|     } | ||||
|  | ||||
|     pub fn unbound(self) -> UnboundedSender<T, U> { | ||||
|         UnboundedSender { | ||||
|             giver: Arc::new(self.giver), | ||||
|             inner: self.inner, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T, U> UnboundedSender<T, U> { | ||||
|     pub fn is_ready(&self) -> bool { | ||||
|         self.giver.is_wanting() | ||||
|     } | ||||
|  | ||||
|     pub fn is_closed(&self) -> bool { | ||||
|         self.giver.is_canceled() | ||||
|     } | ||||
|  | ||||
|     pub fn try_send(&mut self, val: T) -> Result<RetryPromise<T, U>, T> { | ||||
|         let (tx, rx) = oneshot::channel(); | ||||
|         self.inner.unbounded_send(Envelope(Some((val, Callback::Retry(tx))))) | ||||
|             .map(move |_| rx) | ||||
|             .map_err(|e| e.into_inner().0.take().expect("envelope not dropped").0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T, U> Clone for UnboundedSender<T, U> { | ||||
|     fn clone(&self) -> Self { | ||||
|         UnboundedSender { | ||||
|             giver: self.giver.clone(), | ||||
|             inner: self.inner.clone(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct Receiver<T, U> { | ||||
|     //inner: mpsc::Receiver<(T, Callback<T, U>)>, | ||||
|     inner: mpsc::Receiver<Envelope<T, U>>, | ||||
|     inner: mpsc::UnboundedReceiver<Envelope<T, U>>, | ||||
|     taker: want::Taker, | ||||
| } | ||||
|  | ||||
| @@ -166,19 +229,21 @@ mod tests { | ||||
|     #[cfg(feature = "nightly")] | ||||
|     extern crate test; | ||||
|  | ||||
|     use futures::{future, Future}; | ||||
|     use futures::{future, Future, Stream}; | ||||
|  | ||||
|     #[cfg(feature = "nightly")] | ||||
|     use futures::{Stream}; | ||||
|  | ||||
|     #[derive(Debug)] | ||||
|     struct Custom(i32); | ||||
|  | ||||
|     #[test] | ||||
|     fn drop_receiver_sends_cancel_errors() { | ||||
|         let _ = pretty_env_logger::try_init(); | ||||
|  | ||||
|         future::lazy(|| { | ||||
|             #[derive(Debug)] | ||||
|             struct Custom(i32); | ||||
|             let (mut tx, rx) = super::channel::<Custom, ()>(); | ||||
|             let (mut tx, mut rx) = super::channel::<Custom, ()>(); | ||||
|  | ||||
|             // must poll once for try_send to succeed | ||||
|             assert!(rx.poll().expect("rx empty").is_not_ready()); | ||||
|  | ||||
|             let promise = tx.try_send(Custom(43)).unwrap(); | ||||
|             drop(rx); | ||||
| @@ -198,6 +263,40 @@ mod tests { | ||||
|         }).wait().unwrap(); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn sender_checks_for_want_on_send() { | ||||
|         future::lazy(|| { | ||||
|             let (mut tx, mut rx) = super::channel::<Custom, ()>(); | ||||
|             // one is allowed to buffer, second is rejected | ||||
|             let _ = tx.try_send(Custom(1)).expect("1 buffered"); | ||||
|             tx.try_send(Custom(2)).expect_err("2 not ready"); | ||||
|  | ||||
|             assert!(rx.poll().expect("rx 1").is_ready()); | ||||
|             // Even though 1 has been popped, only 1 could be buffered for the | ||||
|             // lifetime of the channel. | ||||
|             tx.try_send(Custom(2)).expect_err("2 still not ready"); | ||||
|  | ||||
|             assert!(rx.poll().expect("rx empty").is_not_ready()); | ||||
|             let _ = tx.try_send(Custom(2)).expect("2 ready"); | ||||
|  | ||||
|             Ok::<(), ()>(()) | ||||
|         }).wait().unwrap(); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn unbounded_sender_doesnt_bound_on_want() { | ||||
|         let (tx, rx) = super::channel::<Custom, ()>(); | ||||
|         let mut tx = tx.unbound(); | ||||
|  | ||||
|         let _ = tx.try_send(Custom(1)).unwrap(); | ||||
|         let _ = tx.try_send(Custom(2)).unwrap(); | ||||
|         let _ = tx.try_send(Custom(3)).unwrap(); | ||||
|  | ||||
|         drop(rx); | ||||
|  | ||||
|         let _ = tx.try_send(Custom(4)).unwrap_err(); | ||||
|     } | ||||
|  | ||||
|     #[cfg(feature = "nightly")] | ||||
|     #[bench] | ||||
|     fn giver_queue_throughput(b: &mut test::Bencher) { | ||||
|   | ||||
| @@ -6,16 +6,16 @@ use std::sync::Arc; | ||||
| use std::time::Duration; | ||||
|  | ||||
| use futures::{Async, Future, Poll}; | ||||
| use futures::future::{self, Executor}; | ||||
| use futures::future::{self, Either, Executor}; | ||||
| use futures::sync::oneshot; | ||||
| use http::{Method, Request, Response, Uri, Version}; | ||||
| use http::header::{Entry, HeaderValue, HOST}; | ||||
| use http::uri::Scheme; | ||||
| use tokio_executor::spawn; | ||||
| pub use tokio_service::Service; | ||||
|  | ||||
| use body::{Body, Payload}; | ||||
| use self::pool::Pool; | ||||
| use common::Exec; | ||||
| use self::pool::{Pool, Poolable, Reservation}; | ||||
|  | ||||
| pub use self::connect::{Connect, HttpConnector}; | ||||
|  | ||||
| @@ -37,6 +37,7 @@ pub struct Client<C, B = Body> { | ||||
|     pool: Pool<PoolClient<B>>, | ||||
|     retry_canceled_requests: bool, | ||||
|     set_host: bool, | ||||
|     ver: Ver, | ||||
| } | ||||
|  | ||||
| impl Client<HttpConnector, Body> { | ||||
| @@ -143,7 +144,7 @@ where C: Connect + Sync + 'static, | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         if self.set_host { | ||||
|         if self.set_host && self.ver == Ver::Http1 { | ||||
|             if let Entry::Vacant(entry) = req.headers_mut().entry(HOST).expect("HOST is always valid header name") { | ||||
|                 let hostname = uri.host().expect("authority implies host"); | ||||
|                 let host = if let Some(port) = uri.port() { | ||||
| @@ -171,50 +172,78 @@ where C: Connect + Sync + 'static, | ||||
|     //TODO: replace with `impl Future` when stable | ||||
|     fn send_request(&self, mut req: Request<B>, domain: &str) -> Box<Future<Item=Response<Body>, Error=ClientError<B>> + Send> { | ||||
|         let url = req.uri().clone(); | ||||
|         let checkout = self.pool.checkout(domain); | ||||
|         let ver = self.ver; | ||||
|         let pool_key = (Arc::new(domain.to_string()), self.ver); | ||||
|         let checkout = self.pool.checkout(pool_key.clone()); | ||||
|         let connect = { | ||||
|             let executor = self.executor.clone(); | ||||
|             let pool = self.pool.clone(); | ||||
|             let pool_key = Arc::new(domain.to_string()); | ||||
|             let h1_writev = self.h1_writev; | ||||
|             let connector = self.connector.clone(); | ||||
|             let dst = Destination { | ||||
|                 uri: url, | ||||
|             }; | ||||
|             future::lazy(move || { | ||||
|                 connector.connect(dst) | ||||
|                     .map_err(::Error::new_connect) | ||||
|                     .and_then(move |(io, connected)| { | ||||
|                         conn::Builder::new() | ||||
|                             .h1_writev(h1_writev) | ||||
|                             .handshake_no_upgrades(io) | ||||
|                             .and_then(move |(tx, conn)| { | ||||
|                                 executor.execute(conn.map_err(|e| debug!("client connection error: {}", e))); | ||||
|                                 Ok(pool.pooled(pool_key, PoolClient { | ||||
|                                     is_proxied: connected.is_proxied, | ||||
|                                     tx: tx, | ||||
|                                 })) | ||||
|                             }) | ||||
|                     }) | ||||
|                 if let Some(connecting) = pool.connecting(&pool_key) { | ||||
|                     Either::A(connector.connect(dst) | ||||
|                         .map_err(::Error::new_connect) | ||||
|                         .and_then(move |(io, connected)| { | ||||
|                             conn::Builder::new() | ||||
|                                 .h1_writev(h1_writev) | ||||
|                                 .http2_only(pool_key.1 == Ver::Http2) | ||||
|                                 .handshake_no_upgrades(io) | ||||
|                                 .and_then(move |(tx, conn)| { | ||||
|                                     executor.execute(conn.map_err(|e| { | ||||
|                                         debug!("client connection error: {}", e) | ||||
|                                     })); | ||||
|  | ||||
|                                     // Wait for 'conn' to ready up before we | ||||
|                                     // declare this tx as usable | ||||
|                                     tx.when_ready() | ||||
|                                 }) | ||||
|                                 .map(move |tx| { | ||||
|                                     pool.pooled(connecting, PoolClient { | ||||
|                                         is_proxied: connected.is_proxied, | ||||
|                                         tx: match ver { | ||||
|                                             Ver::Http1 => PoolTx::Http1(tx), | ||||
|                                             Ver::Http2 => PoolTx::Http2(tx.into_http2()), | ||||
|                                         }, | ||||
|                                     }) | ||||
|                                 }) | ||||
|                         })) | ||||
|                 } else { | ||||
|                     let canceled = ::Error::new_canceled(Some("HTTP/2 connection in progress")); | ||||
|                     Either::B(future::err(canceled)) | ||||
|                 } | ||||
|             }) | ||||
|         }; | ||||
|  | ||||
|         let race = checkout.select(connect) | ||||
|             .map(|(pooled, _work)| pooled) | ||||
|             .map_err(|(e, _checkout)| { | ||||
|                 // the Pool Checkout cannot error, so the only error | ||||
|                 // is from the Connector | ||||
|                 // XXX: should wait on the Checkout? Problem is | ||||
|                 // that if the connector is failing, it may be that we | ||||
|                 // never had a pooled stream at all | ||||
|                 ClientError::Normal(e) | ||||
|             .or_else(|(e, other)| { | ||||
|                 // Either checkout or connect could get canceled: | ||||
|                 // | ||||
|                 // 1. Connect is canceled if this is HTTP/2 and there is | ||||
|                 //    an outstanding HTTP/2 connecting task. | ||||
|                 // 2. Checkout is canceled if the pool cannot deliver an | ||||
|                 //    idle connection reliably. | ||||
|                 // | ||||
|                 // In both cases, we should just wait for the other future. | ||||
|                 if e.is_canceled() { | ||||
|                     //trace!("checkout/connect race canceled: {}", e); | ||||
|                     Either::A(other.map_err(ClientError::Normal)) | ||||
|                 } else { | ||||
|                     Either::B(future::err(ClientError::Normal(e))) | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|         let executor = self.executor.clone(); | ||||
|         let resp = race.and_then(move |mut pooled| { | ||||
|             let conn_reused = pooled.is_reused(); | ||||
|             set_relative_uri(req.uri_mut(), pooled.is_proxied); | ||||
|             let fut = pooled.tx.send_request_retryable(req) | ||||
|             if ver == Ver::Http1 { | ||||
|                 set_relative_uri(req.uri_mut(), pooled.is_proxied); | ||||
|             } | ||||
|             let fut = pooled.send_request_retryable(req) | ||||
|                 .map_err(move |(err, orig_req)| { | ||||
|                     if let Some(req) = orig_req { | ||||
|                         ClientError::Canceled { | ||||
| @@ -235,14 +264,14 @@ where C: Connect + Sync + 'static, | ||||
|                     // for a new request to start. | ||||
|                     // | ||||
|                     // It won't be ready if there is a body to stream. | ||||
|                     if pooled.tx.is_ready() { | ||||
|                     if pooled.is_ready() { | ||||
|                         drop(pooled); | ||||
|                     } else if !res.body().is_empty() { | ||||
|                         let (delayed_tx, delayed_rx) = oneshot::channel(); | ||||
|                         res.body_mut().delayed_eof(delayed_rx); | ||||
|                         executor.execute( | ||||
|                             future::poll_fn(move || { | ||||
|                                 pooled.tx.poll_ready() | ||||
|                                 pooled.poll_ready() | ||||
|                             }) | ||||
|                             .then(move |_| { | ||||
|                                 // At this point, `pooled` is dropped, and had a chance | ||||
| @@ -291,6 +320,7 @@ impl<C, B> Clone for Client<C, B> { | ||||
|             pool: self.pool.clone(), | ||||
|             retry_canceled_requests: self.retry_canceled_requests, | ||||
|             set_host: self.set_host, | ||||
|             ver: self.ver, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -366,15 +396,74 @@ where | ||||
|  | ||||
| struct PoolClient<B> { | ||||
|     is_proxied: bool, | ||||
|     tx: conn::SendRequest<B>, | ||||
|     tx: PoolTx<B>, | ||||
| } | ||||
|  | ||||
| impl<B> self::pool::Closed for PoolClient<B> | ||||
| enum PoolTx<B> { | ||||
|     Http1(conn::SendRequest<B>), | ||||
|     Http2(conn::Http2SendRequest<B>), | ||||
| } | ||||
|  | ||||
| impl<B> PoolClient<B> { | ||||
|     fn poll_ready(&mut self) -> Poll<(), ::Error> { | ||||
|         match self.tx { | ||||
|             PoolTx::Http1(ref mut tx) => tx.poll_ready(), | ||||
|             PoolTx::Http2(_) => Ok(Async::Ready(())), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn is_ready(&self) -> bool { | ||||
|         match self.tx { | ||||
|             PoolTx::Http1(ref tx) => tx.is_ready(), | ||||
|             PoolTx::Http2(ref tx) => tx.is_ready(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<B: Payload + 'static> PoolClient<B> { | ||||
|     //TODO: replace with `impl Future` when stable | ||||
|     fn send_request_retryable(&mut self, req: Request<B>) -> Box<Future<Item=Response<Body>, Error=(::Error, Option<Request<B>>)> + Send> | ||||
|     where | ||||
|         B: Send, | ||||
|     { | ||||
|         match self.tx { | ||||
|             PoolTx::Http1(ref mut tx) => tx.send_request_retryable(req), | ||||
|             PoolTx::Http2(ref mut tx) => tx.send_request_retryable(req), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<B> Poolable for PoolClient<B> | ||||
| where | ||||
|     B: 'static, | ||||
| { | ||||
|     fn is_closed(&self) -> bool { | ||||
|         self.tx.is_closed() | ||||
|         match self.tx { | ||||
|             PoolTx::Http1(ref tx) => tx.is_closed(), | ||||
|             PoolTx::Http2(ref tx) => tx.is_closed(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn reserve(self) -> Reservation<Self> { | ||||
|         match self.tx { | ||||
|             PoolTx::Http1(tx) => { | ||||
|                 Reservation::Unique(PoolClient { | ||||
|                     is_proxied: self.is_proxied, | ||||
|                     tx: PoolTx::Http1(tx), | ||||
|                 }) | ||||
|             }, | ||||
|             PoolTx::Http2(tx) => { | ||||
|                 let b = PoolClient { | ||||
|                     is_proxied: self.is_proxied, | ||||
|                     tx: PoolTx::Http2(tx.clone()), | ||||
|                 }; | ||||
|                 let a = PoolClient { | ||||
|                     is_proxied: self.is_proxied, | ||||
|                     tx: PoolTx::Http2(tx), | ||||
|                 }; | ||||
|                 Reservation::Shared(a, b) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -387,17 +476,24 @@ enum ClientError<B> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// A marker to identify what version a pooled connection is. | ||||
| #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||||
| enum Ver { | ||||
|     Http1, | ||||
|     Http2, | ||||
| } | ||||
|  | ||||
| fn set_relative_uri(uri: &mut Uri, is_proxied: bool) { | ||||
|     if is_proxied && uri.scheme_part() != Some(&Scheme::HTTPS) { | ||||
|         return; | ||||
|     } | ||||
|     let path = match uri.path_and_query() { | ||||
|         Some(path) => { | ||||
|         Some(path) if path.as_str() != "/" => { | ||||
|             let mut parts = ::http::uri::Parts::default(); | ||||
|             parts.path_and_query = Some(path.clone()); | ||||
|             Uri::from_parts(parts).expect("path is valid uri") | ||||
|         }, | ||||
|         None => { | ||||
|         _none_or_just_slash => { | ||||
|             "/".parse().expect("/ is valid path") | ||||
|         } | ||||
|     }; | ||||
| @@ -416,6 +512,7 @@ pub struct Builder { | ||||
|     max_idle: usize, | ||||
|     retry_canceled_requests: bool, | ||||
|     set_host: bool, | ||||
|     ver: Ver, | ||||
| } | ||||
|  | ||||
| impl Default for Builder { | ||||
| @@ -428,6 +525,7 @@ impl Default for Builder { | ||||
|             max_idle: 5, | ||||
|             retry_canceled_requests: true, | ||||
|             set_host: true, | ||||
|             ver: Ver::Http1, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -467,6 +565,20 @@ impl Builder { | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set whether the connection **must** use HTTP/2. | ||||
|     /// | ||||
|     /// Note that setting this to true prevents HTTP/1 from being allowed. | ||||
|     /// | ||||
|     /// Default is false. | ||||
|     pub fn http2_only(&mut self, val: bool) -> &mut Self { | ||||
|         self.ver = if val { | ||||
|             Ver::Http2 | ||||
|         } else { | ||||
|             Ver::Http1 | ||||
|         }; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set whether to retry requests that get disrupted before ever starting | ||||
|     /// to write. | ||||
|     /// | ||||
| @@ -534,6 +646,7 @@ impl Builder { | ||||
|             pool: Pool::new(self.keep_alive, self.keep_alive_timeout), | ||||
|             retry_canceled_requests: self.retry_canceled_requests, | ||||
|             set_host: self.set_host, | ||||
|             ver: self.ver, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -546,33 +659,20 @@ impl fmt::Debug for Builder { | ||||
|             .field("http1_writev", &self.h1_writev) | ||||
|             .field("max_idle", &self.max_idle) | ||||
|             .field("set_host", &self.set_host) | ||||
|             .field("version", &self.ver) | ||||
|             .finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ===== impl Exec ===== | ||||
| #[cfg(test)] | ||||
| mod unit_tests { | ||||
|     use super::*; | ||||
|  | ||||
| #[derive(Clone)] | ||||
| enum Exec { | ||||
|     Default, | ||||
|     Executor(Arc<Executor<Box<Future<Item=(), Error=()> + Send>> + Send + Sync>), | ||||
| } | ||||
|     #[test] | ||||
|     fn set_relative_uri_with_implicit_path() { | ||||
|         let mut uri = "http://hyper.rs".parse().unwrap(); | ||||
|         set_relative_uri(&mut uri, false); | ||||
|  | ||||
|  | ||||
| impl Exec { | ||||
|     fn execute<F>(&self, fut: F) | ||||
|     where | ||||
|         F: Future<Item=(), Error=()> + Send + 'static, | ||||
|     { | ||||
|         match *self { | ||||
|             Exec::Default => spawn(fut), | ||||
|             Exec::Executor(ref e) => { | ||||
|                 let _ = e.execute(Box::new(fut)) | ||||
|                     .map_err(|err| { | ||||
|                         panic!("executor error: {:?}", err.kind()); | ||||
|                     }); | ||||
|             }, | ||||
|         } | ||||
|         assert_eq!(uri.to_string(), "/"); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| use std::collections::{HashMap, VecDeque}; | ||||
| use std::collections::{HashMap, HashSet, VecDeque}; | ||||
| use std::fmt; | ||||
| use std::ops::{Deref, DerefMut}; | ||||
| use std::sync::{Arc, Mutex, Weak}; | ||||
| @@ -8,10 +8,10 @@ use futures::{Future, Async, Poll, Stream}; | ||||
| use futures::sync::oneshot; | ||||
| use futures_timer::Interval; | ||||
|  | ||||
| use common::Never; | ||||
| use super::Exec; | ||||
| use common::{Exec, Never}; | ||||
| use super::Ver; | ||||
|  | ||||
| pub struct Pool<T> { | ||||
| pub(super) struct Pool<T> { | ||||
|     inner: Arc<Mutex<PoolInner<T>>>, | ||||
| } | ||||
|  | ||||
| @@ -20,15 +20,42 @@ pub struct Pool<T> { | ||||
| // This is a trait to allow the `client::pool::tests` to work for `i32`. | ||||
| // | ||||
| // See https://github.com/hyperium/hyper/issues/1429 | ||||
| pub trait Closed { | ||||
| pub(super) trait Poolable: Sized { | ||||
|     fn is_closed(&self) -> bool; | ||||
|     /// Reserve this connection. | ||||
|     /// | ||||
|     /// Allows for HTTP/2 to return a shared reservation. | ||||
|     fn reserve(self) -> Reservation<Self>; | ||||
| } | ||||
|  | ||||
| /// When checking out a pooled connection, it might be that the connection | ||||
| /// only supports a single reservation, or it might be usable for many. | ||||
| /// | ||||
| /// Specifically, HTTP/1 requires a unique reservation, but HTTP/2 can be | ||||
| /// used for multiple requests. | ||||
| pub(super) enum Reservation<T> { | ||||
|     /// This connection could be used multiple times, the first one will be | ||||
|     /// reinserted into the `idle` pool, and the second will be given to | ||||
|     /// the `Checkout`. | ||||
|     #[allow(unused)] | ||||
|     Shared(T, T), | ||||
|     /// This connection requires unique access. It will be returned after | ||||
|     /// use is complete. | ||||
|     Unique(T), | ||||
| } | ||||
|  | ||||
| /// Simple type alias in case the key type needs to be adjusted. | ||||
| type Key = (Arc<String>, Ver); | ||||
|  | ||||
| struct PoolInner<T> { | ||||
|     // A flag that a connection is being estabilished, and the connection | ||||
|     // should be shared. This prevents making multiple HTTP/2 connections | ||||
|     // to the same host. | ||||
|     connecting: HashSet<Key>, | ||||
|     enabled: bool, | ||||
|     // These are internal Conns sitting in the event loop in the KeepAlive | ||||
|     // state, waiting to receive a new Request to send on the socket. | ||||
|     idle: HashMap<Arc<String>, Vec<Idle<T>>>, | ||||
|     idle: HashMap<Key, Vec<Idle<T>>>, | ||||
|     // These are outstanding Checkouts that are waiting for a socket to be | ||||
|     // able to send a Request one. This is used when "racing" for a new | ||||
|     // connection. | ||||
| @@ -38,7 +65,7 @@ struct PoolInner<T> { | ||||
|     // this list is checked for any parked Checkouts, and tries to notify | ||||
|     // them that the Conn could be used instead of waiting for a brand new | ||||
|     // connection. | ||||
|     parked: HashMap<Arc<String>, VecDeque<oneshot::Sender<T>>>, | ||||
|     parked: HashMap<Key, VecDeque<oneshot::Sender<T>>>, | ||||
|     timeout: Option<Duration>, | ||||
|     // A oneshot channel is used to allow the interval to be notified when | ||||
|     // the Pool completely drops. That way, the interval can cancel immediately. | ||||
| @@ -49,6 +76,7 @@ impl<T> Pool<T> { | ||||
|     pub fn new(enabled: bool, timeout: Option<Duration>) -> Pool<T> { | ||||
|         Pool { | ||||
|             inner: Arc::new(Mutex::new(PoolInner { | ||||
|                 connecting: HashSet::new(), | ||||
|                 enabled: enabled, | ||||
|                 idle: HashMap::new(), | ||||
|                 idle_interval_ref: None, | ||||
| @@ -59,40 +87,69 @@ impl<T> Pool<T> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T: Closed> Pool<T> { | ||||
|     pub fn checkout(&self, key: &str) -> Checkout<T> { | ||||
| impl<T: Poolable> Pool<T> { | ||||
|     /// Returns a `Checkout` which is a future that resolves if an idle | ||||
|     /// connection becomes available. | ||||
|     pub fn checkout(&self, key: Key) -> Checkout<T> { | ||||
|         Checkout { | ||||
|             key: Arc::new(key.to_owned()), | ||||
|             key, | ||||
|             pool: self.clone(), | ||||
|             parked: None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn take(&self, key: &Arc<String>) -> Option<Pooled<T>> { | ||||
|     /// Ensure that there is only ever 1 connecting task for HTTP/2 | ||||
|     /// connections. This does nothing for HTTP/1. | ||||
|     pub(super) fn connecting(&self, key: &Key) -> Option<Connecting<T>> { | ||||
|         if key.1 == Ver::Http2 { | ||||
|             let mut inner = self.inner.lock().unwrap(); | ||||
|             if inner.connecting.insert(key.clone()) { | ||||
|                 let connecting = Connecting { | ||||
|                     key: key.clone(), | ||||
|                     pool: Arc::downgrade(&self.inner), | ||||
|                 }; | ||||
|                 Some(connecting) | ||||
|             } else { | ||||
|                 trace!("HTTP/2 connecting already in progress for {:?}", key.0); | ||||
|                 None | ||||
|             } | ||||
|         } else { | ||||
|             Some(Connecting { | ||||
|                 key: key.clone(), | ||||
|                 // in HTTP/1's case, there is never a lock, so we don't | ||||
|                 // need to do anything in Drop. | ||||
|                 pool: Weak::new(), | ||||
|             }) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn take(&self, key: &Key) -> Option<Pooled<T>> { | ||||
|         let entry = { | ||||
|             let mut inner = self.inner.lock().unwrap(); | ||||
|             let expiration = Expiration::new(inner.timeout); | ||||
|             let mut should_remove = false; | ||||
|             let entry = inner.idle.get_mut(key).and_then(|list| { | ||||
|                 trace!("take; url = {:?}, expiration = {:?}", key, expiration.0); | ||||
|                 while let Some(entry) = list.pop() { | ||||
|                     if !expiration.expires(entry.idle_at) { | ||||
|                         if !entry.value.is_closed() { | ||||
|                             should_remove = list.is_empty(); | ||||
|                             return Some(entry); | ||||
|                         } | ||||
|             let maybe_entry = inner.idle.get_mut(key) | ||||
|                 .and_then(|list| { | ||||
|                     trace!("take? {:?}: expiration = {:?}", key, expiration.0); | ||||
|                     // A block to end the mutable borrow on list, | ||||
|                     // so the map below can check is_empty() | ||||
|                     { | ||||
|                         let popper = IdlePopper { | ||||
|                             key, | ||||
|                             list, | ||||
|                         }; | ||||
|                         popper.pop(&expiration) | ||||
|                     } | ||||
|                     trace!("removing unacceptable pooled {:?}", key); | ||||
|                     // every other case the Idle should just be dropped | ||||
|                     // 1. Idle but expired | ||||
|                     // 2. Busy (something else somehow took it?) | ||||
|                     // 3. Disabled don't reuse of course | ||||
|                 } | ||||
|                 should_remove = true; | ||||
|                 None | ||||
|             }); | ||||
|                         .map(|e| (e, list.is_empty())) | ||||
|                 }); | ||||
|  | ||||
|             if should_remove { | ||||
|             let (entry, empty) = if let Some((e, empty)) = maybe_entry { | ||||
|                 (Some(e), empty) | ||||
|             } else { | ||||
|                 // No entry found means nuke the list for sure. | ||||
|                 (None, true) | ||||
|             }; | ||||
|             if empty { | ||||
|                 //TODO: This could be done with the HashMap::entry API instead. | ||||
|                 inner.idle.remove(key); | ||||
|             } | ||||
|             entry | ||||
| @@ -101,17 +158,35 @@ impl<T: Closed> Pool<T> { | ||||
|         entry.map(|e| self.reuse(key, e.value)) | ||||
|     } | ||||
|  | ||||
|     pub(super) fn pooled(&self, mut connecting: Connecting<T>, value: T) -> Pooled<T> { | ||||
|         let value = match value.reserve() { | ||||
|             Reservation::Shared(to_insert, to_return) => { | ||||
|                 debug_assert_eq!( | ||||
|                     connecting.key.1, | ||||
|                     Ver::Http2, | ||||
|                     "shared reservation without Http2" | ||||
|                 ); | ||||
|                 let mut inner = self.inner.lock().unwrap(); | ||||
|                 inner.put(connecting.key.clone(), to_insert); | ||||
|                 // Do this here instead of Drop for Connecting because we | ||||
|                 // already have a lock, no need to lock the mutex twice. | ||||
|                 inner.connected(&connecting.key); | ||||
|                 // prevent the Drop of Connecting from repeating inner.connected() | ||||
|                 connecting.pool = Weak::new(); | ||||
|  | ||||
|     pub fn pooled(&self, key: Arc<String>, value: T) -> Pooled<T> { | ||||
|                 to_return | ||||
|             }, | ||||
|             Reservation::Unique(value) => value, | ||||
|         }; | ||||
|         Pooled { | ||||
|             is_reused: false, | ||||
|             key: key, | ||||
|             key: connecting.key.clone(), | ||||
|             pool: Arc::downgrade(&self.inner), | ||||
|             value: Some(value) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn reuse(&self, key: &Arc<String>, value: T) -> Pooled<T> { | ||||
|     fn reuse(&self, key: &Key, value: T) -> Pooled<T> { | ||||
|         debug!("reuse idle connection for {:?}", key); | ||||
|         Pooled { | ||||
|             is_reused: true, | ||||
| @@ -121,8 +196,8 @@ impl<T: Closed> Pool<T> { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn park(&mut self, key: Arc<String>, tx: oneshot::Sender<T>) { | ||||
|         trace!("park; waiting for idle connection: {:?}", key); | ||||
|     fn park(&mut self, key: Key, tx: oneshot::Sender<T>) { | ||||
|         trace!("checkout waiting for idle connection: {:?}", key); | ||||
|         self.inner.lock().unwrap() | ||||
|             .parked.entry(key) | ||||
|             .or_insert(VecDeque::new()) | ||||
| @@ -130,19 +205,83 @@ impl<T: Closed> Pool<T> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T: Closed> PoolInner<T> { | ||||
|     fn put(&mut self, key: Arc<String>, value: T) { | ||||
| /// Pop off this list, looking for a usable connection that hasn't expired. | ||||
| struct IdlePopper<'a, T: 'a> { | ||||
|     key: &'a Key, | ||||
|     list: &'a mut Vec<Idle<T>>, | ||||
| } | ||||
|  | ||||
| impl<'a, T: Poolable + 'a> IdlePopper<'a, T> { | ||||
|     fn pop(self, expiration: &Expiration) -> Option<Idle<T>> { | ||||
|         while let Some(entry) = self.list.pop() { | ||||
|             // If the connection has been closed, or is older than our idle | ||||
|             // timeout, simply drop it and keep looking... | ||||
|             // | ||||
|             // TODO: Actually, since the `idle` list is pushed to the end always, | ||||
|             // that would imply that if *this* entry is expired, then anything | ||||
|             // "earlier" in the list would *have* to be expired also... Right? | ||||
|             // | ||||
|             // In that case, we could just break out of the loop and drop the | ||||
|             // whole list... | ||||
|             if entry.value.is_closed() || expiration.expires(entry.idle_at) { | ||||
|                 trace!("remove unacceptable pooled connection for {:?}", self.key); | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             let value = match entry.value.reserve() { | ||||
|                 Reservation::Shared(to_reinsert, to_checkout) => { | ||||
|                     self.list.push(Idle { | ||||
|                         idle_at: Instant::now(), | ||||
|                         value: to_reinsert, | ||||
|                     }); | ||||
|                     to_checkout | ||||
|                 }, | ||||
|                 Reservation::Unique(unique) => { | ||||
|                     unique | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|             return Some(Idle { | ||||
|                 idle_at: entry.idle_at, | ||||
|                 value, | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         None | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T: Poolable> PoolInner<T> { | ||||
|     fn put(&mut self, key: Key, value: T) { | ||||
|         if !self.enabled { | ||||
|             return; | ||||
|         } | ||||
|         if key.1 == Ver::Http2 && self.idle.contains_key(&key) { | ||||
|             trace!("Pool::put; existing idle HTTP/2 connection for {:?}", key); | ||||
|             return; | ||||
|         } | ||||
|         trace!("Pool::put {:?}", key); | ||||
|         let mut remove_parked = false; | ||||
|         let mut value = Some(value); | ||||
|         if let Some(parked) = self.parked.get_mut(&key) { | ||||
|             while let Some(tx) = parked.pop_front() { | ||||
|                 if !tx.is_canceled() { | ||||
|                     match tx.send(value.take().unwrap()) { | ||||
|                         Ok(()) => break, | ||||
|                     let reserved = value.take().expect("value already sent"); | ||||
|                     let reserved = match reserved.reserve() { | ||||
|                         Reservation::Shared(to_keep, to_send) => { | ||||
|                             value = Some(to_keep); | ||||
|                             to_send | ||||
|                         }, | ||||
|                         Reservation::Unique(uniq) => uniq, | ||||
|                     }; | ||||
|                     match tx.send(reserved) { | ||||
|                         Ok(()) => { | ||||
|                             if value.is_none() { | ||||
|                                 break; | ||||
|                             } else { | ||||
|                                 continue; | ||||
|                             } | ||||
|                         }, | ||||
|                         Err(e) => { | ||||
|                             value = Some(e); | ||||
|                         } | ||||
| @@ -170,6 +309,20 @@ impl<T: Closed> PoolInner<T> { | ||||
|             None => trace!("Pool::put found parked {:?}", key), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// A `Connecting` task is complete. Not necessarily successfully, | ||||
|     /// but the lock is going away, so clean up. | ||||
|     fn connected(&mut self, key: &Key) { | ||||
|         let existed = self.connecting.remove(key); | ||||
|         debug_assert!( | ||||
|             existed, | ||||
|             "Connecting dropped, key not in pool.connecting" | ||||
|         ); | ||||
|         // cancel any waiters. if there are any, it's because | ||||
|         // this Connecting task didn't complete successfully. | ||||
|         // those waiters would never receive a connection. | ||||
|         self.parked.remove(key); | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T> PoolInner<T> { | ||||
| @@ -177,7 +330,7 @@ impl<T> PoolInner<T> { | ||||
|     /// and possibly inserted into the pool that it is waiting for an idle | ||||
|     /// connection. If a user ever dropped that future, we need to clean out | ||||
|     /// those parked senders. | ||||
|     fn clean_parked(&mut self, key: &Arc<String>) { | ||||
|     fn clean_parked(&mut self, key: &Key) { | ||||
|         let mut remove_parked = false; | ||||
|         if let Some(parked) = self.parked.get_mut(key) { | ||||
|             parked.retain(|tx| { | ||||
| @@ -191,7 +344,7 @@ impl<T> PoolInner<T> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T: Closed> PoolInner<T> { | ||||
| impl<T: Poolable> PoolInner<T> { | ||||
|     fn clear_expired(&mut self) { | ||||
|         let dur = if let Some(dur) = self.timeout { | ||||
|             dur | ||||
| @@ -218,7 +371,7 @@ impl<T: Closed> PoolInner<T> { | ||||
| } | ||||
|  | ||||
|  | ||||
| impl<T: Closed + Send + 'static> Pool<T> { | ||||
| impl<T: Poolable + Send + 'static> Pool<T> { | ||||
|     pub(super) fn spawn_expired_interval(&self, exec: &Exec) { | ||||
|         let (dur, rx) = { | ||||
|             let mut inner = self.inner.lock().unwrap(); | ||||
| @@ -257,14 +410,16 @@ impl<T> Clone for Pool<T> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct Pooled<T: Closed> { | ||||
| /// A wrapped poolable value that tries to reinsert to the Pool on Drop. | ||||
| // Note: The bounds `T: Poolable` is needed for the Drop impl. | ||||
| pub(super) struct Pooled<T: Poolable> { | ||||
|     value: Option<T>, | ||||
|     is_reused: bool, | ||||
|     key: Arc<String>, | ||||
|     key: Key, | ||||
|     pool: Weak<Mutex<PoolInner<T>>>, | ||||
| } | ||||
|  | ||||
| impl<T: Closed> Pooled<T> { | ||||
| impl<T: Poolable> Pooled<T> { | ||||
|     pub fn is_reused(&self) -> bool { | ||||
|         self.is_reused | ||||
|     } | ||||
| @@ -278,22 +433,28 @@ impl<T: Closed> Pooled<T> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T: Closed> Deref for Pooled<T> { | ||||
| impl<T: Poolable> Deref for Pooled<T> { | ||||
|     type Target = T; | ||||
|     fn deref(&self) -> &T { | ||||
|         self.as_ref() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T: Closed> DerefMut for Pooled<T> { | ||||
| impl<T: Poolable> DerefMut for Pooled<T> { | ||||
|     fn deref_mut(&mut self) -> &mut T { | ||||
|         self.as_mut() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T: Closed> Drop for Pooled<T> { | ||||
| impl<T: Poolable> Drop for Pooled<T> { | ||||
|     fn drop(&mut self) { | ||||
|         if let Some(value) = self.value.take() { | ||||
|             if value.is_closed() { | ||||
|                 // If we *already* know the connection is done here, | ||||
|                 // it shouldn't be re-inserted back into the pool. | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             if let Some(inner) = self.pool.upgrade() { | ||||
|                 if let Ok(mut inner) = inner.lock() { | ||||
|                     inner.put(self.key.clone(), value); | ||||
| @@ -305,7 +466,7 @@ impl<T: Closed> Drop for Pooled<T> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T: Closed> fmt::Debug for Pooled<T> { | ||||
| impl<T: Poolable> fmt::Debug for Pooled<T> { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         f.debug_struct("Pooled") | ||||
|             .field("key", &self.key) | ||||
| @@ -318,33 +479,30 @@ struct Idle<T> { | ||||
|     value: T, | ||||
| } | ||||
|  | ||||
| pub struct Checkout<T> { | ||||
|     key: Arc<String>, | ||||
| pub(super) struct Checkout<T> { | ||||
|     key: Key, | ||||
|     pool: Pool<T>, | ||||
|     parked: Option<oneshot::Receiver<T>>, | ||||
| } | ||||
|  | ||||
| struct NotParked; | ||||
|  | ||||
| impl<T: Closed> Checkout<T> { | ||||
|     fn poll_parked(&mut self) -> Poll<Pooled<T>, NotParked> { | ||||
|         let mut drop_parked = false; | ||||
| impl<T: Poolable> Checkout<T> { | ||||
|     fn poll_parked(&mut self) -> Poll<Option<Pooled<T>>, ::Error> { | ||||
|         static CANCELED: &str = "pool checkout failed"; | ||||
|         if let Some(ref mut rx) = self.parked { | ||||
|             match rx.poll() { | ||||
|                 Ok(Async::Ready(value)) => { | ||||
|                     if !value.is_closed() { | ||||
|                         return Ok(Async::Ready(self.pool.reuse(&self.key, value))); | ||||
|                         Ok(Async::Ready(Some(self.pool.reuse(&self.key, value)))) | ||||
|                     } else { | ||||
|                         Err(::Error::new_canceled(Some(CANCELED))) | ||||
|                     } | ||||
|                     drop_parked = true; | ||||
|                 }, | ||||
|                 Ok(Async::NotReady) => return Ok(Async::NotReady), | ||||
|                 Err(_canceled) => drop_parked = true, | ||||
|                 Ok(Async::NotReady) => Ok(Async::NotReady), | ||||
|                 Err(_canceled) => Err(::Error::new_canceled(Some(CANCELED))), | ||||
|             } | ||||
|         } else { | ||||
|             Ok(Async::Ready(None)) | ||||
|         } | ||||
|         if drop_parked { | ||||
|             self.parked.take(); | ||||
|         } | ||||
|         Err(NotParked) | ||||
|     } | ||||
|  | ||||
|     fn park(&mut self) { | ||||
| @@ -357,14 +515,13 @@ impl<T: Closed> Checkout<T> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T: Closed> Future for Checkout<T> { | ||||
| impl<T: Poolable> Future for Checkout<T> { | ||||
|     type Item = Pooled<T>; | ||||
|     type Error = ::Error; | ||||
|  | ||||
|     fn poll(&mut self) -> Poll<Self::Item, Self::Error> { | ||||
|         match self.poll_parked() { | ||||
|             Ok(async) => return Ok(async), | ||||
|             Err(_not_parked) => (), | ||||
|         if let Some(pooled) = try_ready!(self.poll_parked()) { | ||||
|             return Ok(Async::Ready(pooled)); | ||||
|         } | ||||
|  | ||||
|         let entry = self.pool.take(&self.key); | ||||
| @@ -387,6 +544,27 @@ impl<T> Drop for Checkout<T> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub(super) struct Connecting<T: Poolable> { | ||||
|     key: Key, | ||||
|     pool: Weak<Mutex<PoolInner<T>>>, | ||||
| } | ||||
|  | ||||
| impl<T: Poolable> Drop for Connecting<T> { | ||||
|     fn drop(&mut self) { | ||||
|         if let Some(pool) = self.pool.upgrade() { | ||||
|             // No need to panic on drop, that could abort! | ||||
|             if let Ok(mut inner) = pool.lock() { | ||||
|                 debug_assert_eq!( | ||||
|                     self.key.1, | ||||
|                     Ver::Http2, | ||||
|                     "Connecting constructed without Http2" | ||||
|                 ); | ||||
|                 inner.connected(&self.key); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct Expiration(Option<Duration>); | ||||
|  | ||||
| impl Expiration { | ||||
| @@ -411,7 +589,7 @@ struct IdleInterval<T> { | ||||
|     pool_drop_notifier: oneshot::Receiver<Never>, | ||||
| } | ||||
|  | ||||
| impl<T: Closed + 'static> Future for IdleInterval<T> { | ||||
| impl<T: Poolable + 'static> Future for IdleInterval<T> { | ||||
|     type Item = (); | ||||
|     type Error = (); | ||||
|  | ||||
| @@ -441,28 +619,58 @@ impl<T: Closed + 'static> Future for IdleInterval<T> { | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use std::sync::Arc; | ||||
|     use std::sync::{Arc, Weak}; | ||||
|     use std::time::Duration; | ||||
|     use futures::{Async, Future}; | ||||
|     use futures::future; | ||||
|     use super::{Closed, Pool, Exec}; | ||||
|     use super::{Connecting, Key, Poolable, Pool, Reservation, Exec, Ver}; | ||||
|  | ||||
|     impl Closed for i32 { | ||||
|     /// Test unique reservations. | ||||
|     #[derive(Debug, PartialEq, Eq)] | ||||
|     struct Uniq<T>(T); | ||||
|  | ||||
|     impl<T> Poolable for Uniq<T> { | ||||
|         fn is_closed(&self) -> bool { | ||||
|             false | ||||
|         } | ||||
|  | ||||
|         fn reserve(self) -> Reservation<Self> { | ||||
|             Reservation::Unique(self) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|     #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||||
|     struct Share<T>(T); | ||||
|  | ||||
|     impl<T> Poolable for Share<T> { | ||||
|         fn is_closed(&self) -> bool { | ||||
|             false | ||||
|         } | ||||
|  | ||||
|         fn reserve(self) -> Reservation<Self> { | ||||
|             Reservation::Shared(self.clone(), self) | ||||
|         } | ||||
|     } | ||||
|     */ | ||||
|  | ||||
|     fn c<T: Poolable>(key: Key) -> Connecting<T> { | ||||
|         Connecting { | ||||
|             key, | ||||
|             pool: Weak::new(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_pool_checkout_smoke() { | ||||
|         let pool = Pool::new(true, Some(Duration::from_secs(5))); | ||||
|         let key = Arc::new("foo".to_string()); | ||||
|         let pooled = pool.pooled(key.clone(), 41); | ||||
|         let key = (Arc::new("foo".to_string()), Ver::Http1); | ||||
|         let pooled = pool.pooled(c(key.clone()), Uniq(41)); | ||||
|  | ||||
|         drop(pooled); | ||||
|  | ||||
|         match pool.checkout(&key).poll().unwrap() { | ||||
|             Async::Ready(pooled) => assert_eq!(*pooled, 41), | ||||
|         match pool.checkout(key).poll().unwrap() { | ||||
|             Async::Ready(pooled) => assert_eq!(*pooled, Uniq(41)), | ||||
|             _ => panic!("not ready"), | ||||
|         } | ||||
|     } | ||||
| @@ -471,11 +679,11 @@ mod tests { | ||||
|     fn test_pool_checkout_returns_none_if_expired() { | ||||
|         future::lazy(|| { | ||||
|             let pool = Pool::new(true, Some(Duration::from_millis(100))); | ||||
|             let key = Arc::new("foo".to_string()); | ||||
|             let pooled = pool.pooled(key.clone(), 41); | ||||
|             let key = (Arc::new("foo".to_string()), Ver::Http1); | ||||
|             let pooled = pool.pooled(c(key.clone()), Uniq(41)); | ||||
|             drop(pooled); | ||||
|             ::std::thread::sleep(pool.inner.lock().unwrap().timeout.unwrap()); | ||||
|             assert!(pool.checkout(&key).poll().unwrap().is_not_ready()); | ||||
|             assert!(pool.checkout(key).poll().unwrap().is_not_ready()); | ||||
|             ::futures::future::ok::<(), ()>(()) | ||||
|         }).wait().unwrap(); | ||||
|     } | ||||
| @@ -484,17 +692,17 @@ mod tests { | ||||
|     fn test_pool_checkout_removes_expired() { | ||||
|         future::lazy(|| { | ||||
|             let pool = Pool::new(true, Some(Duration::from_millis(100))); | ||||
|             let key = Arc::new("foo".to_string()); | ||||
|             let key = (Arc::new("foo".to_string()), Ver::Http1); | ||||
|  | ||||
|             pool.pooled(key.clone(), 41); | ||||
|             pool.pooled(key.clone(), 5); | ||||
|             pool.pooled(key.clone(), 99); | ||||
|             pool.pooled(c(key.clone()), Uniq(41)); | ||||
|             pool.pooled(c(key.clone()), Uniq(5)); | ||||
|             pool.pooled(c(key.clone()), Uniq(99)); | ||||
|  | ||||
|             assert_eq!(pool.inner.lock().unwrap().idle.get(&key).map(|entries| entries.len()), Some(3)); | ||||
|             ::std::thread::sleep(pool.inner.lock().unwrap().timeout.unwrap()); | ||||
|  | ||||
|             // checkout.poll() should clean out the expired | ||||
|             pool.checkout(&key).poll().unwrap(); | ||||
|             pool.checkout(key.clone()).poll().unwrap(); | ||||
|             assert!(pool.inner.lock().unwrap().idle.get(&key).is_none()); | ||||
|  | ||||
|             Ok::<(), ()>(()) | ||||
| @@ -509,11 +717,11 @@ mod tests { | ||||
|  | ||||
|         let executor = runtime.executor(); | ||||
|         pool.spawn_expired_interval(&Exec::Executor(Arc::new(executor))); | ||||
|         let key = Arc::new("foo".to_string()); | ||||
|         let key = (Arc::new("foo".to_string()), Ver::Http1); | ||||
|  | ||||
|         pool.pooled(key.clone(), 41); | ||||
|         pool.pooled(key.clone(), 5); | ||||
|         pool.pooled(key.clone(), 99); | ||||
|         pool.pooled(c(key.clone()), Uniq(41)); | ||||
|         pool.pooled(c(key.clone()), Uniq(5)); | ||||
|         pool.pooled(c(key.clone()), Uniq(99)); | ||||
|  | ||||
|         assert_eq!(pool.inner.lock().unwrap().idle.get(&key).map(|entries| entries.len()), Some(3)); | ||||
|  | ||||
| @@ -527,10 +735,10 @@ mod tests { | ||||
|     #[test] | ||||
|     fn test_pool_checkout_task_unparked() { | ||||
|         let pool = Pool::new(true, Some(Duration::from_secs(10))); | ||||
|         let key = Arc::new("foo".to_string()); | ||||
|         let pooled = pool.pooled(key.clone(), 41); | ||||
|         let key = (Arc::new("foo".to_string()), Ver::Http1); | ||||
|         let pooled = pool.pooled(c(key.clone()), Uniq(41)); | ||||
|  | ||||
|         let checkout = pool.checkout(&key).join(future::lazy(move || { | ||||
|         let checkout = pool.checkout(key).join(future::lazy(move || { | ||||
|             // the checkout future will park first, | ||||
|             // and then this lazy future will be polled, which will insert | ||||
|             // the pooled back into the pool | ||||
| @@ -539,17 +747,17 @@ mod tests { | ||||
|             drop(pooled); | ||||
|             Ok(()) | ||||
|         })).map(|(entry, _)| entry); | ||||
|         assert_eq!(*checkout.wait().unwrap(), 41); | ||||
|         assert_eq!(*checkout.wait().unwrap(), Uniq(41)); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_pool_checkout_drop_cleans_up_parked() { | ||||
|         future::lazy(|| { | ||||
|             let pool = Pool::<i32>::new(true, Some(Duration::from_secs(10))); | ||||
|             let key = Arc::new("localhost:12345".to_string()); | ||||
|             let pool = Pool::<Uniq<i32>>::new(true, Some(Duration::from_secs(10))); | ||||
|             let key = (Arc::new("localhost:12345".to_string()), Ver::Http1); | ||||
|  | ||||
|             let mut checkout1 = pool.checkout(&key); | ||||
|             let mut checkout2 = pool.checkout(&key); | ||||
|             let mut checkout1 = pool.checkout(key.clone()); | ||||
|             let mut checkout2 = pool.checkout(key.clone()); | ||||
|  | ||||
|             // first poll needed to get into Pool's parked | ||||
|             checkout1.poll().unwrap(); | ||||
| @@ -567,4 +775,32 @@ mod tests { | ||||
|             ::futures::future::ok::<(), ()>(()) | ||||
|         }).wait().unwrap(); | ||||
|     } | ||||
|  | ||||
|     #[derive(Debug)] | ||||
|     struct CanClose { | ||||
|         val: i32, | ||||
|         closed: bool, | ||||
|     } | ||||
|  | ||||
|     impl Poolable for CanClose { | ||||
|         fn is_closed(&self) -> bool { | ||||
|             self.closed | ||||
|         } | ||||
|  | ||||
|         fn reserve(self) -> Reservation<Self> { | ||||
|             Reservation::Unique(self) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn pooled_drop_if_closed_doesnt_reinsert() { | ||||
|         let pool = Pool::new(true, Some(Duration::from_secs(10))); | ||||
|         let key = (Arc::new("localhost:12345".to_string()), Ver::Http1); | ||||
|         pool.pooled(c(key.clone()), CanClose { | ||||
|             val: 57, | ||||
|             closed: true, | ||||
|         }); | ||||
|  | ||||
|         assert!(!pool.inner.lock().unwrap().idle.contains_key(&key)); | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										38
									
								
								src/common/exec.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/common/exec.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| use std::fmt; | ||||
| use std::sync::Arc; | ||||
|  | ||||
| use futures::future::{Executor, Future}; | ||||
| use tokio_executor::spawn; | ||||
|  | ||||
| /// Either the user provides an executor for background tasks, or we use | ||||
| /// `tokio::spawn`. | ||||
| #[derive(Clone)] | ||||
| pub(crate) enum Exec { | ||||
|     Default, | ||||
|     Executor(Arc<Executor<Box<Future<Item=(), Error=()> + Send>> + Send + Sync>), | ||||
| } | ||||
|  | ||||
|  | ||||
| impl Exec { | ||||
|     pub(crate) fn execute<F>(&self, fut: F) | ||||
|     where | ||||
|         F: Future<Item=(), Error=()> + Send + 'static, | ||||
|     { | ||||
|         match *self { | ||||
|             Exec::Default => spawn(fut), | ||||
|             Exec::Executor(ref e) => { | ||||
|                 let _ = e.execute(Box::new(fut)) | ||||
|                     .map_err(|err| { | ||||
|                         panic!("executor error: {:?}", err.kind()); | ||||
|                     }); | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl fmt::Debug for Exec { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         f.debug_struct("Exec") | ||||
|             .finish() | ||||
|     } | ||||
| } | ||||
| @@ -1,2 +1,6 @@ | ||||
| mod exec; | ||||
|  | ||||
| pub(crate) use self::exec::Exec; | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub enum Never {} | ||||
|   | ||||
							
								
								
									
										11
									
								
								src/error.rs
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								src/error.rs
									
									
									
									
									
								
							| @@ -55,6 +55,9 @@ pub(crate) enum Kind { | ||||
|     /// Error calling AsyncWrite::shutdown() | ||||
|     Shutdown, | ||||
|  | ||||
|     /// A general error from h2. | ||||
|     Http2, | ||||
|  | ||||
|     /// User tried to create a Request with bad version. | ||||
|     UnsupportedVersion, | ||||
|     /// User tried to create a CONNECT Request with the Client. | ||||
| @@ -215,6 +218,10 @@ impl Error { | ||||
|     pub(crate) fn new_shutdown(cause: io::Error) -> Error { | ||||
|         Error::new(Kind::Shutdown, Some(Box::new(cause))) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_h2(cause: ::h2::Error) -> Error { | ||||
|         Error::new(Kind::Http2, Some(Box::new(cause))) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl fmt::Debug for Error { | ||||
| @@ -259,6 +266,7 @@ impl StdError for Error { | ||||
|             Kind::BodyWrite => "error write a body to connection", | ||||
|             Kind::BodyUser => "error from user's Payload stream", | ||||
|             Kind::Shutdown => "error shutting down connection", | ||||
|             Kind::Http2 => "http2 general error", | ||||
|             Kind::UnsupportedVersion => "request has unsupported HTTP version", | ||||
|             Kind::UnsupportedRequestMethod => "request has unsupported HTTP method", | ||||
|  | ||||
| @@ -319,3 +327,6 @@ trait AssertSendSync: Send + Sync + 'static {} | ||||
| #[doc(hidden)] | ||||
| impl AssertSendSync for Error {} | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
| } | ||||
|   | ||||
| @@ -20,6 +20,7 @@ extern crate bytes; | ||||
| #[macro_use] extern crate futures; | ||||
| extern crate futures_cpupool; | ||||
| extern crate futures_timer; | ||||
| extern crate h2; | ||||
| extern crate http; | ||||
| extern crate httparse; | ||||
| extern crate iovec; | ||||
| @@ -36,6 +37,7 @@ extern crate want; | ||||
| extern crate test; | ||||
|  | ||||
| pub use http::{ | ||||
|     HeaderMap, | ||||
|     Method, | ||||
|     Request, | ||||
|     Response, | ||||
|   | ||||
| @@ -129,7 +129,7 @@ where I: AsyncRead + AsyncWrite, | ||||
|                     let must_error = self.should_error_on_eof(); | ||||
|                     self.state.close_read(); | ||||
|                     self.io.consume_leading_lines(); | ||||
|                     let was_mid_parse = !self.io.read_buf().is_empty(); | ||||
|                     let was_mid_parse = e.is_parse() || !self.io.read_buf().is_empty(); | ||||
|                     return if was_mid_parse || must_error { | ||||
|                         debug!("parse error ({}) with {} bytes", e, self.io.read_buf().len()); | ||||
|                         self.on_parse_error(e) | ||||
| @@ -566,7 +566,7 @@ where I: AsyncRead + AsyncWrite, | ||||
|         match self.io.io_mut().shutdown() { | ||||
|             Ok(Async::NotReady) => Ok(Async::NotReady), | ||||
|             Ok(Async::Ready(())) => { | ||||
|                 trace!("shut down IO"); | ||||
|                 trace!("shut down IO complete"); | ||||
|                 Ok(Async::Ready(())) | ||||
|             } | ||||
|             Err(e) => { | ||||
| @@ -599,6 +599,12 @@ where I: AsyncRead + AsyncWrite, | ||||
|             Ok(()) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Used in h1::dispatch tests | ||||
|     #[cfg(test)] | ||||
|     pub(super) fn io_mut(&mut self) -> &mut I { | ||||
|         self.io.io_mut() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<I, B: AsRef<[u8]>, T> fmt::Debug for Conn<I, B, T> { | ||||
|   | ||||
| @@ -7,8 +7,8 @@ use tokio_service::Service; | ||||
| use body::{Body, Payload}; | ||||
| use proto::{BodyLength, Conn, Http1Transaction, MessageHead, RequestHead, RequestLine, ResponseHead}; | ||||
|  | ||||
| pub(crate) struct Dispatcher<D, Bs, I, B, T> { | ||||
|     conn: Conn<I, B, T>, | ||||
| pub(crate) struct Dispatcher<D, Bs: Payload, I, T> { | ||||
|     conn: Conn<I, Bs::Data, T>, | ||||
|     dispatch: D, | ||||
|     body_tx: Option<::body::Sender>, | ||||
|     body_rx: Option<Bs>, | ||||
| @@ -31,23 +31,20 @@ pub struct Server<S: Service> { | ||||
| } | ||||
|  | ||||
| pub struct Client<B> { | ||||
|     callback: Option<::client::dispatch::Callback<ClientMsg<B>, Response<Body>>>, | ||||
|     callback: Option<::client::dispatch::Callback<Request<B>, Response<Body>>>, | ||||
|     rx: ClientRx<B>, | ||||
| } | ||||
|  | ||||
| pub type ClientMsg<B> = Request<B>; | ||||
| type ClientRx<B> = ::client::dispatch::Receiver<Request<B>, Response<Body>>; | ||||
|  | ||||
| type ClientRx<B> = ::client::dispatch::Receiver<ClientMsg<B>, Response<Body>>; | ||||
|  | ||||
| impl<D, Bs, I, B, T> Dispatcher<D, Bs, I, B, T> | ||||
| impl<D, Bs, I, T> Dispatcher<D, Bs, I, T> | ||||
| where | ||||
|     D: Dispatch<PollItem=MessageHead<T::Outgoing>, PollBody=Bs, RecvItem=MessageHead<T::Incoming>>, | ||||
|     I: AsyncRead + AsyncWrite, | ||||
|     B: AsRef<[u8]>, | ||||
|     T: Http1Transaction, | ||||
|     Bs: Payload<Data=B>, | ||||
|     Bs: Payload, | ||||
| { | ||||
|     pub fn new(dispatch: D, conn: Conn<I, B, T>) -> Self { | ||||
|     pub fn new(dispatch: D, conn: Conn<I, Bs::Data, T>) -> Self { | ||||
|         Dispatcher { | ||||
|             conn: conn, | ||||
|             dispatch: dispatch, | ||||
| @@ -286,13 +283,12 @@ where | ||||
| } | ||||
|  | ||||
|  | ||||
| impl<D, Bs, I, B, T> Future for Dispatcher<D, Bs, I, B, T> | ||||
| impl<D, Bs, I, T> Future for Dispatcher<D, Bs, I, T> | ||||
| where | ||||
|     D: Dispatch<PollItem=MessageHead<T::Outgoing>, PollBody=Bs, RecvItem=MessageHead<T::Incoming>>, | ||||
|     I: AsyncRead + AsyncWrite, | ||||
|     B: AsRef<[u8]>, | ||||
|     T: Http1Transaction, | ||||
|     Bs: Payload<Data=B>, | ||||
|     Bs: Payload, | ||||
| { | ||||
|     type Item = (); | ||||
|     type Error = ::Error; | ||||
| @@ -493,11 +489,18 @@ mod tests { | ||||
|     fn client_read_bytes_before_writing_request() { | ||||
|         let _ = pretty_env_logger::try_init(); | ||||
|         ::futures::lazy(|| { | ||||
|             let io = AsyncIo::new_buf(b"HTTP/1.1 200 OK\r\n\r\n".to_vec(), 100); | ||||
|             // Block at 0 for now, but we will release this response before | ||||
|             // the request is ready to write later... | ||||
|             let io = AsyncIo::new_buf(b"HTTP/1.1 200 OK\r\n\r\n".to_vec(), 0); | ||||
|             let (mut tx, rx) = ::client::dispatch::channel(); | ||||
|             let conn = Conn::<_, ::Chunk, ClientTransaction>::new(io); | ||||
|             let mut dispatcher = Dispatcher::new(Client::new(rx), conn); | ||||
|  | ||||
|             // First poll is needed to allow tx to send... | ||||
|             assert!(dispatcher.poll().expect("nothing is ready").is_not_ready()); | ||||
|             // Unblock our IO, which has a response before we've sent request! | ||||
|             dispatcher.conn.io_mut().block_in(100); | ||||
|  | ||||
|             let res_rx = tx.try_send(::Request::new(::Body::empty())).unwrap(); | ||||
|  | ||||
|             let a1 = dispatcher.poll().expect("error should be sent on channel"); | ||||
| @@ -506,13 +509,6 @@ mod tests { | ||||
|                 .expect("callback poll") | ||||
|                 .expect_err("callback response"); | ||||
|  | ||||
|             /* | ||||
|             let err = match async { | ||||
|                 Async::Ready(result) => result.unwrap_err(), | ||||
|                 Async::Pending => panic!("callback should be ready"), | ||||
|             }; | ||||
|             */ | ||||
|  | ||||
|             match (err.0.kind(), err.1) { | ||||
|                 (&::error::Kind::Canceled, Some(_)) => (), | ||||
|                 other => panic!("expected Canceled, got {:?}", other), | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| pub(crate) use self::conn::Conn; | ||||
| pub(crate) use self::dispatch::Dispatcher; | ||||
| pub use self::decode::Decoder; | ||||
| pub use self::encode::{EncodedBuf, Encoder}; | ||||
| pub use self::io::Cursor; //TODO: move out of h1::io | ||||
|  | ||||
| mod conn; | ||||
| mod date; | ||||
|   | ||||
							
								
								
									
										144
									
								
								src/proto/h2/client.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								src/proto/h2/client.rs
									
									
									
									
									
										Normal 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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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
									
								
							
							
						
						
									
										198
									
								
								src/proto/h2/server.rs
									
									
									
									
									
										Normal 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)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -6,8 +6,8 @@ use headers; | ||||
|  | ||||
| pub(crate) use self::h1::{dispatch, Conn}; | ||||
|  | ||||
| mod h1; | ||||
| //mod h2; | ||||
| pub(crate) mod h1; | ||||
| pub(crate) mod h2; | ||||
|  | ||||
|  | ||||
| /// An Incoming Message head. Includes request/status line, and headers. | ||||
|   | ||||
| @@ -12,6 +12,7 @@ use std::fmt; | ||||
|  | ||||
| use bytes::Bytes; | ||||
| use futures::{Future, Poll}; | ||||
| use futures::future::{Either}; | ||||
| use tokio_io::{AsyncRead, AsyncWrite}; | ||||
|  | ||||
| use proto; | ||||
| @@ -27,12 +28,18 @@ where | ||||
|     S: HyperService, | ||||
|     S::ResponseBody: Payload, | ||||
| { | ||||
|     pub(super) conn: proto::dispatch::Dispatcher< | ||||
|         proto::dispatch::Server<S>, | ||||
|         S::ResponseBody, | ||||
|         I, | ||||
|         <S::ResponseBody as Payload>::Data, | ||||
|         proto::ServerTransaction, | ||||
|     pub(super) conn: Either< | ||||
|         proto::h1::Dispatcher< | ||||
|             proto::h1::dispatch::Server<S>, | ||||
|             S::ResponseBody, | ||||
|             I, | ||||
|             proto::ServerTransaction, | ||||
|         >, | ||||
|         proto::h2::Server< | ||||
|             I, | ||||
|             S, | ||||
|             S::ResponseBody, | ||||
|         >, | ||||
|     >, | ||||
| } | ||||
|  | ||||
| @@ -62,12 +69,23 @@ impl<I, B, S> Connection<I, S> | ||||
| where | ||||
|     S: Service<Request=Request<Body>, Response=Response<B>> + 'static, | ||||
|     S::Error: Into<Box<::std::error::Error + Send + Sync>>, | ||||
|     S::Future: Send, | ||||
|     I: AsyncRead + AsyncWrite + 'static, | ||||
|     B: Payload + 'static, | ||||
| { | ||||
|     /// Disables keep-alive for this connection. | ||||
|     pub fn disable_keep_alive(&mut self) { | ||||
|         self.conn.disable_keep_alive() | ||||
|     /// Start a graceful shutdown process for this connection. | ||||
|     /// | ||||
|     /// This `Connection` should continue to be polled until shutdown | ||||
|     /// can finish. | ||||
|     pub fn graceful_shutdown(&mut self) { | ||||
|         match self.conn { | ||||
|             Either::A(ref mut h1) => { | ||||
|                 h1.disable_keep_alive(); | ||||
|             }, | ||||
|             Either::B(ref mut h2) => { | ||||
|                 h2.graceful_shutdown(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Return the inner IO object, and additional information. | ||||
| @@ -76,7 +94,14 @@ where | ||||
|     /// that the connection is "done". Otherwise, it may not have finished | ||||
|     /// flushing all necessary HTTP bytes. | ||||
|     pub fn into_parts(self) -> Parts<I> { | ||||
|         let (io, read_buf) = self.conn.into_inner(); | ||||
|         let (io, read_buf) = match self.conn { | ||||
|             Either::A(h1) => { | ||||
|                 h1.into_inner() | ||||
|             }, | ||||
|             Either::B(_h2) => { | ||||
|                 panic!("h2 cannot into_inner"); | ||||
|             } | ||||
|         }; | ||||
|         Parts { | ||||
|             io: io, | ||||
|             read_buf: read_buf, | ||||
| @@ -92,8 +117,13 @@ 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> { | ||||
|         try_ready!(self.conn.poll_without_shutdown()); | ||||
|         Ok(().into()) | ||||
|         match self.conn { | ||||
|             Either::A(ref mut h1) => { | ||||
|                 try_ready!(h1.poll_without_shutdown()); | ||||
|                 Ok(().into()) | ||||
|             }, | ||||
|             Either::B(ref mut h2) => h2.poll(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -101,6 +131,7 @@ impl<I, B, S> Future for Connection<I, S> | ||||
| where | ||||
|     S: Service<Request=Request<Body>, Response=Response<B>> + 'static, | ||||
|     S::Error: Into<Box<::std::error::Error + Send + Sync>>, | ||||
|     S::Future: Send, | ||||
|     I: AsyncRead + AsyncWrite + 'static, | ||||
|     B: Payload + 'static, | ||||
| { | ||||
|   | ||||
| @@ -8,13 +8,12 @@ mod service; | ||||
|  | ||||
| use std::fmt; | ||||
| use std::io; | ||||
| use std::marker::PhantomData; | ||||
| use std::net::{SocketAddr, TcpListener as StdTcpListener}; | ||||
| use std::sync::{Arc, Mutex, Weak}; | ||||
| use std::time::Duration; | ||||
|  | ||||
| use futures::task::{self, Task}; | ||||
| use futures::future::{self}; | ||||
| use futures::future::{self, Either, Executor}; | ||||
| use futures::{Future, Stream, Poll, Async}; | ||||
| use futures_timer::Delay; | ||||
| use http::{Request, Response}; | ||||
| @@ -25,6 +24,7 @@ use tokio::net::TcpListener; | ||||
| pub use tokio_service::{NewService, Service}; | ||||
|  | ||||
| use body::{Body, Payload}; | ||||
| use common::Exec; | ||||
| use proto; | ||||
| use self::addr_stream::AddrStream; | ||||
| use self::hyper_service::HyperService; | ||||
| @@ -37,23 +37,22 @@ pub use self::service::{const_service, service_fn}; | ||||
| /// This structure is used to create instances of `Server` or to spawn off tasks | ||||
| /// which handle a connection to an HTTP server. Each instance of `Http` can be | ||||
| /// configured with various protocol-level options such as keepalive. | ||||
| pub struct Http<B = ::Chunk> { | ||||
|     max_buf_size: Option<usize>, | ||||
| #[derive(Clone, Debug)] | ||||
| pub struct Http { | ||||
|     exec: Exec, | ||||
|     http2: bool, | ||||
|     keep_alive: bool, | ||||
|     max_buf_size: Option<usize>, | ||||
|     pipeline: bool, | ||||
|     sleep_on_errors: bool, | ||||
|     _marker: PhantomData<B>, | ||||
| } | ||||
|  | ||||
| /// An instance of a server created through `Http::bind`. | ||||
| /// | ||||
| /// This server is intended as a convenience for creating a TCP listener on an | ||||
| /// address and then serving TCP connections accepted with the service provided. | ||||
| pub struct Server<S, B> | ||||
| where | ||||
|     B: Payload, | ||||
| { | ||||
|     protocol: Http<B::Data>, | ||||
| pub struct Server<S> { | ||||
|     protocol: Http, | ||||
|     new_service: S, | ||||
|     handle: Handle, | ||||
|     listener: TcpListener, | ||||
| @@ -105,19 +104,28 @@ impl fmt::Debug for AddrIncoming { | ||||
|  | ||||
| // ===== impl Http ===== | ||||
|  | ||||
| impl<B: AsRef<[u8]> + 'static> Http<B> { | ||||
| impl Http { | ||||
|     /// Creates a new instance of the HTTP protocol, ready to spawn a server or | ||||
|     /// start accepting connections. | ||||
|     pub fn new() -> Http<B> { | ||||
|     pub fn new() -> Http { | ||||
|         Http { | ||||
|             exec: Exec::Default, | ||||
|             http2: false, | ||||
|             keep_alive: true, | ||||
|             max_buf_size: None, | ||||
|             pipeline: false, | ||||
|             sleep_on_errors: false, | ||||
|             _marker: PhantomData, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Sets whether HTTP2 is required. | ||||
|     /// | ||||
|     /// Default is false | ||||
|     pub fn http2_only(&mut self, val: bool) -> &mut Self { | ||||
|         self.http2 = val; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Enables or disables HTTP keep-alive. | ||||
|     /// | ||||
|     /// Default is true. | ||||
| @@ -142,6 +150,17 @@ impl<B: AsRef<[u8]> + 'static> Http<B> { | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set the executor used to spawn background tasks. | ||||
|     /// | ||||
|     /// Default uses implicit default (like `tokio::spawn`). | ||||
|     pub fn executor<E>(&mut self, exec: E) -> &mut Self | ||||
|     where | ||||
|         E: Executor<Box<Future<Item=(), Error=()> + Send>> + Send + Sync + 'static | ||||
|     { | ||||
|         self.exec = Exec::Executor(Arc::new(exec)); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Swallow connection accept errors. Instead of passing up IO errors when | ||||
|     /// the server is under heavy load the errors will be ignored. Some | ||||
|     /// connection accept errors (like "connection reset") can be ignored, some | ||||
| @@ -164,11 +183,11 @@ impl<B: AsRef<[u8]> + 'static> Http<B> { | ||||
|     /// | ||||
|     /// The returned `Server` contains one method, `run`, which is used to | ||||
|     /// actually run the server. | ||||
|     pub fn bind<S, Bd>(&self, addr: &SocketAddr, new_service: S) -> ::Result<Server<S, Bd>> | ||||
|     pub fn bind<S, Bd>(&self, addr: &SocketAddr, new_service: S) -> ::Result<Server<S>> | ||||
|     where | ||||
|         S: NewService<Request=Request<Body>, Response=Response<Bd>> + 'static, | ||||
|         S::Error: Into<Box<::std::error::Error + Send + Sync>>, | ||||
|         Bd: Payload<Data=B>, | ||||
|         Bd: Payload, | ||||
|     { | ||||
|         let handle = Handle::current(); | ||||
|         let std_listener = StdTcpListener::bind(addr).map_err(::Error::new_listen)?; | ||||
| @@ -193,7 +212,7 @@ impl<B: AsRef<[u8]> + 'static> Http<B> { | ||||
|     where | ||||
|         S: NewService<Request=Request<Body>, Response=Response<Bd>>, | ||||
|         S::Error: Into<Box<::std::error::Error + Send + Sync>>, | ||||
|         Bd: Payload<Data=B>, | ||||
|         Bd: Payload, | ||||
|     { | ||||
|         let handle = Handle::current(); | ||||
|         let std_listener = StdTcpListener::bind(addr).map_err(::Error::new_listen)?; | ||||
| @@ -217,7 +236,7 @@ impl<B: AsRef<[u8]> + 'static> Http<B> { | ||||
|     where | ||||
|         S: NewService<Request = Request<Body>, Response = Response<Bd>>, | ||||
|         S::Error: Into<Box<::std::error::Error + Send + Sync>>, | ||||
|         Bd: Payload<Data=B>, | ||||
|         Bd: Payload, | ||||
|     { | ||||
|         let std_listener = StdTcpListener::bind(addr).map_err(::Error::new_listen)?; | ||||
|         let listener = TcpListener::from_std(std_listener, &handle).map_err(::Error::new_listen)?; | ||||
| @@ -238,18 +257,12 @@ impl<B: AsRef<[u8]> + 'static> Http<B> { | ||||
|         I::Item: AsyncRead + AsyncWrite, | ||||
|         S: NewService<Request = Request<Body>, Response = Response<Bd>>, | ||||
|         S::Error: Into<Box<::std::error::Error + Send + Sync>>, | ||||
|         Bd: Payload<Data=B>, | ||||
|         Bd: Payload, | ||||
|     { | ||||
|         Serve { | ||||
|             incoming: incoming, | ||||
|             new_service: new_service, | ||||
|             protocol: Http { | ||||
|                 keep_alive: self.keep_alive, | ||||
|                 max_buf_size: self.max_buf_size, | ||||
|                 pipeline: self.pipeline, | ||||
|                 sleep_on_errors: self.sleep_on_errors, | ||||
|                 _marker: PhantomData, | ||||
|             }, | ||||
|             protocol: self.clone(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -276,11 +289,10 @@ impl<B: AsRef<[u8]> + 'static> Http<B> { | ||||
|     /// #     S: Service<Request=Request<Body>, Response=Response<Body>, Error=hyper::Error> + Send + 'static, | ||||
|     /// #     S::Future: Send | ||||
|     /// # { | ||||
|     /// let http = Http::<hyper::Chunk>::new(); | ||||
|     /// let http = Http::new(); | ||||
|     /// let conn = http.serve_connection(some_io, some_service); | ||||
|     /// | ||||
|     /// let fut = conn | ||||
|     ///     .map(|_| ()) | ||||
|     ///     .map_err(|e| eprintln!("server connection error: {}", e)); | ||||
|     /// | ||||
|     /// tokio::spawn(fut); | ||||
| @@ -291,44 +303,33 @@ impl<B: AsRef<[u8]> + 'static> Http<B> { | ||||
|     where | ||||
|         S: Service<Request = Request<Body>, Response = Response<Bd>>, | ||||
|         S::Error: Into<Box<::std::error::Error + Send + Sync>>, | ||||
|         S::Future: Send + 'static, | ||||
|         Bd: Payload, | ||||
|         I: AsyncRead + AsyncWrite, | ||||
|     { | ||||
|         let mut conn = proto::Conn::new(io); | ||||
|         if !self.keep_alive { | ||||
|             conn.disable_keep_alive(); | ||||
|         } | ||||
|         conn.set_flush_pipeline(self.pipeline); | ||||
|         if let Some(max) = self.max_buf_size { | ||||
|             conn.set_max_buf_size(max); | ||||
|         } | ||||
|         let either = if !self.http2 { | ||||
|             let mut conn = proto::Conn::new(io); | ||||
|             if !self.keep_alive { | ||||
|                 conn.disable_keep_alive(); | ||||
|             } | ||||
|             conn.set_flush_pipeline(self.pipeline); | ||||
|             if let Some(max) = self.max_buf_size { | ||||
|                 conn.set_max_buf_size(max); | ||||
|             } | ||||
|             let sd = proto::h1::dispatch::Server::new(service); | ||||
|             Either::A(proto::h1::Dispatcher::new(sd, conn)) | ||||
|         } else { | ||||
|             let h2 = proto::h2::Server::new(io, service, self.exec.clone()); | ||||
|             Either::B(h2) | ||||
|         }; | ||||
|  | ||||
|         Connection { | ||||
|             conn: proto::dispatch::Dispatcher::new(proto::dispatch::Server::new(service), conn), | ||||
|             conn: either, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| impl<B> Clone for Http<B> { | ||||
|     fn clone(&self) -> Http<B> { | ||||
|         Http { | ||||
|             ..*self | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<B> fmt::Debug for Http<B> { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         f.debug_struct("Http") | ||||
|             .field("keep_alive", &self.keep_alive) | ||||
|             .field("pipeline", &self.pipeline) | ||||
|             .finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| // ===== impl Server ===== | ||||
|  | ||||
|  | ||||
| @@ -351,12 +352,12 @@ impl Future for Run { | ||||
| } | ||||
|  | ||||
|  | ||||
| impl<S, B> Server<S, B> | ||||
| impl<S, B> Server<S> | ||||
| where | ||||
|     S: NewService<Request = Request<Body>, Response = Response<B>> + Send + 'static, | ||||
|     S::Error: Into<Box<::std::error::Error + Send + Sync>>, | ||||
|     <S as NewService>::Instance: Send, | ||||
|     <<S as NewService>::Instance as Service>::Future: Send, | ||||
|     <<S as NewService>::Instance as Service>::Future: Send + 'static, | ||||
|     B: Payload + Send + 'static, | ||||
|     B::Data: Send, | ||||
| { | ||||
| @@ -479,7 +480,7 @@ where | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<S: fmt::Debug, B: Payload> fmt::Debug for Server<S, B> | ||||
| impl<S: fmt::Debug> fmt::Debug for Server<S> | ||||
| { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         f.debug_struct("Server") | ||||
| @@ -516,6 +517,7 @@ where | ||||
|     I::Item: AsyncRead + AsyncWrite, | ||||
|     S: NewService<Request=Request<Body>, Response=Response<B>>, | ||||
|     S::Error: Into<Box<::std::error::Error + Send + Sync>>, | ||||
|     <S::Instance as Service>::Future: Send + 'static, | ||||
|     B: Payload, | ||||
| { | ||||
|     type Item = Connection<I::Item, S::Instance>; | ||||
|   | ||||
							
								
								
									
										146
									
								
								tests/integration.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								tests/integration.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,146 @@ | ||||
| #[macro_use] | ||||
| mod support; | ||||
| use self::support::*; | ||||
|  | ||||
| t! { | ||||
|     get_1, | ||||
|     client: | ||||
|         request: | ||||
|             uri: "/", | ||||
|             ; | ||||
|         response: | ||||
|             status: 200, | ||||
|             ; | ||||
|     server: | ||||
|         request: | ||||
|             uri: "/", | ||||
|             ; | ||||
|         response: | ||||
|             ; | ||||
| } | ||||
|  | ||||
| t! { | ||||
|     get_implicit_path, | ||||
|     client: | ||||
|         request: | ||||
|             uri: "", | ||||
|             ; | ||||
|         response: | ||||
|             status: 200, | ||||
|             ; | ||||
|     server: | ||||
|         request: | ||||
|             uri: "/", | ||||
|             ; | ||||
|         response: | ||||
|             ; | ||||
| } | ||||
|  | ||||
| t! { | ||||
|     get_body, | ||||
|     client: | ||||
|         request: | ||||
|             uri: "/", | ||||
|             ; | ||||
|         response: | ||||
|             status: 200, | ||||
|             headers: { | ||||
|                 "content-length" => 11, | ||||
|             }, | ||||
|             body: "hello world", | ||||
|             ; | ||||
|     server: | ||||
|         request: | ||||
|             uri: "/", | ||||
|             ; | ||||
|         response: | ||||
|             headers: { | ||||
|                 "content-length" => 11, | ||||
|             }, | ||||
|             body: "hello world", | ||||
|             ; | ||||
| } | ||||
|  | ||||
| t! { | ||||
|     get_body_chunked, | ||||
|     client: | ||||
|         request: | ||||
|             uri: "/", | ||||
|             ; | ||||
|         response: | ||||
|             status: 200, | ||||
|             headers: { | ||||
|                 // h2 doesn't actually receive the transfer-encoding header | ||||
|             }, | ||||
|             body: "hello world", | ||||
|             ; | ||||
|     server: | ||||
|         request: | ||||
|             uri: "/", | ||||
|             ; | ||||
|         response: | ||||
|             headers: { | ||||
|                 // http2 should strip this header | ||||
|                 "transfer-encoding" => "chunked", | ||||
|             }, | ||||
|             body: "hello world", | ||||
|             ; | ||||
| } | ||||
|  | ||||
| t! { | ||||
|     post_chunked, | ||||
|     client: | ||||
|         request: | ||||
|             method: "POST", | ||||
|             uri: "/post_chunked", | ||||
|             headers: { | ||||
|                 // http2 should strip this header | ||||
|                 "transfer-encoding" => "chunked", | ||||
|             }, | ||||
|             body: "hello world", | ||||
|             ; | ||||
|         response: | ||||
|             ; | ||||
|     server: | ||||
|         request: | ||||
|             method: "POST", | ||||
|             uri: "/post_chunked", | ||||
|             body: "hello world", | ||||
|             ; | ||||
|         response: | ||||
|             ; | ||||
| } | ||||
|  | ||||
| t! { | ||||
|     get_2, | ||||
|     client: | ||||
|         request: | ||||
|             uri: "/1", | ||||
|             ; | ||||
|         response: | ||||
|             status: 200, | ||||
|             ; | ||||
|         request: | ||||
|             uri: "/2", | ||||
|             ; | ||||
|         response: | ||||
|             status: 200, | ||||
|             ; | ||||
|     server: | ||||
|         request: | ||||
|             uri: "/1", | ||||
|             ; | ||||
|         response: | ||||
|             ; | ||||
|         request: | ||||
|             uri: "/2", | ||||
|             ; | ||||
|         response: | ||||
|             ; | ||||
| } | ||||
|  | ||||
| t! { | ||||
|     get_parallel_http2, | ||||
|     parallel: 0..10 | ||||
| } | ||||
|  | ||||
| @@ -95,7 +95,7 @@ fn get_implicitly_empty() { | ||||
|         .map_err(|_| unreachable!()) | ||||
|         .and_then(|(item, _incoming)| { | ||||
|             let socket = item.unwrap(); | ||||
|             Http::<hyper::Chunk>::new().serve_connection(socket, GetImplicitlyEmpty) | ||||
|             Http::new().serve_connection(socket, GetImplicitlyEmpty) | ||||
|         }); | ||||
|  | ||||
|     fut.wait().unwrap(); | ||||
| @@ -110,7 +110,6 @@ fn get_implicitly_empty() { | ||||
|  | ||||
|         fn call(&self, req: Request<Body>) -> Self::Future { | ||||
|             Box::new(req.into_body() | ||||
|                  | ||||
|                 .concat2() | ||||
|                 .map(|buf| { | ||||
|                     assert!(buf.is_empty()); | ||||
| @@ -776,13 +775,13 @@ fn disable_keep_alive_mid_request() { | ||||
|         .map_err(|_| unreachable!()) | ||||
|         .and_then(|(item, _incoming)| { | ||||
|             let socket = item.unwrap(); | ||||
|             Http::<hyper::Chunk>::new().serve_connection(socket, HelloWorld) | ||||
|             Http::new().serve_connection(socket, HelloWorld) | ||||
|                 .select2(rx1) | ||||
|                 .then(|r| { | ||||
|                     match r { | ||||
|                         Ok(Either::A(_)) => panic!("expected rx first"), | ||||
|                         Ok(Either::B(((), mut conn))) => { | ||||
|                             conn.disable_keep_alive(); | ||||
|                             conn.graceful_shutdown(); | ||||
|                             tx2.send(()).unwrap(); | ||||
|                             conn | ||||
|                         } | ||||
| @@ -841,13 +840,13 @@ fn disable_keep_alive_post_request() { | ||||
|                 stream: socket, | ||||
|                 _debug: dropped2, | ||||
|             }; | ||||
|             Http::<hyper::Chunk>::new().serve_connection(transport, HelloWorld) | ||||
|             Http::new().serve_connection(transport, HelloWorld) | ||||
|                 .select2(rx1) | ||||
|                 .then(|r| { | ||||
|                     match r { | ||||
|                         Ok(Either::A(_)) => panic!("expected rx first"), | ||||
|                         Ok(Either::B(((), mut conn))) => { | ||||
|                             conn.disable_keep_alive(); | ||||
|                             conn.graceful_shutdown(); | ||||
|                             conn | ||||
|                         } | ||||
|                         Err(Either::A((e, _))) => panic!("unexpected error {}", e), | ||||
| @@ -883,7 +882,7 @@ fn empty_parse_eof_does_not_return_error() { | ||||
|         .map_err(|_| unreachable!()) | ||||
|         .and_then(|(item, _incoming)| { | ||||
|             let socket = item.unwrap(); | ||||
|             Http::<hyper::Chunk>::new().serve_connection(socket, HelloWorld) | ||||
|             Http::new().serve_connection(socket, HelloWorld) | ||||
|         }); | ||||
|  | ||||
|     fut.wait().unwrap(); | ||||
| @@ -905,8 +904,7 @@ fn nonempty_parse_eof_returns_error() { | ||||
|         .map_err(|_| unreachable!()) | ||||
|         .and_then(|(item, _incoming)| { | ||||
|             let socket = item.unwrap(); | ||||
|             Http::<hyper::Chunk>::new().serve_connection(socket, HelloWorld) | ||||
|                 .map(|_| ()) | ||||
|             Http::new().serve_connection(socket, HelloWorld) | ||||
|         }); | ||||
|  | ||||
|     fut.wait().unwrap_err(); | ||||
| @@ -933,14 +931,13 @@ fn returning_1xx_response_is_error() { | ||||
|         .map_err(|_| unreachable!()) | ||||
|         .and_then(|(item, _incoming)| { | ||||
|             let socket = item.unwrap(); | ||||
|             Http::<hyper::Chunk>::new() | ||||
|             Http::new() | ||||
|                 .serve_connection(socket, service_fn(|_| { | ||||
|                     Ok::<_, hyper::Error>(Response::builder() | ||||
|                         .status(StatusCode::CONTINUE) | ||||
|                         .body(Body::empty()) | ||||
|                         .unwrap()) | ||||
|                 })) | ||||
|                 .map(|_| ()) | ||||
|         }); | ||||
|  | ||||
|     fut.wait().unwrap_err(); | ||||
| @@ -981,7 +978,7 @@ fn upgrades() { | ||||
|         .map_err(|_| -> hyper::Error { unreachable!() }) | ||||
|         .and_then(|(item, _incoming)| { | ||||
|             let socket = item.unwrap(); | ||||
|             let conn = Http::<hyper::Chunk>::new() | ||||
|             let conn = Http::new() | ||||
|                 .serve_connection(socket, service_fn(|_| { | ||||
|                     let res = Response::builder() | ||||
|                         .status(101) | ||||
| @@ -1034,9 +1031,8 @@ fn parse_errors_send_4xx_response() { | ||||
|         .map_err(|_| unreachable!()) | ||||
|         .and_then(|(item, _incoming)| { | ||||
|             let socket = item.unwrap(); | ||||
|             Http::<hyper::Chunk>::new() | ||||
|             Http::new() | ||||
|                 .serve_connection(socket, HelloWorld) | ||||
|                 .map(|_| ()) | ||||
|         }); | ||||
|  | ||||
|     fut.wait().unwrap_err(); | ||||
| @@ -1063,9 +1059,8 @@ fn illegal_request_length_returns_400_response() { | ||||
|         .map_err(|_| unreachable!()) | ||||
|         .and_then(|(item, _incoming)| { | ||||
|             let socket = item.unwrap(); | ||||
|             Http::<hyper::Chunk>::new() | ||||
|             Http::new() | ||||
|                 .serve_connection(socket, HelloWorld) | ||||
|                 .map(|_| ()) | ||||
|         }); | ||||
|  | ||||
|     fut.wait().unwrap_err(); | ||||
| @@ -1096,10 +1091,9 @@ fn max_buf_size() { | ||||
|         .map_err(|_| unreachable!()) | ||||
|         .and_then(|(item, _incoming)| { | ||||
|             let socket = item.unwrap(); | ||||
|             Http::<hyper::Chunk>::new() | ||||
|             Http::new() | ||||
|                 .max_buf_size(MAX) | ||||
|                 .serve_connection(socket, HelloWorld) | ||||
|                 .map(|_| ()) | ||||
|         }); | ||||
|  | ||||
|     fut.wait().unwrap_err(); | ||||
| @@ -1140,7 +1134,7 @@ fn streaming_body() { | ||||
|         .map_err(|_| unreachable!()) | ||||
|         .and_then(|(item, _incoming)| { | ||||
|             let socket = item.unwrap(); | ||||
|             Http::<hyper::Chunk>::new() | ||||
|             Http::new() | ||||
|                 .keep_alive(false) | ||||
|                 .serve_connection(socket, service_fn(|_| { | ||||
|                     static S: &'static [&'static [u8]] = &[&[b'x'; 1_000] as &[u8]; 1_00] as _; | ||||
| @@ -1149,7 +1143,6 @@ fn streaming_body() { | ||||
|                     let b = hyper::Body::wrap_stream(b); | ||||
|                     Ok::<_, hyper::Error>(Response::new(b)) | ||||
|                 })) | ||||
|                 .map(|_| ()) | ||||
|         }); | ||||
|  | ||||
|     fut.join(rx).wait().unwrap(); | ||||
|   | ||||
							
								
								
									
										328
									
								
								tests/support/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										328
									
								
								tests/support/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,328 @@ | ||||
| pub extern crate futures; | ||||
| pub extern crate hyper; | ||||
| pub extern crate tokio; | ||||
|  | ||||
| pub use std::net::SocketAddr; | ||||
| pub use self::futures::{future, Future, Stream}; | ||||
| pub use self::futures::sync::oneshot; | ||||
| pub use self::hyper::{HeaderMap, StatusCode}; | ||||
| pub use self::tokio::runtime::Runtime; | ||||
|  | ||||
| macro_rules! t { | ||||
|     ( | ||||
|         $name:ident, | ||||
|         parallel: $range:expr | ||||
|     ) => ( | ||||
|         #[test] | ||||
|         fn $name() { | ||||
|  | ||||
|             let mut c = vec![]; | ||||
|             let mut s = vec![]; | ||||
|  | ||||
|             for _i in $range { | ||||
|                 c.push(( | ||||
|                     __CReq { | ||||
|                         uri: "/", | ||||
|                         ..Default::default() | ||||
|                     }, | ||||
|                     __CRes { | ||||
|                         ..Default::default() | ||||
|                     }, | ||||
|                 )); | ||||
|                 s.push(( | ||||
|                     __SReq { | ||||
|                         uri: "/", | ||||
|                         ..Default::default() | ||||
|                     }, | ||||
|                     __SRes { | ||||
|                         ..Default::default() | ||||
|                     }, | ||||
|                 )); | ||||
|             } | ||||
|  | ||||
|             __run_test(__TestConfig { | ||||
|                 client_version: 2, | ||||
|                 client_msgs: c, | ||||
|                 server_version: 2, | ||||
|                 server_msgs: s, | ||||
|                 parallel: true, | ||||
|                 connections: 1, | ||||
|             }); | ||||
|  | ||||
|         } | ||||
|     ); | ||||
|     ( | ||||
|         $name:ident, | ||||
|         client: $( | ||||
|             request: $( | ||||
|                 $c_req_prop:ident: $c_req_val:tt, | ||||
|             )*; | ||||
|             response: $( | ||||
|                 $c_res_prop:ident: $c_res_val:tt, | ||||
|             )*; | ||||
|         )* | ||||
|         server: $( | ||||
|             request: $( | ||||
|                 $s_req_prop:ident: $s_req_val:tt, | ||||
|             )*; | ||||
|             response: $( | ||||
|                 $s_res_prop:ident: $s_res_val:tt, | ||||
|             )*; | ||||
|         )* | ||||
|     ) => ( | ||||
|         #[test] | ||||
|         fn $name() { | ||||
|             let c = vec![$(( | ||||
|                 __CReq { | ||||
|                     $($c_req_prop: __internal_req_res_prop!($c_req_prop: $c_req_val),)* | ||||
|                     ..Default::default() | ||||
|                 }, | ||||
|                 __CRes { | ||||
|                     $($c_res_prop: __internal_req_res_prop!($c_res_prop: $c_res_val),)* | ||||
|                     ..Default::default() | ||||
|                 } | ||||
|             ),)*]; | ||||
|             let s = vec![$(( | ||||
|                 __SReq { | ||||
|                     $($s_req_prop: __internal_req_res_prop!($s_req_prop: $s_req_val),)* | ||||
|                     ..Default::default() | ||||
|                 }, | ||||
|                 __SRes { | ||||
|                     $($s_res_prop: __internal_req_res_prop!($s_res_prop: $s_res_val),)* | ||||
|                     ..Default::default() | ||||
|                 } | ||||
|             ),)*]; | ||||
|  | ||||
|             __run_test(__TestConfig { | ||||
|                 client_version: 1, | ||||
|                 client_msgs: c.clone(), | ||||
|                 server_version: 1, | ||||
|                 server_msgs: s.clone(), | ||||
|                 parallel: false, | ||||
|                 connections: 1, | ||||
|             }); | ||||
|  | ||||
|             __run_test(__TestConfig { | ||||
|                 client_version: 2, | ||||
|                 client_msgs: c, | ||||
|                 server_version: 2, | ||||
|                 server_msgs: s, | ||||
|                 parallel: false, | ||||
|                 connections: 1, | ||||
|             }); | ||||
|         } | ||||
|     ); | ||||
| } | ||||
|  | ||||
| macro_rules! __internal_req_res_prop { | ||||
|     (method: $prop_val:expr) => ( | ||||
|         $prop_val | ||||
|     ); | ||||
|     (status: $prop_val:expr) => ( | ||||
|         StatusCode::from_u16($prop_val).expect("status code") | ||||
|     ); | ||||
|     (headers: $map:tt) => ({ | ||||
|         #[allow(unused_mut)] | ||||
|         let mut headers = HeaderMap::new(); | ||||
|         __internal_headers!(headers, $map); | ||||
|         headers | ||||
|     }); | ||||
|     ($prop_name:ident: $prop_val:expr) => ( | ||||
|         From::from($prop_val) | ||||
|     ) | ||||
| } | ||||
|  | ||||
| macro_rules! __internal_headers { | ||||
|     ($headers:ident, { $($name:expr => $val:expr,)* }) => { | ||||
|         $( | ||||
|         $headers.insert($name, $val.to_string().parse().expect("header value")); | ||||
|         )* | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, Default)] | ||||
| pub struct __CReq { | ||||
|     pub method: &'static str, | ||||
|     pub uri: &'static str, | ||||
|     pub headers: HeaderMap, | ||||
|     pub body: Vec<u8>, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, Default)] | ||||
| pub struct __CRes { | ||||
|     pub status: hyper::StatusCode, | ||||
|     pub body: Vec<u8>, | ||||
|     pub headers: HeaderMap, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, Default)] | ||||
| pub struct __SReq { | ||||
|     pub method: &'static str, | ||||
|     pub uri: &'static str, | ||||
|     pub headers: HeaderMap, | ||||
|     pub body: Vec<u8>, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, Default)] | ||||
| pub struct __SRes { | ||||
|     pub status: hyper::StatusCode, | ||||
|     pub body: Vec<u8>, | ||||
|     pub headers: HeaderMap, | ||||
| } | ||||
|  | ||||
| pub struct __TestConfig { | ||||
|     pub client_version: usize, | ||||
|     pub client_msgs: Vec<(__CReq, __CRes)>, | ||||
|  | ||||
|     pub server_version: usize, | ||||
|     pub server_msgs: Vec<(__SReq, __SRes)>, | ||||
|  | ||||
|     pub parallel: bool, | ||||
|     pub connections: usize, | ||||
| } | ||||
|  | ||||
| pub fn __run_test(cfg: __TestConfig) { | ||||
|     extern crate pretty_env_logger; | ||||
|     use hyper::{Body, Client, Request, Response}; | ||||
|     use hyper::client::HttpConnector; | ||||
|     use std::sync::Arc; | ||||
|     let _ = pretty_env_logger::try_init(); | ||||
|     let rt = Runtime::new().expect("new rt"); | ||||
|     let handle = rt.reactor().clone(); | ||||
|  | ||||
|     let connector = HttpConnector::new_with_handle(1, handle.clone()); | ||||
|     let client = Client::builder() | ||||
|         .http2_only(cfg.client_version == 2) | ||||
|         .executor(rt.executor()) | ||||
|         .build::<_, Body>(connector); | ||||
|  | ||||
|     let serve_handles = ::std::sync::Mutex::new( | ||||
|         cfg.server_msgs | ||||
|     ); | ||||
|     let service = hyper::server::service_fn(move |req: Request<Body>| -> Box<Future<Item=Response<Body>, Error=hyper::Error> + Send> { | ||||
|         let (sreq, sres) = serve_handles.lock() | ||||
|             .unwrap() | ||||
|             .remove(0); | ||||
|  | ||||
|         assert_eq!(req.uri().path(), sreq.uri); | ||||
|         assert_eq!(req.method(), &sreq.method); | ||||
|         for (name, value) in &sreq.headers { | ||||
|             assert_eq!( | ||||
|                 req.headers()[name], | ||||
|                 value | ||||
|             ); | ||||
|         } | ||||
|         let sbody = sreq.body; | ||||
|         Box::new(req.into_body() | ||||
|             .concat2() | ||||
|             .map(move |body| { | ||||
|                 assert_eq!(body.as_ref(), sbody.as_slice()); | ||||
|  | ||||
|                 let mut res = Response::builder() | ||||
|                     .status(sres.status) | ||||
|                     .body(sres.body.into()) | ||||
|                     .expect("Response::build"); | ||||
|                 *res.headers_mut() = sres.headers; | ||||
|                 res | ||||
|             })) | ||||
|     }); | ||||
|     let new_service = hyper::server::const_service(service); | ||||
|  | ||||
|     let serve = hyper::server::Http::new() | ||||
|         .http2_only(cfg.server_version == 2) | ||||
|         .executor(rt.executor()) | ||||
|         .serve_addr_handle( | ||||
|             &SocketAddr::from(([127, 0, 0, 1], 0)), | ||||
|             &handle, | ||||
|             new_service, | ||||
|         ) | ||||
|         .expect("serve_addr_handle"); | ||||
|  | ||||
|     let addr = serve.incoming_ref().local_addr(); | ||||
|     let exe = rt.executor(); | ||||
|     let (shutdown_tx, shutdown_rx) = oneshot::channel(); | ||||
|     let (success_tx, success_rx) = oneshot::channel(); | ||||
|     let expected_connections = cfg.connections; | ||||
|     let server = serve | ||||
|         .fold(0, move |cnt, conn: hyper::server::Connection<_, _>| { | ||||
|             exe.spawn(conn.map_err(|e| panic!("server connection error: {}", e))); | ||||
|             Ok::<_, hyper::Error>(cnt + 1) | ||||
|         }) | ||||
|         .map(move |cnt| { | ||||
|             assert_eq!(cnt, expected_connections); | ||||
|         }) | ||||
|         .map_err(|e| panic!("serve error: {}", e)) | ||||
|         .select2(shutdown_rx) | ||||
|         .map(move |_| { | ||||
|             let _ = success_tx.send(()); | ||||
|         }) | ||||
|         .map_err(|_| panic!("shutdown not ok")); | ||||
|  | ||||
|     rt.executor().spawn(server); | ||||
|  | ||||
|     let make_request = Arc::new(move |client: &Client<HttpConnector>, creq: __CReq, cres: __CRes| { | ||||
|         let uri = format!("http://{}{}", addr, creq.uri); | ||||
|         let mut req = Request::builder() | ||||
|             .method(creq.method) | ||||
|             .uri(uri) | ||||
|             //.headers(creq.headers) | ||||
|             .body(creq.body.into()) | ||||
|             .expect("Request::build"); | ||||
|         *req.headers_mut() = creq.headers; | ||||
|         let cstatus = cres.status; | ||||
|         let cheaders = cres.headers; | ||||
|         let cbody = cres.body; | ||||
|  | ||||
|         client.request(req) | ||||
|             .and_then(move |res| { | ||||
|                 assert_eq!(res.status(), cstatus); | ||||
|                 //assert_eq!(res.version(), c_version); | ||||
|                 for (name, value) in &cheaders { | ||||
|                     assert_eq!( | ||||
|                         res.headers()[name], | ||||
|                         value | ||||
|                     ); | ||||
|                 } | ||||
|                 res.into_body().concat2() | ||||
|             }) | ||||
|             .map(move |body| { | ||||
|                 assert_eq!(body.as_ref(), cbody.as_slice()); | ||||
|             }) | ||||
|             .map_err(|e| panic!("client error: {}", e)) | ||||
|     }); | ||||
|  | ||||
|  | ||||
|     let client_futures: Box<Future<Item=(), Error=()> + Send> = if cfg.parallel { | ||||
|         let mut client_futures = vec![]; | ||||
|         for (creq, cres) in cfg.client_msgs { | ||||
|             client_futures.push(make_request(&client, creq, cres)); | ||||
|         } | ||||
|         drop(client); | ||||
|         Box::new(future::join_all(client_futures).map(|_| ())) | ||||
|     } else { | ||||
|         let mut client_futures: Box<Future<Item=Client<HttpConnector>, Error=()> + Send> = | ||||
|             Box::new(future::ok(client)); | ||||
|         for (creq, cres) in cfg.client_msgs { | ||||
|             let mk_request = make_request.clone(); | ||||
|             client_futures = Box::new( | ||||
|                 client_futures | ||||
|                 .and_then(move |client| { | ||||
|                     let fut = mk_request(&client, creq, cres); | ||||
|                     fut.map(move |()| client) | ||||
|                 }) | ||||
|             ); | ||||
|         } | ||||
|         Box::new(client_futures.map(|_| ())) | ||||
|     }; | ||||
|  | ||||
|     let client_futures = client_futures.map(move |_| { | ||||
|         let _ = shutdown_tx.send(()); | ||||
|     }); | ||||
|     rt.executor().spawn(client_futures); | ||||
|     rt.shutdown_on_idle().wait().expect("rt"); | ||||
|     success_rx | ||||
|         .map_err(|_| "something panicked") | ||||
|         .wait() | ||||
|         .expect("shutdown succeeded"); | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user