feat(body): introduce an Entity trait to represent bodies

This dedicated `Entity` trait replaces the previous `Stream<Item=impl
AsRef<[u8]>, Error=hyper::Error>`. This allows for several improvements
immediately, and prepares for HTTP2 support.

- The `Entity::is_end_stream` makes up for change away from
  `Option<Body>`, which was previously used to know if the body should be
  empty. Since `Request` and `Response` now require a body to be set,
  this method can be used to tell hyper that the body is actually empty.

  It also provides the possibility of slight optimizations when polling
  for data, by allowing to check `is_end_stream` before polling again.
  This can allow a consumer to know that a body stream has ended without
  polling for `None` afterwards.

- The `Entity::content_length` method allows a body to automatically
  declare a size, in case a user doesn't set a `Content-Length` or
  `Transfer-Encoding` header.

- It's now possible to send and receive trailers, though this will be
  for HTTP2 connections only.

By being a trait owned by hyper, new methods can be added later as new
features are wanted (with default implementations).

The `hyper::Body` type now implements `Entity` instead of `Stream`,
provides a better channel option, and is easier to use with custom
streams via `Body::wrap_stream`.

BREAKING CHANGE: All code that was assuming the body was a `Stream` must
  be adjusted to use an `Entity` instead.

  Using `hyper::Body` as a `Stream` can call `Body::into_stream`
  to get a stream wrapper.

  Passing a custom `impl Stream` will need to either implement
  `Entity`, or as an easier option, switch to `Body::wrap_stream`.

  `Body::pair` has been replaced with `Body::channel`, which returns a
  `hyper::body::Sender` instead of a `futures::sync::mpsc::Sender`.

Closes #1438
This commit is contained in:
Sean McArthur
2018-03-14 12:40:24 -07:00
parent 3cd48b45fb
commit fbc449e49c
18 changed files with 811 additions and 485 deletions

View File

