Skip to content

Commit

Permalink
feat(server): add Server::serve_service
Browse files Browse the repository at this point in the history
This adds `Server::serve_service` which is a convenience for doing

```rust
Server::bind(&addr).serve(make_service_fn(|_| async move {
    Ok::<_, Infallible>(service_fn(handler))
}));
```

That now becomes

```rust
Server::bind(&addr).serve_service(service_fn(handler));
```

It basically copies [`tower::make::Shared`][] into Hyper so users don't need
to add another dependency to do this.

Fixes #2154

[`tower::make::Shared`]: https://docs.rs/tower/0.4.7/tower/make/struct.Shared.html
  • Loading branch information
davidpdrsn committed May 6, 2021
1 parent d1d2f32 commit 4931c8b
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 27 deletions.
39 changes: 20 additions & 19 deletions src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,8 @@
//! // Construct our SocketAddr to listen on...
//! let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
//!
//! // And a MakeService to handle each connection...
//! let make_service = make_service_fn(|_conn| async {
//! Ok::<_, Infallible>(service_fn(handle))
//! });
//!
//! // Then bind and serve...
//! let server = Server::bind(&addr).serve(make_service);
//! let server = Server::bind(&addr).serve_service(service_fn(handle));
//!
//! // And run forever...
//! if let Err(e) = server.await {
Expand All @@ -51,26 +46,30 @@
//! # fn main() {}
//! ```
//!
//! If you don't need the connection and your service implements `Clone` you can use
//! [`tower::make::Shared`] instead of `make_service_fn` which is a bit simpler:
//! If you need the incoming connection to handle the request you can use [`make_service_fn`] to
//! create a [`Service`] on demand:
//!
//! ```no_run
//! # use std::convert::Infallible;
//! # use std::net::SocketAddr;
//! # use hyper::{Body, Request, Response, Server};
//! # use hyper::service::{make_service_fn, service_fn};
//! # use tower::make::Shared;
//! # async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
//! # Ok(Response::new(Body::from("Hello World")))
//! # }
//! use std::convert::Infallible;
//! use std::net::SocketAddr;
//! use hyper::{Body, Request, Response, Server};
//! use hyper::service::{make_service_fn, service_fn};
//! use hyper::server::conn::AddrStream;
//!
//! async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
//! Ok(Response::new(Body::from("Hello World")))
//! }
//!
//! # #[cfg(feature = "runtime")]
//! #[tokio::main]
//! async fn main() {
//! // Construct our SocketAddr to listen on...
//! let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
//!
//! // Shared is a MakeService that produces services by cloning an inner service...
//! let make_service = Shared::new(service_fn(handle));
//! // And a MakeService to handle each connection...
//! let make_service = make_service_fn(|conn: &AddrStream| async {
//! Ok::<_, Infallible>(service_fn(handle))
//! });
//!
//! // Then bind and serve...
//! let server = Server::bind(&addr).serve(make_service);
Expand All @@ -84,7 +83,9 @@
//! # fn main() {}
//! ```
//!
//! [`tower::make::Shared`]: https://docs.rs/tower/latest/tower/make/struct.Shared.html
//! [`make_service_fn`]: crate::service::make_service_fn
//! [`Server::serve_service`]: crate::server::Server::serve_service
//! [`Service`]: crate::service::Service
pub mod accept;

