mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-31 00:00:10 +02:00
Reorganize method routers for handlers and services (#405)
* Re-organize method routing for handlers * Re-organize method routing for services * changelog
This commit is contained in:
@@ -0,0 +1,485 @@
|
||||
//! Routing for handlers based on HTTP methods.
|
||||
|
||||
use crate::{
|
||||
body::{box_body, BoxBody},
|
||||
handler::Handler,
|
||||
routing::{EmptyRouter, MethodFilter},
|
||||
util::{Either, EitherProj},
|
||||
};
|
||||
use futures_util::{future::BoxFuture, ready};
|
||||
use http::Method;
|
||||
use http::{Request, Response};
|
||||
use http_body::Empty;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
fmt,
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tower::util::Oneshot;
|
||||
use tower::ServiceExt;
|
||||
use tower_service::Service;
|
||||
|
||||
/// Route requests with any standard HTTP method to the given handler.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// routing::any,
|
||||
/// Router,
|
||||
/// };
|
||||
///
|
||||
/// async fn handler() {}
|
||||
///
|
||||
/// let app = Router::new().route("/", any(handler));
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// Note that this only accepts the standard HTTP methods. If you need to
|
||||
/// support non-standard methods use [`Handler::into_service`]:
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// handler::Handler,
|
||||
/// Router,
|
||||
/// };
|
||||
///
|
||||
/// async fn handler() {}
|
||||
///
|
||||
/// let app = Router::new().route("/", handler.into_service());
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
pub fn any<H, B, T>(handler: H) -> MethodRouter<H, B, T, EmptyRouter>
|
||||
where
|
||||
H: Handler<B, T>,
|
||||
{
|
||||
on(MethodFilter::all(), handler)
|
||||
}
|
||||
|
||||
/// Route `CONNECT` requests to the given handler.
|
||||
///
|
||||
/// See [`get`] for an example.
|
||||
pub fn connect<H, B, T>(handler: H) -> MethodRouter<H, B, T, EmptyRouter>
|
||||
where
|
||||
H: Handler<B, T>,
|
||||
{
|
||||
on(MethodFilter::CONNECT, handler)
|
||||
}
|
||||
|
||||
/// Route `DELETE` requests to the given handler.
|
||||
///
|
||||
/// See [`get`] for an example.
|
||||
pub fn delete<H, B, T>(handler: H) -> MethodRouter<H, B, T, EmptyRouter>
|
||||
where
|
||||
H: Handler<B, T>,
|
||||
{
|
||||
on(MethodFilter::DELETE, handler)
|
||||
}
|
||||
|
||||
/// Route `GET` requests to the given handler.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// routing::get,
|
||||
/// Router,
|
||||
/// };
|
||||
///
|
||||
/// async fn handler() {}
|
||||
///
|
||||
/// // Requests to `GET /` will go to `handler`.
|
||||
/// let app = Router::new().route("/", get(handler));
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// Note that `get` routes will also be called for `HEAD` requests but will have
|
||||
/// the response body removed. Make sure to add explicit `HEAD` routes
|
||||
/// afterwards.
|
||||
pub fn get<H, B, T>(handler: H) -> MethodRouter<H, B, T, EmptyRouter>
|
||||
where
|
||||
H: Handler<B, T>,
|
||||
{
|
||||
on(MethodFilter::GET | MethodFilter::HEAD, handler)
|
||||
}
|
||||
|
||||
/// Route `HEAD` requests to the given handler.
|
||||
///
|
||||
/// See [`get`] for an example.
|
||||
pub fn head<H, B, T>(handler: H) -> MethodRouter<H, B, T, EmptyRouter>
|
||||
where
|
||||
H: Handler<B, T>,
|
||||
{
|
||||
on(MethodFilter::HEAD, handler)
|
||||
}
|
||||
|
||||
/// Route `OPTIONS` requests to the given handler.
|
||||
///
|
||||
/// See [`get`] for an example.
|
||||
pub fn options<H, B, T>(handler: H) -> MethodRouter<H, B, T, EmptyRouter>
|
||||
where
|
||||
H: Handler<B, T>,
|
||||
{
|
||||
on(MethodFilter::OPTIONS, handler)
|
||||
}
|
||||
|
||||
/// Route `PATCH` requests to the given handler.
|
||||
///
|
||||
/// See [`get`] for an example.
|
||||
pub fn patch<H, B, T>(handler: H) -> MethodRouter<H, B, T, EmptyRouter>
|
||||
where
|
||||
H: Handler<B, T>,
|
||||
{
|
||||
on(MethodFilter::PATCH, handler)
|
||||
}
|
||||
|
||||
/// Route `POST` requests to the given handler.
|
||||
///
|
||||
/// See [`get`] for an example.
|
||||
pub fn post<H, B, T>(handler: H) -> MethodRouter<H, B, T, EmptyRouter>
|
||||
where
|
||||
H: Handler<B, T>,
|
||||
{
|
||||
on(MethodFilter::POST, handler)
|
||||
}
|
||||
|
||||
/// Route `PUT` requests to the given handler.
|
||||
///
|
||||
/// See [`get`] for an example.
|
||||
pub fn put<H, B, T>(handler: H) -> MethodRouter<H, B, T, EmptyRouter>
|
||||
where
|
||||
H: Handler<B, T>,
|
||||
{
|
||||
on(MethodFilter::PUT, handler)
|
||||
}
|
||||
|
||||
/// Route `TRACE` requests to the given handler.
|
||||
///
|
||||
/// See [`get`] for an example.
|
||||
pub fn trace<H, B, T>(handler: H) -> MethodRouter<H, B, T, EmptyRouter>
|
||||
where
|
||||
H: Handler<B, T>,
|
||||
{
|
||||
on(MethodFilter::TRACE, handler)
|
||||
}
|
||||
|
||||
/// Route requests with the given method to the handler.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// routing::on,
|
||||
/// Router,
|
||||
/// routing::MethodFilter,
|
||||
/// };
|
||||
///
|
||||
/// async fn handler() {}
|
||||
///
|
||||
/// // Requests to `POST /` will go to `handler`.
|
||||
/// let app = Router::new().route("/", on(MethodFilter::POST, handler));
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
pub fn on<H, B, T>(method: MethodFilter, handler: H) -> MethodRouter<H, B, T, EmptyRouter>
|
||||
where
|
||||
H: Handler<B, T>,
|
||||
{
|
||||
MethodRouter {
|
||||
method,
|
||||
handler,
|
||||
fallback: EmptyRouter::method_not_allowed(),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// A handler [`Service`] that accepts requests based on a [`MethodFilter`] and
|
||||
/// allows chaining additional handlers.
|
||||
pub struct MethodRouter<H, B, T, F> {
|
||||
pub(crate) method: MethodFilter,
|
||||
pub(crate) handler: H,
|
||||
pub(crate) fallback: F,
|
||||
pub(crate) _marker: PhantomData<fn() -> (B, T)>,
|
||||
}
|
||||
|
||||
impl<H, B, T, F> fmt::Debug for MethodRouter<H, B, T, F>
|
||||
where
|
||||
T: fmt::Debug,
|
||||
F: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("MethodRouter")
|
||||
.field("method", &self.method)
|
||||
.field("handler", &format_args!("{}", std::any::type_name::<H>()))
|
||||
.field("fallback", &self.fallback)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, B, T, F> Clone for MethodRouter<H, B, T, F>
|
||||
where
|
||||
H: Clone,
|
||||
F: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
method: self.method,
|
||||
handler: self.handler.clone(),
|
||||
fallback: self.fallback.clone(),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, B, T, F> Copy for MethodRouter<H, B, T, F>
|
||||
where
|
||||
H: Copy,
|
||||
F: Copy,
|
||||
{
|
||||
}
|
||||
|
||||
impl<H, B, T, F> MethodRouter<H, B, T, F> {
|
||||
/// Chain an additional handler that will accept all requests regardless of
|
||||
/// its HTTP method.
|
||||
///
|
||||
/// See [`MethodRouter::get`] for an example.
|
||||
pub fn any<H2, T2>(self, handler: H2) -> MethodRouter<H2, B, T2, Self>
|
||||
where
|
||||
H2: Handler<B, T2>,
|
||||
{
|
||||
self.on(MethodFilter::all(), handler)
|
||||
}
|
||||
|
||||
/// Chain an additional handler that will only accept `CONNECT` requests.
|
||||
///
|
||||
/// See [`MethodRouter::get`] for an example.
|
||||
pub fn connect<H2, T2>(self, handler: H2) -> MethodRouter<H2, B, T2, Self>
|
||||
where
|
||||
H2: Handler<B, T2>,
|
||||
{
|
||||
self.on(MethodFilter::CONNECT, handler)
|
||||
}
|
||||
|
||||
/// Chain an additional handler that will only accept `DELETE` requests.
|
||||
///
|
||||
/// See [`MethodRouter::get`] for an example.
|
||||
pub fn delete<H2, T2>(self, handler: H2) -> MethodRouter<H2, B, T2, Self>
|
||||
where
|
||||
H2: Handler<B, T2>,
|
||||
{
|
||||
self.on(MethodFilter::DELETE, handler)
|
||||
}
|
||||
|
||||
/// Chain an additional handler that will only accept `GET` requests.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{routing::post, Router};
|
||||
///
|
||||
/// async fn handler() {}
|
||||
///
|
||||
/// async fn other_handler() {}
|
||||
///
|
||||
/// // Requests to `GET /` will go to `handler` and `POST /` will go to
|
||||
/// // `other_handler`.
|
||||
/// let app = Router::new().route("/", post(handler).get(other_handler));
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// Note that `get` routes will also be called for `HEAD` requests but will have
|
||||
/// the response body removed. Make sure to add explicit `HEAD` routes
|
||||
/// afterwards.
|
||||
pub fn get<H2, T2>(self, handler: H2) -> MethodRouter<H2, B, T2, Self>
|
||||
where
|
||||
H2: Handler<B, T2>,
|
||||
{
|
||||
self.on(MethodFilter::GET | MethodFilter::HEAD, handler)
|
||||
}
|
||||
|
||||
/// Chain an additional handler that will only accept `HEAD` requests.
|
||||
///
|
||||
/// See [`MethodRouter::get`] for an example.
|
||||
pub fn head<H2, T2>(self, handler: H2) -> MethodRouter<H2, B, T2, Self>
|
||||
where
|
||||
H2: Handler<B, T2>,
|
||||
{
|
||||
self.on(MethodFilter::HEAD, handler)
|
||||
}
|
||||
|
||||
/// Chain an additional handler that will only accept `OPTIONS` requests.
|
||||
///
|
||||
/// See [`MethodRouter::get`] for an example.
|
||||
pub fn options<H2, T2>(self, handler: H2) -> MethodRouter<H2, B, T2, Self>
|
||||
where
|
||||
H2: Handler<B, T2>,
|
||||
{
|
||||
self.on(MethodFilter::OPTIONS, handler)
|
||||
}
|
||||
|
||||
/// Chain an additional handler that will only accept `PATCH` requests.
|
||||
///
|
||||
/// See [`MethodRouter::get`] for an example.
|
||||
pub fn patch<H2, T2>(self, handler: H2) -> MethodRouter<H2, B, T2, Self>
|
||||
where
|
||||
H2: Handler<B, T2>,
|
||||
{
|
||||
self.on(MethodFilter::PATCH, handler)
|
||||
}
|
||||
|
||||
/// Chain an additional handler that will only accept `POST` requests.
|
||||
///
|
||||
/// See [`MethodRouter::get`] for an example.
|
||||
pub fn post<H2, T2>(self, handler: H2) -> MethodRouter<H2, B, T2, Self>
|
||||
where
|
||||
H2: Handler<B, T2>,
|
||||
{
|
||||
self.on(MethodFilter::POST, handler)
|
||||
}
|
||||
|
||||
/// Chain an additional handler that will only accept `PUT` requests.
|
||||
///
|
||||
/// See [`MethodRouter::get`] for an example.
|
||||
pub fn put<H2, T2>(self, handler: H2) -> MethodRouter<H2, B, T2, Self>
|
||||
where
|
||||
H2: Handler<B, T2>,
|
||||
{
|
||||
self.on(MethodFilter::PUT, handler)
|
||||
}
|
||||
|
||||
/// Chain an additional handler that will only accept `TRACE` requests.
|
||||
///
|
||||
/// See [`MethodRouter::get`] for an example.
|
||||
pub fn trace<H2, T2>(self, handler: H2) -> MethodRouter<H2, B, T2, Self>
|
||||
where
|
||||
H2: Handler<B, T2>,
|
||||
{
|
||||
self.on(MethodFilter::TRACE, handler)
|
||||
}
|
||||
|
||||
/// Chain an additional handler that will accept requests matching the given
|
||||
/// `MethodFilter`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// routing::get,
|
||||
/// Router,
|
||||
/// routing::MethodFilter
|
||||
/// };
|
||||
///
|
||||
/// async fn handler() {}
|
||||
///
|
||||
/// async fn other_handler() {}
|
||||
///
|
||||
/// // Requests to `GET /` will go to `handler` and `DELETE /` will go to
|
||||
/// // `other_handler`
|
||||
/// let app = Router::new().route("/", get(handler).on(MethodFilter::DELETE, other_handler));
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
pub fn on<H2, T2>(self, method: MethodFilter, handler: H2) -> MethodRouter<H2, B, T2, Self>
|
||||
where
|
||||
H2: Handler<B, T2>,
|
||||
{
|
||||
MethodRouter {
|
||||
method,
|
||||
handler,
|
||||
fallback: self,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, B, T, F> Service<Request<B>> for MethodRouter<H, B, T, F>
|
||||
where
|
||||
H: Handler<B, T>,
|
||||
F: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible> + Clone,
|
||||
B: Send + 'static,
|
||||
{
|
||||
type Response = Response<BoxBody>;
|
||||
type Error = Infallible;
|
||||
type Future = MethodRouterFuture<F, B>;
|
||||
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Request<B>) -> Self::Future {
|
||||
let req_method = req.method().clone();
|
||||
|
||||
let fut = if self.method.matches(req.method()) {
|
||||
let fut = Handler::call(self.handler.clone(), req);
|
||||
Either::A { inner: fut }
|
||||
} else {
|
||||
let fut = self.fallback.clone().oneshot(req);
|
||||
Either::B { inner: fut }
|
||||
};
|
||||
|
||||
MethodRouterFuture {
|
||||
inner: fut,
|
||||
req_method,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// The response future for [`MethodRouter`].
|
||||
pub struct MethodRouterFuture<F, B>
|
||||
where
|
||||
F: Service<Request<B>>
|
||||
{
|
||||
#[pin]
|
||||
pub(super) inner: Either<
|
||||
BoxFuture<'static, Response<BoxBody>>,
|
||||
Oneshot<F, Request<B>>,
|
||||
>,
|
||||
pub(super) req_method: Method,
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, B> Future for MethodRouterFuture<F, B>
|
||||
where
|
||||
F: Service<Request<B>, Response = Response<BoxBody>>,
|
||||
{
|
||||
type Output = Result<Response<BoxBody>, F::Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
let response = match this.inner.project() {
|
||||
EitherProj::A { inner } => ready!(inner.poll(cx)),
|
||||
EitherProj::B { inner } => ready!(inner.poll(cx))?,
|
||||
};
|
||||
|
||||
if this.req_method == &Method::HEAD {
|
||||
let response = response.map(|_| box_body(Empty::new()));
|
||||
Poll::Ready(Ok(response))
|
||||
} else {
|
||||
Poll::Ready(Ok(response))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, B> fmt::Debug for MethodRouterFuture<F, B>
|
||||
where
|
||||
F: Service<Request<B>>,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("MethodRouterFuture").finish()
|
||||
}
|
||||
}
|
||||
+22
-15
@@ -1,4 +1,4 @@
|
||||
//! Routing between [`Service`]s.
|
||||
//! Routing between [`Service`]s and handlers.
|
||||
|
||||
use self::future::{EmptyRouterFuture, NestedFuture, RouteFuture, RoutesFuture};
|
||||
use crate::{
|
||||
@@ -28,12 +28,19 @@ use tower_layer::Layer;
|
||||
use tower_service::Service;
|
||||
|
||||
pub mod future;
|
||||
pub mod handler_method_router;
|
||||
pub mod service_method_router;
|
||||
|
||||
mod method_filter;
|
||||
mod or;
|
||||
|
||||
pub use self::method_filter::MethodFilter;
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use self::handler_method_router::{
|
||||
any, connect, delete, get, head, on, options, patch, post, put, trace, MethodRouter,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
struct RouteId(u64);
|
||||
|
||||
@@ -109,7 +116,7 @@ where
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{handler::{get, delete}, Router};
|
||||
/// use axum::{routing::{get, delete}, Router};
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// .route("/", get(root))
|
||||
@@ -136,7 +143,7 @@ where
|
||||
/// Panics if the route overlaps with another route:
|
||||
///
|
||||
/// ```should_panic
|
||||
/// use axum::{handler::get, Router};
|
||||
/// use axum::{routing::get, Router};
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// .route("/", get(|| async {}))
|
||||
@@ -149,7 +156,7 @@ where
|
||||
/// This also applies to `nest` which is similar to a wildcard route:
|
||||
///
|
||||
/// ```should_panic
|
||||
/// use axum::{handler::get, Router};
|
||||
/// use axum::{routing::get, Router};
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// // this is similar to `/api/*`
|
||||
@@ -164,7 +171,7 @@ where
|
||||
/// Note that routes like `/:key` and `/foo` are considered overlapping:
|
||||
///
|
||||
/// ```should_panic
|
||||
/// use axum::{handler::get, Router};
|
||||
/// use axum::{routing::get, Router};
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// .route("/foo", get(|| async {}))
|
||||
@@ -204,7 +211,7 @@ where
|
||||
///
|
||||
/// ```
|
||||
/// use axum::{
|
||||
/// handler::get,
|
||||
/// routing::get,
|
||||
/// Router,
|
||||
/// };
|
||||
/// use http::Uri;
|
||||
@@ -239,7 +246,7 @@ where
|
||||
/// ```
|
||||
/// use axum::{
|
||||
/// extract::Path,
|
||||
/// handler::get,
|
||||
/// routing::get,
|
||||
/// Router,
|
||||
/// };
|
||||
/// use std::collections::HashMap;
|
||||
@@ -265,7 +272,7 @@ where
|
||||
/// ```
|
||||
/// use axum::{
|
||||
/// Router,
|
||||
/// service::get,
|
||||
/// routing::service_method_router::get,
|
||||
/// error_handling::HandleErrorExt,
|
||||
/// http::StatusCode,
|
||||
/// };
|
||||
@@ -294,7 +301,7 @@ where
|
||||
/// the prefix stripped.
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{handler::get, http::Uri, Router};
|
||||
/// use axum::{routing::get, http::Uri, Router};
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// .route("/foo/*rest", get(|uri: Uri| async {
|
||||
@@ -366,7 +373,7 @@ where
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// handler::get,
|
||||
/// routing::get,
|
||||
/// Router,
|
||||
/// };
|
||||
/// use tower::limit::{ConcurrencyLimitLayer, ConcurrencyLimit};
|
||||
@@ -395,7 +402,7 @@ where
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// handler::get,
|
||||
/// routing::get,
|
||||
/// Router,
|
||||
/// };
|
||||
/// use tower_http::trace::TraceLayer;
|
||||
@@ -440,7 +447,7 @@ where
|
||||
///
|
||||
/// ```
|
||||
/// use axum::{
|
||||
/// handler::get,
|
||||
/// routing::get,
|
||||
/// Router,
|
||||
/// };
|
||||
///
|
||||
@@ -470,7 +477,7 @@ where
|
||||
/// ```
|
||||
/// use axum::{
|
||||
/// extract::ConnectInfo,
|
||||
/// handler::get,
|
||||
/// routing::get,
|
||||
/// Router,
|
||||
/// };
|
||||
/// use std::net::SocketAddr;
|
||||
@@ -496,7 +503,7 @@ where
|
||||
/// ```
|
||||
/// use axum::{
|
||||
/// extract::connect_info::{ConnectInfo, Connected},
|
||||
/// handler::get,
|
||||
/// routing::get,
|
||||
/// Router,
|
||||
/// };
|
||||
/// use hyper::server::conn::AddrStream;
|
||||
@@ -555,7 +562,7 @@ where
|
||||
///
|
||||
/// ```
|
||||
/// use axum::{
|
||||
/// handler::get,
|
||||
/// routing::get,
|
||||
/// Router,
|
||||
/// };
|
||||
/// #
|
||||
|
||||
@@ -0,0 +1,565 @@
|
||||
//! Routing for [`Service`'s] based on HTTP methods.
|
||||
//!
|
||||
//! Most of the time applications will be written by composing
|
||||
//! [handlers](crate::handler), however sometimes you might have some general
|
||||
//! [`Service`] that you want to route requests to. That is enabled by the
|
||||
//! functions in this module.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! Using [`Redirect`] to redirect requests can be done like so:
|
||||
//!
|
||||
//! ```
|
||||
//! use tower_http::services::Redirect;
|
||||
//! use axum::{
|
||||
//! body::Body,
|
||||
//! routing::{get, service_method_router as service},
|
||||
//! http::Request,
|
||||
//! Router,
|
||||
//! };
|
||||
//!
|
||||
//! async fn handler(request: Request<Body>) { /* ... */ }
|
||||
//!
|
||||
//! let redirect_service = Redirect::<Body>::permanent("/new".parse().unwrap());
|
||||
//!
|
||||
//! let app = Router::new()
|
||||
//! .route("/old", service::get(redirect_service))
|
||||
//! .route("/new", get(handler));
|
||||
//! # async {
|
||||
//! # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
//! # };
|
||||
//! ```
|
||||
//!
|
||||
//! # Regarding backpressure and `Service::poll_ready`
|
||||
//!
|
||||
//! Generally routing to one of multiple services and backpressure doesn't mix
|
||||
//! well. Ideally you would want ensure a service is ready to receive a request
|
||||
//! before calling it. However, in order to know which service to call, you need
|
||||
//! the request...
|
||||
//!
|
||||
//! One approach is to not consider the router service itself ready until all
|
||||
//! destination services are ready. That is the approach used by
|
||||
//! [`tower::steer::Steer`].
|
||||
//!
|
||||
//! Another approach is to always consider all services ready (always return
|
||||
//! `Poll::Ready(Ok(()))`) from `Service::poll_ready` and then actually drive
|
||||
//! readiness inside the response future returned by `Service::call`. This works
|
||||
//! well when your services don't care about backpressure and are always ready
|
||||
//! anyway.
|
||||
//!
|
||||
//! axum expects that all services used in your app wont care about
|
||||
//! backpressure and so it uses the latter strategy. However that means you
|
||||
//! should avoid routing to a service (or using a middleware) that _does_ care
|
||||
//! about backpressure. At the very least you should [load shed] so requests are
|
||||
//! dropped quickly and don't keep piling up.
|
||||
//!
|
||||
//! It also means that if `poll_ready` returns an error then that error will be
|
||||
//! returned in the response future from `call` and _not_ from `poll_ready`. In
|
||||
//! that case, the underlying service will _not_ be discarded and will continue
|
||||
//! to be used for future requests. Services that expect to be discarded if
|
||||
//! `poll_ready` fails should _not_ be used with axum.
|
||||
//!
|
||||
//! One possible approach is to only apply backpressure sensitive middleware
|
||||
//! around your entire app. This is possible because axum applications are
|
||||
//! themselves services:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use axum::{
|
||||
//! routing::get,
|
||||
//! Router,
|
||||
//! };
|
||||
//! use tower::ServiceBuilder;
|
||||
//! # let some_backpressure_sensitive_middleware =
|
||||
//! # tower::layer::util::Identity::new();
|
||||
//!
|
||||
//! async fn handler() { /* ... */ }
|
||||
//!
|
||||
//! let app = Router::new().route("/", get(handler));
|
||||
//!
|
||||
//! let app = ServiceBuilder::new()
|
||||
//! .layer(some_backpressure_sensitive_middleware)
|
||||
//! .service(app);
|
||||
//! # async {
|
||||
//! # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
//! # };
|
||||
//! ```
|
||||
//!
|
||||
//! However when applying middleware around your whole application in this way
|
||||
//! you have to take care that errors are still being handled with
|
||||
//! appropriately.
|
||||
//!
|
||||
//! Also note that handlers created from async functions don't care about
|
||||
//! backpressure and are always ready. So if you're not using any Tower
|
||||
//! middleware you don't have to worry about any of this.
|
||||
//!
|
||||
//! [`Redirect`]: tower_http::services::Redirect
|
||||
//! [load shed]: tower::load_shed
|
||||
//! [`Service`'s]: tower::Service
|
||||
|
||||
use crate::{
|
||||
body::{box_body, BoxBody},
|
||||
routing::{EmptyRouter, MethodFilter},
|
||||
util::{Either, EitherProj},
|
||||
BoxError,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures_util::ready;
|
||||
use http::{Method, Request, Response};
|
||||
use http_body::Empty;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::marker::PhantomData;
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tower::util::Oneshot;
|
||||
use tower::ServiceExt as _;
|
||||
use tower_service::Service;
|
||||
|
||||
/// Route requests with any standard HTTP method to the given service.
|
||||
///
|
||||
/// See [`get`] for an example.
|
||||
///
|
||||
/// Note that this only accepts the standard HTTP methods. If you need to
|
||||
/// support non-standard methods you can route directly to a [`Service`].
|
||||
pub fn any<S, B>(svc: S) -> MethodRouter<S, EmptyRouter<S::Error>, B>
|
||||
where
|
||||
S: Service<Request<B>> + Clone,
|
||||
{
|
||||
on(MethodFilter::all(), svc)
|
||||
}
|
||||
|
||||
/// Route `CONNECT` requests to the given service.
|
||||
///
|
||||
/// See [`get`] for an example.
|
||||
pub fn connect<S, B>(svc: S) -> MethodRouter<S, EmptyRouter<S::Error>, B>
|
||||
where
|
||||
S: Service<Request<B>> + Clone,
|
||||
{
|
||||
on(MethodFilter::CONNECT, svc)
|
||||
}
|
||||
|
||||
/// Route `DELETE` requests to the given service.
|
||||
///
|
||||
/// See [`get`] for an example.
|
||||
pub fn delete<S, B>(svc: S) -> MethodRouter<S, EmptyRouter<S::Error>, B>
|
||||
where
|
||||
S: Service<Request<B>> + Clone,
|
||||
{
|
||||
on(MethodFilter::DELETE, svc)
|
||||
}
|
||||
|
||||
/// Route `GET` requests to the given service.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// http::Request,
|
||||
/// Router,
|
||||
/// routing::service_method_router as service,
|
||||
/// };
|
||||
/// use http::Response;
|
||||
/// use std::convert::Infallible;
|
||||
/// use hyper::Body;
|
||||
///
|
||||
/// let service = tower::service_fn(|request: Request<Body>| async {
|
||||
/// Ok::<_, Infallible>(Response::new(Body::empty()))
|
||||
/// });
|
||||
///
|
||||
/// // Requests to `GET /` will go to `service`.
|
||||
/// let app = Router::new().route("/", service::get(service));
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// Note that `get` routes will also be called for `HEAD` requests but will have
|
||||
/// the response body removed. Make sure to add explicit `HEAD` routes
|
||||
/// afterwards.
|
||||
pub fn get<S, B>(svc: S) -> MethodRouter<S, EmptyRouter<S::Error>, B>
|
||||
where
|
||||
S: Service<Request<B>> + Clone,
|
||||
{
|
||||
on(MethodFilter::GET | MethodFilter::HEAD, svc)
|
||||
}
|
||||
|
||||
/// Route `HEAD` requests to the given service.
|
||||
///
|
||||
/// See [`get`] for an example.
|
||||
pub fn head<S, B>(svc: S) -> MethodRouter<S, EmptyRouter<S::Error>, B>
|
||||
where
|
||||
S: Service<Request<B>> + Clone,
|
||||
{
|
||||
on(MethodFilter::HEAD, svc)
|
||||
}
|
||||
|
||||
/// Route `OPTIONS` requests to the given service.
|
||||
///
|
||||
/// See [`get`] for an example.
|
||||
pub fn options<S, B>(svc: S) -> MethodRouter<S, EmptyRouter<S::Error>, B>
|
||||
where
|
||||
S: Service<Request<B>> + Clone,
|
||||
{
|
||||
on(MethodFilter::OPTIONS, svc)
|
||||
}
|
||||
|
||||
/// Route `PATCH` requests to the given service.
|
||||
///
|
||||
/// See [`get`] for an example.
|
||||
pub fn patch<S, B>(svc: S) -> MethodRouter<S, EmptyRouter<S::Error>, B>
|
||||
where
|
||||
S: Service<Request<B>> + Clone,
|
||||
{
|
||||
on(MethodFilter::PATCH, svc)
|
||||
}
|
||||
|
||||
/// Route `POST` requests to the given service.
|
||||
///
|
||||
/// See [`get`] for an example.
|
||||
pub fn post<S, B>(svc: S) -> MethodRouter<S, EmptyRouter<S::Error>, B>
|
||||
where
|
||||
S: Service<Request<B>> + Clone,
|
||||
{
|
||||
on(MethodFilter::POST, svc)
|
||||
}
|
||||
|
||||
/// Route `PUT` requests to the given service.
|
||||
///
|
||||
/// See [`get`] for an example.
|
||||
pub fn put<S, B>(svc: S) -> MethodRouter<S, EmptyRouter<S::Error>, B>
|
||||
where
|
||||
S: Service<Request<B>> + Clone,
|
||||
{
|
||||
on(MethodFilter::PUT, svc)
|
||||
}
|
||||
|
||||
/// Route `TRACE` requests to the given service.
|
||||
///
|
||||
/// See [`get`] for an example.
|
||||
pub fn trace<S, B>(svc: S) -> MethodRouter<S, EmptyRouter<S::Error>, B>
|
||||
where
|
||||
S: Service<Request<B>> + Clone,
|
||||
{
|
||||
on(MethodFilter::TRACE, svc)
|
||||
}
|
||||
|
||||
/// Route requests with the given method to the service.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// http::Request,
|
||||
/// routing::on,
|
||||
/// Router,
|
||||
/// routing::{MethodFilter, service_method_router as service},
|
||||
/// };
|
||||
/// use http::Response;
|
||||
/// use std::convert::Infallible;
|
||||
/// use hyper::Body;
|
||||
///
|
||||
/// let service = tower::service_fn(|request: Request<Body>| async {
|
||||
/// Ok::<_, Infallible>(Response::new(Body::empty()))
|
||||
/// });
|
||||
///
|
||||
/// // Requests to `POST /` will go to `service`.
|
||||
/// let app = Router::new().route("/", service::on(MethodFilter::POST, service));
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
pub fn on<S, B>(method: MethodFilter, svc: S) -> MethodRouter<S, EmptyRouter<S::Error>, B>
|
||||
where
|
||||
S: Service<Request<B>> + Clone,
|
||||
{
|
||||
MethodRouter {
|
||||
method,
|
||||
svc,
|
||||
fallback: EmptyRouter::method_not_allowed(),
|
||||
_request_body: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Service`] that accepts requests based on a [`MethodFilter`] and allows
|
||||
/// chaining additional services.
|
||||
#[derive(Debug)] // TODO(david): don't require debug for B
|
||||
pub struct MethodRouter<S, F, B> {
|
||||
pub(crate) method: MethodFilter,
|
||||
pub(crate) svc: S,
|
||||
pub(crate) fallback: F,
|
||||
pub(crate) _request_body: PhantomData<fn() -> B>,
|
||||
}
|
||||
|
||||
impl<S, F, B> Clone for MethodRouter<S, F, B>
|
||||
where
|
||||
S: Clone,
|
||||
F: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
method: self.method,
|
||||
svc: self.svc.clone(),
|
||||
fallback: self.fallback.clone(),
|
||||
_request_body: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, F, B> MethodRouter<S, F, B> {
|
||||
/// Chain an additional service that will accept all requests regardless of
|
||||
/// its HTTP method.
|
||||
///
|
||||
/// See [`MethodRouter::get`] for an example.
|
||||
pub fn any<T>(self, svc: T) -> MethodRouter<T, Self, B>
|
||||
where
|
||||
T: Service<Request<B>> + Clone,
|
||||
{
|
||||
self.on(MethodFilter::all(), svc)
|
||||
}
|
||||
|
||||
/// Chain an additional service that will only accept `CONNECT` requests.
|
||||
///
|
||||
/// See [`MethodRouter::get`] for an example.
|
||||
pub fn connect<T>(self, svc: T) -> MethodRouter<T, Self, B>
|
||||
where
|
||||
T: Service<Request<B>> + Clone,
|
||||
{
|
||||
self.on(MethodFilter::CONNECT, svc)
|
||||
}
|
||||
|
||||
/// Chain an additional service that will only accept `DELETE` requests.
|
||||
///
|
||||
/// See [`MethodRouter::get`] for an example.
|
||||
pub fn delete<T>(self, svc: T) -> MethodRouter<T, Self, B>
|
||||
where
|
||||
T: Service<Request<B>> + Clone,
|
||||
{
|
||||
self.on(MethodFilter::DELETE, svc)
|
||||
}
|
||||
|
||||
/// Chain an additional service that will only accept `GET` requests.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// http::Request,
|
||||
/// Router,
|
||||
/// routing::{MethodFilter, on, service_method_router as service},
|
||||
/// };
|
||||
/// use http::Response;
|
||||
/// use std::convert::Infallible;
|
||||
/// use hyper::Body;
|
||||
///
|
||||
/// let service = tower::service_fn(|request: Request<Body>| async {
|
||||
/// Ok::<_, Infallible>(Response::new(Body::empty()))
|
||||
/// });
|
||||
///
|
||||
/// let other_service = tower::service_fn(|request: Request<Body>| async {
|
||||
/// Ok::<_, Infallible>(Response::new(Body::empty()))
|
||||
/// });
|
||||
///
|
||||
/// // Requests to `GET /` will go to `service` and `POST /` will go to
|
||||
/// // `other_service`.
|
||||
/// let app = Router::new().route("/", service::post(service).get(other_service));
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// Note that `get` routes will also be called for `HEAD` requests but will have
|
||||
/// the response body removed. Make sure to add explicit `HEAD` routes
|
||||
/// afterwards.
|
||||
pub fn get<T>(self, svc: T) -> MethodRouter<T, Self, B>
|
||||
where
|
||||
T: Service<Request<B>> + Clone,
|
||||
{
|
||||
self.on(MethodFilter::GET | MethodFilter::HEAD, svc)
|
||||
}
|
||||
|
||||
/// Chain an additional service that will only accept `HEAD` requests.
|
||||
///
|
||||
/// See [`MethodRouter::get`] for an example.
|
||||
pub fn head<T>(self, svc: T) -> MethodRouter<T, Self, B>
|
||||
where
|
||||
T: Service<Request<B>> + Clone,
|
||||
{
|
||||
self.on(MethodFilter::HEAD, svc)
|
||||
}
|
||||
|
||||
/// Chain an additional service that will only accept `OPTIONS` requests.
|
||||
///
|
||||
/// See [`MethodRouter::get`] for an example.
|
||||
pub fn options<T>(self, svc: T) -> MethodRouter<T, Self, B>
|
||||
where
|
||||
T: Service<Request<B>> + Clone,
|
||||
{
|
||||
self.on(MethodFilter::OPTIONS, svc)
|
||||
}
|
||||
|
||||
/// Chain an additional service that will only accept `PATCH` requests.
|
||||
///
|
||||
/// See [`MethodRouter::get`] for an example.
|
||||
pub fn patch<T>(self, svc: T) -> MethodRouter<T, Self, B>
|
||||
where
|
||||
T: Service<Request<B>> + Clone,
|
||||
{
|
||||
self.on(MethodFilter::PATCH, svc)
|
||||
}
|
||||
|
||||
/// Chain an additional service that will only accept `POST` requests.
|
||||
///
|
||||
/// See [`MethodRouter::get`] for an example.
|
||||
pub fn post<T>(self, svc: T) -> MethodRouter<T, Self, B>
|
||||
where
|
||||
T: Service<Request<B>> + Clone,
|
||||
{
|
||||
self.on(MethodFilter::POST, svc)
|
||||
}
|
||||
|
||||
/// Chain an additional service that will only accept `PUT` requests.
|
||||
///
|
||||
/// See [`MethodRouter::get`] for an example.
|
||||
pub fn put<T>(self, svc: T) -> MethodRouter<T, Self, B>
|
||||
where
|
||||
T: Service<Request<B>> + Clone,
|
||||
{
|
||||
self.on(MethodFilter::PUT, svc)
|
||||
}
|
||||
|
||||
/// Chain an additional service that will only accept `TRACE` requests.
|
||||
///
|
||||
/// See [`MethodRouter::get`] for an example.
|
||||
pub fn trace<T>(self, svc: T) -> MethodRouter<T, Self, B>
|
||||
where
|
||||
T: Service<Request<B>> + Clone,
|
||||
{
|
||||
self.on(MethodFilter::TRACE, svc)
|
||||
}
|
||||
|
||||
/// Chain an additional service that will accept requests matching the given
|
||||
/// `MethodFilter`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// http::Request,
|
||||
/// Router,
|
||||
/// routing::{MethodFilter, on, service_method_router as service},
|
||||
/// };
|
||||
/// use http::Response;
|
||||
/// use std::convert::Infallible;
|
||||
/// use hyper::Body;
|
||||
///
|
||||
/// let service = tower::service_fn(|request: Request<Body>| async {
|
||||
/// Ok::<_, Infallible>(Response::new(Body::empty()))
|
||||
/// });
|
||||
///
|
||||
/// let other_service = tower::service_fn(|request: Request<Body>| async {
|
||||
/// Ok::<_, Infallible>(Response::new(Body::empty()))
|
||||
/// });
|
||||
///
|
||||
/// // Requests to `DELETE /` will go to `service`
|
||||
/// let app = Router::new().route("/", service::on(MethodFilter::DELETE, service));
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
pub fn on<T>(self, method: MethodFilter, svc: T) -> MethodRouter<T, Self, B>
|
||||
where
|
||||
T: Service<Request<B>> + Clone,
|
||||
{
|
||||
MethodRouter {
|
||||
method,
|
||||
svc,
|
||||
fallback: self,
|
||||
_request_body: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, F, B, ResBody> Service<Request<B>> for MethodRouter<S, F, B>
|
||||
where
|
||||
S: Service<Request<B>, Response = Response<ResBody>> + Clone,
|
||||
ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static,
|
||||
ResBody::Error: Into<BoxError>,
|
||||
F: Service<Request<B>, Response = Response<BoxBody>, Error = S::Error> + Clone,
|
||||
{
|
||||
type Response = Response<BoxBody>;
|
||||
type Error = S::Error;
|
||||
type Future = MethodRouterFuture<S, F, B>;
|
||||
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Request<B>) -> Self::Future {
|
||||
let req_method = req.method().clone();
|
||||
|
||||
let f = if self.method.matches(req.method()) {
|
||||
let fut = self.svc.clone().oneshot(req);
|
||||
Either::A { inner: fut }
|
||||
} else {
|
||||
let fut = self.fallback.clone().oneshot(req);
|
||||
Either::B { inner: fut }
|
||||
};
|
||||
|
||||
MethodRouterFuture {
|
||||
inner: f,
|
||||
req_method,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// The response future for [`MethodRouter`].
|
||||
pub struct MethodRouterFuture<S, F, B>
|
||||
where
|
||||
S: Service<Request<B>>,
|
||||
F: Service<Request<B>>
|
||||
{
|
||||
#[pin]
|
||||
pub(super) inner: Either<
|
||||
Oneshot<S, Request<B>>,
|
||||
Oneshot<F, Request<B>>,
|
||||
>,
|
||||
pub(super) req_method: Method,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, F, B, ResBody> Future for MethodRouterFuture<S, F, B>
|
||||
where
|
||||
S: Service<Request<B>, Response = Response<ResBody>> + Clone,
|
||||
ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static,
|
||||
ResBody::Error: Into<BoxError>,
|
||||
F: Service<Request<B>, Response = Response<BoxBody>, Error = S::Error>,
|
||||
{
|
||||
type Output = Result<Response<BoxBody>, S::Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
|
||||
let response = match this.inner.project() {
|
||||
EitherProj::A { inner } => ready!(inner.poll(cx))?.map(box_body),
|
||||
EitherProj::B { inner } => ready!(inner.poll(cx))?,
|
||||
};
|
||||
|
||||
if this.req_method == &Method::HEAD {
|
||||
let response = response.map(|_| box_body(Empty::new()));
|
||||
Poll::Ready(Ok(response))
|
||||
} else {
|
||||
Poll::Ready(Ok(response))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn traits() {
|
||||
use crate::tests::*;
|
||||
|
||||
assert_send::<MethodRouter<(), (), NotSendSync>>();
|
||||
assert_sync::<MethodRouter<(), (), NotSendSync>>();
|
||||
}
|
||||
Reference in New Issue
Block a user