@@ -11,10 +11,11 @@
use std::fmt;
use bytes::Bytes;
use futures::{Future, Poll, Stream};
use futures::{Future, Poll};
use tokio_io::{AsyncRead, AsyncWrite};
use proto::{self, Body};
use proto;
use proto::body::{Body, Entity};
use super::{HyperService, Request, Response, Service};
/// A future binding a connection with a Service.
@@ -24,14 +25,13 @@ use super::{HyperService, Request, Response, Service};
pub struct Connection<I, S>
where
S: HyperService,
S::ResponseBody: Stream<Error=::Error>,
<S::ResponseBody as Stream>::Item: AsRef<[u8]>,
S::ResponseBody: Entity<Error=::Error>,
{
pub(super) conn: proto::dispatch::Dispatcher<
proto::dispatch::Server<S>,
S::ResponseBody,
I,
<S::ResponseBody as Stream>::Item,
<S::ResponseBody as Entity>::Data,
proto::ServerTransaction,
>,
}
@@ -61,8 +61,7 @@ pub struct Parts<T> {
impl<I, B, S> Connection<I, S>
where S: Service<Request = Request<Body>, Response = Response<B>, Error = ::Error> + 'static,
I: AsyncRead + AsyncWrite + 'static,
B: Stream<Error=::Error> + 'static,
B::Item: AsRef<[u8]>,
B: Entity<Error=::Error> + 'static,
{
/// Disables keep-alive for this connection.
pub fn disable_keep_alive(&mut self) {
@@ -99,8 +98,7 @@ where S: Service<Request = Request<Body>, Response = Response<B>, Error = ::Erro
impl<I, B, S> Future for Connection<I, S>
where S: Service<Request = Request<Body>, Response = Response<B>, Error = ::Error> + 'static,
I: AsyncRead + AsyncWrite + 'static,
B: Stream<Error=::Error> + 'static,
B::Item: AsRef<[u8]>,
B: Entity<Error=::Error> + 'static,
{
type Item = ();
type Error = ::Error;
@@ -113,8 +111,7 @@ where S: Service<Request = Request<Body>, Response = Response<B>, Error = ::Erro
impl<I, S> fmt::Debug for Connection<I, S>
where
S: HyperService,
S::ResponseBody: Stream<Error=::Error>,
<S::ResponseBody as Stream>::Item: AsRef<[u8]>,
S::ResponseBody: Entity<Error=::Error>,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Connection")

View File

@@ -23,7 +23,8 @@ use tokio::reactor::{Core, Handle, Timeout};
use tokio::net::TcpListener;
pub use tokio_service::{NewService, Service};
use proto::{self, Body};
use proto::body::{Body, Entity};
use proto;
use self::addr_stream::AddrStream;
use self::hyper_service::HyperService;
@@ -48,10 +49,10 @@ pub struct Http<B = ::Chunk> {
/// 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: Stream<Error=::Error>,
B::Item: AsRef<[u8]>,
where
B: Entity<Error=::Error>,
{
protocol: Http<B::Item>,
protocol: Http<B::Data>,
new_service: S,
reactor: Core,
listener: TcpListener,
@@ -90,7 +91,6 @@ pub struct AddrIncoming {
timeout: Option<Timeout>,
}
// ===== impl Http =====
impl<B: AsRef<[u8]> + 'static> Http<B> {
@@ -154,7 +154,7 @@ impl<B: AsRef<[u8]> + 'static> Http<B> {
/// actually run the server.
pub fn bind<S, Bd>(&self, addr: &SocketAddr, new_service: S) -> ::Result<Server<S, Bd>>
where S: NewService<Request = Request<Body>, Response = Response<Bd>, Error = ::Error> + 'static,
Bd: Stream<Item=B, Error=::Error>,
Bd: Entity<Data=B, Error=::Error>,
{
let core = try!(Core::new());
let handle = core.handle();
@@ -179,7 +179,7 @@ impl<B: AsRef<[u8]> + 'static> Http<B> {
/// connection.
pub fn serve_addr_handle<S, Bd>(&self, addr: &SocketAddr, handle: &Handle, new_service: S) -> ::Result<Serve<AddrIncoming, S>>
where S: NewService<Request = Request<Body>, Response = Response<Bd>, Error = ::Error>,
Bd: Stream<Item=B, Error=::Error>,
Bd: Entity<Data=B, Error=::Error>,
{
let listener = TcpListener::bind(addr, &handle)?;
let mut incoming = AddrIncoming::new(listener, handle.clone(), self.sleep_on_errors)?;
@@ -196,7 +196,7 @@ impl<B: AsRef<[u8]> + 'static> Http<B> {
where I: Stream<Error=::std::io::Error>,
I::Item: AsyncRead + AsyncWrite,
S: NewService<Request = Request<Body>, Response = Response<Bd>, Error = ::Error>,
Bd: Stream<Item=B, Error=::Error>,
Bd: Entity<Data=B, Error=::Error>,
{
Serve {
incoming: incoming,
@@ -246,10 +246,8 @@ impl<B: AsRef<[u8]> + 'static> Http<B> {
/// ```
pub fn serve_connection<S, I, Bd>(&self, io: I, service: S) -> Connection<I, S>
where S: Service<Request = Request<Body>, Response = Response<Bd>, Error = ::Error>,
Bd: Stream<Error=::Error>,
Bd::Item: AsRef<[u8]>,
Bd: Entity<Error=::Error>,
I: AsyncRead + AsyncWrite,
{
let mut conn = proto::Conn::new(io);
if !self.keep_alive {
@@ -290,8 +288,7 @@ impl<B> fmt::Debug for Http<B> {
impl<S, B> Server<S, B>
where S: NewService<Request = Request<Body>, Response = Response<B>, Error = ::Error> + 'static,
B: Stream<Error=::Error> + 'static,
B::Item: AsRef<[u8]>,
B: Entity<Error=::Error> + 'static,
{
/// Returns the local address that this server is bound to.
pub fn local_addr(&self) -> ::Result<SocketAddr> {
@@ -407,8 +404,7 @@ impl<S, B> Server<S, B>
}
}
impl<S: fmt::Debug, B: Stream<Error=::Error>> fmt::Debug for Server<S, B>
where B::Item: AsRef<[u8]>
impl<S: fmt::Debug, B: Entity<Error=::Error>> fmt::Debug for Server<S, B>
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Server")
@@ -445,8 +441,7 @@ where
I: Stream<Error=io::Error>,
I::Item: AsyncRead + AsyncWrite,
S: NewService<Request=Request<Body>, Response=Response<B>, Error=::Error>,
B: Stream<Error=::Error>,
B::Item: AsRef<[u8]>,
B: Entity<Error=::Error>,
{
type Item = Connection<I::Item, S::Instance>;
type Error = ::Error;
@@ -720,7 +715,7 @@ impl Future for WaitUntilZero {
}
mod hyper_service {
use super::{Body, Request, Response, Service, Stream};
use super::{Body, Entity, Request, Response, Service};
/// A "trait alias" for any type that implements `Service` with hyper's
/// Request, Response, and Error types, and a streaming body.
///
@@ -751,8 +746,7 @@ mod hyper_service {
Response=Response<B>,
Error=::Error,
>,
B: Stream<Error=::Error>,
B::Item: AsRef<[u8]>,
B: Entity<Error=::Error>,
{}
impl<S, B> HyperService for S
@@ -763,8 +757,7 @@ mod hyper_service {
Error=::Error,
>,
S: Sealed,
B: Stream<Error=::Error>,
B::Item: AsRef<[u8]>,
B: Entity<Error=::Error>,
{
type ResponseBody = B;
type Sealed = Opaque;