Expand Down
55 changes: 49 additions & 6 deletions src/server/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use super::accept::Accept;
use crate::body::{Body, HttpBody};
use crate::common::exec::{ConnStreamExec, Exec, NewSvcExec};
use crate::common::{task, Future, Pin, Poll, Unpin};
use crate::service::{HttpService, MakeServiceRef};
use crate::service::{HttpService, MakeServiceRef, Shared, SharedFuture};
// Renamed `Http` as `Http_` for now so that people upgrading don't see an
// error that `hyper::server::Http` is private...
use super::conn::{Http as Http_, NoopWatcher, SpawnAll};
Expand Down Expand Up @@ -418,22 +418,65 @@ impl<I, E> Builder<I, E> {
/// }
/// # }
/// ```
pub fn serve<S, B>(self, new_service: S) -> Server<I, S, E>
pub fn serve<M, B>(self, new_service: M) -> Server<I, M, E>
where
I: Accept,
I::Error: Into<Box<dyn StdError + Send + Sync>>,
I::Conn: AsyncRead + AsyncWrite + Unpin + Send + 'static,
S: MakeServiceRef<I::Conn, Body, ResBody = B>,
S::Error: Into<Box<dyn StdError + Send + Sync>>,
M: MakeServiceRef<I::Conn, Body, ResBody = B>,
M::Error: Into<Box<dyn StdError + Send + Sync>>,
B: HttpBody + 'static,
B::Error: Into<Box<dyn StdError + Send + Sync>>,
E: NewSvcExec<I::Conn, S::Future, S::Service, E, NoopWatcher>,
E: ConnStreamExec<<S::Service as HttpService<Body>>::Future, B>,
E: NewSvcExec<I::Conn, M::Future, M::Service, E, NoopWatcher>,
E: ConnStreamExec<<M::Service as HttpService<Body>>::Future, B>,
{
let serve = self.protocol.serve(self.incoming, new_service);
let spawn_all = serve.spawn_all();
Server { spawn_all }
}

/// Consume this `Builder`, creating a [`Server`](Server) that will run the given service.
///
/// # Example
///
/// ```
/// # #[cfg(feature = "tcp")]
/// # async fn run() {
/// use hyper::{Body, Error, Response, Server};
/// use hyper::service::{make_service_fn, service_fn};
///
/// // Construct our SocketAddr to listen on...
/// let addr = ([127, 0, 0, 1], 3000).into();
///
/// // Our service used to respond to requests...
/// let service = service_fn(|_req| async {
/// Ok::<_, Error>(Response::new(Body::from("Hello World")))
/// });
///
/// // Then bind and serve...
/// let server = Server::bind(&addr).serve_service(service);
///
/// // Run forever-ish...
/// if let Err(err) = server.await {
/// eprintln!("server error: {}", err);
/// }
/// # }
/// ```
pub fn serve_service<S, B>(self, service: S) -> Server<I, Shared<S>, E>
where
S: HttpService<Body, ResBody = B> + Clone,
S::Error: Into<Box<dyn StdError + Send + Sync>>,
B: HttpBody + 'static,
B::Error: Into<Box<dyn StdError + Send + Sync>>,
I: Accept,
I::Error: Into<Box<dyn StdError + Send + Sync>>,
I::Conn: AsyncRead + AsyncWrite + Unpin + Send + 'static,
E: NewSvcExec<I::Conn, SharedFuture<S>, S, E, NoopWatcher>,
E: ConnStreamExec<S::Future, B>,
{
let make_service = Shared::new(service);
self.serve(make_service)
}
}

#[cfg(feature = "tcp")]
Expand Down
47 changes: 46 additions & 1 deletion src/service/make.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use std::convert::Infallible;
use std::error::Error as StdError;
use std::fmt;

use tokio::io::{AsyncRead, AsyncWrite};

use pin_project::pin_project;

use super::{HttpService, Service};
use crate::body::HttpBody;
use crate::common::{task, Future, Poll};
use crate::common::{task, Future, Pin, Poll};

// The same "trait alias" as tower::MakeConnection, but inlined to reduce
// dependencies.
Expand Down Expand Up @@ -174,6 +177,48 @@ impl<F> fmt::Debug for MakeServiceFn<F> {
}
}

/// A `MakeService` that produces services by cloning an inner service.
#[derive(Debug, Clone, Copy)]
pub struct Shared<S> {
svc: S,
}

impl<S> Shared<S> {
pub(crate) fn new(svc: S) -> Self {
Self { svc }
}
}

impl<T, S> Service<T> for Shared<S>
where
S: Clone,
{
type Error = Infallible;
type Response = S;
type Future = SharedFuture<S>;

fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}

fn call(&mut self, _target: T) -> Self::Future {
SharedFuture(futures_util::future::ok(self.svc.clone()))
}
}

/// Response future for [`Shared`].
#[pin_project]
#[derive(Debug)]
pub struct SharedFuture<S>(#[pin] futures_util::future::Ready<Result<S, Infallible>>);

impl<S> Future for SharedFuture<S> {
type Output = Result<S, Infallible>;

fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
self.project().0.poll(cx)
}
}

mod sealed {
pub trait Sealed<X> {}

Expand Down
2 changes: 1 addition & 1 deletion src/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub(super) use self::http::HttpService;
#[cfg(all(any(feature = "http1", feature = "http2"), feature = "client"))]
pub(super) use self::make::MakeConnection;
#[cfg(all(any(feature = "http1", feature = "http2"), feature = "server"))]
pub(super) use self::make::MakeServiceRef;
pub(super) use self::make::{MakeServiceRef, Shared, SharedFuture};
#[cfg(all(any(feature = "http1", feature = "http2"), feature = "client"))]
pub(super) use self::oneshot::{oneshot, Oneshot};

Expand Down

0 comments on commit 4931c8b

Please sign in to comment.