//! 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_routing as service},
//! http::Request,
//! Router,
//! };
//!
//! async fn handler(request: Request
) { /* ... */ }
//!
//! let redirect_service = Redirect::::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::{MethodFilter, MethodNotAllowed},
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(svc: S) -> MethodRouter, B>
where
S: Service> + Clone,
{
on(MethodFilter::all(), svc)
}
/// Route `DELETE` requests to the given service.
///
/// See [`get`] for an example.
pub fn delete(svc: S) -> MethodRouter, B>
where
S: Service> + Clone,
{
on(MethodFilter::DELETE, svc)
}
/// Route `GET` requests to the given service.
///
/// # Example
///
/// ```rust
/// use axum::{
/// http::Request,
/// Router,
/// routing::service_method_routing as service,
/// };
/// use http::Response;
/// use std::convert::Infallible;
/// use hyper::Body;
///
/// let service = tower::service_fn(|request: Request| 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(svc: S) -> MethodRouter, B>
where
S: Service> + Clone,
{
on(MethodFilter::GET | MethodFilter::HEAD, svc)
}
/// Route `HEAD` requests to the given service.
///
/// See [`get`] for an example.
pub fn head(svc: S) -> MethodRouter, B>
where
S: Service> + Clone,
{
on(MethodFilter::HEAD, svc)
}
/// Route `OPTIONS` requests to the given service.
///
/// See [`get`] for an example.
pub fn options(svc: S) -> MethodRouter, B>
where
S: Service> + Clone,
{
on(MethodFilter::OPTIONS, svc)
}
/// Route `PATCH` requests to the given service.
///
/// See [`get`] for an example.
pub fn patch(svc: S) -> MethodRouter, B>
where
S: Service> + Clone,
{
on(MethodFilter::PATCH, svc)
}
/// Route `POST` requests to the given service.
///
/// See [`get`] for an example.
pub fn post(svc: S) -> MethodRouter, B>
where
S: Service> + Clone,
{
on(MethodFilter::POST, svc)
}
/// Route `PUT` requests to the given service.
///
/// See [`get`] for an example.
pub fn put(svc: S) -> MethodRouter, B>
where
S: Service> + Clone,
{
on(MethodFilter::PUT, svc)
}
/// Route `TRACE` requests to the given service.
///
/// See [`get`] for an example.
pub fn trace(svc: S) -> MethodRouter, B>
where
S: Service> + 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_routing as service},
/// };
/// use http::Response;
/// use std::convert::Infallible;
/// use hyper::Body;
///
/// let service = tower::service_fn(|request: Request| 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(method: MethodFilter, svc: S) -> MethodRouter, B>
where
S: Service> + Clone,
{
MethodRouter {
method,
svc,
fallback: MethodNotAllowed::new(),
_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 {
pub(crate) method: MethodFilter,
pub(crate) svc: S,
pub(crate) fallback: F,
pub(crate) _request_body: PhantomData B>,
}
impl Clone for MethodRouter
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 MethodRouter {
/// Chain an additional service that will accept all requests regardless of
/// its HTTP method.
///
/// See [`MethodRouter::get`] for an example.
pub fn any(self, svc: T) -> MethodRouter
where
T: Service> + Clone,
{
self.on(MethodFilter::all(), svc)
}
/// Chain an additional service that will only accept `DELETE` requests.
///
/// See [`MethodRouter::get`] for an example.
pub fn delete(self, svc: T) -> MethodRouter
where
T: Service> + 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_routing as service},
/// };
/// use http::Response;
/// use std::convert::Infallible;
/// use hyper::Body;
///
/// let service = tower::service_fn(|request: Request| async {
/// Ok::<_, Infallible>(Response::new(Body::empty()))
/// });
///
/// let other_service = tower::service_fn(|request: Request| 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(self, svc: T) -> MethodRouter
where
T: Service> + 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(self, svc: T) -> MethodRouter
where
T: Service> + 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(self, svc: T) -> MethodRouter
where
T: Service> + 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(self, svc: T) -> MethodRouter
where
T: Service> + 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(self, svc: T) -> MethodRouter
where
T: Service> + 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(self, svc: T) -> MethodRouter
where
T: Service> + 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(self, svc: T) -> MethodRouter
where
T: Service> + 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_routing as service},
/// };
/// use http::Response;
/// use std::convert::Infallible;
/// use hyper::Body;
///
/// let service = tower::service_fn(|request: Request| async {
/// Ok::<_, Infallible>(Response::new(Body::empty()))
/// });
///
/// let other_service = tower::service_fn(|request: Request| 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(self, method: MethodFilter, svc: T) -> MethodRouter
where
T: Service> + Clone,
{
MethodRouter {
method,
svc,
fallback: self,
_request_body: PhantomData,
}
}
}
impl Service> for MethodRouter
where
S: Service, Response = Response> + Clone,
ResBody: http_body::Body + Send + Sync + 'static,
ResBody::Error: Into,
F: Service, Response = Response, Error = S::Error> + Clone,
{
type Response = Response;
type Error = S::Error;
type Future = MethodRouterFuture;
#[inline]
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request) -> 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
where
S: Service>,
F: Service>
{
#[pin]
pub(super) inner: Either<
Oneshot>,
Oneshot>,
>,
pub(super) req_method: Method,
}
}
impl Future for MethodRouterFuture
where
S: Service, Response = Response> + Clone,
ResBody: http_body::Body + Send + Sync + 'static,
ResBody::Error: Into,
F: Service, Response = Response, Error = S::Error>,
{
type Output = Result, S::Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll {
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::>();
assert_sync::>();
}