mirror of
https://github.com/tokio-rs/axum.git
synced 2025-01-11 12:31:25 +01:00
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:
parent
1a764bb8d7
commit
0ee7379d4f
13 changed files with 203 additions and 242 deletions
33
CHANGELOG.md
33
CHANGELOG.md
|
@ -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)
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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!"
|
||||
// }
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>>
|
||||
|
|
|
@ -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<(), ()>>();
|
||||
|
||||
|
|
|
@ -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>>,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue