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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 203 additions and 242 deletions

View File

@ -7,20 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
# 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])
- Wildcard routes like `.route("/api/users/*rest", service)` are now supported.
- The order routes are added in no longer matters.
- Adding a conflicting route will now cause a panic instead of silently making
- **added:** Wildcard routes like `.route("/api/users/*rest", service)` are now supported.
- **fixed:** The order routes are added in no longer matters.
- **fixed:** Adding a conflicting route will now cause a panic instead of silently making
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
will cause a panic when constructing the router. This might be fixed in the future.
- Improve performance of `BoxRoute` ([#339])
- Expand accepted content types for JSON requests ([#378])
- **fixed:** 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`
([#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
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])
@ -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
[#396]: https://github.com/tokio-rs/axum/pull/396
[#402]: https://github.com/tokio-rs/axum/pull/402
[#404]: https://github.com/tokio-rs/axum/pull/404
# 0.2.8 (07. October, 2021)

View File

@ -27,7 +27,6 @@ ws = ["tokio-tungstenite", "sha-1", "base64"]
async-trait = "0.1.43"
bitflags = "1.0"
bytes = "1.0"
dyn-clone = "1.0"
futures-util = { version = "0.3", default-features = false, features = ["alloc"] }
http = "0.2"
http-body = "0.4.3"

View File

@ -13,7 +13,6 @@ use axum::{
handler::{delete, get, Handler},
http::StatusCode,
response::IntoResponse,
routing::BoxRoute,
Router,
};
use std::{
@ -108,7 +107,7 @@ async fn list_keys(Extension(state): Extension<SharedState>) -> String {
.join("\n")
}
fn admin_routes() -> Router<BoxRoute> {
fn admin_routes() -> Router {
async fn delete_all_keys(Extension(state): Extension<SharedState>) {
state.write().unwrap().db.clear();
}
@ -122,7 +121,6 @@ fn admin_routes() -> Router<BoxRoute> {
.route("/key/:key", delete(remove_key))
// Require bearer auth for all admin routes
.layer(RequireAuthorizationLayer::bearer("secret-token"))
.boxed()
}
fn handle_error(error: BoxError) -> impl IntoResponse {

View File

@ -6,7 +6,6 @@
use axum::{
handler::{get, post},
routing::BoxRoute,
Json, Router,
};
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
/// without having to create an HTTP server.
#[allow(dead_code)]
fn app() -> Router<BoxRoute> {
fn app() -> Router {
Router::new()
.route("/", get(|| async { "Hello, World!" }))
.route(
@ -43,7 +42,6 @@ fn app() -> Router<BoxRoute> {
)
// We can still add middleware
.layer(TraceLayer::new_for_http())
.boxed()
}
#[cfg(test)]
@ -59,7 +57,7 @@ mod tests {
async fn hello_world() {
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.
let response = app
.oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())

View File

@ -4,26 +4,31 @@
//! 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]
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();
fn main() {}
let app = Router::new().route("/", get(handler));
// use axum::{handler::get, Router};
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();
}
// #[tokio::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();
async fn handler() -> &'static str {
"Hello, World!"
}
// // let app = Router::new().route("/", get(handler));
// // 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!"
// }

View File

@ -3,19 +3,18 @@ use std::task::{Context, Poll};
use tower::ServiceExt;
use tower_service::Service;
/// A `Clone + Send + Sync` boxed `Service`
/// A `Clone + Send` boxed `Service`
pub(crate) struct CloneBoxService<T, U, E>(
Box<
dyn CloneService<T, Response = U, Error = E, Future = BoxFuture<'static, Result<U, E>>>
+ Send
+ Sync,
+ Send,
>,
);
impl<T, U, E> CloneBoxService<T, U, E> {
pub(crate) fn new<S>(inner: S) -> Self
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,
{
let inner = inner.map_future(|f| Box::pin(f) as _);
@ -48,22 +47,18 @@ trait CloneService<R>: Service<R> {
&self,
) -> Box<
dyn CloneService<R, Response = Self::Response, Error = Self::Error, Future = Self::Future>
+ Send
+ Sync,
+ Send,
>;
}
impl<R, T> CloneService<R> for T
where
T: Service<R> + Send + Sync + Clone + 'static,
T: Service<R> + Send + Clone + 'static,
{
fn clone_box(
&self,
) -> Box<
dyn CloneService<R, Response = T::Response, Error = T::Error, Future = T::Future>
+ Send
+ Sync,
> {
) -> Box<dyn CloneService<R, Response = T::Response, Error = T::Error, Future = T::Future> + Send>
{
Box::new(self.clone())
}
}

View File

@ -360,15 +360,13 @@
//! http::Request,
//! handler::get,
//! Router,
//! routing::BoxRoute
//! };
//! use tower_http::services::ServeFile;
//! use http::Response;
//!
//! fn api_routes() -> Router<BoxRoute> {
//! fn api_routes() -> Router {
//! Router::new()
//! .route("/users", get(|_: Request<Body>| async { /* ... */ }))
//! .boxed()
//! }
//!
//! let app = Router::new()

View File

@ -2,14 +2,12 @@
use crate::{
body::BoxBody,
clone_box_service::CloneBoxService,
routing::{FromEmptyRouter, UriStack},
};
use http::{Request, Response};
use pin_project_lite::pin_project;
use std::{
convert::Infallible,
fmt,
future::Future,
pin::Pin,
task::{Context, Poll},
@ -17,43 +15,22 @@ use std::{
use tower::util::Oneshot;
use tower_service::Service;
pub use super::or::ResponseFuture as OrResponseFuture;
opaque_future! {
/// Response future for [`EmptyRouter`](super::EmptyRouter).
pub type EmptyRouterFuture<E> =
std::future::Ready<Result<Response<BoxBody>, E>>;
}
pin_project! {
/// The response future for [`BoxRoute`](super::BoxRoute).
pub struct BoxRouteFuture<B> {
#[pin]
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()
}
opaque_future! {
/// Response future for [`Routes`](super::Routes).
pub type RoutesFuture =
futures_util::future::BoxFuture<'static, Result<Response<BoxBody>, Infallible>>;
}
pin_project! {
/// The response future for [`Route`](super::Route).
#[derive(Debug)]
pub struct RouteFuture<S, F, B>
pub(crate) struct RouteFuture<S, F, B>
where
S: Service<Request<B>>,
F: Service<Request<B>>
@ -119,7 +96,7 @@ where
pin_project! {
/// The response future for [`Nested`](super::Nested).
#[derive(Debug)]
pub struct NestedFuture<S, F, B>
pub(crate) struct NestedFuture<S, F, B>
where
S: Service<Request<B>>,
F: Service<Request<B>>

View File

@ -1,8 +1,8 @@
//! Routing between [`Service`]s.
use self::future::{BoxRouteFuture, EmptyRouterFuture, NestedFuture, RouteFuture};
use self::future::{EmptyRouterFuture, NestedFuture, RouteFuture, RoutesFuture};
use crate::{
body::{box_body, BoxBody},
body::{box_body, Body, BoxBody},
clone_box_service::CloneBoxService,
extract::{
connect_info::{Connected, IntoMakeServiceWithConnectInfo},
@ -22,8 +22,8 @@ use std::{
marker::PhantomData,
task::{Context, Poll},
};
use tower::{util::ServiceExt, ServiceBuilder};
use tower_http::map_response_body::MapResponseBodyLayer;
use tower::util::ServiceExt;
use tower_http::map_response_body::MapResponseBody;
use tower_layer::Layer;
use tower_service::Service;
@ -32,7 +32,7 @@ pub mod future;
mod method_filter;
mod or;
pub use self::{method_filter::MethodFilter, or::Or};
pub use self::method_filter::MethodFilter;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct RouteId(u64);
@ -46,35 +46,30 @@ impl RouteId {
}
/// The router type for composing handlers and services.
#[derive(Clone)]
pub struct Router<S> {
routes: S,
pub struct Router<B = Body> {
routes: Routes<B>,
node: Node<RouteId>,
}
impl Router<EmptyRouter> {
/// Create a new `Router`.
///
/// Unless you add additional routes this will respond to `404 Not Found` to
/// all requests.
pub fn new() -> Self {
impl<B> Clone for Router<B> {
fn clone(&self) -> Self {
Self {
routes: EmptyRouter::not_found(),
node: Node::new(),
routes: self.routes.clone(),
node: self.node.clone(),
}
}
}
impl Default for Router<EmptyRouter> {
impl<B> Default for Router<B>
where
B: Send + Sync + 'static,
{
fn default() -> Self {
Self::new()
}
}
impl<S> fmt::Debug for Router<S>
where
S: fmt::Debug,
{
impl<B> fmt::Debug for Router<B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Router")
.field("routes", &self.routes)
@ -84,7 +79,21 @@ where
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.
///
/// `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();
/// # };
/// ```
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
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();
@ -175,11 +188,11 @@ impl<S> Router<S> {
}
Router {
routes: Route {
routes: Routes(CloneBoxService::new(Route {
id,
svc,
fallback: self.routes,
},
})),
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
///
/// Nested routes are similar to wildcard routes. The difference is that
@ -305,9 +314,13 @@ impl<S> Router<S> {
/// for more details.
///
/// [`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
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();
@ -326,64 +339,15 @@ impl<S> Router<S> {
}
Router {
routes: Nested {
routes: Routes(CloneBoxService::new(Nested {
id,
svc,
fallback: self.routes,
},
})),
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.
///
/// 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();
/// # };
/// ```
pub fn layer<L>(self, layer: L) -> Router<L::Service>
pub fn layer<L, LayeredReqBody, LayeredResBody>(self, layer: L) -> Router<LayeredReqBody>
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
@ -481,10 +455,7 @@ impl<S> Router<S> {
/// ```
///
/// [`MakeService`]: tower::make::MakeService
pub fn into_make_service(self) -> IntoMakeService<Self>
where
S: Clone,
{
pub fn into_make_service(self) -> IntoMakeService<Self> {
IntoMakeService::new(self)
}
@ -572,7 +543,6 @@ impl<S> Router<S> {
self,
) -> IntoMakeServiceWithConnectInfo<Self, C>
where
S: Clone,
C: Connected<Target>,
{
IntoMakeServiceWithConnectInfo::new(self)
@ -606,35 +576,43 @@ impl<S> Router<S> {
/// # 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
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,
second: other,
})
}
fn map<F, T>(self, f: F) -> Router<T>
fn map<F, T, B2>(self, f: F) -> Router<B2>
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 {
routes: f(self.routes),
routes: Routes(CloneBoxService::new(f(self.routes))),
node: self.node,
}
}
}
impl<ReqBody, ResBody, S> Service<Request<ReqBody>> for Router<S>
impl<B> Service<Request<B>> for Router<B>
where
S: Service<Request<ReqBody>, Response = Response<ResBody>, Error = Infallible> + Clone,
ReqBody: Send + Sync + 'static,
B: Send + Sync + 'static,
{
type Response = Response<ResBody>;
type Response = Response<BoxBody>;
type Error = Infallible;
type Future = S::Future;
type Future = RoutesFuture;
#[inline]
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
@ -642,7 +620,7 @@ where
}
#[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() {
let original_uri = OriginalUri(req.uri().clone());
req.extensions_mut().insert(original_uri);
@ -827,10 +805,8 @@ struct FromEmptyRouter<B> {
request: Request<B>,
}
/// A route that sends requests to one of two [`Service`]s depending on the
/// path.
#[derive(Debug, Clone)]
pub struct Route<S, T> {
struct Route<S, T> {
id: RouteId,
svc: S,
fallback: T,
@ -869,7 +845,7 @@ where
///
/// Created with [`Router::nest`].
#[derive(Debug, Clone)]
pub struct Nested<S, T> {
struct Nested<S, T> {
id: RouteId,
svc: S,
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 {
let path_and_query = if let Some(path_and_query) = uri.path_and_query() {
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)]
mod tests {
use super::*;
@ -1014,7 +988,6 @@ mod tests {
use crate::tests::*;
assert_send::<Router<()>>();
assert_sync::<Router<()>>();
assert_send::<Route<(), ()>>();
assert_sync::<Route<(), ()>>();
@ -1022,9 +995,6 @@ mod tests {
assert_send::<EmptyRouter<NotSendSync>>();
assert_sync::<EmptyRouter<NotSendSync>>();
assert_send::<BoxRoute<()>>();
assert_sync::<BoxRoute<()>>();
assert_send::<Nested<(), ()>>();
assert_sync::<Nested<(), ()>>();

View File

@ -20,7 +20,7 @@ use tower_service::Service;
///
/// [`Router::or`]: super::Router::or
#[derive(Debug, Clone, Copy)]
pub struct Or<A, B> {
pub(crate) struct Or<A, B> {
pub(super) first: A,
pub(super) second: B,
}
@ -62,7 +62,7 @@ where
pin_project! {
/// Response future for [`Or`].
pub struct ResponseFuture<A, B, ReqBody>
pub(crate) struct ResponseFuture<A, B, ReqBody>
where
A: Service<Request<ReqBody>>,
B: Service<Request<ReqBody>>,

View File

@ -25,8 +25,8 @@ use std::{
task::{Context, Poll},
time::Duration,
};
use tower::service_fn;
use tower::timeout::TimeoutLayer;
use tower::{service_fn, ServiceBuilder};
use tower_service::Service;
pub(crate) use helpers::*;
@ -251,8 +251,7 @@ async fn boxing() {
"hi from POST"
}),
)
.layer(tower_http::compression::CompressionLayer::new())
.boxed();
.layer(tower_http::compression::CompressionLayer::new());
let client = TestClient::new(app);
@ -532,10 +531,13 @@ async fn wildcard_sees_whole_url() {
async fn middleware_applies_to_routes_above() {
let app = Router::new()
.route("/one", get(std::future::pending::<()>))
.layer(TimeoutLayer::new(Duration::new(0, 0)))
.layer(HandleErrorLayer::new(|_: BoxError| {
StatusCode::REQUEST_TIMEOUT
}))
.layer(
ServiceBuilder::new()
.layer(HandleErrorLayer::new(|_: BoxError| {
StatusCode::REQUEST_TIMEOUT
}))
.layer(TimeoutLayer::new(Duration::new(0, 0))),
)
.route("/two", get(|| async {}));
let client = TestClient::new(app);

View File

@ -1,7 +1,6 @@
use super::*;
use crate::body::box_body;
use crate::error_handling::HandleErrorExt;
use crate::routing::EmptyRouter;
use std::collections::HashMap;
#[tokio::test]
@ -256,5 +255,5 @@ async fn multiple_top_level_nests() {
#[tokio::test]
#[should_panic(expected = "Invalid route: nested routes 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());
}

View File

@ -135,8 +135,11 @@ async fn layer_and_handle_error() {
let one = Router::new().route("/foo", get(|| async {}));
let two = Router::new()
.route("/timeout", get(futures::future::pending::<()>))
.layer(TimeoutLayer::new(Duration::from_millis(10)))
.layer(HandleErrorLayer::new(|_| StatusCode::REQUEST_TIMEOUT));
.layer(
ServiceBuilder::new()
.layer(HandleErrorLayer::new(|_| StatusCode::REQUEST_TIMEOUT))
.layer(TimeoutLayer::new(Duration::from_millis(10))),
);
let app = one.or(two);
let client = TestClient::new(app);
@ -159,8 +162,8 @@ async fn nesting() {
#[tokio::test]
async fn boxed() {
let one = Router::new().route("/foo", get(|| async {})).boxed();
let two = Router::new().route("/bar", get(|| async {})).boxed();
let one = Router::new().route("/foo", get(|| async {}));
let two = Router::new().route("/bar", get(|| async {}));
let app = one.or(two);
let client = TestClient::new(app);