feat(service): introduce hyper-specific Service

This introduces the `hyper::service` module, which replaces
`tokio-service`.

Since the trait is specific to hyper, its associated
types have been adjusted. It didn't make sense to need to define
`Service<Request=http::Request>`, since we already know the context is
HTTP. Instead, the request and response bodies are associated types now,
and slightly stricter bounds have been placed on `Error`.

The helpers `service_fn` and `service_fn_ok` should be sufficient for
now to ease creating `Service`s.

The `NewService` trait now allows service creation to also be
asynchronous.

These traits are similar to `tower` in nature, and possibly will be
replaced completely by it in the future. For now, hyper defining its own
allows the traits to have better context, and prevents breaking changes
in `tower` from affecting hyper.

Closes #1461

BREAKING CHANGE: The `Service` trait has changed: it has some changed
  associated types, and `call` is now bound to `&mut self`.

  The `NewService` trait has changed: it has some changed associated
  types, and `new_service` now returns a `Future`.

  `Client` no longer implements `Service` for now.

  `hyper::server::conn::Serve` now returns `Connecting` instead of
  `Connection`s, since `new_service` can now return a `Future`. The
  `Connecting` is a future wrapping the new service future, returning
  a `Connection` afterwards. In many cases, `Future::flatten` can be
  used.
This commit is contained in:
Sean McArthur
2018-04-17 13:03:59 -07:00
parent 71a15c25f5
commit 2dc6202fe7
24 changed files with 749 additions and 582 deletions

35
src/service/mod.rs Normal file
View File

@@ -0,0 +1,35 @@
//! Services and NewServices
//!
//! - A [`Service`](Service) is a trait representing an asynchronous function
//! of a request to a response. It's similar to
//! `async fn(Request) -> Result<Response, Error>`.
//! - A [`NewService`](NewService) is a trait creating specific instances of a
//! `Service`.
//!
//! These types are conceptually similar to those in
//! [tower](https://crates.io/crates/tower), while being specific to hyper.
//!
//! # Service
//!
//! In hyper, especially in the server setting, a `Service` is usually bound
//! to a single connection. It defines how to respond to **all** requests that
//! connection will receive.
//!
//! While it's possible to implement `Service` for a type manually, the helpers
//! [`service_fn`](service_fn) and [`service_fn_ok`](service_fn_ok) should be
//! sufficient for most cases.
//!
//! # NewService
//!
//! Since a `Service` is bound to a single connection, a [`Server`](::Server)
//! needs a way to make them as it accepts connections. This is what a
//! `NewService` does.
//!
//! Resources that need to be shared by all `Service`s can be put into a
//! `NewService`, and then passed to individual `Service`s when `new_service`
//! is called.
mod new_service;
mod service;
pub use self::new_service::{NewService};
pub use self::service::{service_fn, service_fn_ok, Service};

View File

@@ -0,0 +1,55 @@
use std::error::Error as StdError;
use futures::{Future, IntoFuture};
use body::Payload;
use super::Service;
/// An asynchronous constructor of `Service`s.
pub trait NewService {
/// The `Payload` body of the `http::Request`.
type ReqBody: Payload;
/// The `Payload` body of the `http::Response`.
type ResBody: Payload;
/// The error type that can be returned by `Service`s.
type Error: Into<Box<StdError + Send + Sync>>;
/// The resolved `Service` from `new_service()`.
type Service: Service<
ReqBody=Self::ReqBody,
ResBody=Self::ResBody,
Error=Self::Error,
>;
/// The future returned from `new_service` of a `Service`.
type Future: Future<Item=Self::Service, Error=Self::InitError>;
/// The error type that can be returned when creating a new `Service.
type InitError: Into<Box<StdError + Send + Sync>>;
/// Create a new `Service`.
fn new_service(&self) -> Self::Future;
}
impl<F, R, S> NewService for F
where
F: Fn() -> R,
R: IntoFuture<Item=S>,
R::Error: Into<Box<StdError + Send + Sync>>,
S: Service,
{
type ReqBody = S::ReqBody;
type ResBody = S::ResBody;
type Error = S::Error;
type Service = S;
type Future = R::Future;
type InitError = R::Error;
fn new_service(&self) -> Self::Future {
(*self)().into_future()
}
}

165
src/service/service.rs Normal file
View File

