From 9512d14c998c4bfa08bafec899838d2d61f16c04 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Tue, 2 Nov 2021 17:11:18 +0100 Subject: [PATCH] Add `Handler::{into_make_service, into_make_service_with_connect_info}` (#444) * Make `IntoMakeService(WithConnectInfo)?` work with any Service * Add `Handler::{into_make_service, into_make_service_with_connect_info}` These are useful if you want to run a handler without a `Router`, for example to make a proxy. --- CHANGELOG.md | 4 ++ src/extract/connect_info.rs | 32 +++++++----- src/handler/mod.rs | 90 +++++++++++++++++++++++++++++--- src/routing/into_make_service.rs | 44 ++++++---------- src/routing/mod.rs | 4 +- 5 files changed, 122 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fc8bad2..0620fe9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 is because bodies are streams and requiring streams to be `Sync` is unnecessary. - **added:** Implement `IntoResponse` for `http_body::combinators::UnsyncBoxBody`. + - **added:** Add `Handler::into_make_service` for serving a handler without a + `Router`. + - **added:** Add `Handler::into_make_service_with_connect_info` for serving a + handler without a `Router`, and storing info about the incoming connection. - Routing: - Big internal refactoring of routing leading to several improvements ([#363]) - **added:** Wildcard routes like `.route("/api/users/*rest", service)` are now supported. diff --git a/src/extract/connect_info.rs b/src/extract/connect_info.rs index 5e3ce170..18b419d8 100644 --- a/src/extract/connect_info.rs +++ b/src/extract/connect_info.rs @@ -5,7 +5,7 @@ //! [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info use super::{Extension, FromRequest, RequestParts}; -use crate::{AddExtension, AddExtensionLayer, Router}; +use crate::{AddExtension, AddExtensionLayer}; use async_trait::async_trait; use hyper::server::conn::AddrStream; use std::future::ready; @@ -25,8 +25,8 @@ use tower_service::Service; /// /// [`MakeService`]: tower::make::MakeService /// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info -pub struct IntoMakeServiceWithConnectInfo { - router: Router, +pub struct IntoMakeServiceWithConnectInfo { + svc: S, _connect_info: PhantomData C>, } @@ -36,19 +36,22 @@ fn traits() { assert_send::>(); } -impl IntoMakeServiceWithConnectInfo { - pub(crate) fn new(router: Router) -> Self { +impl IntoMakeServiceWithConnectInfo { + pub(crate) fn new(svc: S) -> Self { Self { - router, + svc, _connect_info: PhantomData, } } } -impl fmt::Debug for IntoMakeServiceWithConnectInfo { +impl fmt::Debug for IntoMakeServiceWithConnectInfo +where + S: fmt::Debug, +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("IntoMakeServiceWithConnectInfo") - .field("router", &self.router) + .field("svc", &self.svc) .finish() } } @@ -73,13 +76,14 @@ impl Connected<&AddrStream> for SocketAddr { } } -impl Service for IntoMakeServiceWithConnectInfo +impl Service for IntoMakeServiceWithConnectInfo where + S: Clone, C: Connected, { - type Response = AddExtension, ConnectInfo>; + type Response = AddExtension>; type Error = Infallible; - type Future = ResponseFuture; + type Future = ResponseFuture; #[inline] fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { @@ -88,15 +92,15 @@ where fn call(&mut self, target: T) -> Self::Future { let connect_info = ConnectInfo(C::connect_info(target)); - let svc = AddExtensionLayer::new(connect_info).layer(self.router.clone()); + let svc = AddExtensionLayer::new(connect_info).layer(self.svc.clone()); ResponseFuture::new(ready(Ok(svc))) } } opaque_future! { /// Response future for [`IntoMakeServiceWithConnectInfo`]. - pub type ResponseFuture = - std::future::Ready, ConnectInfo>, Infallible>>; + pub type ResponseFuture = + std::future::Ready>, Infallible>>; } /// Extractor for getting connection information produced by a [`Connected`]. diff --git a/src/handler/mod.rs b/src/handler/mod.rs index 50ac8032..bbaf6b9b 100644 --- a/src/handler/mod.rs +++ b/src/handler/mod.rs @@ -70,8 +70,12 @@ use crate::{ body::{box_body, BoxBody}, - extract::{FromRequest, RequestParts}, + extract::{ + connect_info::{Connected, IntoMakeServiceWithConnectInfo}, + FromRequest, RequestParts, + }, response::IntoResponse, + routing::IntoMakeService, BoxError, }; use async_trait::async_trait; @@ -156,30 +160,102 @@ pub trait Handler: Clone + Send + Sized + 'static { /// Convert the handler into a [`Service`]. /// + /// This is commonly used together with [`Router::fallback`]: + /// + /// ```rust + /// use axum::{ + /// Server, + /// handler::Handler, + /// http::{Uri, Method, StatusCode}, + /// response::IntoResponse, + /// routing::{get, Router}, + /// }; + /// use tower::make::Shared; + /// use std::net::SocketAddr; + /// + /// async fn handler(method: Method, uri: Uri) -> impl IntoResponse { + /// (StatusCode::NOT_FOUND, format!("Nothing to see at {} {}", method, uri)) + /// } + /// + /// let app = Router::new() + /// .route("/", get(|| async {})) + /// .fallback(handler.into_service()); + /// + /// # async { + /// Server::bind(&SocketAddr::from(([127, 0, 0, 1], 3000))) + /// .serve(app.into_make_service()) + /// .await?; + /// # Ok::<_, hyper::Error>(()) + /// # }; + /// ``` + /// + /// [`Router::fallback`]: crate::routing::Router::fallback + fn into_service(self) -> IntoService { + IntoService::new(self) + } + + /// Convert the handler into a [`MakeService`]. + /// /// 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)) + /// .serve(handler.into_make_service()) /// .await?; /// # Ok::<_, hyper::Error>(()) /// # }; /// ``` - fn into_service(self) -> IntoService { - IntoService::new(self) + /// + /// [`MakeService`]: tower::make::MakeService + fn into_make_service(self) -> IntoMakeService> { + IntoMakeService::new(self.into_service()) + } + + /// Convert the handler into a [`MakeService`] which stores information + /// about the incoming connection. + /// + /// See [`Router::into_make_service_with_connect_info`] for more details. + /// + /// ```rust + /// use axum::{ + /// Server, + /// handler::Handler, + /// response::IntoResponse, + /// extract::ConnectInfo, + /// }; + /// use std::net::SocketAddr; + /// + /// async fn handler(ConnectInfo(addr): ConnectInfo) -> impl IntoResponse { + /// format!("Hello {}", addr) + /// } + /// + /// # async { + /// Server::bind(&SocketAddr::from(([127, 0, 0, 1], 3000))) + /// .serve(handler.into_make_service_with_connect_info::()) + /// .await?; + /// # Ok::<_, hyper::Error>(()) + /// # }; + /// ``` + /// + /// [`MakeService`]: tower::make::MakeService + /// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info + fn into_make_service_with_connect_info( + self, + ) -> IntoMakeServiceWithConnectInfo, C> + where + C: Connected, + { + IntoMakeServiceWithConnectInfo::new(self.into_service()) } } diff --git a/src/routing/into_make_service.rs b/src/routing/into_make_service.rs index 10e5c4fa..142cf9f8 100644 --- a/src/routing/into_make_service.rs +++ b/src/routing/into_make_service.rs @@ -1,7 +1,5 @@ -use super::Router; use std::{ convert::Infallible, - fmt, future::ready, task::{Context, Poll}, }; @@ -10,36 +8,24 @@ use tower_service::Service; /// A [`MakeService`] that produces axum router services. /// /// [`MakeService`]: tower::make::MakeService -pub struct IntoMakeService { - router: Router, +#[derive(Debug, Clone)] +pub struct IntoMakeService { + svc: S, } -impl IntoMakeService { - pub(super) fn new(router: Router) -> Self { - Self { router } +impl IntoMakeService { + pub(crate) fn new(svc: S) -> Self { + Self { svc } } } -impl Clone for IntoMakeService { - fn clone(&self) -> Self { - Self { - router: self.router.clone(), - } - } -} - -impl fmt::Debug for IntoMakeService { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("IntoMakeService") - .field("router", &self.router) - .finish() - } -} - -impl Service for IntoMakeService { - type Response = Router; +impl Service for IntoMakeService +where + S: Clone, +{ + type Response = S; type Error = Infallible; - type Future = IntoMakeServiceFuture; + type Future = IntoMakeServiceFuture; #[inline] fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { @@ -47,14 +33,14 @@ impl Service for IntoMakeService { } fn call(&mut self, _target: T) -> Self::Future { - IntoMakeServiceFuture::new(ready(Ok(self.router.clone()))) + IntoMakeServiceFuture::new(ready(Ok(self.svc.clone()))) } } opaque_future! { /// Response future for [`IntoMakeService`]. - pub type IntoMakeServiceFuture = - std::future::Ready, Infallible>>; + pub type IntoMakeServiceFuture = + std::future::Ready>; } #[cfg(test)] diff --git a/src/routing/mod.rs b/src/routing/mod.rs index 89587fb5..4c5efadb 100644 --- a/src/routing/mod.rs +++ b/src/routing/mod.rs @@ -302,14 +302,14 @@ where /// ``` /// /// [`MakeService`]: tower::make::MakeService - pub fn into_make_service(self) -> IntoMakeService { + pub fn into_make_service(self) -> IntoMakeService { IntoMakeService::new(self) } #[doc = include_str!("../docs/routing/into_make_service_with_connect_info.md")] pub fn into_make_service_with_connect_info( self, - ) -> IntoMakeServiceWithConnectInfo + ) -> IntoMakeServiceWithConnectInfo where C: Connected, {