Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
breaking change if you depend on axum with `default_features = false`. ([#286])
- **breaking:** Remove `routing::Layered` as it didn't actually do anything and
thus wasn't necessary
- **breaking:** Replace `Router::boxed` with `routing::BoxRoute::layer`. This
fixes compile time issues with `Router::boxed`. Routes are now boxed with
`.layer(BoxRoute::<Body>::layer())`.

[#339]: https://github.com/tokio-rs/axum/pull/339
[#286]: https://github.com/tokio-rs/axum/pull/286
Expand Down
12 changes: 8 additions & 4 deletions examples/key-value-store/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//! ```

use axum::{
body::Bytes,
body::{Body, Bytes},
extract::{ContentLengthLimit, Extension, Path},
handler::{delete, get, Handler},
http::StatusCode,
Expand Down Expand Up @@ -121,9 +121,13 @@ fn admin_routes() -> Router<BoxRoute> {
Router::new()
.route("/keys", delete(delete_all_keys))
.route("/key/:key", delete(remove_key))
// Require bearer auth for all admin routes
.layer(RequireAuthorizationLayer::bearer("secret-token"))
.boxed()
.layer(
ServiceBuilder::new()
// Box the router so we can name the type
.layer(BoxRoute::<Body>::layer())
// Require bearer auth for all admin routes
.layer(RequireAuthorizationLayer::bearer("secret-token")),
)
}

fn handle_error(error: BoxError) -> Result<impl IntoResponse, Infallible> {
Expand Down
1 change: 1 addition & 0 deletions examples/testing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ tracing-subscriber = "0.2"
tower-http = { version = "0.1", features = ["trace"] }
serde_json = "1.0"
hyper = { version = "0.14", features = ["full"] }
tower = "0.4"

[dev-dependencies]
tower = { version = "0.4", features = ["util"] }
9 changes: 7 additions & 2 deletions examples/testing/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
//! ```

use axum::{
body::Body,
handler::{get, post},
routing::BoxRoute,
Json, Router,
};
use tower::ServiceBuilder;
use tower_http::trace::TraceLayer;

#[tokio::main]
Expand Down Expand Up @@ -42,8 +44,11 @@ fn app() -> Router<BoxRoute> {
}),
)
// We can still add middleware
.layer(TraceLayer::new_for_http())
.boxed()
.layer(
ServiceBuilder::new()
.layer(BoxRoute::<Body>::layer())
.layer(TraceLayer::new_for_http()),
)
}

#[cfg(test)]
Expand Down
11 changes: 10 additions & 1 deletion src/clone_box_service.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use futures_util::future::BoxFuture;
use std::task::{Context, Poll};
use std::{
fmt,
task::{Context, Poll},
};
use tower::ServiceExt;
use tower_service::Service;

Expand Down Expand Up @@ -43,6 +46,12 @@ impl<T, U, E> Clone for CloneBoxService<T, U, E> {
}
}

impl<T, U, E> fmt::Debug for CloneBoxService<T, U, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("CloneBoxService").finish()
}
}

trait CloneService<R>: Service<R> {
fn clone_box(
&self,
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@
//! fn api_routes() -> Router<BoxRoute> {
//! Router::new()
//! .route("/users", get(|_: Request<Body>| async { /* ... */ }))
//! .boxed()
//! .layer(BoxRoute::<Body>::layer())
//! }
//!
//! let app = Router::new()
Expand Down
163 changes: 163 additions & 0 deletions src/routing/box_route.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
use crate::{
body::{box_body, Body, BoxBody},
clone_box_service::CloneBoxService,
BoxError,
};
use bytes::Bytes;
use http::{Request, Response};
use pin_project_lite::pin_project;
use std::{
convert::Infallible,
fmt,
future::Future,
marker::PhantomData,
pin::Pin,
task::{Context, Poll},
};
use tower::{util::Oneshot, ServiceBuilder, ServiceExt};
use tower_http::map_response_body::MapResponseBodyLayer;
use tower_layer::Layer;
use tower_service::Service;

/// [`Layer`] that applies the [`BoxRoute`] middleware.
///
/// Created with [`BoxRoute::layer`]. See [`BoxRoute`] for more details.
pub struct BoxRouteLayer<B = Body, E = Infallible>(PhantomData<fn() -> (B, E)>);

impl<B, E> fmt::Debug for BoxRouteLayer<B, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("BoxRouteLayer").finish()
}
}

impl<B, E> Clone for BoxRouteLayer<B, E> {
fn clone(&self) -> Self {
Self(self.0)
}
}

impl<S, ReqBody, ResBody> Layer<S> for BoxRouteLayer<ReqBody, S::Error>
where
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone + Send + Sync + 'static,
S::Error: Into<BoxError> + Send,
S::Future: Send,
ReqBody: Send + 'static,
ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static,
ResBody::Error: Into<BoxError>,
{
type Service = BoxRoute<ReqBody, S::Error>;

fn layer(&self, inner: S) -> Self::Service {
ServiceBuilder::new()
.layer_fn(BoxRoute)
.layer_fn(CloneBoxService::new)
.layer(MapResponseBodyLayer::new(box_body))
.service(inner)
}
}

/// A boxed route trait object.
///
/// This makes it easier to name the types of routers to, for example, return
/// them from functions. Applied with `.layer(BoxRoute::<Body>::layer())`:
///
/// ```rust
/// use axum::{
/// body::Body,
/// handler::get,
/// routing::BoxRoute,
/// Router,
/// };
///
/// async fn first_handler() { /* ... */ }
///
/// async fn second_handler() { /* ... */ }
///
/// async fn third_handler() { /* ... */ }
///
/// fn app() -> Router<BoxRoute> {
/// Router::new()
/// .route("/", get(first_handler).post(second_handler))
/// .route("/foo", get(third_handler))
/// .layer(BoxRoute::<Body>::layer())
/// }
/// ```
///
/// Note that its important to specify the request body type with
/// `BoxRoute::<Body>::layer()` as this improves compile times.
pub struct BoxRoute<B = Body, E = Infallible>(CloneBoxService<Request<B>, Response<BoxBody>, E>);

impl<B, E> BoxRoute<B, E> {
/// Get a [`BoxRouteLayer`] which is a [`Layer`] that applies the
/// [`BoxRoute`] middleware.
pub fn layer() -> BoxRouteLayer<B, E> {
BoxRouteLayer(PhantomData)
}
}

impl<B, E> Clone for BoxRoute<B, E> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}

impl<B, E> fmt::Debug for BoxRoute<B, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BoxRoute").finish()
}
}

impl<B, E> Service<Request<B>> for BoxRoute<B, E>
where
E: Into<BoxError>,
{
type Response = Response<BoxBody>;
type Error = E;
type Future = BoxRouteFuture<B, E>;

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

#[inline]
fn call(&mut self, req: Request<B>) -> Self::Future {
BoxRouteFuture {
inner: self.0.clone().oneshot(req),
}
}
}

pin_project! {
/// The response future for [`BoxRoute`].
pub struct BoxRouteFuture<B, E>
where
E: Into<BoxError>,
{
#[pin]
pub(super) inner: Oneshot<
CloneBoxService<Request<B>, Response<BoxBody>, E>,
Request<B>,
>,
}
}

impl<B, E> Future for BoxRouteFuture<B, E>
where
E: Into<BoxError>,
{
type Output = Result<Response<BoxBody>, E>;

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

impl<B, E> fmt::Debug for BoxRouteFuture<B, E>
where
E: Into<BoxError>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BoxRouteFuture").finish()
}
}
Loading