mirror of
https://github.com/tokio-rs/axum.git
synced 2025-01-11 12:31:25 +01:00
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:
parent
421faceae3
commit
0fd17d4181
4 changed files with 139 additions and 1 deletions
|
@ -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>,
|
||||
>;
|
||||
}
|
||||
|
|
73
src/handler/into_service.rs
Normal file
73
src/handler/into_service.rs
Normal 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 }
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue