mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-29 00:00:18 +02:00
wip
This commit is contained in:
@@ -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());
|
||||||
|
|||||||
+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
|
||||||
// ```
|
// ```
|
||||||
|
|||||||
+4
-4
@@ -21,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,
|
||||||
@@ -98,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())
|
||||||
@@ -113,7 +114,7 @@ where
|
|||||||
request: Request<B>,
|
request: Request<B>,
|
||||||
state: S,
|
state: S,
|
||||||
) -> RouteFuture<B, Infallible> {
|
) -> RouteFuture<B, Infallible> {
|
||||||
todo!()
|
self.into_route(state).call(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,8 +194,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn call_with_state(self: Box<Self>, request: Request<B2>, state: S) -> RouteFuture<B2, E2> {
|
fn call_with_state(self: Box<Self>, request: Request<B2>, state: S) -> RouteFuture<B2, E2> {
|
||||||
let route = (self.layer)(self.inner.into_route(state));
|
(self.layer)(self.inner.into_route(state)).call(request)
|
||||||
route.call(request)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -359,7 +359,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);
|
||||||
@@ -382,7 +382,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
@@ -475,7 +475,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")]
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
+177
-251
@@ -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`].
|
||||||
///
|
///
|
||||||
@@ -707,7 +707,7 @@ where
|
|||||||
|
|
||||||
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.
|
/// TODO(david): docs
|
||||||
///
|
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::*;
|
||||||
|
|||||||
+59
-102
@@ -20,7 +20,6 @@ use std::{
|
|||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
use sync_wrapper::SyncWrapper;
|
use sync_wrapper::SyncWrapper;
|
||||||
use tower::util::{BoxCloneService, Oneshot};
|
|
||||||
use tower_layer::Layer;
|
use tower_layer::Layer;
|
||||||
use tower_service::Service;
|
use tower_service::Service;
|
||||||
|
|
||||||
@@ -34,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,
|
||||||
@@ -175,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"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -325,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
|
||||||
@@ -401,11 +396,32 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert this router into a [`RouterService`] by providing the state.
|
/// TODO(david): docs
|
||||||
///
|
pub fn with_state<S2>(self, state: S) -> Router<S2, B> {
|
||||||
/// Once this method has been called you cannot add more routes. So it must be called as last.
|
let routes = self
|
||||||
pub fn with_state(self, state: S) -> RouterService<B> {
|
.routes
|
||||||
RouterService::new(self, state)
|
.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(
|
pub(crate) fn call_with_state(
|
||||||
@@ -446,29 +462,22 @@ where
|
|||||||
MatchError::NotFound
|
MatchError::NotFound
|
||||||
| MatchError::ExtraTrailingSlash
|
| MatchError::ExtraTrailingSlash
|
||||||
| MatchError::MissingTrailingSlash,
|
| MatchError::MissingTrailingSlash,
|
||||||
) => {
|
) => match &mut self.fallback {
|
||||||
match &mut self.fallback {
|
Fallback::Default(fallback) => {
|
||||||
Fallback::Default(fallback) => {
|
if let Some(super_fallback) = req.extensions_mut().remove::<SuperFallback<B>>()
|
||||||
if let Some(super_fallback) =
|
{
|
||||||
req.extensions_mut().remove::<SuperFallback<B>>()
|
let mut super_fallback = super_fallback.0.into_inner();
|
||||||
{
|
super_fallback.call(req)
|
||||||
let mut super_fallback = super_fallback.0.into_inner();
|
} else {
|
||||||
super_fallback.call(req)
|
fallback.call(req)
|
||||||
} else {
|
|
||||||
fallback.call(req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Fallback::Service(fallback) => fallback.call(req),
|
|
||||||
Fallback::BoxedHandler(handler) => {
|
|
||||||
todo!()
|
|
||||||
// handler.clone().into_route(state).call(req)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Fallback::Service(fallback) => fallback.call(req),
|
||||||
|
Fallback::BoxedHandler(handler) => handler.clone().into_route(state).call(req),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(david): fix duplication
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn call_route(
|
fn call_route(
|
||||||
&self,
|
&self,
|
||||||
@@ -494,12 +503,8 @@ where
|
|||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
match endpont {
|
match endpont {
|
||||||
Endpoint::MethodRouter(method_router) => {
|
Endpoint::MethodRouter(mut method_router) => method_router.call_with_state(req, state),
|
||||||
// method_router.call(req)
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
Endpoint::Route(mut route) => route.call(req),
|
Endpoint::Route(mut route) => route.call(req),
|
||||||
// TODO(david): optimize?
|
|
||||||
Endpoint::NestedRouter(router) => router.call_with_state(req, state),
|
Endpoint::NestedRouter(router) => router.call_with_state(req, state),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -509,16 +514,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.
|
||||||
///
|
///
|
||||||
@@ -545,16 +540,18 @@ where
|
|||||||
/// `()`). Use [`RouterService::into_make_service`] otherwise.
|
/// `()`). 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(()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -637,16 +634,6 @@ 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,
|
||||||
@@ -662,6 +649,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> {
|
||||||
@@ -690,24 +685,6 @@ pub(crate) enum FallbackRoute<B, E = Infallible> {
|
|||||||
Service(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> {
|
impl<B, E> fmt::Debug for FallbackRoute<B, E> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
@@ -726,18 +703,6 @@ impl<B, E> Clone for FallbackRoute<B, E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
#[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>),
|
||||||
@@ -750,14 +715,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,
|
||||||
@@ -765,7 +722,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) => {
|
||||||
|
|||||||
@@ -1,224 +0,0 @@
|
|||||||
use super::{
|
|
||||||
future::RouteFuture, url_params, FallbackRoute, IntoMakeService, Node, Route, RouteId, Router,
|
|
||||||
SuperFallback,
|
|
||||||
};
|
|
||||||
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),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user