Split RouterService off of Router (#1381)

This commit is contained in:
Jonas Platte 2022-09-22 12:10:55 +02:00 committed by GitHub
parent 18e3fac5d3
commit 69d64cecc3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 371 additions and 225 deletions

View file

@ -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()

View file

@ -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)]

View file

@ -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

View file

@ -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

View file

@ -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;
```

View file

@ -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();

View file

@ -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);

View file

@ -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();
/// # };

View file

@ -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);

View file

@ -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")]

View file

@ -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();

View file

@ -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
View 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),
}
}
}

View file

@ -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);

View file

@ -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("/")

View file

@ -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));

View file

@ -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]

View file

@ -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 }),

View file

@ -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::*;

View file

@ -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,

View file

@ -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())

View file

@ -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 {})
}
}
});

View file

@ -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()

View file

@ -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))

View file

@ -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());

View file

@ -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();