@@ -0,0 +1,165 @@
use std::error::Error as StdError;
use std::fmt;
use std::marker::PhantomData;
use futures::{future, Future, IntoFuture};
use body::Payload;
use common::Never;
use ::{Request, Response};
/// An asynchronous function from `Request` to `Response`.
pub trait Service {
/// The `Payload` body of the `http::Request`.
type ReqBody: Payload;
/// The `Payload` body of the `http::Response`.
type ResBody: Payload;
/// The error type that can occur within this `Service.
///
/// Note: Returning an `Error` to a hyper server will cause the connection
/// to be abruptly aborted. In most cases, it is better to return a `Response`
/// with a 4xx or 5xx status code.
type Error: Into<Box<StdError + Send + Sync>>;
/// The `Future` returned by this `Service`.
type Future: Future<Item=Response<Self::ResBody>, Error=Self::Error>;
/// Calls this `Service` with a request, returning a `Future` of the response.
fn call(&mut self, req: Request<Self::ReqBody>) -> Self::Future;
}
/// Create a `Service` from a function.
///
/// # Example
///
/// ```rust
/// use hyper::{Body, Request, Response, Version};
/// use hyper::service::service_fn;
///
/// let service = service_fn(|req: Request<Body>| {
/// if req.version() == Version::HTTP_11 {
/// Ok(Response::new(Body::from("Hello World")))
/// } else {
/// // Note: it's usually better to return a Response
/// // with an appropriate StatusCode instead of an Err.
/// Err("not HTTP/1.1, abort connection")
/// }
/// });
/// ```
pub fn service_fn<F, R, S>(f: F) -> ServiceFn<F, R>
where
F: FnMut(Request<R>) -> S,
S: IntoFuture,
{
ServiceFn {
f,
_req: PhantomData,
}
}
/// Create a `Service` from a function that never errors.
///
/// # Example
///
/// ```rust
/// use hyper::{Body, Request, Response};
/// use hyper::service::service_fn_ok;
///
/// let service = service_fn_ok(|req: Request<Body>| {
/// println!("request: {} {}", req.method(), req.uri());
/// Response::new(Body::from("Hello World"))
/// });
/// ```
pub fn service_fn_ok<F, R, S>(f: F) -> ServiceFnOk<F, R>
where
F: FnMut(Request<R>) -> Response<S>,
S: Payload,
{
ServiceFnOk {
f,
_req: PhantomData,
}
}
// Not exported from crate as this will likely be replaced with `impl Service`.
pub struct ServiceFn<F, R> {
f: F,
_req: PhantomData<fn(R)>,
}
impl<F, ReqBody, Ret, ResBody> Service for ServiceFn<F, ReqBody>
where
F: FnMut(Request<ReqBody>) -> Ret,
ReqBody: Payload,
Ret: IntoFuture<Item=Response<ResBody>>,
Ret::Error: Into<Box<StdError + Send + Sync>>,
ResBody: Payload,
{
type ReqBody = ReqBody;
type ResBody = ResBody;
type Error = Ret::Error;
type Future = Ret::Future;
fn call(&mut self, req: Request<Self::ReqBody>) -> Self::Future {
(self.f)(req).into_future()
}
}
impl<F, R> IntoFuture for ServiceFn<F, R> {
type Future = future::FutureResult<Self::Item, Self::Error>;
type Item = Self;
type Error = Never;
fn into_future(self) -> Self::Future {
future::ok(self)
}
}
impl<F, R> fmt::Debug for ServiceFn<F, R> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("impl Service")
.finish()
}
}
// Not exported from crate as this will likely be replaced with `impl Service`.
pub struct ServiceFnOk<F, R> {
f: F,
_req: PhantomData<fn(R)>,
}
impl<F, ReqBody, ResBody> Service for ServiceFnOk<F, ReqBody>
where
F: FnMut(Request<ReqBody>) -> Response<ResBody>,
ReqBody: Payload,
ResBody: Payload,
{
type ReqBody = ReqBody;
type ResBody = ResBody;
type Error = Never;
type Future = future::FutureResult<Response<ResBody>, Never>;
fn call(&mut self, req: Request<Self::ReqBody>) -> Self::Future {
future::ok((self.f)(req))
}
}
impl<F, R> IntoFuture for ServiceFnOk<F, R> {
type Future = future::FutureResult<Self::Item, Self::Error>;
type Item = Self;
type Error = Never;
fn into_future(self) -> Self::Future {
future::ok(self)
}
}
impl<F, R> fmt::Debug for ServiceFnOk<F, R> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("impl Service")
.finish()
}
}