mirror of
https://github.com/tokio-rs/axum.git
synced 2024-12-29 07:48:39 +01:00
Breakup routing module (#424)
The routing module had gotten quite hard to navigate so this breaks it into smaller modules. No user facing changes.
This commit is contained in:
parent
91981db8c7
commit
302a720271
6 changed files with 278 additions and 243 deletions
|
@ -1,19 +1,15 @@
|
|||
//! Future types.
|
||||
|
||||
use crate::{body::BoxBody, clone_box_service::CloneBoxService};
|
||||
use crate::body::BoxBody;
|
||||
use futures_util::future::Either;
|
||||
use http::{Request, Response};
|
||||
use pin_project_lite::pin_project;
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
future::{ready, Future},
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use std::{convert::Infallible, future::ready};
|
||||
use tower::util::Oneshot;
|
||||
use tower_service::Service;
|
||||
|
||||
pub use super::method_not_allowed::MethodNotAllowedFuture;
|
||||
pub use super::{
|
||||
into_make_service::IntoMakeService, method_not_allowed::MethodNotAllowedFuture,
|
||||
route::RouteFuture,
|
||||
};
|
||||
|
||||
opaque_future! {
|
||||
/// Response future for [`Router`](super::Router).
|
||||
|
@ -33,62 +29,3 @@ impl<B> RouterFuture<B> {
|
|||
RouterFuture::new(Either::Right(ready(Ok(response))))
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// Response future for [`Route`](super::Route).
|
||||
pub struct RouteFuture<B> {
|
||||
#[pin]
|
||||
future: Oneshot<
|
||||
CloneBoxService<Request<B>, Response<BoxBody>, Infallible>,
|
||||
Request<B>,
|
||||
>
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> RouteFuture<B> {
|
||||
pub(crate) fn new(
|
||||
future: Oneshot<CloneBoxService<Request<B>, Response<BoxBody>, Infallible>, Request<B>>,
|
||||
) -> Self {
|
||||
RouteFuture { future }
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> Future for RouteFuture<B> {
|
||||
type Output = Result<Response<BoxBody>, Infallible>;
|
||||
|
||||
#[inline]
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
self.project().future.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// The response future for [`Nested`](super::Nested).
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct NestedFuture<S, B>
|
||||
where
|
||||
S: Service<Request<B>>,
|
||||
{
|
||||
#[pin]
|
||||
pub(super) inner: Oneshot<S, Request<B>>
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B> Future for NestedFuture<S, B>
|
||||
where
|
||||
S: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible>,
|
||||
B: Send + Sync + 'static,
|
||||
{
|
||||
type Output = Result<Response<BoxBody>, Infallible>;
|
||||
|
||||
#[inline]
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
self.project().inner.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
opaque_future! {
|
||||
/// Response future from [`MakeRouteService`] services.
|
||||
pub type MakeRouteServiceFuture<S> =
|
||||
std::future::Ready<Result<S, Infallible>>;
|
||||
}
|
||||
|
|
57
src/routing/into_make_service.rs
Normal file
57
src/routing/into_make_service.rs
Normal file
|
@ -0,0 +1,57 @@
|
|||
use std::{
|
||||
convert::Infallible,
|
||||
future::ready,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tower_service::Service;
|
||||
|
||||
/// A [`MakeService`] that produces axum router services.
|
||||
///
|
||||
/// [`MakeService`]: tower::make::MakeService
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IntoMakeService<S> {
|
||||
service: S,
|
||||
}
|
||||
|
||||
impl<S> IntoMakeService<S> {
|
||||
pub(super) fn new(service: S) -> Self {
|
||||
Self { service }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> Service<T> for IntoMakeService<S>
|
||||
where
|
||||
S: Clone,
|
||||
{
|
||||
type Response = S;
|
||||
type Error = Infallible;
|
||||
type Future = MakeRouteServiceFuture<S>;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, _target: T) -> Self::Future {
|
||||
MakeRouteServiceFuture::new(ready(Ok(self.service.clone())))
|
||||
}
|
||||
}
|
||||
|
||||
opaque_future! {
|
||||
/// Response future from [`MakeRouteService`] services.
|
||||
pub type MakeRouteServiceFuture<S> =
|
||||
std::future::Ready<Result<S, Infallible>>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn traits() {
|
||||
use crate::tests::*;
|
||||
|
||||
assert_send::<IntoMakeService<()>>();
|
||||
assert_sync::<IntoMakeService<()>>();
|
||||
}
|
||||
}
|
|
@ -67,3 +67,16 @@ opaque_future! {
|
|||
pub type MethodNotAllowedFuture<E> =
|
||||
std::future::Ready<Result<Response<BoxBody>, E>>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn traits() {
|
||||
use crate::tests::*;
|
||||
|
||||
assert_send::<MethodNotAllowed<NotSendSync>>();
|
||||
assert_sync::<MethodNotAllowed<NotSendSync>>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
//! Routing between [`Service`]s and handlers.
|
||||
|
||||
use self::future::{NestedFuture, RouteFuture, RouterFuture};
|
||||
use self::future::RouterFuture;
|
||||
use self::not_found::NotFound;
|
||||
use crate::{
|
||||
body::{box_body, Body, BoxBody},
|
||||
clone_box_service::CloneBoxService,
|
||||
extract::{
|
||||
connect_info::{Connected, IntoMakeServiceWithConnectInfo},
|
||||
OriginalUri,
|
||||
|
@ -19,7 +18,6 @@ use std::{
|
|||
collections::HashMap,
|
||||
convert::Infallible,
|
||||
fmt,
|
||||
future::ready,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
@ -32,12 +30,15 @@ pub mod future;
|
|||
pub mod handler_method_router;
|
||||
pub mod service_method_router;
|
||||
|
||||
mod into_make_service;
|
||||
mod method_filter;
|
||||
mod method_not_allowed;
|
||||
mod nested;
|
||||
mod not_found;
|
||||
mod route;
|
||||
|
||||
pub use self::method_filter::MethodFilter;
|
||||
pub(crate) use self::method_not_allowed::MethodNotAllowed;
|
||||
pub use self::{into_make_service::IntoMakeService, method_filter::MethodFilter, route::Route};
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use self::handler_method_router::{
|
||||
|
@ -353,7 +354,7 @@ where
|
|||
panic!("Invalid route: {}", err);
|
||||
}
|
||||
|
||||
self.routes.insert(id, Route::new(Nested { svc }));
|
||||
self.routes.insert(id, Route::new(nested::Nested { svc }));
|
||||
|
||||
self
|
||||
}
|
||||
|
@ -754,7 +755,8 @@ where
|
|||
.collect::<Vec<_>>();
|
||||
|
||||
if let Some(tail) = match_.params.get(NEST_TAIL_PARAM) {
|
||||
req.extensions_mut().insert(NestMatchTail(tail.to_string()));
|
||||
req.extensions_mut()
|
||||
.insert(nested::NestMatchTail(tail.to_string()));
|
||||
}
|
||||
|
||||
insert_url_params(&mut req, params);
|
||||
|
@ -769,9 +771,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct NestMatchTail(String);
|
||||
|
||||
impl<B> Service<Request<B>> for Router<B>
|
||||
where
|
||||
B: Send + Sync + 'static,
|
||||
|
@ -824,18 +823,33 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) struct UriStack(Vec<Uri>);
|
||||
|
||||
impl UriStack {
|
||||
fn push<B>(req: &mut Request<B>) {
|
||||
let uri = req.uri().clone();
|
||||
|
||||
if let Some(stack) = req.extensions_mut().get_mut::<Self>() {
|
||||
stack.0.push(uri);
|
||||
fn with_path(uri: &Uri, new_path: &str) -> Uri {
|
||||
let path_and_query = if let Some(path_and_query) = uri.path_and_query() {
|
||||
let new_path = if new_path.starts_with('/') {
|
||||
Cow::Borrowed(new_path)
|
||||
} else {
|
||||
req.extensions_mut().insert(Self(vec![uri]));
|
||||
Cow::Owned(format!("/{}", new_path))
|
||||
};
|
||||
|
||||
if let Some(query) = path_and_query.query() {
|
||||
Some(
|
||||
format!("{}?{}", new_path, query)
|
||||
.parse::<http::uri::PathAndQuery>()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
Some(new_path.parse().unwrap())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut parts = http::uri::Parts::default();
|
||||
parts.scheme = uri.scheme().cloned();
|
||||
parts.authority = uri.authority().cloned();
|
||||
parts.path_and_query = path_and_query;
|
||||
|
||||
Uri::from_parts(parts).unwrap()
|
||||
}
|
||||
|
||||
// we store the potential error here such that users can handle invalid path
|
||||
|
@ -881,150 +895,6 @@ pub(crate) struct InvalidUtf8InPathParam {
|
|||
pub(crate) key: ByteStr,
|
||||
}
|
||||
|
||||
/// A [`Service`] that has been nested inside a router at some path.
|
||||
///
|
||||
/// Created with [`Router::nest`].
|
||||
#[derive(Debug, Clone)]
|
||||
struct Nested<S> {
|
||||
svc: S,
|
||||
}
|
||||
|
||||
impl<B, S> Service<Request<B>> for Nested<S>
|
||||
where
|
||||
S: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible> + Clone,
|
||||
B: Send + Sync + 'static,
|
||||
{
|
||||
type Response = Response<BoxBody>;
|
||||
type Error = Infallible;
|
||||
type Future = NestedFuture<S, B>;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: Request<B>) -> Self::Future {
|
||||
// strip the prefix from the URI just before calling the inner service
|
||||
// such that any surrounding middleware still see the full path
|
||||
if let Some(tail) = req.extensions_mut().remove::<NestMatchTail>() {
|
||||
UriStack::push(&mut req);
|
||||
let new_uri = with_path(req.uri(), &tail.0);
|
||||
*req.uri_mut() = new_uri;
|
||||
}
|
||||
|
||||
NestedFuture {
|
||||
inner: self.svc.clone().oneshot(req),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn with_path(uri: &Uri, new_path: &str) -> Uri {
|
||||
let path_and_query = if let Some(path_and_query) = uri.path_and_query() {
|
||||
let new_path = if new_path.starts_with('/') {
|
||||
Cow::Borrowed(new_path)
|
||||
} else {
|
||||
Cow::Owned(format!("/{}", new_path))
|
||||
};
|
||||
|
||||
if let Some(query) = path_and_query.query() {
|
||||
Some(
|
||||
format!("{}?{}", new_path, query)
|
||||
.parse::<http::uri::PathAndQuery>()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
Some(new_path.parse().unwrap())
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut parts = http::uri::Parts::default();
|
||||
parts.scheme = uri.scheme().cloned();
|
||||
parts.authority = uri.authority().cloned();
|
||||
parts.path_and_query = path_and_query;
|
||||
|
||||
Uri::from_parts(parts).unwrap()
|
||||
}
|
||||
|
||||
/// A [`MakeService`] that produces axum router services.
|
||||
///
|
||||
/// [`MakeService`]: tower::make::MakeService
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IntoMakeService<S> {
|
||||
service: S,
|
||||
}
|
||||
|
||||
impl<S> IntoMakeService<S> {
|
||||
fn new(service: S) -> Self {
|
||||
Self { service }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> Service<T> for IntoMakeService<S>
|
||||
where
|
||||
S: Clone,
|
||||
{
|
||||
type Response = S;
|
||||
type Error = Infallible;
|
||||
type Future = future::MakeRouteServiceFuture<S>;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, _target: T) -> Self::Future {
|
||||
future::MakeRouteServiceFuture::new(ready(Ok(self.service.clone())))
|
||||
}
|
||||
}
|
||||
|
||||
/// How routes are stored inside a [`Router`].
|
||||
///
|
||||
/// You normally shouldn't need to care about this type.
|
||||
pub struct Route<B = Body>(CloneBoxService<Request<B>, Response<BoxBody>, Infallible>);
|
||||
|
||||
impl<B> Route<B> {
|
||||
fn new<T>(svc: T) -> Self
|
||||
where
|
||||
T: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ 'static,
|
||||
T::Future: Send + 'static,
|
||||
{
|
||||
Self(CloneBoxService::new(svc))
|
||||
}
|
||||
}
|
||||
|
||||
impl<ReqBody> Clone for Route<ReqBody> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<ReqBody> fmt::Debug for Route<ReqBody> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Route").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> Service<Request<B>> for Route<B> {
|
||||
type Response = Response<BoxBody>;
|
||||
type Error = Infallible;
|
||||
type Future = RouteFuture<B>;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn call(&mut self, req: Request<B>) -> Self::Future {
|
||||
RouteFuture::new(self.0.clone().oneshot(req))
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper around `matchit::Node` that supports merging two `Node`s.
|
||||
#[derive(Clone, Default)]
|
||||
struct Node {
|
||||
|
@ -1109,16 +979,5 @@ mod tests {
|
|||
use crate::tests::*;
|
||||
|
||||
assert_send::<Router<()>>();
|
||||
|
||||
assert_send::<Route<()>>();
|
||||
|
||||
assert_send::<MethodNotAllowed<NotSendSync>>();
|
||||
assert_sync::<MethodNotAllowed<NotSendSync>>();
|
||||
|
||||
assert_send::<Nested<()>>();
|
||||
assert_sync::<Nested<()>>();
|
||||
|
||||
assert_send::<IntoMakeService<()>>();
|
||||
assert_sync::<IntoMakeService<()>>();
|
||||
}
|
||||
}
|
||||
|
|
69
src/routing/nested.rs
Normal file
69
src/routing/nested.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
use crate::body::BoxBody;
|
||||
use http::{Request, Response, Uri};
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tower::util::Oneshot;
|
||||
use tower::ServiceExt;
|
||||
use tower_service::Service;
|
||||
|
||||
/// A [`Service`] that has been nested inside a router at some path.
|
||||
///
|
||||
/// Created with [`Router::nest`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct Nested<S> {
|
||||
pub(super) svc: S,
|
||||
}
|
||||
|
||||
impl<B, S> Service<Request<B>> for Nested<S>
|
||||
where
|
||||
S: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible> + Clone,
|
||||
B: Send + Sync + 'static,
|
||||
{
|
||||
type Response = Response<BoxBody>;
|
||||
type Error = Infallible;
|
||||
type Future = Oneshot<S, Request<B>>;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: Request<B>) -> Self::Future {
|
||||
// strip the prefix from the URI just before calling the inner service
|
||||
// such that any surrounding middleware still see the full path
|
||||
if let Some(tail) = req.extensions_mut().remove::<NestMatchTail>() {
|
||||
UriStack::push(&mut req);
|
||||
let new_uri = super::with_path(req.uri(), &tail.0);
|
||||
*req.uri_mut() = new_uri;
|
||||
}
|
||||
|
||||
self.svc.clone().oneshot(req)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct UriStack(Vec<Uri>);
|
||||
|
||||
impl UriStack {
|
||||
fn push<B>(req: &mut Request<B>) {
|
||||
let uri = req.uri().clone();
|
||||
|
||||
if let Some(stack) = req.extensions_mut().get_mut::<Self>() {
|
||||
stack.0.push(uri);
|
||||
} else {
|
||||
req.extensions_mut().insert(Self(vec![uri]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct NestMatchTail(pub(super) String);
|
||||
|
||||
#[test]
|
||||
fn traits() {
|
||||
use crate::tests::*;
|
||||
|
||||
assert_send::<Nested<()>>();
|
||||
assert_sync::<Nested<()>>();
|
||||
}
|
100
src/routing/route.rs
Normal file
100
src/routing/route.rs
Normal file
|
@ -0,0 +1,100 @@
|
|||
use crate::{
|
||||
body::{Body, BoxBody},
|
||||
clone_box_service::CloneBoxService,
|
||||
};
|
||||
use http::{Request, Response};
|
||||
use pin_project_lite::pin_project;
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
fmt,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tower::{util::Oneshot, ServiceExt};
|
||||
use tower_service::Service;
|
||||
|
||||
/// How routes are stored inside a [`Router`](super::Router).
|
||||
///
|
||||
/// You normally shouldn't need to care about this type.
|
||||
pub struct Route<B = Body>(CloneBoxService<Request<B>, Response<BoxBody>, Infallible>);
|
||||
|
||||
impl<B> Route<B> {
|
||||
pub(super) fn new<T>(svc: T) -> Self
|
||||
where
|
||||
T: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ 'static,
|
||||
T::Future: Send + 'static,
|
||||
{
|
||||
Self(CloneBoxService::new(svc))
|
||||
}
|
||||
}
|
||||
|
||||
impl<ReqBody> Clone for Route<ReqBody> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<ReqBody> fmt::Debug for Route<ReqBody> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Route").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> Service<Request<B>> for Route<B> {
|
||||
type Response = Response<BoxBody>;
|
||||
type Error = Infallible;
|
||||
type Future = RouteFuture<B>;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn call(&mut self, req: Request<B>) -> Self::Future {
|
||||
RouteFuture::new(self.0.clone().oneshot(req))
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// Response future for [`Route`].
|
||||
pub struct RouteFuture<B> {
|
||||
#[pin]
|
||||
future: Oneshot<
|
||||
CloneBoxService<Request<B>, Response<BoxBody>, Infallible>,
|
||||
Request<B>,
|
||||
>
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> RouteFuture<B> {
|
||||
pub(crate) fn new(
|
||||
future: Oneshot<CloneBoxService<Request<B>, Response<BoxBody>, Infallible>, Request<B>>,
|
||||
) -> Self {
|
||||
RouteFuture { future }
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> Future for RouteFuture<B> {
|
||||
type Output = Result<Response<BoxBody>, Infallible>;
|
||||
|
||||
#[inline]
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
self.project().future.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn traits() {
|
||||
use crate::tests::*;
|
||||
assert_send::<Route<()>>();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue