mirror of
https://github.com/tokio-rs/axum.git
synced 2025-03-13 19:27:53 +01:00
Split RouterService off of Router (#1381)
This commit is contained in:
parent
18e3fac5d3
commit
69d64cecc3
26 changed files with 371 additions and 225 deletions
|
@ -258,7 +258,8 @@ mod tests {
|
|||
let app = Router::<_, Body>::with_state(state)
|
||||
.route("/set", get(set_cookie))
|
||||
.route("/get", get(get_cookie))
|
||||
.route("/remove", get(remove_cookie));
|
||||
.route("/remove", get(remove_cookie))
|
||||
.into_service();
|
||||
|
||||
let res = app
|
||||
.clone()
|
||||
|
@ -344,7 +345,9 @@ mod tests {
|
|||
custom_key: CustomKey(Key::generate()),
|
||||
};
|
||||
|
||||
let app = Router::<_, Body>::with_state(state).route("/get", get(get_cookie));
|
||||
let app = Router::<_, Body>::with_state(state)
|
||||
.route("/get", get(get_cookie))
|
||||
.into_service();
|
||||
|
||||
let res = app
|
||||
.clone()
|
||||
|
|
|
@ -95,7 +95,7 @@ pub mod __private {
|
|||
pub(crate) mod test_helpers {
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use axum::{body::HttpBody, BoxError};
|
||||
use axum::{body::HttpBody, BoxError, Router};
|
||||
|
||||
mod test_client {
|
||||
#![allow(dead_code)]
|
||||
|
|
|
@ -159,7 +159,7 @@ impl<B> From<Resource<B>> for Router<B> {
|
|||
mod tests {
|
||||
#[allow(unused_imports)]
|
||||
use super::*;
|
||||
use axum::{extract::Path, http::Method, Router};
|
||||
use axum::{extract::Path, http::Method, routing::RouterService, Router};
|
||||
use http::Request;
|
||||
use tower::{Service, ServiceExt};
|
||||
|
||||
|
@ -174,7 +174,7 @@ mod tests {
|
|||
.update(|Path(id): Path<u64>| async move { format!("users#update id={}", id) })
|
||||
.destroy(|Path(id): Path<u64>| async move { format!("users#destroy id={}", id) });
|
||||
|
||||
let mut app = Router::new().merge(users);
|
||||
let mut app = Router::new().merge(users).into_service();
|
||||
|
||||
assert_eq!(
|
||||
call_route(&mut app, Method::GET, "/users").await,
|
||||
|
@ -217,7 +217,7 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
async fn call_route(app: &mut Router<()>, method: Method, uri: &str) -> String {
|
||||
async fn call_route(app: &mut RouterService, method: Method, uri: &str) -> String {
|
||||
let res = app
|
||||
.ready()
|
||||
.await
|
||||
|
|
|
@ -555,7 +555,7 @@ async fn rewrite_request_uri<B>(req: Request<B>, next: Next<B>) -> Response {
|
|||
// this can be any `tower::Layer`
|
||||
let middleware = axum::middleware::from_fn(rewrite_request_uri);
|
||||
|
||||
let app = Router::new();
|
||||
let app = Router::new().into_service();
|
||||
|
||||
// apply the layer around the whole `Router`
|
||||
// this way the middleware will run before `Router` receives the request
|
||||
|
|
|
@ -16,10 +16,10 @@ let user_routes = Router::new().route("/:id", get(|| async {}));
|
|||
let team_routes = Router::new().route("/", post(|| async {}));
|
||||
|
||||
let api_routes = Router::new()
|
||||
.nest("/users", user_routes)
|
||||
.nest("/teams", team_routes);
|
||||
.nest("/users", user_routes.into_service())
|
||||
.nest("/teams", team_routes.into_service());
|
||||
|
||||
let app = Router::new().nest("/api", api_routes);
|
||||
let app = Router::new().nest("/api", api_routes.into_service());
|
||||
|
||||
// Our app now accepts
|
||||
// - GET /api/users/:id
|
||||
|
@ -58,7 +58,7 @@ async fn users_get(Path(params): Path<HashMap<String, String>>) {
|
|||
|
||||
let users_api = Router::new().route("/users/:id", get(users_get));
|
||||
|
||||
let app = Router::new().nest("/:version/api", users_api);
|
||||
let app = Router::new().nest("/:version/api", users_api.into_service());
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
|
@ -82,7 +82,7 @@ let app = Router::new()
|
|||
.route("/foo/*rest", get(|uri: Uri| async {
|
||||
// `uri` will contain `/foo`
|
||||
}))
|
||||
.nest("/bar", nested_router);
|
||||
.nest("/bar", nested_router.into_service());
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
|
@ -103,7 +103,7 @@ async fn fallback() -> (StatusCode, &'static str) {
|
|||
let api_routes = Router::new().nest("/users", get(|| async {}));
|
||||
|
||||
let app = Router::new()
|
||||
.nest("/api", api_routes)
|
||||
.nest("/api", api_routes.into_service())
|
||||
.fallback(fallback);
|
||||
# let _: Router = app;
|
||||
```
|
||||
|
@ -135,7 +135,7 @@ let api_routes = Router::new()
|
|||
.fallback(api_fallback);
|
||||
|
||||
let app = Router::new()
|
||||
.nest("/api", api_routes)
|
||||
.nest("/api", api_routes.into_service())
|
||||
.fallback(fallback);
|
||||
# let _: Router = app;
|
||||
```
|
||||
|
|
|
@ -69,7 +69,7 @@ use axum::{routing::get, Router};
|
|||
|
||||
let app = Router::new().route_service(
|
||||
"/",
|
||||
Router::new().route("/foo", get(|| async {})),
|
||||
Router::new().route("/foo", get(|| async {})).into_service(),
|
||||
);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
|
|
|
@ -148,7 +148,7 @@ mod tests {
|
|||
"/:key",
|
||||
get(|path: MatchedPath| async move { path.as_str().to_owned() }),
|
||||
)
|
||||
.nest("/api", api)
|
||||
.nest("/api", api.into_service())
|
||||
.nest(
|
||||
"/public",
|
||||
Router::new()
|
||||
|
@ -156,7 +156,8 @@ mod tests {
|
|||
// have to set the middleware here since otherwise the
|
||||
// matched path is just `/public/*` since we're nesting
|
||||
// this router
|
||||
.layer(layer_fn(SetMatchedPathExtension)),
|
||||
.layer(layer_fn(SetMatchedPathExtension))
|
||||
.into_service(),
|
||||
)
|
||||
.nest("/foo", handler.into_service())
|
||||
.layer(layer_fn(SetMatchedPathExtension));
|
||||
|
@ -197,10 +198,12 @@ mod tests {
|
|||
async fn nested_opaque_routers_append_to_matched_path() {
|
||||
let app = Router::new().nest(
|
||||
"/:a",
|
||||
Router::new().route(
|
||||
"/:b",
|
||||
get(|path: MatchedPath| async move { path.as_str().to_owned() }),
|
||||
),
|
||||
Router::new()
|
||||
.route(
|
||||
"/:b",
|
||||
get(|path: MatchedPath| async move { path.as_str().to_owned() }),
|
||||
)
|
||||
.into_service(),
|
||||
);
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
|
|
@ -38,7 +38,7 @@ use sync_wrapper::SyncWrapper;
|
|||
/// }),
|
||||
/// );
|
||||
///
|
||||
/// let app = Router::new().nest("/api", api_routes);
|
||||
/// let app = Router::new().nest("/api", api_routes.into_service());
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
|
@ -75,7 +75,7 @@ use sync_wrapper::SyncWrapper;
|
|||
/// }),
|
||||
/// );
|
||||
///
|
||||
/// let app = Router::new().nest("/api", api_routes);
|
||||
/// let app = Router::new().nest("/api", api_routes.into_service());
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
|
|
|
@ -375,7 +375,7 @@ mod tests {
|
|||
format!("you said: {}", body)
|
||||
}
|
||||
|
||||
let client = TestClient::new(handle.into_service());
|
||||
let client = TestClient::from_service(handle.into_service());
|
||||
|
||||
let res = client.post("/").body("hi there!").send().await;
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
|
|
|
@ -470,7 +470,7 @@ pub use self::extension::Extension;
|
|||
#[cfg(feature = "json")]
|
||||
pub use self::json::Json;
|
||||
#[doc(inline)]
|
||||
pub use self::routing::Router;
|
||||
pub use self::routing::{Router, RouterService};
|
||||
|
||||
#[doc(inline)]
|
||||
#[cfg(feature = "headers")]
|
||||
|
|
|
@ -404,6 +404,7 @@ mod tests {
|
|||
.layer(from_fn(insert_header));
|
||||
|
||||
let res = app
|
||||
.into_service()
|
||||
.oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
|
@ -1,24 +1,17 @@
|
|||
//! Routing between [`Service`]s and handlers.
|
||||
|
||||
use self::{future::RouteFuture, not_found::NotFound};
|
||||
use self::not_found::NotFound;
|
||||
use crate::{
|
||||
body::{Body, HttpBody},
|
||||
extract::connect_info::IntoMakeServiceWithConnectInfo,
|
||||
handler::Handler,
|
||||
response::Response,
|
||||
util::try_downcast,
|
||||
Extension,
|
||||
};
|
||||
use axum_core::response::IntoResponse;
|
||||
use http::Request;
|
||||
use matchit::MatchError;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
convert::Infallible,
|
||||
fmt,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use std::{collections::HashMap, convert::Infallible, fmt, sync::Arc};
|
||||
use tower::{util::MapResponseLayer, ServiceBuilder};
|
||||
use tower_layer::Layer;
|
||||
use tower_service::Service;
|
||||
|
@ -33,10 +26,14 @@ mod route;
|
|||
mod strip_prefix;
|
||||
pub(crate) mod url_params;
|
||||
|
||||
mod service;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use self::{into_make_service::IntoMakeService, method_filter::MethodFilter, route::Route};
|
||||
pub use self::{
|
||||
into_make_service::IntoMakeService, method_filter::MethodFilter, route::Route,
|
||||
service::RouterService,
|
||||
};
|
||||
|
||||
pub use self::method_routing::{
|
||||
any, any_service, delete, delete_service, get, get_service, head, head_service, on, on_service,
|
||||
|
@ -226,9 +223,12 @@ where
|
|||
panic!("Paths must start with a `/`");
|
||||
}
|
||||
|
||||
let service = match try_downcast::<Router<S, B>, _>(service) {
|
||||
let service = match try_downcast::<RouterService<B>, _>(service) {
|
||||
Ok(_) => {
|
||||
panic!("Invalid route: `Router::route_service` cannot be used with `Router`s. Use `Router::nest` instead")
|
||||
panic!(
|
||||
"Invalid route: `Router::route_service` cannot be used with `RouterService`s. \
|
||||
Use `Router::nest` instead"
|
||||
);
|
||||
}
|
||||
Err(svc) => svc,
|
||||
};
|
||||
|
@ -438,6 +438,11 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Convert this router into a [`RouterService`].
|
||||
pub fn into_service(self) -> RouterService<B> {
|
||||
RouterService::new(self)
|
||||
}
|
||||
|
||||
/// Convert this router into a [`MakeService`], that is a [`Service`] whose
|
||||
/// response is another service.
|
||||
///
|
||||
|
@ -461,73 +466,15 @@ where
|
|||
/// ```
|
||||
///
|
||||
/// [`MakeService`]: tower::make::MakeService
|
||||
pub fn into_make_service(self) -> IntoMakeService<Self> {
|
||||
IntoMakeService::new(self)
|
||||
pub fn into_make_service(self) -> IntoMakeService<RouterService<B>> {
|
||||
IntoMakeService::new(self.into_service())
|
||||
}
|
||||
|
||||
#[doc = include_str!("../docs/routing/into_make_service_with_connect_info.md")]
|
||||
pub fn into_make_service_with_connect_info<C>(self) -> IntoMakeServiceWithConnectInfo<Self, C> {
|
||||
IntoMakeServiceWithConnectInfo::new(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn call_route(
|
||||
&self,
|
||||
match_: matchit::Match<&RouteId>,
|
||||
mut req: Request<B>,
|
||||
) -> RouteFuture<B, Infallible> {
|
||||
let id = *match_.value;
|
||||
|
||||
#[cfg(feature = "matched-path")]
|
||||
{
|
||||
fn set_matched_path(
|
||||
id: RouteId,
|
||||
route_id_to_path: &HashMap<RouteId, Arc<str>>,
|
||||
extensions: &mut http::Extensions,
|
||||
) {
|
||||
if let Some(matched_path) = route_id_to_path.get(&id) {
|
||||
use crate::extract::MatchedPath;
|
||||
|
||||
let matched_path = if let Some(previous) = extensions.get::<MatchedPath>() {
|
||||
// a previous `MatchedPath` might exist if we're inside a nested Router
|
||||
let previous = if let Some(previous) =
|
||||
previous.as_str().strip_suffix(NEST_TAIL_PARAM_CAPTURE)
|
||||
{
|
||||
previous
|
||||
} else {
|
||||
previous.as_str()
|
||||
};
|
||||
|
||||
let matched_path = format!("{}{}", previous, matched_path);
|
||||
matched_path.into()
|
||||
} else {
|
||||
Arc::clone(matched_path)
|
||||
};
|
||||
extensions.insert(MatchedPath(matched_path));
|
||||
} else {
|
||||
#[cfg(debug_assertions)]
|
||||
panic!("should always have a matched path for a route id");
|
||||
}
|
||||
}
|
||||
|
||||
set_matched_path(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();
|
||||
|
||||
match &mut route {
|
||||
Endpoint::MethodRouter(inner) => inner
|
||||
.clone()
|
||||
.with_state_arc(Arc::clone(&self.state))
|
||||
.call(req),
|
||||
Endpoint::Route(inner) => inner.call(req),
|
||||
}
|
||||
pub fn into_make_service_with_connect_info<C>(
|
||||
self,
|
||||
) -> IntoMakeServiceWithConnectInfo<RouterService<B>, C> {
|
||||
IntoMakeServiceWithConnectInfo::new(self.into_service())
|
||||
}
|
||||
|
||||
/// Get a reference to the state.
|
||||
|
@ -536,48 +483,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<S, B> Service<Request<B>> for Router<S, B>
|
||||
where
|
||||
B: HttpBody + Send + 'static,
|
||||
S: Send + Sync + '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_) => self.call_route(match_, req),
|
||||
Err(
|
||||
MatchError::NotFound
|
||||
| MatchError::ExtraTrailingSlash
|
||||
| MatchError::MissingTrailingSlash,
|
||||
) => match &self.fallback {
|
||||
Fallback::Default(inner) => inner.clone().call(req),
|
||||
Fallback::Custom(inner) => inner.clone().call(req),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper around `matchit::Router` that supports merging two `Router`s.
|
||||
#[derive(Clone, Default)]
|
||||
struct Node {
|
||||
|
@ -665,7 +570,7 @@ impl<B, E> Fallback<B, E> {
|
|||
}
|
||||
|
||||
enum Endpoint<S, B> {
|
||||
MethodRouter(MethodRouter<S, B, Infallible>),
|
||||
MethodRouter(MethodRouter<S, B>),
|
||||
Route(Route<B>),
|
||||
}
|
||||
|
||||
|
|
164
axum/src/routing/service.rs
Normal file
164
axum/src/routing/service.rs
Normal file
|
@ -0,0 +1,164 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
convert::Infallible,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use http::Request;
|
||||
use matchit::MatchError;
|
||||
use tower::Service;
|
||||
|
||||
use super::{
|
||||
future::RouteFuture, url_params, Endpoint, Fallback, Node, Route, RouteId, Router,
|
||||
NEST_TAIL_PARAM_CAPTURE,
|
||||
};
|
||||
use crate::{
|
||||
body::{Body, HttpBody},
|
||||
response::Response,
|
||||
};
|
||||
|
||||
/// TOOD: Docs
|
||||
#[derive(Debug)]
|
||||
pub struct RouterService<B = Body> {
|
||||
routes: HashMap<RouteId, Route<B>>,
|
||||
node: Arc<Node>,
|
||||
fallback: Route<B>,
|
||||
}
|
||||
|
||||
impl<B> RouterService<B>
|
||||
where
|
||||
B: HttpBody + Send + 'static,
|
||||
{
|
||||
pub(super) fn new<S>(router: Router<S, B>) -> Self
|
||||
where
|
||||
S: Send + Sync + 'static,
|
||||
{
|
||||
let routes = router
|
||||
.routes
|
||||
.into_iter()
|
||||
.map(|(route_id, endpoint)| {
|
||||
let route = match endpoint {
|
||||
Endpoint::MethodRouter(method_router) => {
|
||||
Route::new(method_router.with_state_arc(Arc::clone(&router.state)))
|
||||
}
|
||||
Endpoint::Route(route) => route,
|
||||
};
|
||||
|
||||
(route_id, route)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
routes,
|
||||
node: router.node,
|
||||
fallback: match router.fallback {
|
||||
Fallback::Default(route) => route,
|
||||
Fallback::Custom(route) => route,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn call_route(
|
||||
&self,
|
||||
match_: matchit::Match<&RouteId>,
|
||||
mut req: Request<B>,
|
||||
) -> RouteFuture<B, Infallible> {
|
||||
let id = *match_.value;
|
||||
|
||||
#[cfg(feature = "matched-path")]
|
||||
{
|
||||
fn set_matched_path(
|
||||
id: RouteId,
|
||||
route_id_to_path: &HashMap<RouteId, Arc<str>>,
|
||||
extensions: &mut http::Extensions,
|
||||
) {
|
||||
if let Some(matched_path) = route_id_to_path.get(&id) {
|
||||
use crate::extract::MatchedPath;
|
||||
|
||||
let matched_path = if let Some(previous) = extensions.get::<MatchedPath>() {
|
||||
// a previous `MatchedPath` might exist if we're inside a nested Router
|
||||
let previous = if let Some(previous) =
|
||||
previous.as_str().strip_suffix(NEST_TAIL_PARAM_CAPTURE)
|
||||
{
|
||||
previous
|
||||
} else {
|
||||
previous.as_str()
|
||||
};
|
||||
|
||||
let matched_path = format!("{}{}", previous, matched_path);
|
||||
matched_path.into()
|
||||
} else {
|
||||
Arc::clone(matched_path)
|
||||
};
|
||||
extensions.insert(MatchedPath(matched_path));
|
||||
} else {
|
||||
#[cfg(debug_assertions)]
|
||||
panic!("should always have a matched path for a route id");
|
||||
}
|
||||
}
|
||||
|
||||
set_matched_path(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)
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
//S: Send + Sync + '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_) => self.call_route(match_, req),
|
||||
Err(
|
||||
MatchError::NotFound
|
||||
| MatchError::ExtraTrailingSlash
|
||||
| MatchError::MissingTrailingSlash,
|
||||
) => self.fallback.clone().call(req),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,7 +18,10 @@ async fn basic() {
|
|||
#[tokio::test]
|
||||
async fn nest() {
|
||||
let app = Router::new()
|
||||
.nest("/foo", Router::new().route("/bar", get(|| async {})))
|
||||
.nest(
|
||||
"/foo",
|
||||
Router::new().route("/bar", get(|| async {})).into_service(),
|
||||
)
|
||||
.fallback(|| async { "fallback" });
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
|
|
@ -19,6 +19,7 @@ mod for_handlers {
|
|||
|
||||
// don't use reqwest because it always strips bodies from HEAD responses
|
||||
let res = app
|
||||
.into_service()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/")
|
||||
|
@ -54,6 +55,7 @@ mod for_services {
|
|||
|
||||
// don't use reqwest because it always strips bodies from HEAD responses
|
||||
let res = app
|
||||
.into_service()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/")
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
use super::*;
|
||||
use crate::{
|
||||
body::HttpBody, error_handling::HandleErrorLayer, extract::OriginalUri, response::IntoResponse,
|
||||
Json,
|
||||
};
|
||||
use crate::{error_handling::HandleErrorLayer, extract::OriginalUri, response::IntoResponse, Json};
|
||||
use serde_json::{json, Value};
|
||||
use tower::{limit::ConcurrencyLimitLayer, timeout::TimeoutLayer};
|
||||
|
||||
|
@ -62,15 +59,7 @@ async fn multiple_ors_balanced_differently() {
|
|||
|
||||
test("four", one.merge(two.merge(three.merge(four)))).await;
|
||||
|
||||
async fn test<S, ResBody>(name: &str, app: S)
|
||||
where
|
||||
S: Service<Request<Body>, Response = Response<ResBody>> + Clone + Send + 'static,
|
||||
ResBody: HttpBody + Send + 'static,
|
||||
ResBody::Data: Send,
|
||||
ResBody::Error: Into<BoxError>,
|
||||
S::Future: Send,
|
||||
S::Error: Into<BoxError>,
|
||||
{
|
||||
async fn test(name: &str, app: Router) {
|
||||
let client = TestClient::new(app);
|
||||
|
||||
for n in ["one", "two", "three", "four"].iter() {
|
||||
|
@ -93,7 +82,7 @@ async fn nested_or() {
|
|||
assert_eq!(client.get("/bar").send().await.text().await, "bar");
|
||||
assert_eq!(client.get("/baz").send().await.text().await, "baz");
|
||||
|
||||
let client = TestClient::new(Router::new().nest("/foo", bar_or_baz));
|
||||
let client = TestClient::new(Router::new().nest("/foo", bar_or_baz.into_service()));
|
||||
assert_eq!(client.get("/foo/bar").send().await.text().await, "bar");
|
||||
assert_eq!(client.get("/foo/baz").send().await.text().await, "baz");
|
||||
}
|
||||
|
@ -156,7 +145,10 @@ async fn layer_and_handle_error() {
|
|||
#[tokio::test]
|
||||
async fn nesting() {
|
||||
let one = Router::new().route("/foo", get(|| async {}));
|
||||
let two = Router::new().nest("/bar", Router::new().route("/baz", get(|| async {})));
|
||||
let two = Router::new().nest(
|
||||
"/bar",
|
||||
Router::new().route("/baz", get(|| async {})).into_service(),
|
||||
);
|
||||
let app = one.merge(two);
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
@ -240,7 +232,12 @@ async fn all_the_uris(
|
|||
|
||||
#[tokio::test]
|
||||
async fn nesting_and_seeing_the_right_uri() {
|
||||
let one = Router::new().nest("/foo/", Router::new().route("/bar", get(all_the_uris)));
|
||||
let one = Router::new().nest(
|
||||
"/foo/",
|
||||
Router::new()
|
||||
.route("/bar", get(all_the_uris))
|
||||
.into_service(),
|
||||
);
|
||||
let two = Router::new().route("/foo", get(all_the_uris));
|
||||
|
||||
let client = TestClient::new(one.merge(two));
|
||||
|
@ -272,7 +269,14 @@ async fn nesting_and_seeing_the_right_uri() {
|
|||
async fn nesting_and_seeing_the_right_uri_at_more_levels_of_nesting() {
|
||||
let one = Router::new().nest(
|
||||
"/foo/",
|
||||
Router::new().nest("/bar", Router::new().route("/baz", get(all_the_uris))),
|
||||
Router::new()
|
||||
.nest(
|
||||
"/bar",
|
||||
Router::new()
|
||||
.route("/baz", get(all_the_uris))
|
||||
.into_service(),
|
||||
)
|
||||
.into_service(),
|
||||
);
|
||||
let two = Router::new().route("/foo", get(all_the_uris));
|
||||
|
||||
|
@ -305,9 +309,21 @@ async fn nesting_and_seeing_the_right_uri_at_more_levels_of_nesting() {
|
|||
async fn nesting_and_seeing_the_right_uri_ors_with_nesting() {
|
||||
let one = Router::new().nest(
|
||||
"/one",
|
||||
Router::new().nest("/bar", Router::new().route("/baz", get(all_the_uris))),
|
||||
Router::new()
|
||||
.nest(
|
||||
"/bar",
|
||||
Router::new()
|
||||
.route("/baz", get(all_the_uris))
|
||||
.into_service(),
|
||||
)
|
||||
.into_service(),
|
||||
);
|
||||
let two = Router::new().nest(
|
||||
"/two",
|
||||
Router::new()
|
||||
.route("/qux", get(all_the_uris))
|
||||
.into_service(),
|
||||
);
|
||||
let two = Router::new().nest("/two", Router::new().route("/qux", get(all_the_uris)));
|
||||
let three = Router::new().route("/three", get(all_the_uris));
|
||||
|
||||
let client = TestClient::new(one.merge(two).merge(three));
|
||||
|
@ -350,7 +366,14 @@ async fn nesting_and_seeing_the_right_uri_ors_with_nesting() {
|
|||
async fn nesting_and_seeing_the_right_uri_ors_with_multi_segment_uris() {
|
||||
let one = Router::new().nest(
|
||||
"/one",
|
||||
Router::new().nest("/foo", Router::new().route("/bar", get(all_the_uris))),
|
||||
Router::new()
|
||||
.nest(
|
||||
"/foo",
|
||||
Router::new()
|
||||
.route("/bar", get(all_the_uris))
|
||||
.into_service(),
|
||||
)
|
||||
.into_service(),
|
||||
);
|
||||
let two = Router::new().route("/two/foo", get(all_the_uris));
|
||||
|
||||
|
|
|
@ -444,11 +444,12 @@ async fn middleware_still_run_for_unmatched_requests() {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic(
|
||||
expected = "Invalid route: `Router::route_service` cannot be used with `Router`s. Use `Router::nest` instead"
|
||||
)]
|
||||
#[should_panic(expected = "\
|
||||
Invalid route: `Router::route_service` cannot be used with `RouterService`s. \
|
||||
Use `Router::nest` instead\
|
||||
")]
|
||||
async fn routing_to_router_panics() {
|
||||
TestClient::new(Router::new().route_service("/", Router::new()));
|
||||
TestClient::new(Router::new().route_service("/", Router::new().into_service()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
|
@ -37,7 +37,7 @@ async fn nesting_apps() {
|
|||
|
||||
let app = Router::new()
|
||||
.route("/", get(|| async { "hi" }))
|
||||
.nest("/:version/api", api_routes);
|
||||
.nest("/:version/api", api_routes.into_service());
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
||||
|
@ -61,7 +61,7 @@ async fn nesting_apps() {
|
|||
#[tokio::test]
|
||||
async fn wrong_method_nest() {
|
||||
let nested_app = Router::new().route("/", get(|| async {}));
|
||||
let app = Router::new().nest("/", nested_app);
|
||||
let app = Router::new().nest("/", nested_app.into_service());
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
||||
|
@ -78,7 +78,7 @@ async fn wrong_method_nest() {
|
|||
#[tokio::test]
|
||||
async fn nesting_router_at_root() {
|
||||
let nested = Router::new().route("/foo", get(|uri: Uri| async move { uri.to_string() }));
|
||||
let app = Router::new().nest("/", nested);
|
||||
let app = Router::new().nest("/", nested.into_service());
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
||||
|
@ -96,7 +96,7 @@ async fn nesting_router_at_root() {
|
|||
#[tokio::test]
|
||||
async fn nesting_router_at_empty_path() {
|
||||
let nested = Router::new().route("/foo", get(|uri: Uri| async move { uri.to_string() }));
|
||||
let app = Router::new().nest("", nested);
|
||||
let app = Router::new().nest("", nested.into_service());
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
||||
|
@ -134,15 +134,18 @@ async fn nesting_handler_at_root() {
|
|||
async fn nested_url_extractor() {
|
||||
let app = Router::new().nest(
|
||||
"/foo",
|
||||
Router::new().nest(
|
||||
"/bar",
|
||||
Router::new()
|
||||
.route("/baz", get(|uri: Uri| async move { uri.to_string() }))
|
||||
.route(
|
||||
"/qux",
|
||||
get(|req: Request<Body>| async move { req.uri().to_string() }),
|
||||
),
|
||||
),
|
||||
Router::new()
|
||||
.nest(
|
||||
"/bar",
|
||||
Router::new()
|
||||
.route("/baz", get(|uri: Uri| async move { uri.to_string() }))
|
||||
.route(
|
||||
"/qux",
|
||||
get(|req: Request<Body>| async move { req.uri().to_string() }),
|
||||
)
|
||||
.into_service(),
|
||||
)
|
||||
.into_service(),
|
||||
);
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
@ -160,13 +163,17 @@ async fn nested_url_extractor() {
|
|||
async fn nested_url_original_extractor() {
|
||||
let app = Router::new().nest(
|
||||
"/foo",
|
||||
Router::new().nest(
|
||||
"/bar",
|
||||
Router::new().route(
|
||||
"/baz",
|
||||
get(|uri: extract::OriginalUri| async move { uri.0.to_string() }),
|
||||
),
|
||||
),
|
||||
Router::new()
|
||||
.nest(
|
||||
"/bar",
|
||||
Router::new()
|
||||
.route(
|
||||
"/baz",
|
||||
get(|uri: extract::OriginalUri| async move { uri.0.to_string() }),
|
||||
)
|
||||
.into_service(),
|
||||
)
|
||||
.into_service(),
|
||||
);
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
@ -180,16 +187,20 @@ async fn nested_url_original_extractor() {
|
|||
async fn nested_service_sees_stripped_uri() {
|
||||
let app = Router::new().nest(
|
||||
"/foo",
|
||||
Router::new().nest(
|
||||
"/bar",
|
||||
Router::new().route_service(
|
||||
"/baz",
|
||||
service_fn(|req: Request<Body>| async move {
|
||||
let body = boxed(Body::from(req.uri().to_string()));
|
||||
Ok::<_, Infallible>(Response::new(body))
|
||||
}),
|
||||
),
|
||||
),
|
||||
Router::new()
|
||||
.nest(
|
||||
"/bar",
|
||||
Router::new()
|
||||
.route_service(
|
||||
"/baz",
|
||||
service_fn(|req: Request<Body>| async move {
|
||||
let body = boxed(Body::from(req.uri().to_string()));
|
||||
Ok::<_, Infallible>(Response::new(body))
|
||||
}),
|
||||
)
|
||||
.into_service(),
|
||||
)
|
||||
.into_service(),
|
||||
);
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
@ -224,7 +235,8 @@ async fn nested_multiple_routes() {
|
|||
"/api",
|
||||
Router::new()
|
||||
.route("/users", get(|| async { "users" }))
|
||||
.route("/teams", get(|| async { "teams" })),
|
||||
.route("/teams", get(|| async { "teams" }))
|
||||
.into_service(),
|
||||
)
|
||||
.route("/", get(|| async { "root" }));
|
||||
|
||||
|
@ -239,7 +251,12 @@ async fn nested_multiple_routes() {
|
|||
#[should_panic = "Invalid route \"/\": insertion failed due to conflict with previously registered route: /*__private__axum_nest_tail_param"]
|
||||
fn nested_at_root_with_other_routes() {
|
||||
let _: Router = Router::new()
|
||||
.nest("/", Router::new().route("/users", get(|| async {})))
|
||||
.nest(
|
||||
"/",
|
||||
Router::new()
|
||||
.route("/users", get(|| async {}))
|
||||
.into_service(),
|
||||
)
|
||||
.route("/", get(|| async {}));
|
||||
}
|
||||
|
||||
|
@ -248,11 +265,15 @@ async fn multiple_top_level_nests() {
|
|||
let app = Router::new()
|
||||
.nest(
|
||||
"/one",
|
||||
Router::new().route("/route", get(|| async { "one" })),
|
||||
Router::new()
|
||||
.route("/route", get(|| async { "one" }))
|
||||
.into_service(),
|
||||
)
|
||||
.nest(
|
||||
"/two",
|
||||
Router::new().route("/route", get(|| async { "two" })),
|
||||
Router::new()
|
||||
.route("/route", get(|| async { "two" }))
|
||||
.into_service(),
|
||||
);
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
@ -264,7 +285,7 @@ async fn multiple_top_level_nests() {
|
|||
#[tokio::test]
|
||||
#[should_panic(expected = "Invalid route: nested routes cannot contain wildcards (*)")]
|
||||
async fn nest_cannot_contain_wildcards() {
|
||||
Router::<_, Body>::new().nest("/one/*rest", Router::new());
|
||||
Router::<_, Body>::new().nest("/one/*rest", Router::new().into_service());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -302,7 +323,10 @@ async fn outer_middleware_still_see_whole_url() {
|
|||
.route("/", get(handler))
|
||||
.route("/foo", get(handler))
|
||||
.route("/foo/bar", get(handler))
|
||||
.nest("/one", Router::new().route("/two", get(handler)))
|
||||
.nest(
|
||||
"/one",
|
||||
Router::new().route("/two", get(handler)).into_service(),
|
||||
)
|
||||
.fallback(handler)
|
||||
.layer(tower::layer::layer_fn(SetUriExtension));
|
||||
|
||||
|
@ -325,6 +349,7 @@ async fn nest_at_capture() {
|
|||
"/:b",
|
||||
get(|Path((a, b)): Path<(String, String)>| async move { format!("a={} b={}", a, b) }),
|
||||
)
|
||||
.into_service()
|
||||
.boxed_clone();
|
||||
|
||||
let app = Router::new().nest("/:a", api_routes);
|
||||
|
@ -355,7 +380,10 @@ async fn nest_with_and_without_trailing() {
|
|||
#[tokio::test]
|
||||
async fn doesnt_call_outer_fallback() {
|
||||
let app = Router::new()
|
||||
.nest("/foo", Router::new().route("/", get(|| async {})))
|
||||
.nest(
|
||||
"/foo",
|
||||
Router::new().route("/", get(|| async {})).into_service(),
|
||||
)
|
||||
.fallback(|| async { (StatusCode::NOT_FOUND, "outer fallback") });
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
@ -373,7 +401,9 @@ async fn doesnt_call_outer_fallback() {
|
|||
async fn nesting_with_root_inner_router() {
|
||||
let app = Router::new().nest(
|
||||
"/foo",
|
||||
Router::new().route("/", get(|| async { "inner route" })),
|
||||
Router::new()
|
||||
.route("/", get(|| async { "inner route" }))
|
||||
.into_service(),
|
||||
);
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
@ -396,7 +426,8 @@ async fn fallback_on_inner() {
|
|||
"/foo",
|
||||
Router::new()
|
||||
.route("/", get(|| async {}))
|
||||
.fallback(|| async { (StatusCode::NOT_FOUND, "inner fallback") }),
|
||||
.fallback(|| async { (StatusCode::NOT_FOUND, "inner fallback") })
|
||||
.into_service(),
|
||||
)
|
||||
.fallback(|| async { (StatusCode::NOT_FOUND, "outer fallback") });
|
||||
|
||||
|
@ -420,7 +451,7 @@ macro_rules! nested_route_test {
|
|||
#[tokio::test]
|
||||
async fn $name() {
|
||||
let inner = Router::new().route($route_path, get(|| async {}));
|
||||
let app = Router::new().nest($nested_path, inner);
|
||||
let app = Router::new().nest($nested_path, inner.into_service());
|
||||
let client = TestClient::new(app);
|
||||
let res = client.get($expected_path).send().await;
|
||||
let status = res.status();
|
||||
|
@ -455,7 +486,7 @@ async fn nesting_with_different_state() {
|
|||
"/foo",
|
||||
get(|State(state): State<&'static str>| async move { state }),
|
||||
)
|
||||
.nest("/nested", inner)
|
||||
.nest("/nested", inner.into_service())
|
||||
.route(
|
||||
"/bar",
|
||||
get(|State(state): State<&'static str>| async move { state }),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#![allow(clippy::blacklisted_name)]
|
||||
|
||||
use crate::{body::HttpBody, BoxError};
|
||||
use crate::{body::HttpBody, BoxError, Router};
|
||||
|
||||
mod test_client;
|
||||
pub(crate) use self::test_client::*;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use super::{BoxError, HttpBody};
|
||||
use super::{BoxError, HttpBody, Router};
|
||||
use bytes::Bytes;
|
||||
use http::{
|
||||
header::{HeaderName, HeaderValue},
|
||||
|
@ -15,7 +15,14 @@ pub(crate) struct TestClient {
|
|||
}
|
||||
|
||||
impl TestClient {
|
||||
pub(crate) fn new<S, ResBody>(svc: S) -> Self
|
||||
pub(crate) fn new<S>(router: Router<S, Body>) -> Self
|
||||
where
|
||||
S: Send + Sync + 'static,
|
||||
{
|
||||
Self::from_service(router.into_service())
|
||||
}
|
||||
|
||||
pub(crate) fn from_service<S, ResBody>(svc: S) -> Self
|
||||
where
|
||||
S: Service<Request<Body>, Response = http::Response<ResBody>> + Clone + Send + 'static,
|
||||
ResBody: HttpBody + Send + 'static,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//! ```
|
||||
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum::{http, routing::get, Router};
|
||||
use axum::{http, routing::get, Router, RouterService};
|
||||
use std::net::SocketAddr;
|
||||
|
||||
fn app() -> Router {
|
||||
|
@ -50,7 +50,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_get() {
|
||||
let app = app();
|
||||
let app = app().into_service();
|
||||
|
||||
let response = app
|
||||
.oneshot(Request::get("/get-head").body(Body::empty()).unwrap())
|
||||
|
@ -66,7 +66,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_implicit_head() {
|
||||
let app = app();
|
||||
let app = app().into_service();
|
||||
|
||||
let response = app
|
||||
.oneshot(Request::head("/get-head").body(Body::empty()).unwrap())
|
||||
|
|
|
@ -35,15 +35,17 @@ async fn main() {
|
|||
.with(tracing_subscriber::fmt::layer())
|
||||
.init();
|
||||
|
||||
let router = Router::new().route("/", get(|| async { "Hello, World!" }));
|
||||
let router_svc = Router::new()
|
||||
.route("/", get(|| async { "Hello, World!" }))
|
||||
.into_service();
|
||||
|
||||
let service = tower::service_fn(move |req: Request<Body>| {
|
||||
let router = router.clone();
|
||||
let router_svc = router_svc.clone();
|
||||
async move {
|
||||
if req.method() == Method::CONNECT {
|
||||
proxy(req).await
|
||||
} else {
|
||||
router.oneshot(req).await.map_err(|err| match err {})
|
||||
router_svc.oneshot(req).await.map_err(|err| match err {})
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -52,7 +52,7 @@ async fn main() {
|
|||
)
|
||||
.route("/keys", get(list_keys))
|
||||
// Nest our admin routes under `/admin`
|
||||
.nest("/admin", admin_routes(shared_state))
|
||||
.nest("/admin", admin_routes(shared_state).into_service())
|
||||
// Add middleware to all routes
|
||||
.layer(
|
||||
ServiceBuilder::new()
|
||||
|
|
|
@ -104,6 +104,7 @@ mod tests {
|
|||
|
||||
async fn send_request_get_body(query: &str) -> String {
|
||||
let body = app()
|
||||
.into_service()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri(format!("/?{}", query))
|
||||
|
|
|
@ -55,7 +55,7 @@ async fn main() {
|
|||
.init();
|
||||
|
||||
// build the rest service
|
||||
let rest = Router::new().route("/", get(web_root));
|
||||
let rest = Router::new().route("/", get(web_root)).into_service();
|
||||
|
||||
// build the grpc service
|
||||
let grpc = GreeterServer::new(GrpcServiceImpl::default());
|
||||
|
|
|
@ -61,7 +61,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn hello_world() {
|
||||
let app = app();
|
||||
let app = app().into_service();
|
||||
|
||||
// `Router` implements `tower::Service<Request<Body>>` so we can
|
||||
// call it like any tower service, no need to run an HTTP server.
|
||||
|
@ -78,7 +78,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn json() {
|
||||
let app = app();
|
||||
let app = app().into_service();
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
|
@ -103,7 +103,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn not_found() {
|
||||
let app = app();
|
||||
let app = app().into_service();
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
|
@ -154,7 +154,7 @@ mod tests {
|
|||
// in multiple request
|
||||
#[tokio::test]
|
||||
async fn multiple_request() {
|
||||
let mut app = app();
|
||||
let mut app = app().into_service();
|
||||
|
||||
let request = Request::builder().uri("/").body(Body::empty()).unwrap();
|
||||
let response = app.ready().await.unwrap().call(request).await.unwrap();
|
||||
|
|
Loading…
Add table
Reference in a new issue