mirror of
https://github.com/tokio-rs/axum.git
synced 2026-09-08 00:00:24 +02:00
Change Router::with_state and impl Service for Router<()> (#1552)
* Implement `Service` for `Router<(), B>` * wip * wip * fix some tests * fix examples * fix doc tests * clean up docs * changelog * fix * also call `with_state` when converting `MethodRouter` into a `MakeService` * suggestions from review
This commit is contained in:
@@ -68,7 +68,7 @@ use std::{convert::Infallible, fmt, marker::PhantomData};
|
|||||||
/// .route("/set", post(set_secret))
|
/// .route("/set", post(set_secret))
|
||||||
/// .route("/get", get(get_secret))
|
/// .route("/get", get(get_secret))
|
||||||
/// .with_state(state);
|
/// .with_state(state);
|
||||||
/// # let _: axum::routing::RouterService = app;
|
/// # let _: axum::Router = app;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// If you have been using `Arc<AppState>` you cannot implement `FromRef<Arc<AppState>> for Key`.
|
/// If you have been using `Arc<AppState>` you cannot implement `FromRef<Arc<AppState>> for Key`.
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ use std::{convert::Infallible, fmt, marker::PhantomData};
|
|||||||
/// .route("/sessions", post(create_session))
|
/// .route("/sessions", post(create_session))
|
||||||
/// .route("/me", get(me))
|
/// .route("/me", get(me))
|
||||||
/// .with_state(state);
|
/// .with_state(state);
|
||||||
/// # let _: axum::routing::RouterService = app;
|
/// # let _: axum::Router = app;
|
||||||
/// ```
|
/// ```
|
||||||
/// If you have been using `Arc<AppState>` you cannot implement `FromRef<Arc<AppState>> for Key`.
|
/// If you have been using `Arc<AppState>` you cannot implement `FromRef<Arc<AppState>> for Key`.
|
||||||
/// You can use a new type instead:
|
/// You can use a new type instead:
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ impl<B> From<Resource<B>> for Router<B> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use super::*;
|
use super::*;
|
||||||
use axum::{extract::Path, http::Method, routing::RouterService, Router};
|
use axum::{extract::Path, http::Method, Router};
|
||||||
use http::Request;
|
use http::Request;
|
||||||
use tower::{Service, ServiceExt};
|
use tower::{Service, ServiceExt};
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ mod tests {
|
|||||||
.update(|Path(id): Path<u64>| async move { format!("users#update id={}", id) })
|
.update(|Path(id): Path<u64>| async move { format!("users#update id={}", id) })
|
||||||
.destroy(|Path(id): Path<u64>| async move { format!("users#destroy id={}", id) });
|
.destroy(|Path(id): Path<u64>| async move { format!("users#destroy id={}", id) });
|
||||||
|
|
||||||
let mut app = Router::new().merge(users).into_service();
|
let mut app = Router::new().merge(users);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
call_route(&mut app, Method::GET, "/users").await,
|
call_route(&mut app, Method::GET, "/users").await,
|
||||||
@@ -205,7 +205,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn call_route(app: &mut RouterService, method: Method, uri: &str) -> String {
|
async fn call_route(app: &mut Router, method: Method, uri: &str) -> String {
|
||||||
let res = app
|
let res = app
|
||||||
.ready()
|
.ready()
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ mod tests {
|
|||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn works_with_router_with_state() {
|
fn works_with_router_with_state() {
|
||||||
let _: axum::RouterService = Router::new()
|
let _: Router = Router::new()
|
||||||
.merge(SpaRouter::new("/assets", "test_files"))
|
.merge(SpaRouter::new("/assets", "test_files"))
|
||||||
.route("/", get(|_: axum::extract::State<String>| async {}))
|
.route("/", get(|_: axum::extract::State<String>| async {}))
|
||||||
.with_state(String::new());
|
.with_state(String::new());
|
||||||
|
|||||||
@@ -606,7 +606,7 @@ pub fn derive_typed_path(input: TokenStream) -> TokenStream {
|
|||||||
/// let app = Router::new()
|
/// let app = Router::new()
|
||||||
/// .route("/", get(handler).post(other_handler))
|
/// .route("/", get(handler).post(other_handler))
|
||||||
/// .with_state(state);
|
/// .with_state(state);
|
||||||
/// # let _: axum::routing::RouterService = app;
|
/// # let _: axum::Router = app;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [`FromRef`]: https://docs.rs/axum/latest/axum/extract/trait.FromRef.html
|
/// [`FromRef`]: https://docs.rs/axum/latest/axum/extract/trait.FromRef.html
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ fn main() {
|
|||||||
auth_token: Default::default(),
|
auth_token: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let _: axum::routing::RouterService = Router::new()
|
let _: axum::Router = Router::new()
|
||||||
.route("/", get(handler))
|
.route("/", get(handler))
|
||||||
.with_state(state);
|
.with_state(state);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use axum::{
|
|||||||
use axum_macros::FromRequest;
|
use axum_macros::FromRequest;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let _: axum::routing::RouterService = Router::new()
|
let _: axum::Router = Router::new()
|
||||||
.route("/a", get(|_: AppState| async {}))
|
.route("/a", get(|_: AppState| async {}))
|
||||||
.route("/b", get(|_: InnerState| async {}))
|
.route("/b", get(|_: InnerState| async {}))
|
||||||
.with_state(AppState::default());
|
.with_state(AppState::default());
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use axum::{
|
|||||||
use axum_macros::FromRequestParts;
|
use axum_macros::FromRequestParts;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let _: axum::routing::RouterService = Router::new()
|
let _: axum::Router = Router::new()
|
||||||
.route("/a", get(|_: AppState| async {}))
|
.route("/a", get(|_: AppState| async {}))
|
||||||
.route("/b", get(|_: InnerState| async {}))
|
.route("/b", get(|_: InnerState| async {}))
|
||||||
.route("/c", get(|_: AppState, _: InnerState| async {}))
|
.route("/c", get(|_: AppState, _: InnerState| async {}))
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use axum::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let _: axum::routing::RouterService = Router::new()
|
let _: axum::Router = Router::new()
|
||||||
.route("/b", get(|_: Extractor| async {}))
|
.route("/b", get(|_: Extractor| async {}))
|
||||||
.with_state(AppState::default());
|
.with_state(AppState::default());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use axum::{
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let _: axum::routing::RouterService = Router::new()
|
let _: axum::Router = Router::new()
|
||||||
.route("/b", get(|_: Extractor| async {}))
|
.route("/b", get(|_: Extractor| async {}))
|
||||||
.with_state(AppState::default());
|
.with_state(AppState::default());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use axum::{
|
|||||||
use axum_macros::FromRequest;
|
use axum_macros::FromRequest;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let _: axum::routing::RouterService = Router::new()
|
let _: axum::Router = Router::new()
|
||||||
.route("/", get(|_: Extractor| async {}))
|
.route("/", get(|_: Extractor| async {}))
|
||||||
.with_state(AppState::default());
|
.with_state(AppState::default());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use axum::{
|
|||||||
use axum_macros::FromRequest;
|
use axum_macros::FromRequest;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let _: axum::routing::RouterService = Router::new()
|
let _: axum::Router = Router::new()
|
||||||
.route("/", get(|_: Extractor| async {}))
|
.route("/", get(|_: Extractor| async {}))
|
||||||
.with_state(AppState::default());
|
.with_state(AppState::default());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use axum::{
|
|||||||
use axum_macros::FromRequest;
|
use axum_macros::FromRequest;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let _: axum::routing::RouterService = Router::new()
|
let _: axum::Router = Router::new()
|
||||||
.route("/b", get(|_: (), _: AppState| async {}))
|
.route("/b", get(|_: (), _: AppState| async {}))
|
||||||
.route("/c", get(|_: (), _: InnerState| async {}))
|
.route("/c", get(|_: (), _: InnerState| async {}))
|
||||||
.with_state(AppState::default());
|
.with_state(AppState::default());
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use axum::{
|
|||||||
use axum_macros::FromRequest;
|
use axum_macros::FromRequest;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let _: axum::routing::RouterService = Router::new()
|
let _: axum::Router = Router::new()
|
||||||
.route("/b", get(|_: AppState| async {}))
|
.route("/b", get(|_: AppState| async {}))
|
||||||
.with_state(AppState::default());
|
.with_state(AppState::default());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use axum::{
|
|||||||
use axum_macros::FromRequestParts;
|
use axum_macros::FromRequestParts;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let _: axum::routing::RouterService = Router::new()
|
let _: axum::Router = Router::new()
|
||||||
.route("/a", get(|_: AppState, _: InnerState, _: String| async {}))
|
.route("/a", get(|_: AppState, _: InnerState, _: String| async {}))
|
||||||
.route("/b", get(|_: AppState, _: String| async {}))
|
.route("/b", get(|_: AppState, _: String| async {}))
|
||||||
.route("/c", get(|_: InnerState, _: String| async {}))
|
.route("/c", get(|_: InnerState, _: String| async {}))
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use axum::{
|
|||||||
use axum_macros::FromRequest;
|
use axum_macros::FromRequest;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let _: axum::routing::RouterService = Router::new()
|
let _: axum::Router = Router::new()
|
||||||
.route("/a", get(|_: Extractor| async {}))
|
.route("/a", get(|_: Extractor| async {}))
|
||||||
.with_state(AppState::default());
|
.with_state(AppState::default());
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -7,9 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
# Unreleased
|
# Unreleased
|
||||||
|
|
||||||
- **added:** Add `RouterService::{layer, route_layer}` ([#1550])
|
- **breaking:** `RouterService` has been removed since `Router` now implements
|
||||||
|
`Service` when the state is `()`. Use `Router::with_state` to provide the
|
||||||
|
state and get a `Router<()>` ([#1552])
|
||||||
|
|
||||||
[#1550]: https://github.com/tokio-rs/axum/pull/1550
|
[#1552]: https://github.com/tokio-rs/axum/pull/1552
|
||||||
|
|
||||||
# 0.6.0-rc.5 (18. November, 2022)
|
# 0.6.0-rc.5 (18. November, 2022)
|
||||||
|
|
||||||
|
|||||||
+16
-27
@@ -1,7 +1,7 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
extract::State,
|
extract::State,
|
||||||
routing::{get, post},
|
routing::{get, post},
|
||||||
Extension, Json, Router, RouterService, Server,
|
Extension, Json, Router, Server,
|
||||||
};
|
};
|
||||||
use hyper::server::conn::AddrIncoming;
|
use hyper::server::conn::AddrIncoming;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -17,13 +17,9 @@ fn main() {
|
|||||||
ensure_rewrk_is_installed();
|
ensure_rewrk_is_installed();
|
||||||
}
|
}
|
||||||
|
|
||||||
benchmark("minimal").run(|| Router::new().into_service());
|
benchmark("minimal").run(Router::new);
|
||||||
|
|
||||||
benchmark("basic").run(|| {
|
benchmark("basic").run(|| Router::new().route("/", get(|| async { "Hello, World!" })));
|
||||||
Router::new()
|
|
||||||
.route("/", get(|| async { "Hello, World!" }))
|
|
||||||
.into_service()
|
|
||||||
});
|
|
||||||
|
|
||||||
benchmark("routing").path("/foo/bar/baz").run(|| {
|
benchmark("routing").path("/foo/bar/baz").run(|| {
|
||||||
let mut app = Router::new();
|
let mut app = Router::new();
|
||||||
@@ -34,32 +30,26 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
app.route("/foo/bar/baz", get(|| async {})).into_service()
|
app.route("/foo/bar/baz", get(|| async {}))
|
||||||
});
|
});
|
||||||
|
|
||||||
benchmark("receive-json")
|
benchmark("receive-json")
|
||||||
.method("post")
|
.method("post")
|
||||||
.headers(&[("content-type", "application/json")])
|
.headers(&[("content-type", "application/json")])
|
||||||
.body(r#"{"n": 123, "s": "hi there", "b": false}"#)
|
.body(r#"{"n": 123, "s": "hi there", "b": false}"#)
|
||||||
.run(|| {
|
.run(|| Router::new().route("/", post(|_: Json<Payload>| async {})));
|
||||||
Router::new()
|
|
||||||
.route("/", post(|_: Json<Payload>| async {}))
|
|
||||||
.into_service()
|
|
||||||
});
|
|
||||||
|
|
||||||
benchmark("send-json").run(|| {
|
benchmark("send-json").run(|| {
|
||||||
Router::new()
|
Router::new().route(
|
||||||
.route(
|
"/",
|
||||||
"/",
|
get(|| async {
|
||||||
get(|| async {
|
Json(Payload {
|
||||||
Json(Payload {
|
n: 123,
|
||||||
n: 123,
|
s: "hi there".to_owned(),
|
||||||
s: "hi there".to_owned(),
|
b: false,
|
||||||
b: false,
|
})
|
||||||
})
|
}),
|
||||||
}),
|
)
|
||||||
)
|
|
||||||
.into_service()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let state = AppState {
|
let state = AppState {
|
||||||
@@ -75,7 +65,6 @@ fn main() {
|
|||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(|_: Extension<AppState>| async {}))
|
.route("/", get(|_: Extension<AppState>| async {}))
|
||||||
.layer(Extension(state.clone()))
|
.layer(Extension(state.clone()))
|
||||||
.into_service()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
benchmark("state").run(|| {
|
benchmark("state").run(|| {
|
||||||
@@ -133,7 +122,7 @@ impl BenchmarkBuilder {
|
|||||||
|
|
||||||
fn run<F>(self, f: F)
|
fn run<F>(self, f: F)
|
||||||
where
|
where
|
||||||
F: FnOnce() -> RouterService,
|
F: FnOnce() -> Router<()>,
|
||||||
{
|
{
|
||||||
// support only running some benchmarks with
|
// support only running some benchmarks with
|
||||||
// ```
|
// ```
|
||||||
|
|||||||
+45
-6
@@ -1,6 +1,14 @@
|
|||||||
use std::{convert::Infallible, fmt};
|
use std::{convert::Infallible, fmt};
|
||||||
|
|
||||||
use crate::{body::HttpBody, handler::Handler, routing::Route, Router};
|
use http::Request;
|
||||||
|
use tower::Service;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
body::HttpBody,
|
||||||
|
handler::Handler,
|
||||||
|
routing::{future::RouteFuture, Route},
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
|
||||||
pub(crate) struct BoxedIntoRoute<S, B, E>(Box<dyn ErasedIntoRoute<S, B, E>>);
|
pub(crate) struct BoxedIntoRoute<S, B, E>(Box<dyn ErasedIntoRoute<S, B, E>>);
|
||||||
|
|
||||||
@@ -13,6 +21,7 @@ where
|
|||||||
where
|
where
|
||||||
H: Handler<T, S, B>,
|
H: Handler<T, S, B>,
|
||||||
T: 'static,
|
T: 'static,
|
||||||
|
B: HttpBody,
|
||||||
{
|
{
|
||||||
Self(Box::new(MakeErasedHandler {
|
Self(Box::new(MakeErasedHandler {
|
||||||
handler,
|
handler,
|
||||||
@@ -30,6 +39,14 @@ where
|
|||||||
into_route: |router, state| Route::new(router.with_state(state)),
|
into_route: |router, state| Route::new(router.with_state(state)),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn call_with_state(
|
||||||
|
self,
|
||||||
|
request: Request<B>,
|
||||||
|
state: S,
|
||||||
|
) -> RouteFuture<B, Infallible> {
|
||||||
|
self.0.call_with_state(request, state)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, B, E> BoxedIntoRoute<S, B, E> {
|
impl<S, B, E> BoxedIntoRoute<S, B, E> {
|
||||||
@@ -39,7 +56,7 @@ impl<S, B, E> BoxedIntoRoute<S, B, E> {
|
|||||||
B: 'static,
|
B: 'static,
|
||||||
E: 'static,
|
E: 'static,
|
||||||
F: FnOnce(Route<B, E>) -> Route<B2, E2> + Clone + Send + 'static,
|
F: FnOnce(Route<B, E>) -> Route<B2, E2> + Clone + Send + 'static,
|
||||||
B2: 'static,
|
B2: HttpBody + 'static,
|
||||||
E2: 'static,
|
E2: 'static,
|
||||||
{
|
{
|
||||||
BoxedIntoRoute(Box::new(Map {
|
BoxedIntoRoute(Box::new(Map {
|
||||||
@@ -69,6 +86,8 @@ pub(crate) trait ErasedIntoRoute<S, B, E>: Send {
|
|||||||
fn clone_box(&self) -> Box<dyn ErasedIntoRoute<S, B, E>>;
|
fn clone_box(&self) -> Box<dyn ErasedIntoRoute<S, B, E>>;
|
||||||
|
|
||||||
fn into_route(self: Box<Self>, state: S) -> Route<B, E>;
|
fn into_route(self: Box<Self>, state: S) -> Route<B, E>;
|
||||||
|
|
||||||
|
fn call_with_state(self: Box<Self>, request: Request<B>, state: S) -> RouteFuture<B, E>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct MakeErasedHandler<H, S, B> {
|
pub(crate) struct MakeErasedHandler<H, S, B> {
|
||||||
@@ -80,7 +99,7 @@ impl<H, S, B> ErasedIntoRoute<S, B, Infallible> for MakeErasedHandler<H, S, B>
|
|||||||
where
|
where
|
||||||
H: Clone + Send + 'static,
|
H: Clone + Send + 'static,
|
||||||
S: 'static,
|
S: 'static,
|
||||||
B: 'static,
|
B: HttpBody + 'static,
|
||||||
{
|
{
|
||||||
fn clone_box(&self) -> Box<dyn ErasedIntoRoute<S, B, Infallible>> {
|
fn clone_box(&self) -> Box<dyn ErasedIntoRoute<S, B, Infallible>> {
|
||||||
Box::new(self.clone())
|
Box::new(self.clone())
|
||||||
@@ -89,6 +108,14 @@ where
|
|||||||
fn into_route(self: Box<Self>, state: S) -> Route<B> {
|
fn into_route(self: Box<Self>, state: S) -> Route<B> {
|
||||||
(self.into_route)(self.handler, state)
|
(self.into_route)(self.handler, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn call_with_state(
|
||||||
|
self: Box<Self>,
|
||||||
|
request: Request<B>,
|
||||||
|
state: S,
|
||||||
|
) -> RouteFuture<B, Infallible> {
|
||||||
|
self.into_route(state).call(request)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<H, S, B> Clone for MakeErasedHandler<H, S, B>
|
impl<H, S, B> Clone for MakeErasedHandler<H, S, B>
|
||||||
@@ -110,8 +137,8 @@ pub(crate) struct MakeErasedRouter<S, B> {
|
|||||||
|
|
||||||
impl<S, B> ErasedIntoRoute<S, B, Infallible> for MakeErasedRouter<S, B>
|
impl<S, B> ErasedIntoRoute<S, B, Infallible> for MakeErasedRouter<S, B>
|
||||||
where
|
where
|
||||||
S: Clone + Send + 'static,
|
S: Clone + Send + Sync + 'static,
|
||||||
B: 'static,
|
B: HttpBody + Send + 'static,
|
||||||
{
|
{
|
||||||
fn clone_box(&self) -> Box<dyn ErasedIntoRoute<S, B, Infallible>> {
|
fn clone_box(&self) -> Box<dyn ErasedIntoRoute<S, B, Infallible>> {
|
||||||
Box::new(self.clone())
|
Box::new(self.clone())
|
||||||
@@ -120,6 +147,14 @@ where
|
|||||||
fn into_route(self: Box<Self>, state: S) -> Route<B> {
|
fn into_route(self: Box<Self>, state: S) -> Route<B> {
|
||||||
(self.into_route)(self.router, state)
|
(self.into_route)(self.router, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn call_with_state(
|
||||||
|
mut self: Box<Self>,
|
||||||
|
request: Request<B>,
|
||||||
|
state: S,
|
||||||
|
) -> RouteFuture<B, Infallible> {
|
||||||
|
self.router.call_with_state(request, state)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, B> Clone for MakeErasedRouter<S, B>
|
impl<S, B> Clone for MakeErasedRouter<S, B>
|
||||||
@@ -144,7 +179,7 @@ where
|
|||||||
S: 'static,
|
S: 'static,
|
||||||
B: 'static,
|
B: 'static,
|
||||||
E: 'static,
|
E: 'static,
|
||||||
B2: 'static,
|
B2: HttpBody + 'static,
|
||||||
E2: 'static,
|
E2: 'static,
|
||||||
{
|
{
|
||||||
fn clone_box(&self) -> Box<dyn ErasedIntoRoute<S, B2, E2>> {
|
fn clone_box(&self) -> Box<dyn ErasedIntoRoute<S, B2, E2>> {
|
||||||
@@ -157,6 +192,10 @@ where
|
|||||||
fn into_route(self: Box<Self>, state: S) -> Route<B2, E2> {
|
fn into_route(self: Box<Self>, state: S) -> Route<B2, E2> {
|
||||||
(self.layer)(self.inner.into_route(state))
|
(self.layer)(self.inner.into_route(state))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn call_with_state(self: Box<Self>, request: Request<B2>, state: S) -> RouteFuture<B2, E2> {
|
||||||
|
(self.layer)(self.inner.into_route(state)).call(request)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait LayerFn<B, E, B2, E2>: FnOnce(Route<B, E>) -> Route<B2, E2> + Send {
|
pub(crate) trait LayerFn<B, E, B2, E2>: FnOnce(Route<B, E>) -> Route<B2, E2> + Send {
|
||||||
|
|||||||
@@ -466,7 +466,7 @@ let app = Router::new()
|
|||||||
.route("/", get(handler))
|
.route("/", get(handler))
|
||||||
.layer(MyLayer { state: state.clone() })
|
.layer(MyLayer { state: state.clone() })
|
||||||
.with_state(state);
|
.with_state(state);
|
||||||
# let _: axum::routing::RouterService = app;
|
# let _: axum::Router = app;
|
||||||
```
|
```
|
||||||
|
|
||||||
# Passing state from middleware to handlers
|
# Passing state from middleware to handlers
|
||||||
@@ -556,7 +556,7 @@ async fn rewrite_request_uri<B>(req: Request<B>, next: Next<B>) -> Response {
|
|||||||
// this can be any `tower::Layer`
|
// this can be any `tower::Layer`
|
||||||
let middleware = axum::middleware::from_fn(rewrite_request_uri);
|
let middleware = axum::middleware::from_fn(rewrite_request_uri);
|
||||||
|
|
||||||
let app = Router::new().into_service();
|
let app = Router::new();
|
||||||
|
|
||||||
// apply the layer around the whole `Router`
|
// apply the layer around the whole `Router`
|
||||||
// this way the middleware will run before `Router` receives the request
|
// this way the middleware will run before `Router` receives the request
|
||||||
|
|||||||
@@ -2,10 +2,6 @@ Convert this router into a [`MakeService`], that will store `C`'s
|
|||||||
associated `ConnectInfo` in a request extension such that [`ConnectInfo`]
|
associated `ConnectInfo` in a request extension such that [`ConnectInfo`]
|
||||||
can extract it.
|
can extract it.
|
||||||
|
|
||||||
This is a convenience method for routers that don't have any state (i.e. the
|
|
||||||
state type is `()`). Use [`RouterService::into_make_service_with_connect_info`]
|
|
||||||
otherwise.
|
|
||||||
|
|
||||||
This enables extracting things like the client's remote address.
|
This enables extracting things like the client's remote address.
|
||||||
|
|
||||||
Extracting [`std::net::SocketAddr`] is supported out of the box:
|
Extracting [`std::net::SocketAddr`] is supported out of the box:
|
||||||
|
|||||||
@@ -39,13 +39,39 @@ let app = Router::new()
|
|||||||
|
|
||||||
# Merging routers with state
|
# Merging routers with state
|
||||||
|
|
||||||
When combining [`Router`]s with this function, each [`Router`] must have the
|
When combining [`Router`]s with this method, each [`Router`] must have the
|
||||||
same type of state. See ["Combining stateful routers"][combining-stateful-routers]
|
same type of state. If your routers have different types you can use
|
||||||
for details.
|
[`Router::with_state`] to provide the state and make the types match:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use axum::{
|
||||||
|
Router,
|
||||||
|
routing::get,
|
||||||
|
extract::State,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct InnerState {}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct OuterState {}
|
||||||
|
|
||||||
|
async fn inner_handler(state: State<InnerState>) {}
|
||||||
|
|
||||||
|
let inner_router = Router::new()
|
||||||
|
.route("/bar", get(inner_handler))
|
||||||
|
.with_state(InnerState {});
|
||||||
|
|
||||||
|
async fn outer_handler(state: State<OuterState>) {}
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/", get(outer_handler))
|
||||||
|
.merge(inner_router)
|
||||||
|
.with_state(OuterState {});
|
||||||
|
# let _: axum::Router = app;
|
||||||
|
```
|
||||||
|
|
||||||
# Panics
|
# Panics
|
||||||
|
|
||||||
- If two routers that each have a [fallback](Router::fallback) are merged. This
|
- If two routers that each have a [fallback](Router::fallback) are merged. This
|
||||||
is because `Router` only allows a single fallback.
|
is because `Router` only allows a single fallback.
|
||||||
|
|
||||||
[combining-stateful-routers]: crate::extract::State#combining-stateful-routers
|
|
||||||
|
|||||||
@@ -149,12 +149,40 @@ Here requests like `GET /api/not-found` will go to `api_fallback`.
|
|||||||
|
|
||||||
# Nesting routers with state
|
# Nesting routers with state
|
||||||
|
|
||||||
When combining [`Router`]s with this function, each [`Router`] must have the
|
When combining [`Router`]s with this method, each [`Router`] must have the
|
||||||
same type of state. See ["Combining stateful routers"][combining-stateful-routers]
|
same type of state. If your routers have different types you can use
|
||||||
for details.
|
[`Router::with_state`] to provide the state and make the types match:
|
||||||
|
|
||||||
If you want to compose axum services with different types of state, use
|
```rust
|
||||||
[`Router::nest_service`].
|
use axum::{
|
||||||
|
Router,
|
||||||
|
routing::get,
|
||||||
|
extract::State,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct InnerState {}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct OuterState {}
|
||||||
|
|
||||||
|
async fn inner_handler(state: State<InnerState>) {}
|
||||||
|
|
||||||
|
let inner_router = Router::new()
|
||||||
|
.route("/bar", get(inner_handler))
|
||||||
|
.with_state(InnerState {});
|
||||||
|
|
||||||
|
async fn outer_handler(state: State<OuterState>) {}
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/", get(outer_handler))
|
||||||
|
.nest("/foo", inner_router)
|
||||||
|
.with_state(OuterState {});
|
||||||
|
# let _: axum::Router = app;
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the inner router will still inherit the fallback from the outer
|
||||||
|
router.
|
||||||
|
|
||||||
# Panics
|
# Panics
|
||||||
|
|
||||||
@@ -165,4 +193,3 @@ for more details.
|
|||||||
|
|
||||||
[`OriginalUri`]: crate::extract::OriginalUri
|
[`OriginalUri`]: crate::extract::OriginalUri
|
||||||
[fallbacks]: Router::fallback
|
[fallbacks]: Router::fallback
|
||||||
[combining-stateful-routers]: crate::extract::State#combining-stateful-routers
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ use axum::{routing::get, Router};
|
|||||||
|
|
||||||
let app = Router::new().route_service(
|
let app = Router::new().route_service(
|
||||||
"/",
|
"/",
|
||||||
Router::new().route("/foo", get(|| async {})).into_service(),
|
Router::new().route("/foo", get(|| async {})),
|
||||||
);
|
);
|
||||||
# async {
|
# async {
|
||||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ use std::{
|
|||||||
/// ) {
|
/// ) {
|
||||||
/// // use `state`...
|
/// // use `state`...
|
||||||
/// }
|
/// }
|
||||||
/// # let _: axum::routing::RouterService = app;
|
/// # let _: axum::Router = app;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Combining stateful routers
|
/// ## Combining stateful routers
|
||||||
@@ -71,19 +71,19 @@ use std::{
|
|||||||
/// async fn posts_handler(State(state): State<AppState>) {
|
/// async fn posts_handler(State(state): State<AppState>) {
|
||||||
/// // use `state`...
|
/// // use `state`...
|
||||||
/// }
|
/// }
|
||||||
/// # let _: axum::routing::RouterService = app;
|
/// # let _: axum::Router = app;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// However, if you are composing [`Router`]s that are defined in separate scopes,
|
/// However, if you are composing [`Router`]s that are defined in separate scopes,
|
||||||
/// you may need to annotate the [`State`] type explicitly:
|
/// you may need to annotate the [`State`] type explicitly:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use axum::{Router, RouterService, routing::get, extract::State};
|
/// use axum::{Router, routing::get, extract::State};
|
||||||
///
|
///
|
||||||
/// #[derive(Clone)]
|
/// #[derive(Clone)]
|
||||||
/// struct AppState {}
|
/// struct AppState {}
|
||||||
///
|
///
|
||||||
/// fn make_app() -> RouterService {
|
/// fn make_app() -> Router {
|
||||||
/// let state = AppState {};
|
/// let state = AppState {};
|
||||||
///
|
///
|
||||||
/// Router::new()
|
/// Router::new()
|
||||||
@@ -101,19 +101,15 @@ use std::{
|
|||||||
/// async fn posts_handler(State(state): State<AppState>) {
|
/// async fn posts_handler(State(state): State<AppState>) {
|
||||||
/// // use `state`...
|
/// // use `state`...
|
||||||
/// }
|
/// }
|
||||||
/// # let _: axum::routing::RouterService = make_app();
|
/// # let _: axum::Router = make_app();
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// In short, a [`Router`]'s generic state type defaults to `()`
|
/// In short, a [`Router`]'s generic state type defaults to `()`
|
||||||
/// (no state) unless [`Router::with_state`] is called or the value
|
/// (no state) unless [`Router::with_state`] is called or the value
|
||||||
/// of the generic type is given explicitly.
|
/// of the generic type is given explicitly.
|
||||||
///
|
///
|
||||||
/// It's also possible to combine multiple axum services with different state
|
|
||||||
/// types. See [`Router::nest_service`] for details.
|
|
||||||
///
|
|
||||||
/// [`Router`]: crate::Router
|
/// [`Router`]: crate::Router
|
||||||
/// [`Router::merge`]: crate::Router::merge
|
/// [`Router::merge`]: crate::Router::merge
|
||||||
/// [`Router::nest_service`]: crate::Router::nest_service
|
|
||||||
/// [`Router::nest`]: crate::Router::nest
|
/// [`Router::nest`]: crate::Router::nest
|
||||||
/// [`Router::with_state`]: crate::Router::with_state
|
/// [`Router::with_state`]: crate::Router::with_state
|
||||||
///
|
///
|
||||||
@@ -209,7 +205,7 @@ use std::{
|
|||||||
/// State(state): State<AppState>,
|
/// State(state): State<AppState>,
|
||||||
/// ) {
|
/// ) {
|
||||||
/// }
|
/// }
|
||||||
/// # let _: axum::routing::RouterService = app;
|
/// # let _: axum::Router = app;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// For convenience `FromRef` can also be derived using `#[derive(FromRef)]`.
|
/// For convenience `FromRef` can also be derived using `#[derive(FromRef)]`.
|
||||||
|
|||||||
@@ -358,7 +358,7 @@ mod tests {
|
|||||||
format!("you said: {}", body)
|
format!("you said: {}", body)
|
||||||
}
|
}
|
||||||
|
|
||||||
let client = TestClient::from_service(handle.into_service());
|
let client = TestClient::new(handle.into_service());
|
||||||
|
|
||||||
let res = client.post("/").body("hi there!").send().await;
|
let res = client.post("/").body("hi there!").send().await;
|
||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
@@ -381,7 +381,7 @@ mod tests {
|
|||||||
.layer(MapRequestBodyLayer::new(body::boxed))
|
.layer(MapRequestBodyLayer::new(body::boxed))
|
||||||
.with_state("foo");
|
.with_state("foo");
|
||||||
|
|
||||||
let client = TestClient::from_service(svc);
|
let client = TestClient::new(svc);
|
||||||
let res = client.get("/").send().await;
|
let res = client.get("/").send().await;
|
||||||
assert_eq!(res.text().await, "foo");
|
assert_eq!(res.text().await, "foo");
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -476,7 +476,7 @@ pub use self::extension::Extension;
|
|||||||
#[cfg(feature = "json")]
|
#[cfg(feature = "json")]
|
||||||
pub use self::json::Json;
|
pub use self::json::Json;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use self::routing::{Router, RouterService};
|
pub use self::routing::Router;
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
#[cfg(feature = "headers")]
|
#[cfg(feature = "headers")]
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ pub fn from_fn<F, T>(f: F) -> FromFnLayer<F, (), T> {
|
|||||||
/// .route("/", get(|| async { /* ... */ }))
|
/// .route("/", get(|| async { /* ... */ }))
|
||||||
/// .route_layer(middleware::from_fn_with_state(state.clone(), my_middleware))
|
/// .route_layer(middleware::from_fn_with_state(state.clone(), my_middleware))
|
||||||
/// .with_state(state);
|
/// .with_state(state);
|
||||||
/// # let _: axum::routing::RouterService = app;
|
/// # let _: axum::Router = app;
|
||||||
/// ```
|
/// ```
|
||||||
pub fn from_fn_with_state<F, S, T>(state: S, f: F) -> FromFnLayer<F, S, T> {
|
pub fn from_fn_with_state<F, S, T>(state: S, f: F) -> FromFnLayer<F, S, T> {
|
||||||
FromFnLayer {
|
FromFnLayer {
|
||||||
@@ -381,7 +381,6 @@ mod tests {
|
|||||||
.layer(from_fn(insert_header));
|
.layer(from_fn(insert_header));
|
||||||
|
|
||||||
let res = app
|
let res = app
|
||||||
.into_service()
|
|
||||||
.oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
|
.oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ pub fn map_request<F, T>(f: F) -> MapRequestLayer<F, (), T> {
|
|||||||
/// .route("/", get(|| async { /* ... */ }))
|
/// .route("/", get(|| async { /* ... */ }))
|
||||||
/// .route_layer(map_request_with_state(state.clone(), my_middleware))
|
/// .route_layer(map_request_with_state(state.clone(), my_middleware))
|
||||||
/// .with_state(state);
|
/// .with_state(state);
|
||||||
/// # let _: axum::routing::RouterService = app;
|
/// # let _: axum::Router = app;
|
||||||
/// ```
|
/// ```
|
||||||
pub fn map_request_with_state<F, S, T>(state: S, f: F) -> MapRequestLayer<F, S, T> {
|
pub fn map_request_with_state<F, S, T>(state: S, f: F) -> MapRequestLayer<F, S, T> {
|
||||||
MapRequestLayer {
|
MapRequestLayer {
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ pub fn map_response<F, T>(f: F) -> MapResponseLayer<F, (), T> {
|
|||||||
/// .route("/", get(|| async { /* ... */ }))
|
/// .route("/", get(|| async { /* ... */ }))
|
||||||
/// .route_layer(map_response_with_state(state.clone(), my_middleware))
|
/// .route_layer(map_response_with_state(state.clone(), my_middleware))
|
||||||
/// .with_state(state);
|
/// .with_state(state);
|
||||||
/// # let _: axum::routing::RouterService = app;
|
/// # let _: axum::Router = app;
|
||||||
/// ```
|
/// ```
|
||||||
pub fn map_response_with_state<F, S, T>(state: S, f: F) -> MapResponseLayer<F, S, T> {
|
pub fn map_response_with_state<F, S, T>(state: S, f: F) -> MapResponseLayer<F, S, T> {
|
||||||
MapResponseLayer {
|
MapResponseLayer {
|
||||||
|
|||||||
+179
-253
@@ -1,6 +1,6 @@
|
|||||||
//! Route to services and handlers based on HTTP methods.
|
//! Route to services and handlers based on HTTP methods.
|
||||||
|
|
||||||
use super::{FallbackRoute, IntoMakeService};
|
use super::IntoMakeService;
|
||||||
#[cfg(feature = "tokio")]
|
#[cfg(feature = "tokio")]
|
||||||
use crate::extract::connect_info::IntoMakeServiceWithConnectInfo;
|
use crate::extract::connect_info::IntoMakeServiceWithConnectInfo;
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -83,7 +83,7 @@ macro_rules! top_level_service_fn {
|
|||||||
T: Service<Request<B>> + Clone + Send + 'static,
|
T: Service<Request<B>> + Clone + Send + 'static,
|
||||||
T::Response: IntoResponse + 'static,
|
T::Response: IntoResponse + 'static,
|
||||||
T::Future: Send + 'static,
|
T::Future: Send + 'static,
|
||||||
B: Send + 'static,
|
B: HttpBody + Send + 'static,
|
||||||
S: Clone,
|
S: Clone,
|
||||||
{
|
{
|
||||||
on_service(MethodFilter::$method, svc)
|
on_service(MethodFilter::$method, svc)
|
||||||
@@ -143,7 +143,7 @@ macro_rules! top_level_handler_fn {
|
|||||||
pub fn $name<H, T, S, B>(handler: H) -> MethodRouter<S, B, Infallible>
|
pub fn $name<H, T, S, B>(handler: H) -> MethodRouter<S, B, Infallible>
|
||||||
where
|
where
|
||||||
H: Handler<T, S, B>,
|
H: Handler<T, S, B>,
|
||||||
B: Send + 'static,
|
B: HttpBody + Send + 'static,
|
||||||
T: 'static,
|
T: 'static,
|
||||||
S: Clone + Send + Sync + 'static,
|
S: Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
@@ -327,7 +327,7 @@ where
|
|||||||
T: Service<Request<B>> + Clone + Send + 'static,
|
T: Service<Request<B>> + Clone + Send + 'static,
|
||||||
T::Response: IntoResponse + 'static,
|
T::Response: IntoResponse + 'static,
|
||||||
T::Future: Send + 'static,
|
T::Future: Send + 'static,
|
||||||
B: Send + 'static,
|
B: HttpBody + Send + 'static,
|
||||||
S: Clone,
|
S: Clone,
|
||||||
{
|
{
|
||||||
MethodRouter::new().on_service(filter, svc)
|
MethodRouter::new().on_service(filter, svc)
|
||||||
@@ -391,7 +391,7 @@ where
|
|||||||
T: Service<Request<B>> + Clone + Send + 'static,
|
T: Service<Request<B>> + Clone + Send + 'static,
|
||||||
T::Response: IntoResponse + 'static,
|
T::Response: IntoResponse + 'static,
|
||||||
T::Future: Send + 'static,
|
T::Future: Send + 'static,
|
||||||
B: Send + 'static,
|
B: HttpBody + Send + 'static,
|
||||||
S: Clone,
|
S: Clone,
|
||||||
{
|
{
|
||||||
MethodRouter::new()
|
MethodRouter::new()
|
||||||
@@ -430,7 +430,7 @@ top_level_handler_fn!(trace, TRACE);
|
|||||||
pub fn on<H, T, S, B>(filter: MethodFilter, handler: H) -> MethodRouter<S, B, Infallible>
|
pub fn on<H, T, S, B>(filter: MethodFilter, handler: H) -> MethodRouter<S, B, Infallible>
|
||||||
where
|
where
|
||||||
H: Handler<T, S, B>,
|
H: Handler<T, S, B>,
|
||||||
B: Send + 'static,
|
B: HttpBody + Send + 'static,
|
||||||
T: 'static,
|
T: 'static,
|
||||||
S: Clone + Send + Sync + 'static,
|
S: Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
@@ -477,7 +477,7 @@ where
|
|||||||
pub fn any<H, T, S, B>(handler: H) -> MethodRouter<S, B, Infallible>
|
pub fn any<H, T, S, B>(handler: H) -> MethodRouter<S, B, Infallible>
|
||||||
where
|
where
|
||||||
H: Handler<T, S, B>,
|
H: Handler<T, S, B>,
|
||||||
B: Send + 'static,
|
B: HttpBody + Send + 'static,
|
||||||
T: 'static,
|
T: 'static,
|
||||||
S: Clone + Send + Sync + 'static,
|
S: Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
@@ -571,7 +571,7 @@ impl<S, B, E> fmt::Debug for MethodRouter<S, B, E> {
|
|||||||
|
|
||||||
impl<S, B> MethodRouter<S, B, Infallible>
|
impl<S, B> MethodRouter<S, B, Infallible>
|
||||||
where
|
where
|
||||||
B: Send + 'static,
|
B: HttpBody + Send + 'static,
|
||||||
S: Clone,
|
S: Clone,
|
||||||
{
|
{
|
||||||
/// Chain an additional handler that will accept requests matching the given
|
/// Chain an additional handler that will accept requests matching the given
|
||||||
@@ -633,7 +633,7 @@ where
|
|||||||
|
|
||||||
impl<B> MethodRouter<(), B, Infallible>
|
impl<B> MethodRouter<(), B, Infallible>
|
||||||
where
|
where
|
||||||
B: Send + 'static,
|
B: HttpBody + Send + 'static,
|
||||||
{
|
{
|
||||||
/// Convert the handler into a [`MakeService`].
|
/// Convert the handler into a [`MakeService`].
|
||||||
///
|
///
|
||||||
@@ -665,7 +665,7 @@ where
|
|||||||
///
|
///
|
||||||
/// [`MakeService`]: tower::make::MakeService
|
/// [`MakeService`]: tower::make::MakeService
|
||||||
pub fn into_make_service(self) -> IntoMakeService<Self> {
|
pub fn into_make_service(self) -> IntoMakeService<Self> {
|
||||||
IntoMakeService::new(self)
|
IntoMakeService::new(self.with_state(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert the router into a [`MakeService`] which stores information
|
/// Convert the router into a [`MakeService`] which stores information
|
||||||
@@ -701,13 +701,13 @@ where
|
|||||||
/// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info
|
/// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info
|
||||||
#[cfg(feature = "tokio")]
|
#[cfg(feature = "tokio")]
|
||||||
pub fn into_make_service_with_connect_info<C>(self) -> IntoMakeServiceWithConnectInfo<Self, C> {
|
pub fn into_make_service_with_connect_info<C>(self) -> IntoMakeServiceWithConnectInfo<Self, C> {
|
||||||
IntoMakeServiceWithConnectInfo::new(self)
|
IntoMakeServiceWithConnectInfo::new(self.with_state(()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, B, E> MethodRouter<S, B, E>
|
impl<S, B, E> MethodRouter<S, B, E>
|
||||||
where
|
where
|
||||||
B: Send + 'static,
|
B: HttpBody + Send + 'static,
|
||||||
S: Clone,
|
S: Clone,
|
||||||
{
|
{
|
||||||
/// Create a default `MethodRouter` that will respond with `405 Method Not Allowed` to all
|
/// Create a default `MethodRouter` that will respond with `405 Method Not Allowed` to all
|
||||||
@@ -731,21 +731,19 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provide the state.
|
/// Provide the state for the router.
|
||||||
///
|
pub fn with_state<S2>(self, state: S) -> MethodRouter<S2, B, E> {
|
||||||
/// See [`State`](crate::extract::State) for more details about accessing state.
|
MethodRouter {
|
||||||
pub fn with_state(self, state: S) -> WithState<B, E> {
|
get: self.get.with_state(state.clone()),
|
||||||
WithState {
|
head: self.head.with_state(state.clone()),
|
||||||
get: self.get.into_route(&state),
|
delete: self.delete.with_state(state.clone()),
|
||||||
head: self.head.into_route(&state),
|
options: self.options.with_state(state.clone()),
|
||||||
delete: self.delete.into_route(&state),
|
patch: self.patch.with_state(state.clone()),
|
||||||
options: self.options.into_route(&state),
|
post: self.post.with_state(state.clone()),
|
||||||
patch: self.patch.into_route(&state),
|
put: self.put.with_state(state.clone()),
|
||||||
post: self.post.into_route(&state),
|
trace: self.trace.with_state(state.clone()),
|
||||||
put: self.put.into_route(&state),
|
|
||||||
trace: self.trace.into_route(&state),
|
|
||||||
fallback: self.fallback.into_fallback_route(&state),
|
|
||||||
allow_header: self.allow_header,
|
allow_header: self.allow_header,
|
||||||
|
fallback: self.fallback.with_state(state),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -918,10 +916,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[doc = include_str!("../docs/method_routing/layer.md")]
|
#[doc = include_str!("../docs/method_routing/layer.md")]
|
||||||
pub fn layer<L, NewReqBody: 'static, NewError: 'static>(
|
pub fn layer<L, NewReqBody, NewError>(self, layer: L) -> MethodRouter<S, NewReqBody, NewError>
|
||||||
self,
|
|
||||||
layer: L,
|
|
||||||
) -> MethodRouter<S, NewReqBody, NewError>
|
|
||||||
where
|
where
|
||||||
L: Layer<Route<B, E>> + Clone + Send + 'static,
|
L: Layer<Route<B, E>> + Clone + Send + 'static,
|
||||||
L::Service: Service<Request<NewReqBody>> + Clone + Send + 'static,
|
L::Service: Service<Request<NewReqBody>> + Clone + Send + 'static,
|
||||||
@@ -930,6 +925,8 @@ where
|
|||||||
<L::Service as Service<Request<NewReqBody>>>::Future: Send + 'static,
|
<L::Service as Service<Request<NewReqBody>>>::Future: Send + 'static,
|
||||||
E: 'static,
|
E: 'static,
|
||||||
S: 'static,
|
S: 'static,
|
||||||
|
NewReqBody: HttpBody + 'static,
|
||||||
|
NewError: 'static,
|
||||||
{
|
{
|
||||||
let layer_fn = move |route: Route<B, E>| route.layer(layer.clone());
|
let layer_fn = move |route: Route<B, E>| route.layer(layer.clone());
|
||||||
|
|
||||||
@@ -1069,226 +1066,8 @@ where
|
|||||||
self.allow_header = AllowHeader::Skip;
|
self.allow_header = AllowHeader::Skip;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn append_allow_header(allow_header: &mut AllowHeader, method: &'static str) {
|
pub(crate) fn call_with_state(&mut self, req: Request<B>, state: S) -> RouteFuture<B, E> {
|
||||||
match allow_header {
|
|
||||||
AllowHeader::None => {
|
|
||||||
*allow_header = AllowHeader::Bytes(BytesMut::from(method));
|
|
||||||
}
|
|
||||||
AllowHeader::Skip => {}
|
|
||||||
AllowHeader::Bytes(allow_header) => {
|
|
||||||
if let Ok(s) = std::str::from_utf8(allow_header) {
|
|
||||||
if !s.contains(method) {
|
|
||||||
allow_header.extend_from_slice(b",");
|
|
||||||
allow_header.extend_from_slice(method.as_bytes());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
panic!("`allow_header` contained invalid uft-8. This should never happen")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B, E> Service<Request<B>> for MethodRouter<(), B, E>
|
|
||||||
where
|
|
||||||
B: HttpBody + Send + 'static,
|
|
||||||
{
|
|
||||||
type Response = Response;
|
|
||||||
type Error = E;
|
|
||||||
type Future = RouteFuture<B, E>;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&mut self, req: Request<B>) -> Self::Future {
|
|
||||||
self.clone().with_state(()).call(req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, B, E> Clone for MethodRouter<S, B, E> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
get: self.get.clone(),
|
|
||||||
head: self.head.clone(),
|
|
||||||
delete: self.delete.clone(),
|
|
||||||
options: self.options.clone(),
|
|
||||||
patch: self.patch.clone(),
|
|
||||||
post: self.post.clone(),
|
|
||||||
put: self.put.clone(),
|
|
||||||
trace: self.trace.clone(),
|
|
||||||
fallback: self.fallback.clone(),
|
|
||||||
allow_header: self.allow_header.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, B, E> Default for MethodRouter<S, B, E>
|
|
||||||
where
|
|
||||||
B: Send + 'static,
|
|
||||||
S: Clone,
|
|
||||||
{
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum MethodEndpoint<S, B, E> {
|
|
||||||
None,
|
|
||||||
Route(Route<B, E>),
|
|
||||||
BoxedHandler(BoxedIntoRoute<S, B, E>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, B, E> MethodEndpoint<S, B, E>
|
|
||||||
where
|
|
||||||
S: Clone,
|
|
||||||
{
|
|
||||||
fn is_some(&self) -> bool {
|
|
||||||
matches!(self, Self::Route(_) | Self::BoxedHandler(_))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_none(&self) -> bool {
|
|
||||||
matches!(self, Self::None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map<F, B2, E2>(self, f: F) -> MethodEndpoint<S, B2, E2>
|
|
||||||
where
|
|
||||||
S: 'static,
|
|
||||||
B: 'static,
|
|
||||||
E: 'static,
|
|
||||||
F: FnOnce(Route<B, E>) -> Route<B2, E2> + Clone + Send + 'static,
|
|
||||||
B2: 'static,
|
|
||||||
E2: 'static,
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
Self::None => MethodEndpoint::None,
|
|
||||||
Self::Route(route) => MethodEndpoint::Route(f(route)),
|
|
||||||
Self::BoxedHandler(handler) => MethodEndpoint::BoxedHandler(handler.map(f)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_route(self, state: &S) -> Option<Route<B, E>> {
|
|
||||||
match self {
|
|
||||||
Self::None => None,
|
|
||||||
Self::Route(route) => Some(route),
|
|
||||||
Self::BoxedHandler(handler) => Some(handler.into_route(state.clone())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, B, E> Clone for MethodEndpoint<S, B, E> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::None => Self::None,
|
|
||||||
Self::Route(inner) => Self::Route(inner.clone()),
|
|
||||||
Self::BoxedHandler(inner) => Self::BoxedHandler(inner.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, B, E> fmt::Debug for MethodEndpoint<S, B, E> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::None => f.debug_tuple("None").finish(),
|
|
||||||
Self::Route(inner) => inner.fmt(f),
|
|
||||||
Self::BoxedHandler(_) => f.debug_tuple("BoxedHandler").finish(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A [`MethodRouter`] which has access to some state.
|
|
||||||
///
|
|
||||||
/// Implements [`Service`].
|
|
||||||
///
|
|
||||||
/// The state can be extracted with [`State`](crate::extract::State).
|
|
||||||
///
|
|
||||||
/// Created with [`MethodRouter::with_state`]
|
|
||||||
pub struct WithState<B, E> {
|
|
||||||
get: Option<Route<B, E>>,
|
|
||||||
head: Option<Route<B, E>>,
|
|
||||||
delete: Option<Route<B, E>>,
|
|
||||||
options: Option<Route<B, E>>,
|
|
||||||
patch: Option<Route<B, E>>,
|
|
||||||
post: Option<Route<B, E>>,
|
|
||||||
put: Option<Route<B, E>>,
|
|
||||||
trace: Option<Route<B, E>>,
|
|
||||||
fallback: FallbackRoute<B, E>,
|
|
||||||
allow_header: AllowHeader,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B, E> WithState<B, E> {
|
|
||||||
/// Convert the handler into a [`MakeService`].
|
|
||||||
///
|
|
||||||
/// See [`MethodRouter::into_make_service`] for more details.
|
|
||||||
///
|
|
||||||
/// [`MakeService`]: tower::make::MakeService
|
|
||||||
pub fn into_make_service(self) -> IntoMakeService<Self> {
|
|
||||||
IntoMakeService::new(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert the router into a [`MakeService`] which stores information
|
|
||||||
/// about the incoming connection.
|
|
||||||
///
|
|
||||||
/// See [`MethodRouter::into_make_service_with_connect_info`] for more details.
|
|
||||||
///
|
|
||||||
/// [`MakeService`]: tower::make::MakeService
|
|
||||||
#[cfg(feature = "tokio")]
|
|
||||||
pub fn into_make_service_with_connect_info<C>(self) -> IntoMakeServiceWithConnectInfo<Self, C> {
|
|
||||||
IntoMakeServiceWithConnectInfo::new(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B, E> Clone for WithState<B, E> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
get: self.get.clone(),
|
|
||||||
head: self.head.clone(),
|
|
||||||
delete: self.delete.clone(),
|
|
||||||
options: self.options.clone(),
|
|
||||||
patch: self.patch.clone(),
|
|
||||||
post: self.post.clone(),
|
|
||||||
put: self.put.clone(),
|
|
||||||
trace: self.trace.clone(),
|
|
||||||
fallback: self.fallback.clone(),
|
|
||||||
allow_header: self.allow_header.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B, E> fmt::Debug for WithState<B, E> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("WithState")
|
|
||||||
.field("get", &self.get)
|
|
||||||
.field("head", &self.head)
|
|
||||||
.field("delete", &self.delete)
|
|
||||||
.field("options", &self.options)
|
|
||||||
.field("patch", &self.patch)
|
|
||||||
.field("post", &self.post)
|
|
||||||
.field("put", &self.put)
|
|
||||||
.field("trace", &self.trace)
|
|
||||||
.field("fallback", &self.fallback)
|
|
||||||
.field("allow_header", &self.allow_header)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B, E> Service<Request<B>> for WithState<B, E>
|
|
||||||
where
|
|
||||||
B: HttpBody + Send,
|
|
||||||
{
|
|
||||||
type Response = Response;
|
|
||||||
type Error = E;
|
|
||||||
type Future = RouteFuture<B, E>;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&mut self, req: Request<B>) -> Self::Future {
|
|
||||||
macro_rules! call {
|
macro_rules! call {
|
||||||
(
|
(
|
||||||
$req:expr,
|
$req:expr,
|
||||||
@@ -1297,9 +1076,17 @@ where
|
|||||||
$svc:expr
|
$svc:expr
|
||||||
) => {
|
) => {
|
||||||
if $method == Method::$method_variant {
|
if $method == Method::$method_variant {
|
||||||
if let Some(svc) = $svc {
|
match $svc {
|
||||||
return RouteFuture::from_future(svc.oneshot_inner($req))
|
MethodEndpoint::None => {}
|
||||||
.strip_body($method == Method::HEAD);
|
MethodEndpoint::Route(route) => {
|
||||||
|
return RouteFuture::from_future(route.oneshot_inner($req))
|
||||||
|
.strip_body($method == Method::HEAD);
|
||||||
|
}
|
||||||
|
MethodEndpoint::BoxedHandler(handler) => {
|
||||||
|
let mut route = handler.clone().into_route(state);
|
||||||
|
return RouteFuture::from_future(route.oneshot_inner($req))
|
||||||
|
.strip_body($method == Method::HEAD);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1331,7 +1118,15 @@ where
|
|||||||
call!(req, method, DELETE, delete);
|
call!(req, method, DELETE, delete);
|
||||||
call!(req, method, TRACE, trace);
|
call!(req, method, TRACE, trace);
|
||||||
|
|
||||||
let future = RouteFuture::from_future(fallback.oneshot_inner(req));
|
let future = match fallback {
|
||||||
|
Fallback::Default(route) | Fallback::Service(route) => {
|
||||||
|
RouteFuture::from_future(route.oneshot_inner(req))
|
||||||
|
}
|
||||||
|
Fallback::BoxedHandler(handler) => {
|
||||||
|
let mut route = handler.clone().into_route(state);
|
||||||
|
RouteFuture::from_future(route.oneshot_inner(req))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
match allow_header {
|
match allow_header {
|
||||||
AllowHeader::None => future.allow_header(Bytes::new()),
|
AllowHeader::None => future.allow_header(Bytes::new()),
|
||||||
@@ -1341,6 +1136,137 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn append_allow_header(allow_header: &mut AllowHeader, method: &'static str) {
|
||||||
|
match allow_header {
|
||||||
|
AllowHeader::None => {
|
||||||
|
*allow_header = AllowHeader::Bytes(BytesMut::from(method));
|
||||||
|
}
|
||||||
|
AllowHeader::Skip => {}
|
||||||
|
AllowHeader::Bytes(allow_header) => {
|
||||||
|
if let Ok(s) = std::str::from_utf8(allow_header) {
|
||||||
|
if !s.contains(method) {
|
||||||
|
allow_header.extend_from_slice(b",");
|
||||||
|
allow_header.extend_from_slice(method.as_bytes());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
panic!("`allow_header` contained invalid uft-8. This should never happen")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, B, E> Clone for MethodRouter<S, B, E> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
get: self.get.clone(),
|
||||||
|
head: self.head.clone(),
|
||||||
|
delete: self.delete.clone(),
|
||||||
|
options: self.options.clone(),
|
||||||
|
patch: self.patch.clone(),
|
||||||
|
post: self.post.clone(),
|
||||||
|
put: self.put.clone(),
|
||||||
|
trace: self.trace.clone(),
|
||||||
|
fallback: self.fallback.clone(),
|
||||||
|
allow_header: self.allow_header.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, B, E> Default for MethodRouter<S, B, E>
|
||||||
|
where
|
||||||
|
B: HttpBody + Send + 'static,
|
||||||
|
S: Clone,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MethodEndpoint<S, B, E> {
|
||||||
|
None,
|
||||||
|
Route(Route<B, E>),
|
||||||
|
BoxedHandler(BoxedIntoRoute<S, B, E>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, B, E> MethodEndpoint<S, B, E>
|
||||||
|
where
|
||||||
|
S: Clone,
|
||||||
|
{
|
||||||
|
fn is_some(&self) -> bool {
|
||||||
|
matches!(self, Self::Route(_) | Self::BoxedHandler(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_none(&self) -> bool {
|
||||||
|
matches!(self, Self::None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map<F, B2, E2>(self, f: F) -> MethodEndpoint<S, B2, E2>
|
||||||
|
where
|
||||||
|
S: 'static,
|
||||||
|
B: 'static,
|
||||||
|
E: 'static,
|
||||||
|
F: FnOnce(Route<B, E>) -> Route<B2, E2> + Clone + Send + 'static,
|
||||||
|
B2: HttpBody + 'static,
|
||||||
|
E2: 'static,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::None => MethodEndpoint::None,
|
||||||
|
Self::Route(route) => MethodEndpoint::Route(f(route)),
|
||||||
|
Self::BoxedHandler(handler) => MethodEndpoint::BoxedHandler(handler.map(f)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_state<S2>(self, state: S) -> MethodEndpoint<S2, B, E> {
|
||||||
|
match self {
|
||||||
|
MethodEndpoint::None => MethodEndpoint::None,
|
||||||
|
MethodEndpoint::Route(route) => MethodEndpoint::Route(route),
|
||||||
|
MethodEndpoint::BoxedHandler(handler) => {
|
||||||
|
MethodEndpoint::Route(handler.into_route(state))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, B, E> Clone for MethodEndpoint<S, B, E> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::None => Self::None,
|
||||||
|
Self::Route(inner) => Self::Route(inner.clone()),
|
||||||
|
Self::BoxedHandler(inner) => Self::BoxedHandler(inner.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, B, E> fmt::Debug for MethodEndpoint<S, B, E> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::None => f.debug_tuple("None").finish(),
|
||||||
|
Self::Route(inner) => inner.fmt(f),
|
||||||
|
Self::BoxedHandler(_) => f.debug_tuple("BoxedHandler").finish(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B, E> Service<Request<B>> for MethodRouter<(), B, E>
|
||||||
|
where
|
||||||
|
B: HttpBody + Send + 'static,
|
||||||
|
{
|
||||||
|
type Response = Response;
|
||||||
|
type Error = E;
|
||||||
|
type Future = RouteFuture<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 {
|
||||||
|
self.call_with_state(req, ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
+217
-144
@@ -1,6 +1,6 @@
|
|||||||
//! Routing between [`Service`]s and handlers.
|
//! Routing between [`Service`]s and handlers.
|
||||||
|
|
||||||
use self::{not_found::NotFound, strip_prefix::StripPrefix};
|
use self::{future::RouteFuture, not_found::NotFound, strip_prefix::StripPrefix};
|
||||||
#[cfg(feature = "tokio")]
|
#[cfg(feature = "tokio")]
|
||||||
use crate::extract::connect_info::IntoMakeServiceWithConnectInfo;
|
use crate::extract::connect_info::IntoMakeServiceWithConnectInfo;
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -12,8 +12,14 @@ use crate::{
|
|||||||
use axum_core::response::{IntoResponse, Response};
|
use axum_core::response::{IntoResponse, Response};
|
||||||
use http::Request;
|
use http::Request;
|
||||||
use matchit::MatchError;
|
use matchit::MatchError;
|
||||||
use std::{collections::HashMap, convert::Infallible, fmt, sync::Arc};
|
use std::{
|
||||||
use tower::util::{BoxCloneService, Oneshot};
|
collections::HashMap,
|
||||||
|
convert::Infallible,
|
||||||
|
fmt,
|
||||||
|
sync::Arc,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
use sync_wrapper::SyncWrapper;
|
||||||
use tower_layer::Layer;
|
use tower_layer::Layer;
|
||||||
use tower_service::Service;
|
use tower_service::Service;
|
||||||
|
|
||||||
@@ -27,14 +33,10 @@ mod route;
|
|||||||
mod strip_prefix;
|
mod strip_prefix;
|
||||||
pub(crate) mod url_params;
|
pub(crate) mod url_params;
|
||||||
|
|
||||||
mod service;
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{into_make_service::IntoMakeService, method_filter::MethodFilter, route::Route};
|
||||||
into_make_service::IntoMakeService, method_filter::MethodFilter, route::Route,
|
|
||||||
service::RouterService,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use self::method_routing::{
|
pub use self::method_routing::{
|
||||||
any, any_service, delete, delete_service, get, get_service, head, head_service, on, on_service,
|
any, any_service, delete, delete_service, get, get_service, head, head_service, on, on_service,
|
||||||
@@ -168,10 +170,10 @@ where
|
|||||||
T::Response: IntoResponse,
|
T::Response: IntoResponse,
|
||||||
T::Future: Send + 'static,
|
T::Future: Send + 'static,
|
||||||
{
|
{
|
||||||
let service = match try_downcast::<RouterService<B>, _>(service) {
|
let service = match try_downcast::<Router<S, B>, _>(service) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
panic!(
|
panic!(
|
||||||
"Invalid route: `Router::route_service` cannot be used with `RouterService`s. \
|
"Invalid route: `Router::route_service` cannot be used with `Router`s. \
|
||||||
Use `Router::nest` instead"
|
Use `Router::nest` instead"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -212,41 +214,6 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Like [`nest`](Self::nest), but accepts an arbitrary `Service`.
|
/// Like [`nest`](Self::nest), but accepts an arbitrary `Service`.
|
||||||
///
|
|
||||||
/// While [`nest`](Self::nest) requires [`Router`]s with the same type of
|
|
||||||
/// state, you can use this method to combine [`Router`]s with different
|
|
||||||
/// types of state:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use axum::{
|
|
||||||
/// Router,
|
|
||||||
/// routing::get,
|
|
||||||
/// extract::State,
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// #[derive(Clone)]
|
|
||||||
/// struct InnerState {}
|
|
||||||
///
|
|
||||||
/// #[derive(Clone)]
|
|
||||||
/// struct OuterState {}
|
|
||||||
///
|
|
||||||
/// async fn inner_handler(state: State<InnerState>) {}
|
|
||||||
///
|
|
||||||
/// let inner_router = Router::new()
|
|
||||||
/// .route("/bar", get(inner_handler))
|
|
||||||
/// .with_state(InnerState {});
|
|
||||||
///
|
|
||||||
/// async fn outer_handler(state: State<OuterState>) {}
|
|
||||||
///
|
|
||||||
/// let app = Router::new()
|
|
||||||
/// .route("/", get(outer_handler))
|
|
||||||
/// .nest_service("/foo", inner_router)
|
|
||||||
/// .with_state(OuterState {});
|
|
||||||
/// # let _: axum::routing::RouterService = app;
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Note that the inner router will still inherit the fallback from the outer
|
|
||||||
/// router.
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn nest_service<T>(self, path: &str, svc: T) -> Self
|
pub fn nest_service<T>(self, path: &str, svc: T) -> Self
|
||||||
where
|
where
|
||||||
@@ -353,7 +320,7 @@ where
|
|||||||
<L::Service as Service<Request<NewReqBody>>>::Response: IntoResponse + 'static,
|
<L::Service as Service<Request<NewReqBody>>>::Response: IntoResponse + 'static,
|
||||||
<L::Service as Service<Request<NewReqBody>>>::Error: Into<Infallible> + 'static,
|
<L::Service as Service<Request<NewReqBody>>>::Error: Into<Infallible> + 'static,
|
||||||
<L::Service as Service<Request<NewReqBody>>>::Future: Send + 'static,
|
<L::Service as Service<Request<NewReqBody>>>::Future: Send + 'static,
|
||||||
NewReqBody: 'static,
|
NewReqBody: HttpBody + 'static,
|
||||||
{
|
{
|
||||||
let routes = self
|
let routes = self
|
||||||
.routes
|
.routes
|
||||||
@@ -429,11 +396,171 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert this router into a [`RouterService`] by providing the state.
|
/// Provide the state for the router.
|
||||||
///
|
///
|
||||||
/// Once this method has been called you cannot add more routes. So it must be called as last.
|
/// This method returns a router with a different state type. This can be used to nest or merge
|
||||||
pub fn with_state(self, state: S) -> RouterService<B> {
|
/// routers with different state types. See [`Router::nest`] and [`Router::merge`] for more
|
||||||
RouterService::new(self, state)
|
/// details.
|
||||||
|
///
|
||||||
|
/// # Implementing `Service`
|
||||||
|
///
|
||||||
|
/// This can also be used to get a `Router` that implements [`Service`], since it only does so
|
||||||
|
/// when the state is `()`:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use axum::{
|
||||||
|
/// Router,
|
||||||
|
/// body::Body,
|
||||||
|
/// http::Request,
|
||||||
|
/// };
|
||||||
|
/// use tower::{Service, ServiceExt};
|
||||||
|
///
|
||||||
|
/// #[derive(Clone)]
|
||||||
|
/// struct AppState {}
|
||||||
|
///
|
||||||
|
/// // this router doesn't implement `Service` because its state isn't `()`
|
||||||
|
/// let router: Router<AppState> = Router::new();
|
||||||
|
///
|
||||||
|
/// // by providing the state and setting the new state to `()`...
|
||||||
|
/// let router_service: Router<()> = router.with_state(AppState {});
|
||||||
|
///
|
||||||
|
/// // ...makes it implement `Service`
|
||||||
|
/// # async {
|
||||||
|
/// router_service.oneshot(Request::new(Body::empty())).await;
|
||||||
|
/// # };
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # A note about performance
|
||||||
|
///
|
||||||
|
/// If you need a `Router` that implements `Service` but you don't need any state (perhaps
|
||||||
|
/// you're making a library that uses axum internally) then it is recommended to call this
|
||||||
|
/// method before you start serving requests:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use axum::{Router, routing::get};
|
||||||
|
///
|
||||||
|
/// let app = Router::new()
|
||||||
|
/// .route("/", get(|| async { /* ... */ }))
|
||||||
|
/// // even though we don't need any state, call `with_state(())` anyway
|
||||||
|
/// .with_state(());
|
||||||
|
/// # let _: Router = app;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This is not required but it gives axum a chance to update some internals in the router
|
||||||
|
/// which may impact performance and reduce allocations.
|
||||||
|
///
|
||||||
|
/// Note that [`Router::into_make_service`] and [`Router::into_make_service_with_connect_info`]
|
||||||
|
/// do this automatically.
|
||||||
|
pub fn with_state<S2>(self, state: S) -> Router<S2, B> {
|
||||||
|
let routes = self
|
||||||
|
.routes
|
||||||
|
.into_iter()
|
||||||
|
.map(|(id, endpoint)| {
|
||||||
|
let endpoint: Endpoint<S2, B> = match endpoint {
|
||||||
|
Endpoint::MethodRouter(method_router) => {
|
||||||
|
Endpoint::MethodRouter(method_router.with_state(state.clone()))
|
||||||
|
}
|
||||||
|
Endpoint::Route(route) => Endpoint::Route(route),
|
||||||
|
Endpoint::NestedRouter(router) => {
|
||||||
|
Endpoint::Route(router.into_route(state.clone()))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(id, endpoint)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let fallback = self.fallback.with_state(state);
|
||||||
|
|
||||||
|
Router {
|
||||||
|
routes,
|
||||||
|
node: self.node,
|
||||||
|
fallback,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn call_with_state(
|
||||||
|
&mut self,
|
||||||
|
mut req: Request<B>,
|
||||||
|
state: S,
|
||||||
|
) -> RouteFuture<B, Infallible> {
|
||||||
|
#[cfg(feature = "original-uri")]
|
||||||
|
{
|
||||||
|
use crate::extract::OriginalUri;
|
||||||
|
|
||||||
|
if req.extensions().get::<OriginalUri>().is_none() {
|
||||||
|
let original_uri = OriginalUri(req.uri().clone());
|
||||||
|
req.extensions_mut().insert(original_uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = req.uri().path().to_owned();
|
||||||
|
|
||||||
|
match self.node.at(&path) {
|
||||||
|
Ok(match_) => {
|
||||||
|
match &self.fallback {
|
||||||
|
Fallback::Default(_) => {}
|
||||||
|
Fallback::Service(fallback) => {
|
||||||
|
req.extensions_mut()
|
||||||
|
.insert(SuperFallback(SyncWrapper::new(fallback.clone())));
|
||||||
|
}
|
||||||
|
Fallback::BoxedHandler(fallback) => {
|
||||||
|
req.extensions_mut().insert(SuperFallback(SyncWrapper::new(
|
||||||
|
fallback.clone().into_route(state.clone()),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.call_route(match_, req, state)
|
||||||
|
}
|
||||||
|
Err(
|
||||||
|
MatchError::NotFound
|
||||||
|
| MatchError::ExtraTrailingSlash
|
||||||
|
| MatchError::MissingTrailingSlash,
|
||||||
|
) => match &mut self.fallback {
|
||||||
|
Fallback::Default(fallback) => {
|
||||||
|
if let Some(super_fallback) = req.extensions_mut().remove::<SuperFallback<B>>()
|
||||||
|
{
|
||||||
|
let mut super_fallback = super_fallback.0.into_inner();
|
||||||
|
super_fallback.call(req)
|
||||||
|
} else {
|
||||||
|
fallback.call(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Fallback::Service(fallback) => fallback.call(req),
|
||||||
|
Fallback::BoxedHandler(handler) => handler.clone().into_route(state).call(req),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn call_route(
|
||||||
|
&self,
|
||||||
|
match_: matchit::Match<&RouteId>,
|
||||||
|
mut req: Request<B>,
|
||||||
|
state: S,
|
||||||
|
) -> RouteFuture<B, Infallible> {
|
||||||
|
let id = *match_.value;
|
||||||
|
|
||||||
|
#[cfg(feature = "matched-path")]
|
||||||
|
crate::extract::matched_path::set_matched_path_for_request(
|
||||||
|
id,
|
||||||
|
&self.node.route_id_to_path,
|
||||||
|
req.extensions_mut(),
|
||||||
|
);
|
||||||
|
|
||||||
|
url_params::insert_url_params(req.extensions_mut(), match_.params);
|
||||||
|
|
||||||
|
let endpont = self
|
||||||
|
.routes
|
||||||
|
.get(&id)
|
||||||
|
.expect("no route for id. This is a bug in axum. Please file an issue")
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
match endpont {
|
||||||
|
Endpoint::MethodRouter(mut method_router) => method_router.call_with_state(req, state),
|
||||||
|
Endpoint::Route(mut route) => route.call(req),
|
||||||
|
Endpoint::NestedRouter(router) => router.call_with_state(req, state),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,16 +568,6 @@ impl<B> Router<(), B>
|
|||||||
where
|
where
|
||||||
B: HttpBody + Send + 'static,
|
B: HttpBody + Send + 'static,
|
||||||
{
|
{
|
||||||
/// Convert this router into a [`RouterService`].
|
|
||||||
///
|
|
||||||
/// This is a convenience method for routers that don't have any state (i.e. the state type is
|
|
||||||
/// `()`). Use [`Router::with_state`] otherwise.
|
|
||||||
///
|
|
||||||
/// Once this method has been called you cannot add more routes. So it must be called as last.
|
|
||||||
pub fn into_service(self) -> RouterService<B> {
|
|
||||||
RouterService::new(self, ())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert this router into a [`MakeService`], that is a [`Service`] whose
|
/// Convert this router into a [`MakeService`], that is a [`Service`] whose
|
||||||
/// response is another service.
|
/// response is another service.
|
||||||
///
|
///
|
||||||
@@ -473,20 +590,38 @@ where
|
|||||||
/// # };
|
/// # };
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// This is a convenience method for routers that don't have any state (i.e. the state type is
|
|
||||||
/// `()`). Use [`RouterService::into_make_service`] otherwise.
|
|
||||||
///
|
|
||||||
/// [`MakeService`]: tower::make::MakeService
|
/// [`MakeService`]: tower::make::MakeService
|
||||||
pub fn into_make_service(self) -> IntoMakeService<RouterService<B>> {
|
pub fn into_make_service(self) -> IntoMakeService<Self> {
|
||||||
IntoMakeService::new(self.into_service())
|
// call `Router::with_state` such that everything is turned into `Route` eagerly
|
||||||
|
// rather than doing that per request
|
||||||
|
IntoMakeService::new(self.with_state(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc = include_str!("../docs/routing/into_make_service_with_connect_info.md")]
|
#[doc = include_str!("../docs/routing/into_make_service_with_connect_info.md")]
|
||||||
#[cfg(feature = "tokio")]
|
#[cfg(feature = "tokio")]
|
||||||
pub fn into_make_service_with_connect_info<C>(
|
pub fn into_make_service_with_connect_info<C>(self) -> IntoMakeServiceWithConnectInfo<Self, C> {
|
||||||
self,
|
// call `Router::with_state` such that everything is turned into `Route` eagerly
|
||||||
) -> IntoMakeServiceWithConnectInfo<RouterService<B>, C> {
|
// rather than doing that per request
|
||||||
IntoMakeServiceWithConnectInfo::new(self.into_service())
|
IntoMakeServiceWithConnectInfo::new(self.with_state(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> Service<Request<B>> for Router<(), B>
|
||||||
|
where
|
||||||
|
B: HttpBody + Send + 'static,
|
||||||
|
{
|
||||||
|
type Response = Response;
|
||||||
|
type Error = Infallible;
|
||||||
|
type Future = RouteFuture<B, Infallible>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn call(&mut self, req: Request<B>) -> Self::Future {
|
||||||
|
self.call_with_state(req, ())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,23 +684,13 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_fallback_route(self, state: &S) -> FallbackRoute<B, E> {
|
|
||||||
match self {
|
|
||||||
Self::Default(route) => FallbackRoute::Default(route),
|
|
||||||
Self::Service(route) => FallbackRoute::Service(route),
|
|
||||||
Self::BoxedHandler(handler) => {
|
|
||||||
FallbackRoute::Service(handler.into_route(state.clone()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map<F, B2, E2>(self, f: F) -> Fallback<S, B2, E2>
|
fn map<F, B2, E2>(self, f: F) -> Fallback<S, B2, E2>
|
||||||
where
|
where
|
||||||
S: 'static,
|
S: 'static,
|
||||||
B: 'static,
|
B: 'static,
|
||||||
E: 'static,
|
E: 'static,
|
||||||
F: FnOnce(Route<B, E>) -> Route<B2, E2> + Clone + Send + 'static,
|
F: FnOnce(Route<B, E>) -> Route<B2, E2> + Clone + Send + 'static,
|
||||||
B2: 'static,
|
B2: HttpBody + 'static,
|
||||||
E2: 'static,
|
E2: 'static,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
@@ -574,6 +699,14 @@ where
|
|||||||
Self::BoxedHandler(handler) => Fallback::BoxedHandler(handler.map(f)),
|
Self::BoxedHandler(handler) => Fallback::BoxedHandler(handler.map(f)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn with_state<S2>(self, state: S) -> Fallback<S2, B, E> {
|
||||||
|
match self {
|
||||||
|
Fallback::Default(route) => Fallback::Default(route),
|
||||||
|
Fallback::Service(route) => Fallback::Service(route),
|
||||||
|
Fallback::BoxedHandler(handler) => Fallback::Service(handler.into_route(state)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, B, E> Clone for Fallback<S, B, E> {
|
impl<S, B, E> Clone for Fallback<S, B, E> {
|
||||||
@@ -596,61 +729,7 @@ impl<S, B, E> fmt::Debug for Fallback<S, B, E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like `Fallback` but without the `S` param so it can be stored in `RouterService`
|
#[allow(clippy::large_enum_variant)]
|
||||||
pub(crate) enum FallbackRoute<B, E = Infallible> {
|
|
||||||
Default(Route<B, E>),
|
|
||||||
Service(Route<B, E>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B, E> FallbackRoute<B, E> {
|
|
||||||
fn layer<L, NewReqBody, NewError>(self, layer: L) -> FallbackRoute<NewReqBody, NewError>
|
|
||||||
where
|
|
||||||
L: Layer<Route<B, E>> + Clone + Send + 'static,
|
|
||||||
L::Service: Service<Request<NewReqBody>> + Clone + Send + 'static,
|
|
||||||
<L::Service as Service<Request<NewReqBody>>>::Response: IntoResponse + 'static,
|
|
||||||
<L::Service as Service<Request<NewReqBody>>>::Error: Into<NewError> + 'static,
|
|
||||||
<L::Service as Service<Request<NewReqBody>>>::Future: Send + 'static,
|
|
||||||
NewReqBody: 'static,
|
|
||||||
NewError: 'static,
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
FallbackRoute::Default(route) => FallbackRoute::Default(route.layer(layer)),
|
|
||||||
FallbackRoute::Service(route) => FallbackRoute::Service(route.layer(layer)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B, E> fmt::Debug for FallbackRoute<B, E> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Default(inner) => f.debug_tuple("Default").field(inner).finish(),
|
|
||||||
Self::Service(inner) => f.debug_tuple("Service").field(inner).finish(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B, E> Clone for FallbackRoute<B, E> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Default(inner) => Self::Default(inner.clone()),
|
|
||||||
Self::Service(inner) => Self::Service(inner.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B, E> FallbackRoute<B, E> {
|
|
||||||
pub(crate) fn oneshot_inner(
|
|
||||||
&mut self,
|
|
||||||
req: Request<B>,
|
|
||||||
) -> Oneshot<BoxCloneService<Request<B>, Response, E>, Request<B>> {
|
|
||||||
match self {
|
|
||||||
FallbackRoute::Default(inner) => inner.oneshot_inner(req),
|
|
||||||
FallbackRoute::Service(inner) => inner.oneshot_inner(req),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::large_enum_variant)] // This type is only used at init time, probably fine
|
|
||||||
enum Endpoint<S, B> {
|
enum Endpoint<S, B> {
|
||||||
MethodRouter(MethodRouter<S, B>),
|
MethodRouter(MethodRouter<S, B>),
|
||||||
Route(Route<B>),
|
Route(Route<B>),
|
||||||
@@ -662,14 +741,6 @@ where
|
|||||||
B: HttpBody + Send + 'static,
|
B: HttpBody + Send + 'static,
|
||||||
S: Clone + Send + Sync + 'static,
|
S: Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
fn into_route(self, state: S) -> Route<B> {
|
|
||||||
match self {
|
|
||||||
Endpoint::MethodRouter(method_router) => Route::new(method_router.with_state(state)),
|
|
||||||
Endpoint::Route(route) => route,
|
|
||||||
Endpoint::NestedRouter(router) => router.into_route(state),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn layer<L, NewReqBody>(self, layer: L) -> Endpoint<S, NewReqBody>
|
fn layer<L, NewReqBody>(self, layer: L) -> Endpoint<S, NewReqBody>
|
||||||
where
|
where
|
||||||
L: Layer<Route<B>> + Clone + Send + 'static,
|
L: Layer<Route<B>> + Clone + Send + 'static,
|
||||||
@@ -677,7 +748,7 @@ where
|
|||||||
<L::Service as Service<Request<NewReqBody>>>::Response: IntoResponse + 'static,
|
<L::Service as Service<Request<NewReqBody>>>::Response: IntoResponse + 'static,
|
||||||
<L::Service as Service<Request<NewReqBody>>>::Error: Into<Infallible> + 'static,
|
<L::Service as Service<Request<NewReqBody>>>::Error: Into<Infallible> + 'static,
|
||||||
<L::Service as Service<Request<NewReqBody>>>::Future: Send + 'static,
|
<L::Service as Service<Request<NewReqBody>>>::Future: Send + 'static,
|
||||||
NewReqBody: 'static,
|
NewReqBody: HttpBody + 'static,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Endpoint::MethodRouter(method_router) => {
|
Endpoint::MethodRouter(method_router) => {
|
||||||
@@ -721,6 +792,8 @@ enum RouterOrService<S, B, T> {
|
|||||||
Service(T),
|
Service(T),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SuperFallback<B>(SyncWrapper<Route<B>>);
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(warnings)]
|
#[allow(warnings)]
|
||||||
fn traits() {
|
fn traits() {
|
||||||
|
|||||||
@@ -1,225 +0,0 @@
|
|||||||
use super::{
|
|
||||||
future::RouteFuture, url_params, FallbackRoute, IntoMakeService, Node, Route, RouteId, Router,
|
|
||||||
};
|
|
||||||
use crate::{
|
|
||||||
body::{Body, HttpBody},
|
|
||||||
response::Response,
|
|
||||||
};
|
|
||||||
use axum_core::response::IntoResponse;
|
|
||||||
use http::Request;
|
|
||||||
use matchit::MatchError;
|
|
||||||
use std::{
|
|
||||||
collections::HashMap,
|
|
||||||
convert::Infallible,
|
|
||||||
sync::Arc,
|
|
||||||
task::{Context, Poll},
|
|
||||||
};
|
|
||||||
use sync_wrapper::SyncWrapper;
|
|
||||||
use tower::Service;
|
|
||||||
use tower_layer::Layer;
|
|
||||||
|
|
||||||
/// A [`Router`] converted into a [`Service`].
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct RouterService<B = Body> {
|
|
||||||
routes: HashMap<RouteId, Route<B>>,
|
|
||||||
node: Arc<Node>,
|
|
||||||
fallback: FallbackRoute<B>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> RouterService<B>
|
|
||||||
where
|
|
||||||
B: HttpBody + Send + 'static,
|
|
||||||
{
|
|
||||||
pub(super) fn new<S>(router: Router<S, B>, state: S) -> Self
|
|
||||||
where
|
|
||||||
S: Clone + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
let fallback = router.fallback.into_fallback_route(&state);
|
|
||||||
|
|
||||||
let routes = router
|
|
||||||
.routes
|
|
||||||
.into_iter()
|
|
||||||
.map(|(route_id, endpoint)| {
|
|
||||||
let route = endpoint.into_route(state.clone());
|
|
||||||
(route_id, route)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
routes,
|
|
||||||
node: router.node,
|
|
||||||
fallback,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn call_route(
|
|
||||||
&self,
|
|
||||||
match_: matchit::Match<&RouteId>,
|
|
||||||
mut req: Request<B>,
|
|
||||||
) -> RouteFuture<B, Infallible> {
|
|
||||||
let id = *match_.value;
|
|
||||||
|
|
||||||
#[cfg(feature = "matched-path")]
|
|
||||||
crate::extract::matched_path::set_matched_path_for_request(
|
|
||||||
id,
|
|
||||||
&self.node.route_id_to_path,
|
|
||||||
req.extensions_mut(),
|
|
||||||
);
|
|
||||||
|
|
||||||
url_params::insert_url_params(req.extensions_mut(), match_.params);
|
|
||||||
|
|
||||||
let mut route = self
|
|
||||||
.routes
|
|
||||||
.get(&id)
|
|
||||||
.expect("no route for id. This is a bug in axum. Please file an issue")
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
route.call(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Apply a [`tower::Layer`] to all routes in the router.
|
|
||||||
///
|
|
||||||
/// See [`Router::layer`] for more details.
|
|
||||||
pub fn layer<L, NewReqBody>(self, layer: L) -> RouterService<NewReqBody>
|
|
||||||
where
|
|
||||||
L: Layer<Route<B>> + Clone + Send + 'static,
|
|
||||||
L::Service: Service<Request<NewReqBody>> + Clone + Send + 'static,
|
|
||||||
<L::Service as Service<Request<NewReqBody>>>::Response: IntoResponse + 'static,
|
|
||||||
<L::Service as Service<Request<NewReqBody>>>::Error: Into<Infallible> + 'static,
|
|
||||||
<L::Service as Service<Request<NewReqBody>>>::Future: Send + 'static,
|
|
||||||
NewReqBody: 'static,
|
|
||||||
{
|
|
||||||
let routes = self
|
|
||||||
.routes
|
|
||||||
.into_iter()
|
|
||||||
.map(|(id, route)| (id, route.layer(layer.clone())))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let fallback = self.fallback.layer(layer);
|
|
||||||
|
|
||||||
RouterService {
|
|
||||||
routes,
|
|
||||||
node: self.node,
|
|
||||||
fallback,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Apply a [`tower::Layer`] to the router that will only run if the request matches
|
|
||||||
/// a route.
|
|
||||||
///
|
|
||||||
/// See [`Router::route_layer`] for more details.
|
|
||||||
pub fn route_layer<L>(self, layer: L) -> Self
|
|
||||||
where
|
|
||||||
L: Layer<Route<B>> + Clone + Send + 'static,
|
|
||||||
L::Service: Service<Request<B>> + Clone + Send + 'static,
|
|
||||||
<L::Service as Service<Request<B>>>::Response: IntoResponse + 'static,
|
|
||||||
<L::Service as Service<Request<B>>>::Error: Into<Infallible> + 'static,
|
|
||||||
<L::Service as Service<Request<B>>>::Future: Send + 'static,
|
|
||||||
{
|
|
||||||
let routes = self
|
|
||||||
.routes
|
|
||||||
.into_iter()
|
|
||||||
.map(|(id, route)| (id, route.layer(layer.clone())))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
routes,
|
|
||||||
node: self.node,
|
|
||||||
fallback: self.fallback,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert the `RouterService` into a [`MakeService`].
|
|
||||||
///
|
|
||||||
/// See [`Router::into_make_service`] for more details.
|
|
||||||
///
|
|
||||||
/// [`MakeService`]: tower::make::MakeService
|
|
||||||
pub fn into_make_service(self) -> IntoMakeService<Self> {
|
|
||||||
IntoMakeService::new(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert the `RouterService` into a [`MakeService`] which stores information
|
|
||||||
/// about the incoming connection.
|
|
||||||
///
|
|
||||||
/// See [`Router::into_make_service_with_connect_info`] for more details.
|
|
||||||
///
|
|
||||||
/// [`MakeService`]: tower::make::MakeService
|
|
||||||
#[cfg(feature = "tokio")]
|
|
||||||
pub fn into_make_service_with_connect_info<C>(
|
|
||||||
self,
|
|
||||||
) -> crate::extract::connect_info::IntoMakeServiceWithConnectInfo<Self, C> {
|
|
||||||
crate::extract::connect_info::IntoMakeServiceWithConnectInfo::new(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> Clone for RouterService<B> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
routes: self.routes.clone(),
|
|
||||||
node: Arc::clone(&self.node),
|
|
||||||
fallback: self.fallback.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> Service<Request<B>> for RouterService<B>
|
|
||||||
where
|
|
||||||
B: HttpBody + Send + 'static,
|
|
||||||
{
|
|
||||||
type Response = Response;
|
|
||||||
type Error = Infallible;
|
|
||||||
type Future = RouteFuture<B, Infallible>;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn call(&mut self, mut req: Request<B>) -> Self::Future {
|
|
||||||
#[cfg(feature = "original-uri")]
|
|
||||||
{
|
|
||||||
use crate::extract::OriginalUri;
|
|
||||||
|
|
||||||
if req.extensions().get::<OriginalUri>().is_none() {
|
|
||||||
let original_uri = OriginalUri(req.uri().clone());
|
|
||||||
req.extensions_mut().insert(original_uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = req.uri().path().to_owned();
|
|
||||||
|
|
||||||
match self.node.at(&path) {
|
|
||||||
Ok(match_) => {
|
|
||||||
match &self.fallback {
|
|
||||||
FallbackRoute::Default(_) => {}
|
|
||||||
FallbackRoute::Service(fallback) => {
|
|
||||||
req.extensions_mut()
|
|
||||||
.insert(SuperFallback(SyncWrapper::new(fallback.clone())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.call_route(match_, req)
|
|
||||||
}
|
|
||||||
Err(
|
|
||||||
MatchError::NotFound
|
|
||||||
| MatchError::ExtraTrailingSlash
|
|
||||||
| MatchError::MissingTrailingSlash,
|
|
||||||
) => match &mut self.fallback {
|
|
||||||
FallbackRoute::Default(fallback) => {
|
|
||||||
if let Some(super_fallback) = req.extensions_mut().remove::<SuperFallback<B>>()
|
|
||||||
{
|
|
||||||
let mut super_fallback = super_fallback.0.into_inner();
|
|
||||||
super_fallback.call(req)
|
|
||||||
} else {
|
|
||||||
fallback.call(req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FallbackRoute::Service(fallback) => fallback.call(req),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SuperFallback<B>(SyncWrapper<Route<B>>);
|
|
||||||
@@ -56,7 +56,7 @@ async fn fallback_accessing_state() {
|
|||||||
.fallback(|State(state): State<&'static str>| async move { state })
|
.fallback(|State(state): State<&'static str>| async move { state })
|
||||||
.with_state("state");
|
.with_state("state");
|
||||||
|
|
||||||
let client = TestClient::from_service(app);
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
let res = client.get("/does-not-exist").send().await;
|
let res = client.get("/does-not-exist").send().await;
|
||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ mod for_handlers {
|
|||||||
|
|
||||||
// don't use reqwest because it always strips bodies from HEAD responses
|
// don't use reqwest because it always strips bodies from HEAD responses
|
||||||
let res = app
|
let res = app
|
||||||
.into_service()
|
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.uri("/")
|
.uri("/")
|
||||||
@@ -55,7 +54,6 @@ mod for_services {
|
|||||||
|
|
||||||
// don't use reqwest because it always strips bodies from HEAD responses
|
// don't use reqwest because it always strips bodies from HEAD responses
|
||||||
let res = app
|
let res = app
|
||||||
.into_service()
|
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.uri("/")
|
.uri("/")
|
||||||
|
|||||||
@@ -447,11 +447,11 @@ async fn middleware_still_run_for_unmatched_requests() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[should_panic(expected = "\
|
#[should_panic(expected = "\
|
||||||
Invalid route: `Router::route_service` cannot be used with `RouterService`s. \
|
Invalid route: `Router::route_service` cannot be used with `Router`s. \
|
||||||
Use `Router::nest` instead\
|
Use `Router::nest` instead\
|
||||||
")]
|
")]
|
||||||
async fn routing_to_router_panics() {
|
async fn routing_to_router_panics() {
|
||||||
TestClient::new(Router::new().route_service("/", Router::new().into_service()));
|
TestClient::new(Router::new().route_service("/", Router::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -761,7 +761,7 @@ async fn extract_state() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let app = Router::new().route("/", get(handler)).with_state(state);
|
let app = Router::new().route("/", get(handler)).with_state(state);
|
||||||
let client = TestClient::from_service(app);
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
let res = client.get("/").send().await;
|
let res = client.get("/").send().await;
|
||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
@@ -776,7 +776,7 @@ async fn explicitly_set_state() {
|
|||||||
)
|
)
|
||||||
.with_state("...");
|
.with_state("...");
|
||||||
|
|
||||||
let client = TestClient::from_service(app);
|
let client = TestClient::new(app);
|
||||||
let res = client.get("/").send().await;
|
let res = client.get("/").send().await;
|
||||||
assert_eq!(res.text().await, "foo");
|
assert_eq!(res.text().await, "foo");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#![allow(clippy::disallowed_names)]
|
#![allow(clippy::disallowed_names)]
|
||||||
|
|
||||||
use crate::{body::HttpBody, BoxError, Router};
|
use crate::{body::HttpBody, BoxError};
|
||||||
|
|
||||||
mod test_client;
|
mod test_client;
|
||||||
pub(crate) use self::test_client::*;
|
pub(crate) use self::test_client::*;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use super::{BoxError, HttpBody, Router};
|
use super::{BoxError, HttpBody};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use http::{
|
use http::{
|
||||||
header::{HeaderName, HeaderValue},
|
header::{HeaderName, HeaderValue},
|
||||||
@@ -15,11 +15,7 @@ pub(crate) struct TestClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TestClient {
|
impl TestClient {
|
||||||
pub(crate) fn new(router: Router<(), Body>) -> Self {
|
pub(crate) fn new<S, ResBody>(svc: S) -> Self
|
||||||
Self::from_service(router.into_service())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn from_service<S, ResBody>(svc: S) -> Self
|
|
||||||
where
|
where
|
||||||
S: Service<Request<Body>, Response = http::Response<ResBody>> + Clone + Send + 'static,
|
S: Service<Request<Body>, Response = http::Response<ResBody>> + Clone + Send + 'static,
|
||||||
ResBody: HttpBody + Send + 'static,
|
ResBody: HttpBody + Send + 'static,
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get() {
|
async fn test_get() {
|
||||||
let app = app().into_service();
|
let app = app();
|
||||||
|
|
||||||
let response = app
|
let response = app
|
||||||
.oneshot(Request::get("/get-head").body(Body::empty()).unwrap())
|
.oneshot(Request::get("/get-head").body(Body::empty()).unwrap())
|
||||||
@@ -66,7 +66,7 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_implicit_head() {
|
async fn test_implicit_head() {
|
||||||
let app = app().into_service();
|
let app = app();
|
||||||
|
|
||||||
let response = app
|
let response = app
|
||||||
.oneshot(Request::head("/get-head").body(Body::empty()).unwrap())
|
.oneshot(Request::head("/get-head").body(Body::empty()).unwrap())
|
||||||
|
|||||||
@@ -35,9 +35,7 @@ async fn main() {
|
|||||||
.with(tracing_subscriber::fmt::layer())
|
.with(tracing_subscriber::fmt::layer())
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
let router_svc = Router::new()
|
let router_svc = Router::new().route("/", get(|| async { "Hello, World!" }));
|
||||||
.route("/", get(|| async { "Hello, World!" }))
|
|
||||||
.into_service();
|
|
||||||
|
|
||||||
let service = tower::service_fn(move |req: Request<Body>| {
|
let service = tower::service_fn(move |req: Request<Body>| {
|
||||||
let router_svc = router_svc.clone();
|
let router_svc = router_svc.clone();
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ use std::{
|
|||||||
use tower::{BoxError, ServiceBuilder};
|
use tower::{BoxError, ServiceBuilder};
|
||||||
use tower_http::{
|
use tower_http::{
|
||||||
auth::RequireAuthorizationLayer, compression::CompressionLayer, limit::RequestBodyLimitLayer,
|
auth::RequireAuthorizationLayer, compression::CompressionLayer, limit::RequestBodyLimitLayer,
|
||||||
trace::TraceLayer, ServiceBuilderExt,
|
trace::TraceLayer,
|
||||||
};
|
};
|
||||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
|
|
||||||
|
|||||||
@@ -104,7 +104,6 @@ mod tests {
|
|||||||
|
|
||||||
async fn send_request_get_body(query: &str) -> String {
|
async fn send_request_get_body(query: &str) -> String {
|
||||||
let body = app()
|
let body = app()
|
||||||
.into_service()
|
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.uri(format!("/?{}", query))
|
.uri(format!("/?{}", query))
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ async fn main() {
|
|||||||
.init();
|
.init();
|
||||||
|
|
||||||
// build the rest service
|
// build the rest service
|
||||||
let rest = Router::new().route("/", get(web_root)).into_service();
|
let rest = Router::new().route("/", get(web_root));
|
||||||
|
|
||||||
// build the grpc service
|
// build the grpc service
|
||||||
let grpc = GreeterServer::new(GrpcServiceImpl::default());
|
let grpc = GreeterServer::new(GrpcServiceImpl::default());
|
||||||
|
|||||||
@@ -40,8 +40,7 @@ fn main() {
|
|||||||
|
|
||||||
#[allow(clippy::let_and_return)]
|
#[allow(clippy::let_and_return)]
|
||||||
async fn app(request: Request<String>) -> Response {
|
async fn app(request: Request<String>) -> Response {
|
||||||
let mut router = Router::new().route("/api/", get(index)).into_service();
|
let mut router = Router::new().route("/api/", get(index));
|
||||||
|
|
||||||
let response = router.call(request).await.unwrap();
|
let response = router.call(request).await.unwrap();
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn hello_world() {
|
async fn hello_world() {
|
||||||
let app = app().into_service();
|
let app = app();
|
||||||
|
|
||||||
// `Router` 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.
|
||||||
@@ -78,7 +78,7 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn json() {
|
async fn json() {
|
||||||
let app = app().into_service();
|
let app = app();
|
||||||
|
|
||||||
let response = app
|
let response = app
|
||||||
.oneshot(
|
.oneshot(
|
||||||
@@ -103,7 +103,7 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn not_found() {
|
async fn not_found() {
|
||||||
let app = app().into_service();
|
let app = app();
|
||||||
|
|
||||||
let response = app
|
let response = app
|
||||||
.oneshot(
|
.oneshot(
|
||||||
@@ -154,7 +154,7 @@ mod tests {
|
|||||||
// in multiple request
|
// in multiple request
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn multiple_request() {
|
async fn multiple_request() {
|
||||||
let mut app = app().into_service();
|
let mut app = app();
|
||||||
|
|
||||||
let request = Request::builder().uri("/").body(Body::empty()).unwrap();
|
let request = Request::builder().uri("/").body(Body::empty()).unwrap();
|
||||||
let response = app.ready().await.unwrap().call(request).await.unwrap();
|
let response = app.ready().await.unwrap().call(request).await.unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user