Bring back Handler::into_service (#212)

It was removed as part of https://github.com/tokio-rs/axum/pull/184 but
I do actually think it has some utility. So makes sense to keep even if
axum doesn't use it directly for routing.
This commit is contained in:
David Pedersen 2021-08-19 21:16:44 +02:00 committed by GitHub
parent 421faceae3
commit 0fd17d4181
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 139 additions and 1 deletions

View file

@ -2,11 +2,15 @@
use crate::body::{box_body, BoxBody};
use crate::util::{Either, EitherProj};
use futures_util::{future::BoxFuture, ready};
use futures_util::{
future::{BoxFuture, Map},
ready,
};
use http::{Method, Request, Response};
use http_body::Empty;
use pin_project_lite::pin_project;
use std::{
convert::Infallible,
fmt,
future::Future,
pin::Pin,
@ -59,3 +63,12 @@ where
f.debug_struct("OnMethodFuture").finish()
}
}
opaque_future! {
/// The response future for [`IntoService`](super::IntoService).
pub type IntoServiceFuture =
Map<
BoxFuture<'static, Response<BoxBody>>,
fn(Response<BoxBody>) -> Result<Response<BoxBody>, Infallible>,
>;
}

View file

@ -0,0 +1,73 @@
use super::Handler;
use crate::body::BoxBody;
use http::{Request, Response};
use std::{
convert::Infallible,
fmt,
marker::PhantomData,
task::{Context, Poll},
};
use tower::Service;
/// An adapter that makes a [`Handler`] into a [`Service`].
///
/// Created with [`Handler::into_service`].
pub struct IntoService<H, B, T> {
handler: H,
_marker: PhantomData<fn() -> (B, T)>,
}
impl<H, B, T> IntoService<H, B, T> {
pub(super) fn new(handler: H) -> Self {
Self {
handler,
_marker: PhantomData,
}
}
}
impl<H, B, T> fmt::Debug for IntoService<H, B, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("IntoService")
.field(&format_args!("..."))
.finish()
}
}
impl<H, B, T> Clone for IntoService<H, B, T>
where
H: Clone,
{
fn clone(&self) -> Self {
Self {
handler: self.handler.clone(),
_marker: PhantomData,
}
}
}
impl<H, T, B> Service<Request<B>> for IntoService<H, B, T>
where
H: Handler<B, T> + Clone + Send + 'static,
B: Send + 'static,
{
type Response = Response<BoxBody>;
type Error = Infallible;
type Future = super::future::IntoServiceFuture;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
// `IntoService` can only be constructed from async functions which are always ready, or from
// `Layered` which bufferes in `<Layered as Handler>::call` and is therefore also always
// ready.
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<B>) -> Self::Future {
use futures_util::future::FutureExt;
let handler = self.handler.clone();
let future = Handler::call(handler, req).map(Ok::<_, Infallible> as _);
super::future::IntoServiceFuture { future }
}
}

View file

@ -21,6 +21,9 @@ use std::{
use tower::{BoxError, Layer, Service, ServiceExt};
pub mod future;
mod into_service;
pub use self::into_service::IntoService;
/// Route requests to the given handler regardless of the HTTP method of the
/// request.
@ -255,6 +258,34 @@ pub trait Handler<B, T>: Clone + Send + Sized + 'static {
{
Layered::new(layer.layer(any(self)))
}
/// Convert the handler into a [`Service`].
///
/// This allows you to serve a single handler if you don't need any routing:
///
/// ```rust
/// use axum::{
/// Server, handler::Handler, http::{Uri, Method}, response::IntoResponse,
/// };
/// use tower::make::Shared;
/// use std::net::SocketAddr;
///
/// async fn handler(method: Method, uri: Uri, body: String) -> impl IntoResponse {
/// format!("received `{} {}` with body `{:?}`", method, uri, body)
/// }
///
/// let service = handler.into_service();
///
/// # async {
/// Server::bind(&SocketAddr::from(([127, 0, 0, 1], 3000)))
/// .serve(Shared::new(service))
/// .await?;
/// # Ok::<_, hyper::Error>(())
/// # };
/// ```
fn into_service(self) -> IntoService<Self, B, T> {
IntoService::new(self)
}
}
#[async_trait]

View file

@ -3,6 +3,7 @@
use crate::{
extract,
handler::{any, delete, get, on, patch, post, Handler},
response::IntoResponse,
route,
routing::nest,
routing::{MethodFilter, RoutingDsl},
@ -604,6 +605,26 @@ async fn multiple_methods_for_one_handler() {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
async fn handler_into_service() {
async fn handle(body: String) -> impl IntoResponse {
format!("you said: {}", body)
}
let addr = run_in_background(handle.into_service()).await;
let client = reqwest::Client::new();
let res = client
.post(format!("http://{}", addr))
.body("hi there!")
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await.unwrap(), "you said: hi there!");
}
/// Run a `tower::Service` in the background and get a URI for it.
pub(crate) async fn run_in_background<S, ResBody>(svc: S) -> SocketAddr
where