Fix compile time regression by boxing routes internally (#404)

This is a reimplementation of #401 but with the new matchit based router.

Fixes #399
This commit is contained in:
David Pedersen
2021-10-24 20:52:42 +02:00
committed by GitHub
parent 1a764bb8d7
commit 0ee7379d4f
13 changed files with 203 additions and 242 deletions
+25 -8
View File
@@ -7,20 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
# Unreleased # Unreleased
- **fixed:** All known compile time issues are resolved, including those with
`boxed` and those introduced by Rust 1.56 ([#404])
- Big internal refactoring of routing leading to several improvements ([#363]) - Big internal refactoring of routing leading to several improvements ([#363])
- Wildcard routes like `.route("/api/users/*rest", service)` are now supported. - **added:** Wildcard routes like `.route("/api/users/*rest", service)` are now supported.
- The order routes are added in no longer matters. - **fixed:** The order routes are added in no longer matters.
- Adding a conflicting route will now cause a panic instead of silently making - **fixed:** Adding a conflicting route will now cause a panic instead of silently making
a route unreachable. a route unreachable.
- Route matching is faster as number of routes increase. - **fixed:** Route matching is faster as number of routes increase.
- **breaking:** The routes `/foo` and `/:key` are considered to overlap and - **breaking:** The routes `/foo` and `/:key` are considered to overlap and
will cause a panic when constructing the router. This might be fixed in the future. will cause a panic when constructing the router. This might be fixed in the future.
- Improve performance of `BoxRoute` ([#339]) - **fixed:** Expand accepted content types for JSON requests ([#378])
- Expand accepted content types for JSON requests ([#378]) - **breaking:** The router's type is now always `Router` regardless of how many routes or
middleware are applies ([#404])
This means router types are all always nameable:
```rust
fn my_routes() -> Router {
Router::new().route(
"/users",
post(|| async { "Hello, World!" }),
)
}
```
- **breaking:** `Route::boxed` and `BoxRoute` have been removed as they're no longer
necessary ([#404])
- **breaking:** `Route`, `Nested`, `Or` types are now private. They no longer had to be
public because `Router` is internally boxed ([#404])
- **breaking:** Automatically do percent decoding in `extract::Path` - **breaking:** Automatically do percent decoding in `extract::Path`
([#272]) ([#272])
- **breaking:** `Router::boxed` now the inner service to implement `Clone` and
`Sync` in addition to the previous trait bounds ([#339])
- **breaking:** Added feature flags for HTTP1 and JSON. This enables removing a - **breaking:** Added feature flags for HTTP1 and JSON. This enables removing a
few dependencies if your app only uses HTTP2 or doesn't use JSON. Its only a few dependencies if your app only uses HTTP2 or doesn't use JSON. Its only a
breaking change if you depend on axum with `default_features = false`. ([#286]) breaking change if you depend on axum with `default_features = false`. ([#286])
@@ -103,6 +119,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#363]: https://github.com/tokio-rs/axum/pull/363 [#363]: https://github.com/tokio-rs/axum/pull/363
[#396]: https://github.com/tokio-rs/axum/pull/396 [#396]: https://github.com/tokio-rs/axum/pull/396
[#402]: https://github.com/tokio-rs/axum/pull/402 [#402]: https://github.com/tokio-rs/axum/pull/402
[#404]: https://github.com/tokio-rs/axum/pull/404
# 0.2.8 (07. October, 2021) # 0.2.8 (07. October, 2021)
-1
View File
@@ -27,7 +27,6 @@ ws = ["tokio-tungstenite", "sha-1", "base64"]
async-trait = "0.1.43" async-trait = "0.1.43"
bitflags = "1.0" bitflags = "1.0"
bytes = "1.0" bytes = "1.0"
dyn-clone = "1.0"
futures-util = { version = "0.3", default-features = false, features = ["alloc"] } futures-util = { version = "0.3", default-features = false, features = ["alloc"] }
http = "0.2" http = "0.2"
http-body = "0.4.3" http-body = "0.4.3"
+1 -3
View File
@@ -13,7 +13,6 @@ use axum::{
handler::{delete, get, Handler}, handler::{delete, get, Handler},
http::StatusCode, http::StatusCode,
response::IntoResponse, response::IntoResponse,
routing::BoxRoute,
Router, Router,
}; };
use std::{ use std::{
@@ -108,7 +107,7 @@ async fn list_keys(Extension(state): Extension<SharedState>) -> String {
.join("\n") .join("\n")
} }
fn admin_routes() -> Router<BoxRoute> { fn admin_routes() -> Router {
async fn delete_all_keys(Extension(state): Extension<SharedState>) { async fn delete_all_keys(Extension(state): Extension<SharedState>) {
state.write().unwrap().db.clear(); state.write().unwrap().db.clear();
} }
@@ -122,7 +121,6 @@ fn admin_routes() -> Router<BoxRoute> {
.route("/key/:key", delete(remove_key)) .route("/key/:key", delete(remove_key))
// Require bearer auth for all admin routes // Require bearer auth for all admin routes
.layer(RequireAuthorizationLayer::bearer("secret-token")) .layer(RequireAuthorizationLayer::bearer("secret-token"))
.boxed()
} }
fn handle_error(error: BoxError) -> impl IntoResponse { fn handle_error(error: BoxError) -> impl IntoResponse {
+2 -4
View File
@@ -6,7 +6,6 @@
use axum::{ use axum::{
handler::{get, post}, handler::{get, post},
routing::BoxRoute,
Json, Router, Json, Router,
}; };
use tower_http::trace::TraceLayer; use tower_http::trace::TraceLayer;
@@ -32,7 +31,7 @@ async fn main() {
/// Having a function that produces our app makes it easy to call it from tests /// Having a function that produces our app makes it easy to call it from tests
/// without having to create an HTTP server. /// without having to create an HTTP server.
#[allow(dead_code)] #[allow(dead_code)]
fn app() -> Router<BoxRoute> { fn app() -> Router {
Router::new() Router::new()
.route("/", get(|| async { "Hello, World!" })) .route("/", get(|| async { "Hello, World!" }))
.route( .route(
@@ -43,7 +42,6 @@ fn app() -> Router<BoxRoute> {
) )
// We can still add middleware // We can still add middleware
.layer(TraceLayer::new_for_http()) .layer(TraceLayer::new_for_http())
.boxed()
} }
#[cfg(test)] #[cfg(test)]
@@ -59,7 +57,7 @@ mod tests {
async fn hello_world() { async fn hello_world() {
let app = app(); let app = app();
// `BoxRoute<Body>` implements `tower::Service<Request<Body>>` so we can // `Router` implements `tower::Service<Request<Body>>` so we can
// call it like any tower service, no need to run an HTTP server. // call it like any tower service, no need to run an HTTP server.
let response = app let response = app
.oneshot(Request::builder().uri("/").body(Body::empty()).unwrap()) .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
+24 -19
View File
@@ -4,26 +4,31 @@
//! cargo run -p example-tls-rustls //! cargo run -p example-tls-rustls
//! ``` //! ```
use axum::{handler::get, Router}; // NOTE: This example is currently broken since axum-server requires `S: Sync`,
// that isn't necessary and will be fixed in a future release
#[tokio::main] fn main() {}
async fn main() {
// Set the RUST_LOG, if it hasn't been explicitly defined
if std::env::var_os("RUST_LOG").is_none() {
std::env::set_var("RUST_LOG", "example_tls_rustls=debug")
}
tracing_subscriber::fmt::init();
let app = Router::new().route("/", get(handler)); // use axum::{handler::get, Router};
axum_server::bind_rustls("127.0.0.1:3000") // #[tokio::main]
.private_key_file("examples/tls-rustls/self_signed_certs/key.pem") // async fn main() {
.certificate_file("examples/tls-rustls/self_signed_certs/cert.pem") // // Set the RUST_LOG, if it hasn't been explicitly defined
.serve(app) // if std::env::var_os("RUST_LOG").is_none() {
.await // std::env::set_var("RUST_LOG", "example_tls_rustls=debug")
.unwrap(); // }
} // tracing_subscriber::fmt::init();
async fn handler() -> &'static str { // // let app = Router::new().route("/", get(handler));
"Hello, World!"
} // // axum_server::bind_rustls("127.0.0.1:3000")
// // .private_key_file("examples/tls-rustls/self_signed_certs/key.pem")
// // .certificate_file("examples/tls-rustls/self_signed_certs/cert.pem")
// // .serve(app)
// // .await
// // .unwrap();
// }
// async fn handler() -> &'static str {
// "Hello, World!"
// }
+7 -12
View File
@@ -3,19 +3,18 @@ use std::task::{Context, Poll};
use tower::ServiceExt; use tower::ServiceExt;
use tower_service::Service; use tower_service::Service;
/// A `Clone + Send + Sync` boxed `Service` /// A `Clone + Send` boxed `Service`
pub(crate) struct CloneBoxService<T, U, E>( pub(crate) struct CloneBoxService<T, U, E>(
Box< Box<
dyn CloneService<T, Response = U, Error = E, Future = BoxFuture<'static, Result<U, E>>> dyn CloneService<T, Response = U, Error = E, Future = BoxFuture<'static, Result<U, E>>>
+ Send + Send,
+ Sync,
>, >,
); );
impl<T, U, E> CloneBoxService<T, U, E> { impl<T, U, E> CloneBoxService<T, U, E> {
pub(crate) fn new<S>(inner: S) -> Self pub(crate) fn new<S>(inner: S) -> Self
where where
S: Service<T, Response = U, Error = E> + Clone + Send + Sync + 'static, S: Service<T, Response = U, Error = E> + Clone + Send + 'static,
S::Future: Send + 'static, S::Future: Send + 'static,
{ {
let inner = inner.map_future(|f| Box::pin(f) as _); let inner = inner.map_future(|f| Box::pin(f) as _);
@@ -48,22 +47,18 @@ trait CloneService<R>: Service<R> {
&self, &self,
) -> Box< ) -> Box<
dyn CloneService<R, Response = Self::Response, Error = Self::Error, Future = Self::Future> dyn CloneService<R, Response = Self::Response, Error = Self::Error, Future = Self::Future>
+ Send + Send,
+ Sync,
>; >;
} }
impl<R, T> CloneService<R> for T impl<R, T> CloneService<R> for T
where where
T: Service<R> + Send + Sync + Clone + 'static, T: Service<R> + Send + Clone + 'static,
{ {
fn clone_box( fn clone_box(
&self, &self,
) -> Box< ) -> Box<dyn CloneService<R, Response = T::Response, Error = T::Error, Future = T::Future> + Send>
dyn CloneService<R, Response = T::Response, Error = T::Error, Future = T::Future> {
+ Send
+ Sync,
> {
Box::new(self.clone()) Box::new(self.clone())
} }
} }
+1 -3
View File
@@ -360,15 +360,13 @@
//! http::Request, //! http::Request,
//! handler::get, //! handler::get,
//! Router, //! Router,
//! routing::BoxRoute
//! }; //! };
//! use tower_http::services::ServeFile; //! use tower_http::services::ServeFile;
//! use http::Response; //! use http::Response;
//! //!
//! fn api_routes() -> Router<BoxRoute> { //! fn api_routes() -> Router {
//! Router::new() //! Router::new()
//! .route("/users", get(|_: Request<Body>| async { /* ... */ })) //! .route("/users", get(|_: Request<Body>| async { /* ... */ }))
//! .boxed()
//! } //! }
//! //!
//! let app = Router::new() //! let app = Router::new()
+6 -29
View File
@@ -2,14 +2,12 @@
use crate::{ use crate::{
body::BoxBody, body::BoxBody,
clone_box_service::CloneBoxService,
routing::{FromEmptyRouter, UriStack}, routing::{FromEmptyRouter, UriStack},
}; };
use http::{Request, Response}; use http::{Request, Response};
use pin_project_lite::pin_project; use pin_project_lite::pin_project;
use std::{ use std::{
convert::Infallible, convert::Infallible,
fmt,
future::Future, future::Future,
pin::Pin, pin::Pin,
task::{Context, Poll}, task::{Context, Poll},
@@ -17,43 +15,22 @@ use std::{
use tower::util::Oneshot; use tower::util::Oneshot;
use tower_service::Service; use tower_service::Service;
pub use super::or::ResponseFuture as OrResponseFuture;
opaque_future! { opaque_future! {
/// Response future for [`EmptyRouter`](super::EmptyRouter). /// Response future for [`EmptyRouter`](super::EmptyRouter).
pub type EmptyRouterFuture<E> = pub type EmptyRouterFuture<E> =
std::future::Ready<Result<Response<BoxBody>, E>>; std::future::Ready<Result<Response<BoxBody>, E>>;
} }
pin_project! { opaque_future! {
/// The response future for [`BoxRoute`](super::BoxRoute). /// Response future for [`Routes`](super::Routes).
pub struct BoxRouteFuture<B> { pub type RoutesFuture =
#[pin] futures_util::future::BoxFuture<'static, Result<Response<BoxBody>, Infallible>>;
pub(super) inner: Oneshot<
CloneBoxService<Request<B>, Response<BoxBody>, Infallible>,
Request<B>,
>,
}
}
impl<B> Future for BoxRouteFuture<B> {
type Output = Result<Response<BoxBody>, Infallible>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.project().inner.poll(cx)
}
}
impl<B> fmt::Debug for BoxRouteFuture<B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BoxRouteFuture").finish()
}
} }
pin_project! { pin_project! {
/// The response future for [`Route`](super::Route). /// The response future for [`Route`](super::Route).
#[derive(Debug)] #[derive(Debug)]
pub struct RouteFuture<S, F, B> pub(crate) struct RouteFuture<S, F, B>
where where
S: Service<Request<B>>, S: Service<Request<B>>,
F: Service<Request<B>> F: Service<Request<B>>
@@ -119,7 +96,7 @@ where
pin_project! { pin_project! {
/// The response future for [`Nested`](super::Nested). /// The response future for [`Nested`](super::Nested).
#[derive(Debug)] #[derive(Debug)]
pub struct NestedFuture<S, F, B> pub(crate) struct NestedFuture<S, F, B>
where where
S: Service<Request<B>>, S: Service<Request<B>>,
F: Service<Request<B>> F: Service<Request<B>>
+118 -148
View File
@@ -1,8 +1,8 @@
//! Routing between [`Service`]s. //! Routing between [`Service`]s.
use self::future::{BoxRouteFuture, EmptyRouterFuture, NestedFuture, RouteFuture}; use self::future::{EmptyRouterFuture, NestedFuture, RouteFuture, RoutesFuture};
use crate::{ use crate::{
body::{box_body, BoxBody}, body::{box_body, Body, BoxBody},
clone_box_service::CloneBoxService, clone_box_service::CloneBoxService,
extract::{ extract::{
connect_info::{Connected, IntoMakeServiceWithConnectInfo}, connect_info::{Connected, IntoMakeServiceWithConnectInfo},
@@ -22,8 +22,8 @@ use std::{
marker::PhantomData, marker::PhantomData,
task::{Context, Poll}, task::{Context, Poll},
}; };
use tower::{util::ServiceExt, ServiceBuilder}; use tower::util::ServiceExt;
use tower_http::map_response_body::MapResponseBodyLayer; use tower_http::map_response_body::MapResponseBody;
use tower_layer::Layer; use tower_layer::Layer;
use tower_service::Service; use tower_service::Service;
@@ -32,7 +32,7 @@ pub mod future;
mod method_filter; mod method_filter;
mod or; mod or;
pub use self::{method_filter::MethodFilter, or::Or}; pub use self::method_filter::MethodFilter;
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct RouteId(u64); struct RouteId(u64);
@@ -46,35 +46,30 @@ impl RouteId {
} }
/// The router type for composing handlers and services. /// The router type for composing handlers and services.
#[derive(Clone)] pub struct Router<B = Body> {
pub struct Router<S> { routes: Routes<B>,
routes: S,
node: Node<RouteId>, node: Node<RouteId>,
} }
impl Router<EmptyRouter> { impl<B> Clone for Router<B> {
/// Create a new `Router`. fn clone(&self) -> Self {
///
/// Unless you add additional routes this will respond to `404 Not Found` to
/// all requests.
pub fn new() -> Self {
Self { Self {
routes: EmptyRouter::not_found(), routes: self.routes.clone(),
node: Node::new(), node: self.node.clone(),
} }
} }
} }
impl Default for Router<EmptyRouter> { impl<B> Default for Router<B>
where
B: Send + Sync + 'static,
{
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
} }
impl<S> fmt::Debug for Router<S> impl<B> fmt::Debug for Router<B> {
where
S: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Router") f.debug_struct("Router")
.field("routes", &self.routes) .field("routes", &self.routes)
@@ -84,7 +79,21 @@ where
const NEST_TAIL_PARAM: &str = "__axum_nest"; const NEST_TAIL_PARAM: &str = "__axum_nest";
impl<S> Router<S> { impl<B> Router<B>
where
B: Send + Sync + 'static,
{
/// Create a new `Router`.
///
/// Unless you add additional routes this will respond to `404 Not Found` to
/// all requests.
pub fn new() -> Self {
Self {
routes: Routes(CloneBoxService::new(EmptyRouter::not_found())),
node: Node::new(),
}
}
/// Add another route to the router. /// Add another route to the router.
/// ///
/// `path` is a string of path segments separated by `/`. Each segment /// `path` is a string of path segments separated by `/`. Each segment
@@ -164,9 +173,13 @@ impl<S> Router<S> {
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
/// # }; /// # };
/// ``` /// ```
pub fn route<T, B>(mut self, path: &str, svc: T) -> Router<Route<T, S>> pub fn route<T>(mut self, path: &str, svc: T) -> Self
where where
T: Service<Request<B>, Error = Infallible>, T: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible>
+ Clone
+ Send
+ 'static,
T::Future: Send + 'static,
{ {
let id = RouteId::next(); let id = RouteId::next();
@@ -175,11 +188,11 @@ impl<S> Router<S> {
} }
Router { Router {
routes: Route { routes: Routes(CloneBoxService::new(Route {
id, id,
svc, svc,
fallback: self.routes, fallback: self.routes,
}, })),
node: self.node, node: self.node,
} }
} }
@@ -274,10 +287,6 @@ impl<S> Router<S> {
/// # }; /// # };
/// ``` /// ```
/// ///
/// If necessary you can use [`Router::boxed`] to box a group of routes
/// making the type easier to name. This is sometimes useful when working with
/// `nest`.
///
/// # Wildcard routes /// # Wildcard routes
/// ///
/// Nested routes are similar to wildcard routes. The difference is that /// Nested routes are similar to wildcard routes. The difference is that
@@ -305,9 +314,13 @@ impl<S> Router<S> {
/// for more details. /// for more details.
/// ///
/// [`OriginalUri`]: crate::extract::OriginalUri /// [`OriginalUri`]: crate::extract::OriginalUri
pub fn nest<T, B>(mut self, path: &str, svc: T) -> Router<Nested<T, S>> pub fn nest<T>(mut self, path: &str, svc: T) -> Self
where where
T: Service<Request<B>, Error = Infallible>, T: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible>
+ Clone
+ Send
+ 'static,
T::Future: Send + 'static,
{ {
let id = RouteId::next(); let id = RouteId::next();
@@ -326,64 +339,15 @@ impl<S> Router<S> {
} }
Router { Router {
routes: Nested { routes: Routes(CloneBoxService::new(Nested {
id, id,
svc, svc,
fallback: self.routes, fallback: self.routes,
}, })),
node: self.node, node: self.node,
} }
} }
/// Create a boxed route trait object.
///
/// This makes it easier to name the types of routers to, for example,
/// return them from functions:
///
/// ```rust
/// use axum::{
/// body::Body,
/// handler::get,
/// Router,
/// routing::BoxRoute
/// };
///
/// 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))
/// .boxed()
/// }
/// ```
///
/// It also helps with compile times when you have a very large number of
/// routes.
pub fn boxed<ReqBody, ResBody>(self) -> Router<BoxRoute<ReqBody>>
where
S: Service<Request<ReqBody>, Response = Response<ResBody>, Error = Infallible>
+ Clone
+ Send
+ Sync
+ 'static,
S::Future: Send,
ReqBody: Send + 'static,
ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static,
ResBody::Error: Into<BoxError>,
{
self.layer(
ServiceBuilder::new()
.layer_fn(BoxRoute)
.layer_fn(CloneBoxService::new)
.layer(MapResponseBodyLayer::new(box_body)),
)
}
/// Apply a [`tower::Layer`] to the router. /// Apply a [`tower::Layer`] to the router.
/// ///
/// All requests to the router will be processed by the layer's /// All requests to the router will be processed by the layer's
@@ -451,11 +415,21 @@ impl<S> Router<S> {
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
/// # }; /// # };
/// ``` /// ```
pub fn layer<L>(self, layer: L) -> Router<L::Service> pub fn layer<L, LayeredReqBody, LayeredResBody>(self, layer: L) -> Router<LayeredReqBody>
where where
L: Layer<S>, L: Layer<Routes<B>>,
L::Service: Service<
Request<LayeredReqBody>,
Response = Response<LayeredResBody>,
Error = Infallible,
> + Clone
+ Send
+ 'static,
<L::Service as Service<Request<LayeredReqBody>>>::Future: Send + 'static,
LayeredResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static,
LayeredResBody::Error: Into<BoxError>,
{ {
self.map(|svc| layer.layer(svc)) self.map(|svc| MapResponseBody::new(layer.layer(svc), box_body))
} }
/// Convert this router into a [`MakeService`], that is a [`Service`] who's /// Convert this router into a [`MakeService`], that is a [`Service`] who's
@@ -481,10 +455,7 @@ impl<S> Router<S> {
/// ``` /// ```
/// ///
/// [`MakeService`]: tower::make::MakeService /// [`MakeService`]: tower::make::MakeService
pub fn into_make_service(self) -> IntoMakeService<Self> pub fn into_make_service(self) -> IntoMakeService<Self> {
where
S: Clone,
{
IntoMakeService::new(self) IntoMakeService::new(self)
} }
@@ -572,7 +543,6 @@ impl<S> Router<S> {
self, self,
) -> IntoMakeServiceWithConnectInfo<Self, C> ) -> IntoMakeServiceWithConnectInfo<Self, C>
where where
S: Clone,
C: Connected<Target>, C: Connected<Target>,
{ {
IntoMakeServiceWithConnectInfo::new(self) IntoMakeServiceWithConnectInfo::new(self)
@@ -606,35 +576,43 @@ impl<S> Router<S> {
/// # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); /// # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
/// # }; /// # };
/// ``` /// ```
pub fn or<T, B>(self, other: T) -> Router<Or<S, T>> pub fn or<T>(self, other: T) -> Self
where where
T: Service<Request<B>, Error = Infallible>, T: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible>
+ Clone
+ Send
+ 'static,
T::Future: Send + 'static,
{ {
self.map(|first| Or { self.map(|first| or::Or {
first, first,
second: other, second: other,
}) })
} }
fn map<F, T>(self, f: F) -> Router<T> fn map<F, T, B2>(self, f: F) -> Router<B2>
where where
F: FnOnce(S) -> T, F: FnOnce(Routes<B>) -> T,
T: Service<Request<B2>, Response = Response<BoxBody>, Error = Infallible>
+ Clone
+ Send
+ 'static,
T::Future: Send + 'static,
{ {
Router { Router {
routes: f(self.routes), routes: Routes(CloneBoxService::new(f(self.routes))),
node: self.node, node: self.node,
} }
} }
} }
impl<ReqBody, ResBody, S> Service<Request<ReqBody>> for Router<S> impl<B> Service<Request<B>> for Router<B>
where where
S: Service<Request<ReqBody>, Response = Response<ResBody>, Error = Infallible> + Clone, B: Send + Sync + 'static,
ReqBody: Send + Sync + 'static,
{ {
type Response = Response<ResBody>; type Response = Response<BoxBody>;
type Error = Infallible; type Error = Infallible;
type Future = S::Future; type Future = RoutesFuture;
#[inline] #[inline]
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
@@ -642,7 +620,7 @@ where
} }
#[inline] #[inline]
fn call(&mut self, mut req: Request<ReqBody>) -> Self::Future { fn call(&mut self, mut req: Request<B>) -> Self::Future {
if req.extensions().get::<OriginalUri>().is_none() { if req.extensions().get::<OriginalUri>().is_none() {
let original_uri = OriginalUri(req.uri().clone()); let original_uri = OriginalUri(req.uri().clone());
req.extensions_mut().insert(original_uri); req.extensions_mut().insert(original_uri);
@@ -827,10 +805,8 @@ struct FromEmptyRouter<B> {
request: Request<B>, request: Request<B>,
} }
/// A route that sends requests to one of two [`Service`]s depending on the
/// path.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Route<S, T> { struct Route<S, T> {
id: RouteId, id: RouteId,
svc: S, svc: S,
fallback: T, fallback: T,
@@ -869,7 +845,7 @@ where
/// ///
/// Created with [`Router::nest`]. /// Created with [`Router::nest`].
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Nested<S, T> { struct Nested<S, T> {
id: RouteId, id: RouteId,
svc: S, svc: S,
fallback: T, fallback: T,
@@ -906,43 +882,6 @@ where
} }
} }
/// A boxed route trait object.
///
/// See [`Router::boxed`] for more details.
pub struct BoxRoute<B = crate::body::Body>(
CloneBoxService<Request<B>, Response<BoxBody>, Infallible>,
);
impl<B> Clone for BoxRoute<B> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<B> fmt::Debug for BoxRoute<B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BoxRoute").finish()
}
}
impl<B> Service<Request<B>> for BoxRoute<B> {
type Response = Response<BoxBody>;
type Error = Infallible;
type Future = BoxRouteFuture<B>;
#[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),
}
}
}
fn with_path(uri: &Uri, new_path: &str) -> Uri { fn with_path(uri: &Uri, new_path: &str) -> Uri {
let path_and_query = if let Some(path_and_query) = uri.path_and_query() { let path_and_query = if let Some(path_and_query) = uri.path_and_query() {
let new_path = if new_path.starts_with('/') { let new_path = if new_path.starts_with('/') {
@@ -1005,6 +944,41 @@ where
} }
} }
/// How routes are stored inside a [`Router`].
///
/// You normally shouldn't need to care about this type.
pub struct Routes<B = Body>(CloneBoxService<Request<B>, Response<BoxBody>, Infallible>);
impl<ReqBody> Clone for Routes<ReqBody> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<ReqBody> fmt::Debug for Routes<ReqBody> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Router").finish()
}
}
impl<B> Service<Request<B>> for Routes<B> {
type Response = Response<BoxBody>;
type Error = Infallible;
type Future = future::RoutesFuture;
#[inline]
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.poll_ready(cx)
}
#[inline]
fn call(&mut self, req: Request<B>) -> Self::Future {
future::RoutesFuture {
future: self.0.call(req),
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -1014,7 +988,6 @@ mod tests {
use crate::tests::*; use crate::tests::*;
assert_send::<Router<()>>(); assert_send::<Router<()>>();
assert_sync::<Router<()>>();
assert_send::<Route<(), ()>>(); assert_send::<Route<(), ()>>();
assert_sync::<Route<(), ()>>(); assert_sync::<Route<(), ()>>();
@@ -1022,9 +995,6 @@ mod tests {
assert_send::<EmptyRouter<NotSendSync>>(); assert_send::<EmptyRouter<NotSendSync>>();
assert_sync::<EmptyRouter<NotSendSync>>(); assert_sync::<EmptyRouter<NotSendSync>>();
assert_send::<BoxRoute<()>>();
assert_sync::<BoxRoute<()>>();
assert_send::<Nested<(), ()>>(); assert_send::<Nested<(), ()>>();
assert_sync::<Nested<(), ()>>(); assert_sync::<Nested<(), ()>>();
+2 -2
View File
@@ -20,7 +20,7 @@ use tower_service::Service;
/// ///
/// [`Router::or`]: super::Router::or /// [`Router::or`]: super::Router::or
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Or<A, B> { pub(crate) struct Or<A, B> {
pub(super) first: A, pub(super) first: A,
pub(super) second: B, pub(super) second: B,
} }
@@ -62,7 +62,7 @@ where
pin_project! { pin_project! {
/// Response future for [`Or`]. /// Response future for [`Or`].
pub struct ResponseFuture<A, B, ReqBody> pub(crate) struct ResponseFuture<A, B, ReqBody>
where where
A: Service<Request<ReqBody>>, A: Service<Request<ReqBody>>,
B: Service<Request<ReqBody>>, B: Service<Request<ReqBody>>,
+9 -7
View File
@@ -25,8 +25,8 @@ use std::{
task::{Context, Poll}, task::{Context, Poll},
time::Duration, time::Duration,
}; };
use tower::service_fn;
use tower::timeout::TimeoutLayer; use tower::timeout::TimeoutLayer;
use tower::{service_fn, ServiceBuilder};
use tower_service::Service; use tower_service::Service;
pub(crate) use helpers::*; pub(crate) use helpers::*;
@@ -251,8 +251,7 @@ async fn boxing() {
"hi from POST" "hi from POST"
}), }),
) )
.layer(tower_http::compression::CompressionLayer::new()) .layer(tower_http::compression::CompressionLayer::new());
.boxed();
let client = TestClient::new(app); let client = TestClient::new(app);
@@ -532,10 +531,13 @@ async fn wildcard_sees_whole_url() {
async fn middleware_applies_to_routes_above() { async fn middleware_applies_to_routes_above() {
let app = Router::new() let app = Router::new()
.route("/one", get(std::future::pending::<()>)) .route("/one", get(std::future::pending::<()>))
.layer(TimeoutLayer::new(Duration::new(0, 0))) .layer(
.layer(HandleErrorLayer::new(|_: BoxError| { ServiceBuilder::new()
StatusCode::REQUEST_TIMEOUT .layer(HandleErrorLayer::new(|_: BoxError| {
})) StatusCode::REQUEST_TIMEOUT
}))
.layer(TimeoutLayer::new(Duration::new(0, 0))),
)
.route("/two", get(|| async {})); .route("/two", get(|| async {}));
let client = TestClient::new(app); let client = TestClient::new(app);
+1 -2
View File
@@ -1,7 +1,6 @@
use super::*; use super::*;
use crate::body::box_body; use crate::body::box_body;
use crate::error_handling::HandleErrorExt; use crate::error_handling::HandleErrorExt;
use crate::routing::EmptyRouter;
use std::collections::HashMap; use std::collections::HashMap;
#[tokio::test] #[tokio::test]
@@ -256,5 +255,5 @@ async fn multiple_top_level_nests() {
#[tokio::test] #[tokio::test]
#[should_panic(expected = "Invalid route: nested routes cannot contain wildcards (*)")] #[should_panic(expected = "Invalid route: nested routes cannot contain wildcards (*)")]
async fn nest_cannot_contain_wildcards() { async fn nest_cannot_contain_wildcards() {
Router::<EmptyRouter>::new().nest::<_, Body>("/one/*rest", Router::<EmptyRouter>::new()); Router::<Body>::new().nest("/one/*rest", Router::new());
} }
+7 -4
View File
@@ -135,8 +135,11 @@ async fn layer_and_handle_error() {
let one = Router::new().route("/foo", get(|| async {})); let one = Router::new().route("/foo", get(|| async {}));
let two = Router::new() let two = Router::new()
.route("/timeout", get(futures::future::pending::<()>)) .route("/timeout", get(futures::future::pending::<()>))
.layer(TimeoutLayer::new(Duration::from_millis(10))) .layer(
.layer(HandleErrorLayer::new(|_| StatusCode::REQUEST_TIMEOUT)); ServiceBuilder::new()
.layer(HandleErrorLayer::new(|_| StatusCode::REQUEST_TIMEOUT))
.layer(TimeoutLayer::new(Duration::from_millis(10))),
);
let app = one.or(two); let app = one.or(two);
let client = TestClient::new(app); let client = TestClient::new(app);
@@ -159,8 +162,8 @@ async fn nesting() {
#[tokio::test] #[tokio::test]
async fn boxed() { async fn boxed() {
let one = Router::new().route("/foo", get(|| async {})).boxed(); let one = Router::new().route("/foo", get(|| async {}));
let two = Router::new().route("/bar", get(|| async {})).boxed(); let two = Router::new().route("/bar", get(|| async {}));
let app = one.or(two); let app = one.or(two);
let client = TestClient::new(app); let client = TestClient::new(app);