mirror of
https://github.com/tokio-rs/axum.git
synced 2025-03-13 19:27:53 +01:00
New method router (#521)
* Empty crate * basic setup * Support `HEAD` * Add remaining methods * Impl Debug * Add `MethodRouter::merge` * WIP * Support same route with different methods in different calls * Update changelog * Bring back `any` and `any_service` * Address review feedback
This commit is contained in:
parent
3cf8ee3b5e
commit
2bd0310463
25 changed files with 1533 additions and 803 deletions
|
@ -7,7 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
# Unreleased
|
||||
|
||||
- None.
|
||||
- **breaking:** New `MethodRouter` that works similarly to `Router`:
|
||||
- Route to handlers and services with the same type
|
||||
- Add middleware to some routes more easily with `MethodRouter::layer` and
|
||||
`MethodRouter::route_layer`.
|
||||
- Merge method routers with `MethodRouter::merge`
|
||||
- Customize response for unsupported methods with `MethodRouter::fallback`
|
||||
- **fixed:** Adding the same route with different methods now works ie
|
||||
`.route("/", get(_)).route("/", post(_))`.
|
||||
- **breaking:** `routing::handler_method_router` and
|
||||
`routing::service_method_router` has been removed in favor of
|
||||
`routing::{get, get_service, ..., MethodRouter}`.
|
||||
- **breaking:** `HandleErrorExt` has been removed in favor of
|
||||
`MethodRouter::handle_error`.
|
||||
|
||||
# 0.3.3 (13. November, 2021)
|
||||
|
||||
|
|
|
@ -43,14 +43,12 @@ functions as handlers. However if you're embedding general `Service`s or
|
|||
applying middleware, which might produce errors you have to tell axum how to
|
||||
convert those errors into responses.
|
||||
|
||||
You can handle errors from services using [`HandleErrorExt::handle_error`]:
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
Router,
|
||||
body::Body,
|
||||
http::{Request, Response, StatusCode},
|
||||
error_handling::HandleErrorExt, // for `.handle_error()`
|
||||
error_handling::HandleError,
|
||||
};
|
||||
|
||||
async fn thing_that_might_fail() -> Result<(), anyhow::Error> {
|
||||
|
@ -69,7 +67,7 @@ let app = Router::new().route(
|
|||
// we cannot route to `some_fallible_service` directly since it might fail.
|
||||
// we have to use `handle_error` which converts its errors into responses
|
||||
// and changes its error type from `anyhow::Error` to `Infallible`.
|
||||
some_fallible_service.handle_error(handle_anyhow_error),
|
||||
HandleError::new(some_fallible_service, handle_anyhow_error),
|
||||
);
|
||||
|
||||
// handle errors by converting them into something that implements
|
||||
|
|
53
axum/src/docs/method_routing/fallback.md
Normal file
53
axum/src/docs/method_routing/fallback.md
Normal file
|
@ -0,0 +1,53 @@
|
|||
Add a fallback service to the router.
|
||||
|
||||
This service will be called if no routes matches the incoming request.
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
Router,
|
||||
routing::get,
|
||||
handler::Handler,
|
||||
response::IntoResponse,
|
||||
http::{StatusCode, Method, Uri},
|
||||
};
|
||||
|
||||
let handler = get(|| async {}).fallback(fallback.into_service());
|
||||
|
||||
let app = Router::new().route("/", handler);
|
||||
|
||||
async fn fallback(method: Method, uri: Uri) -> impl IntoResponse {
|
||||
(StatusCode::NOT_FOUND, format!("`{}` not allowed for {}", method, uri))
|
||||
}
|
||||
# async {
|
||||
# hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
## When used with `MethodRouter::merge`
|
||||
|
||||
Two routers that both have a fallback cannot be merged. Doing so results in a
|
||||
panic:
|
||||
|
||||
```rust,should_panic
|
||||
use axum::{
|
||||
routing::{get, post},
|
||||
handler::Handler,
|
||||
response::IntoResponse,
|
||||
http::{StatusCode, Uri},
|
||||
};
|
||||
|
||||
let one = get(|| async {})
|
||||
.fallback(fallback_one.into_service());
|
||||
|
||||
let two = post(|| async {})
|
||||
.fallback(fallback_two.into_service());
|
||||
|
||||
let method_route = one.merge(two);
|
||||
|
||||
async fn fallback_one() -> impl IntoResponse {}
|
||||
async fn fallback_two() -> impl IntoResponse {}
|
||||
# let app = axum::Router::new().route("/", method_route);
|
||||
# async {
|
||||
# hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
28
axum/src/docs/method_routing/layer.md
Normal file
28
axum/src/docs/method_routing/layer.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
Apply a [`tower::Layer`] to the router.
|
||||
|
||||
All requests to the router will be processed by the layer's
|
||||
corresponding middleware.
|
||||
|
||||
This can be used to add additional processing to a request for a group
|
||||
of routes.
|
||||
|
||||
Works similarly to [`Router::layer`](super::Router::layer). See that method for
|
||||
more details.
|
||||
|
||||
# Example
|
||||
|
||||
```rust
|
||||
use axum::{routing::get, Router};
|
||||
use tower::limit::ConcurrencyLimitLayer;
|
||||
|
||||
async fn hander() {}
|
||||
|
||||
let app = Router::new().route(
|
||||
"/",
|
||||
// All requests to `GET /` will be sent through `ConcurrencyLimitLayer`
|
||||
get(hander).layer(ConcurrencyLimitLayer::new(64)),
|
||||
);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
25
axum/src/docs/method_routing/merge.md
Normal file
25
axum/src/docs/method_routing/merge.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
Merge two routers into one.
|
||||
|
||||
This is useful for breaking routers into smaller pieces and combining them
|
||||
into one.
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
routing::{get, post},
|
||||
Router,
|
||||
};
|
||||
|
||||
let get = get(|| async {});
|
||||
let post = post(|| async {});
|
||||
|
||||
let merged = get.merge(post);
|
||||
|
||||
let app = Router::new().route("/", merged);
|
||||
|
||||
// Our app now accepts
|
||||
// - GET /
|
||||
// - POST /
|
||||
# async {
|
||||
# hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
30
axum/src/docs/method_routing/route_layer.md
Normal file
30
axum/src/docs/method_routing/route_layer.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
Apply a [`tower::Layer`] to the router that will only run if the request matches
|
||||
a route.
|
||||
|
||||
This works similarly to [`MethodRouter::layer`] except the middleware will only run if
|
||||
the request matches a route. This is useful for middleware that return early
|
||||
(such as authorization) which might otherwise convert a `405 Method Not Allowed` into a
|
||||
`401 Unauthorized`.
|
||||
|
||||
# Example
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use tower_http::auth::RequireAuthorizationLayer;
|
||||
|
||||
let app = Router::new().route(
|
||||
"/foo",
|
||||
get(|| async {})
|
||||
.route_layer(RequireAuthorizationLayer::bearer("password"))
|
||||
);
|
||||
|
||||
// `GET /foo` with a valid token will receive `200 OK`
|
||||
// `GET /foo` with a invalid token will receive `401 Unauthorized`
|
||||
// `POST /FOO` with a invalid token will receive `405 Method Not Allowed`
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
|
@ -72,15 +72,14 @@ let app = Router::new().nest("/:version/api", users_api);
|
|||
```rust
|
||||
use axum::{
|
||||
Router,
|
||||
routing::service_method_routing::get,
|
||||
error_handling::HandleErrorExt,
|
||||
routing::get_service,
|
||||
http::StatusCode,
|
||||
};
|
||||
use std::{io, convert::Infallible};
|
||||
use tower_http::services::ServeDir;
|
||||
|
||||
// Serves files inside the `public` directory at `GET /public/*`
|
||||
let serve_dir_service = ServeDir::new("public")
|
||||
let serve_dir_service = get_service(ServeDir::new("public"))
|
||||
.handle_error(|error: io::Error| {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
|
@ -88,7 +87,7 @@ let serve_dir_service = ServeDir::new("public")
|
|||
)
|
||||
});
|
||||
|
||||
let app = Router::new().nest("/public", get(serve_dir_service));
|
||||
let app = Router::new().nest("/public", serve_dir_service);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
|
|
|
@ -111,8 +111,7 @@ axum also supports routing to general [`Service`]s:
|
|||
use axum::{
|
||||
Router,
|
||||
body::Body,
|
||||
routing::service_method_routing as service,
|
||||
error_handling::HandleErrorExt,
|
||||
routing::{any_service, get_service},
|
||||
http::{Request, StatusCode},
|
||||
};
|
||||
use tower_http::services::ServeFile;
|
||||
|
@ -125,9 +124,9 @@ let app = Router::new()
|
|||
// Any request to `/` goes to a service
|
||||
"/",
|
||||
// Services whose response body is not `axum::body::BoxBody`
|
||||
// can be wrapped in `axum::service::any` (or one of the other routing filters)
|
||||
// can be wrapped in `axum::routing::any_service` (or one of the other routing filters)
|
||||
// to have the response body mapped
|
||||
service::any(service_fn(|_: Request<Body>| async {
|
||||
any_service(service_fn(|_: Request<Body>| async {
|
||||
let res = Response::new(Body::from("Hi from `GET /`"));
|
||||
Ok::<_, Infallible>(res)
|
||||
}))
|
||||
|
@ -146,7 +145,7 @@ let app = Router::new()
|
|||
.route(
|
||||
// GET `/static/Cargo.toml` goes to a service from tower-http
|
||||
"/static/Cargo.toml",
|
||||
service::get(ServeFile::new("Cargo.toml"))
|
||||
get_service(ServeFile::new("Cargo.toml"))
|
||||
// though we must handle any potential errors
|
||||
.handle_error(|error: io::Error| {
|
||||
(
|
||||
|
@ -161,8 +160,10 @@ let app = Router::new()
|
|||
```
|
||||
|
||||
Routing to arbitrary services in this way has complications for backpressure
|
||||
([`Service::poll_ready`]). See the [`service_method_routing`] module for more
|
||||
details.
|
||||
([`Service::poll_ready`]). See the [Routing to services and backpressure] module
|
||||
for more details.
|
||||
|
||||
[Routing to services and backpressure]: /#routing-to-services-and-backpressure
|
||||
|
||||
# Panics
|
||||
|
||||
|
|
|
@ -134,19 +134,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Extension trait to [`Service`] for handling errors by mapping them to
|
||||
/// responses.
|
||||
///
|
||||
/// See [module docs](self) for more details on axum's error handling model.
|
||||
pub trait HandleErrorExt<B>: Service<Request<B>> + Sized {
|
||||
/// Apply a [`HandleError`] middleware.
|
||||
fn handle_error<F>(self, f: F) -> HandleError<Self, F, B> {
|
||||
HandleError::new(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, S> HandleErrorExt<B> for S where S: Service<Request<B>> {}
|
||||
|
||||
pub mod future {
|
||||
//! Future types.
|
||||
|
||||
|
|
|
@ -404,11 +404,4 @@ mod tests {
|
|||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(res.text().await, "you said: hi there!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn traits() {
|
||||
use crate::{routing::MethodRouter, test_helpers::*};
|
||||
assert_send::<MethodRouter<(), NotSendSync, NotSendSync, ()>>();
|
||||
assert_sync::<MethodRouter<(), NotSendSync, NotSendSync, ()>>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
//! - [Responses](#responses)
|
||||
//! - [Error handling](#error-handling)
|
||||
//! - [Middleware](#middleware)
|
||||
//! - [Routing to services and backpressure](#routing-to-services-and-backpressure)
|
||||
//! - [Sharing state with handlers](#sharing-state-with-handlers)
|
||||
//! - [Required dependencies](#required-dependencies)
|
||||
//! - [Examples](#examples)
|
||||
|
@ -160,6 +161,68 @@
|
|||
//!
|
||||
#![doc = include_str!("docs/middleware.md")]
|
||||
//!
|
||||
//! # Routing to services and backpressure
|
||||
//!
|
||||
//! 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.
|
||||
//!
|
||||
//! # Sharing state with handlers
|
||||
//!
|
||||
//! It is common to share some state between handlers for example to share a
|
||||
|
@ -255,8 +318,8 @@
|
|||
//! [`OriginalUri`]: crate::extract::OriginalUri
|
||||
//! [`Service`]: tower::Service
|
||||
//! [`Service::poll_ready`]: tower::Service::poll_ready
|
||||
//! [`Service`'s]: tower::Service
|
||||
//! [`tower::Service`]: tower::Service
|
||||
//! [`handle_error`]: error_handling::HandleErrorExt::handle_error
|
||||
//! [tower-guides]: https://github.com/tower-rs/tower/tree/master/guides
|
||||
//! [`Uuid`]: https://docs.rs/uuid/latest/uuid/
|
||||
//! [`FromRequest`]: crate::extract::FromRequest
|
||||
|
@ -267,6 +330,7 @@
|
|||
//! [`debug_handler`]: https://docs.rs/axum-debug/latest/axum_debug/attr.debug_handler.html
|
||||
//! [`Handler`]: crate::handler::Handler
|
||||
//! [`Infallible`]: std::convert::Infallible
|
||||
//! [load shed]: tower::load_shed
|
||||
|
||||
#.
|
||||
pub type RouterFuture<B> =
|
||||
futures_util::future::Either<
|
||||
Oneshot<super::Route<B>, Request<B>>,
|
||||
RouteFuture<B, Infallible>,
|
||||
std::future::Ready<Result<Response<BoxBody>, Infallible>>,
|
||||
>;
|
||||
}
|
||||
|
||||
impl<B> RouterFuture<B> {
|
||||
pub(super) fn from_oneshot(future: Oneshot<super::Route<B>, Request<B>>) -> Self {
|
||||
pub(super) fn from_future(future: RouteFuture<B, Infallible>) -> Self {
|
||||
Self::new(Either::Left(future))
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use bitflags::bitflags;
|
||||
use http::Method;
|
||||
|
||||
bitflags! {
|
||||
/// A filter that matches one or more HTTP methods.
|
||||
|
@ -22,21 +21,3 @@ bitflags! {
|
|||
const TRACE = 0b100000000;
|
||||
}
|
||||
}
|
||||
|
||||
impl MethodFilter {
|
||||
#[allow(clippy::match_like_matches_macro)]
|
||||
pub(crate) fn matches(self, method: &Method) -> bool {
|
||||
let method = match *method {
|
||||
Method::DELETE => Self::DELETE,
|
||||
Method::GET => Self::GET,
|
||||
Method::HEAD => Self::HEAD,
|
||||
Method::OPTIONS => Self::OPTIONS,
|
||||
Method::PATCH => Self::PATCH,
|
||||
Method::POST => Self::POST,
|
||||
Method::PUT => Self::PUT,
|
||||
Method::TRACE => Self::TRACE,
|
||||
_ => return false,
|
||||
};
|
||||
self.contains(method)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
use crate::body::BoxBody;
|
||||
use http::{Request, Response, StatusCode};
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
fmt,
|
||||
future::ready,
|
||||
marker::PhantomData,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tower_service::Service;
|
||||
|
||||
/// A [`Service`] that responds with `405 Method not allowed` to all requests.
|
||||
///
|
||||
/// This is used as the bottom service in a method router. You shouldn't have to
|
||||
/// use it manually.
|
||||
pub struct MethodNotAllowed<E = Infallible> {
|
||||
_marker: PhantomData<fn() -> E>,
|
||||
}
|
||||
|
||||
impl<E> MethodNotAllowed<E> {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Clone for MethodNotAllowed<E> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> fmt::Debug for MethodNotAllowed<E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("MethodNotAllowed").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E> Service<Request<B>> for MethodNotAllowed<E>
|
||||
where
|
||||
B: Send + 'static,
|
||||
{
|
||||
type Response = Response<BoxBody>;
|
||||
type Error = E;
|
||||
type Future = MethodNotAllowedFuture<E>;
|
||||
|
||||
#[inline]
|
||||
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 res = Response::builder()
|
||||
.status(StatusCode::METHOD_NOT_ALLOWED)
|
||||
.body(crate::body::empty())
|
||||
.unwrap();
|
||||
|
||||
MethodNotAllowedFuture::new(ready(Ok(res)))
|
||||
}
|
||||
}
|
||||
|
||||
opaque_future! {
|
||||
/// Response future for [`MethodNotAllowed`](super::MethodNotAllowed).
|
||||
pub type MethodNotAllowedFuture<E> =
|
||||
std::future::Ready<Result<Response<BoxBody>, E>>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn traits() {
|
||||
use crate::test_helpers::*;
|
||||
|
||||
assert_send::<MethodNotAllowed<NotSendSync>>();
|
||||
assert_sync::<MethodNotAllowed<NotSendSync>>();
|
||||
}
|
||||
}
|
1085
axum/src/routing/method_routing.rs
Normal file
1085
axum/src/routing/method_routing.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -7,6 +7,7 @@ use crate::{
|
|||
connect_info::{Connected, IntoMakeServiceWithConnectInfo},
|
||||
MatchedPath, OriginalUri,
|
||||
},
|
||||
routing::strip_prefix::StripPrefix,
|
||||
util::{ByteStr, PercentDecodedByteStr},
|
||||
BoxError,
|
||||
};
|
||||
|
@ -20,18 +21,16 @@ use std::{
|
|||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tower::{util::ServiceExt, ServiceBuilder};
|
||||
use tower::{layer::layer_fn, ServiceBuilder};
|
||||
use tower_http::map_response_body::MapResponseBodyLayer;
|
||||
use tower_layer::Layer;
|
||||
use tower_service::Service;
|
||||
|
||||
pub mod future;
|
||||
pub mod handler_method_routing;
|
||||
pub mod service_method_routing;
|
||||
|
||||
mod into_make_service;
|
||||
mod method_filter;
|
||||
mod method_not_allowed;
|
||||
mod method_routing;
|
||||
mod not_found;
|
||||
mod route;
|
||||
mod strip_prefix;
|
||||
|
@ -39,14 +38,12 @@ mod strip_prefix;
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use self::{
|
||||
into_make_service::IntoMakeService, method_filter::MethodFilter,
|
||||
method_not_allowed::MethodNotAllowed, route::Route,
|
||||
};
|
||||
pub use self::{into_make_service::IntoMakeService, method_filter::MethodFilter, route::Route};
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use self::handler_method_routing::{
|
||||
any, delete, get, head, on, options, patch, post, put, trace, MethodRouter,
|
||||
pub use self::method_routing::{
|
||||
any, any_service, delete, delete_service, get, get_service, head, head_service, on, on_service,
|
||||
options, options_service, patch, patch_service, post, post_service, put, put_service, trace,
|
||||
trace_service, MethodRouter,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
|
@ -63,7 +60,7 @@ impl RouteId {
|
|||
/// The router type for composing handlers and services.
|
||||
#[derive(Debug)]
|
||||
pub struct Router<B = Body> {
|
||||
routes: HashMap<RouteId, Route<B>>,
|
||||
routes: HashMap<RouteId, Endpoint<B>>,
|
||||
node: Node,
|
||||
fallback: Fallback<B>,
|
||||
nested_at_root: bool,
|
||||
|
@ -131,11 +128,32 @@ where
|
|||
|
||||
let id = RouteId::next();
|
||||
|
||||
let service = match try_downcast::<MethodRouter<B, Infallible>, _>(service) {
|
||||
Ok(method_router) => {
|
||||
if let Some((route_id, Endpoint::MethodRouter(prev_method_router))) = self
|
||||
.node
|
||||
.path_to_route_id
|
||||
.get(path)
|
||||
.and_then(|route_id| self.routes.get(route_id).map(|svc| (*route_id, svc)))
|
||||
{
|
||||
// if we're adding a new `MethodRouter` to a route that already has one just
|
||||
// merge them. This makes `.route("/", get(_)).route("/", post(_))` work
|
||||
let service =
|
||||
Endpoint::MethodRouter(prev_method_router.clone().merge(method_router));
|
||||
self.routes.insert(route_id, service);
|
||||
return self;
|
||||
} else {
|
||||
Endpoint::MethodRouter(method_router)
|
||||
}
|
||||
}
|
||||
Err(service) => Endpoint::Route(Route::new(service)),
|
||||
};
|
||||
|
||||
if let Err(err) = self.node.insert(path, id) {
|
||||
self.panic_on_matchit_error(err);
|
||||
}
|
||||
|
||||
self.routes.insert(id, Route::new(service));
|
||||
self.routes.insert(id, service);
|
||||
|
||||
self
|
||||
}
|
||||
|
@ -179,14 +197,22 @@ where
|
|||
nested_at_root: _,
|
||||
} = router;
|
||||
|
||||
for (id, nested_path) in node.paths {
|
||||
for (id, nested_path) in node.route_id_to_path {
|
||||
let route = routes.remove(&id).unwrap();
|
||||
let full_path = if &*nested_path == "/" {
|
||||
path.to_string()
|
||||
} else {
|
||||
format!("{}{}", path, nested_path)
|
||||
};
|
||||
self = self.route(&full_path, strip_prefix::StripPrefix::new(route, prefix));
|
||||
self = match route {
|
||||
Endpoint::MethodRouter(method_router) => self.route(
|
||||
&full_path,
|
||||
method_router.layer(layer_fn(|s| StripPrefix::new(s, prefix))),
|
||||
),
|
||||
Endpoint::Route(route) => {
|
||||
self.route(&full_path, StripPrefix::new(route, prefix))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
debug_assert!(routes.is_empty());
|
||||
|
@ -248,20 +274,25 @@ where
|
|||
NewResBody::Error: Into<BoxError>,
|
||||
{
|
||||
let layer = ServiceBuilder::new()
|
||||
.layer_fn(Route::new)
|
||||
.layer(MapResponseBodyLayer::new(box_body))
|
||||
.layer(layer);
|
||||
.layer(layer)
|
||||
.into_inner();
|
||||
|
||||
let routes = self
|
||||
.routes
|
||||
.into_iter()
|
||||
.map(|(id, route)| {
|
||||
let route = Layer::layer(&layer, route);
|
||||
let route = match route {
|
||||
Endpoint::MethodRouter(method_router) => {
|
||||
Endpoint::MethodRouter(method_router.layer(&layer))
|
||||
}
|
||||
Endpoint::Route(route) => Endpoint::Route(Route::new(layer.layer(route))),
|
||||
};
|
||||
(id, route)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let fallback = self.fallback.map(|svc| Layer::layer(&layer, svc));
|
||||
let fallback = self.fallback.map(|svc| Route::new(layer.layer(svc)));
|
||||
|
||||
Router {
|
||||
routes,
|
||||
|
@ -284,15 +315,20 @@ where
|
|||
NewResBody::Error: Into<BoxError>,
|
||||
{
|
||||
let layer = ServiceBuilder::new()
|
||||
.layer_fn(Route::new)
|
||||
.layer(MapResponseBodyLayer::new(box_body))
|
||||
.layer(layer);
|
||||
.layer(layer)
|
||||
.into_inner();
|
||||
|
||||
let routes = self
|
||||
.routes
|
||||
.into_iter()
|
||||
.map(|(id, route)| {
|
||||
let route = Layer::layer(&layer, route);
|
||||
let route = match route {
|
||||
Endpoint::MethodRouter(method_router) => {
|
||||
Endpoint::MethodRouter(method_router.layer(&layer))
|
||||
}
|
||||
Endpoint::Route(route) => Endpoint::Route(Route::new(layer.layer(route))),
|
||||
};
|
||||
(id, route)
|
||||
})
|
||||
.collect();
|
||||
|
@ -360,7 +396,7 @@ where
|
|||
let id = *match_.value;
|
||||
req.extensions_mut().insert(id);
|
||||
|
||||
if let Some(matched_path) = self.node.paths.get(&id) {
|
||||
if let Some(matched_path) = self.node.route_id_to_path.get(&id) {
|
||||
let matched_path = if let Some(previous) = req.extensions_mut().get::<MatchedPath>() {
|
||||
// a previous `MatchedPath` might exist if we're inside a nested Router
|
||||
let previous = if let Some(previous) =
|
||||
|
@ -388,13 +424,17 @@ where
|
|||
|
||||
insert_url_params(&mut req, params);
|
||||
|
||||
let route = self
|
||||
let mut route = self
|
||||
.routes
|
||||
.get(&id)
|
||||
.expect("no route for id. This is a bug in axum. Please file an issue")
|
||||
.clone();
|
||||
|
||||
RouterFuture::from_oneshot(route.oneshot(req))
|
||||
let future = match &mut route {
|
||||
Endpoint::MethodRouter(inner) => inner.call(req),
|
||||
Endpoint::Route(inner) => inner.call(req),
|
||||
};
|
||||
RouterFuture::from_future(future)
|
||||
}
|
||||
|
||||
fn panic_on_matchit_error(&self, err: matchit::InsertError) {
|
||||
|
@ -449,10 +489,10 @@ where
|
|||
} else {
|
||||
match &self.fallback {
|
||||
Fallback::Default(inner) => {
|
||||
RouterFuture::from_oneshot(inner.clone().oneshot(req))
|
||||
RouterFuture::from_future(inner.clone().call(req))
|
||||
}
|
||||
Fallback::Custom(inner) => {
|
||||
RouterFuture::from_oneshot(inner.clone().oneshot(req))
|
||||
RouterFuture::from_future(inner.clone().call(req))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -537,7 +577,8 @@ pub(crate) struct InvalidUtf8InPathParam {
|
|||
#[derive(Clone, Default)]
|
||||
struct Node {
|
||||
inner: matchit::Node<RouteId>,
|
||||
paths: HashMap<RouteId, Arc<str>>,
|
||||
route_id_to_path: HashMap<RouteId, Arc<str>>,
|
||||
path_to_route_id: HashMap<Arc<str>, RouteId>,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
|
@ -547,13 +588,18 @@ impl Node {
|
|||
val: RouteId,
|
||||
) -> Result<(), matchit::InsertError> {
|
||||
let path = path.into();
|
||||
|
||||
self.inner.insert(&path, val)?;
|
||||
self.paths.insert(val, path.into());
|
||||
|
||||
let shared_path: Arc<str> = path.into();
|
||||
self.route_id_to_path.insert(val, shared_path.clone());
|
||||
self.path_to_route_id.insert(shared_path, val);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn merge(&mut self, other: Node) -> Result<(), matchit::InsertError> {
|
||||
for (id, path) in other.paths {
|
||||
for (id, path) in other.route_id_to_path {
|
||||
self.insert(&*path, id)?;
|
||||
}
|
||||
Ok(())
|
||||
|
@ -569,16 +615,18 @@ impl Node {
|
|||
|
||||
impl fmt::Debug for Node {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Node").field("paths", &self.paths).finish()
|
||||
f.debug_struct("Node")
|
||||
.field("paths", &self.route_id_to_path)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
enum Fallback<B> {
|
||||
Default(Route<B>),
|
||||
Custom(Route<B>),
|
||||
enum Fallback<B, E = Infallible> {
|
||||
Default(Route<B, E>),
|
||||
Custom(Route<B, E>),
|
||||
}
|
||||
|
||||
impl<B> Clone for Fallback<B> {
|
||||
impl<B, E> Clone for Fallback<B, E> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Fallback::Default(inner) => Fallback::Default(inner.clone()),
|
||||
|
@ -587,7 +635,7 @@ impl<B> Clone for Fallback<B> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<B> fmt::Debug for Fallback<B> {
|
||||
impl<B, E> fmt::Debug for Fallback<B, E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Default(inner) => f.debug_tuple("Default").field(inner).finish(),
|
||||
|
@ -596,10 +644,10 @@ impl<B> fmt::Debug for Fallback<B> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<B> Fallback<B> {
|
||||
fn map<F, B2>(self, f: F) -> Fallback<B2>
|
||||
impl<B, E> Fallback<B, E> {
|
||||
fn map<F, B2, E2>(self, f: F) -> Fallback<B2, E2>
|
||||
where
|
||||
F: FnOnce(Route<B>) -> Route<B2>,
|
||||
F: FnOnce(Route<B, E>) -> Route<B2, E2>,
|
||||
{
|
||||
match self {
|
||||
Fallback::Default(inner) => Fallback::Default(f(inner)),
|
||||
|
@ -622,6 +670,29 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
enum Endpoint<B> {
|
||||
MethodRouter(MethodRouter<B>),
|
||||
Route(Route<B>),
|
||||
}
|
||||
|
||||
impl<B> Clone for Endpoint<B> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Endpoint::MethodRouter(inner) => Endpoint::MethodRouter(inner.clone()),
|
||||
Endpoint::Route(inner) => Endpoint::Route(inner.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> fmt::Debug for Endpoint<B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::MethodRouter(inner) => inner.fmt(f),
|
||||
Self::Route(inner) => inner.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn traits() {
|
||||
use crate::test_helpers::*;
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use crate::{
|
||||
body::{Body, BoxBody},
|
||||
body::{box_body, Body, BoxBody},
|
||||
clone_box_service::CloneBoxService,
|
||||
};
|
||||
use http::{Request, Response};
|
||||
use http_body::Empty;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
|
@ -18,37 +19,36 @@ use tower_service::Service;
|
|||
///
|
||||
/// You normally shouldn't need to care about this type. It's used in
|
||||
/// [`Router::layer`](super::Router::layer).
|
||||
pub struct Route<B = Body>(CloneBoxService<Request<B>, Response<BoxBody>, Infallible>);
|
||||
pub struct Route<B = Body, E = Infallible>(
|
||||
pub(crate) CloneBoxService<Request<B>, Response<BoxBody>, E>,
|
||||
);
|
||||
|
||||
impl<B> Route<B> {
|
||||
impl<B, E> Route<B, E> {
|
||||
pub(super) fn new<T>(svc: T) -> Self
|
||||
where
|
||||
T: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ 'static,
|
||||
T: Service<Request<B>, Response = Response<BoxBody>, Error = E> + Clone + Send + 'static,
|
||||
T::Future: Send + 'static,
|
||||
{
|
||||
Self(CloneBoxService::new(svc))
|
||||
}
|
||||
}
|
||||
|
||||
impl<ReqBody> Clone for Route<ReqBody> {
|
||||
impl<ReqBody, E> Clone for Route<ReqBody, E> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<ReqBody> fmt::Debug for Route<ReqBody> {
|
||||
impl<ReqBody, E> fmt::Debug for Route<ReqBody, E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Route").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> Service<Request<B>> for Route<B> {
|
||||
impl<B, E> Service<Request<B>> for Route<B, E> {
|
||||
type Response = Response<BoxBody>;
|
||||
type Error = Infallible;
|
||||
type Future = RouteFuture<B>;
|
||||
type Error = E;
|
||||
type Future = RouteFuture<B, E>;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
|
@ -63,29 +63,50 @@ impl<B> Service<Request<B>> for Route<B> {
|
|||
|
||||
pin_project! {
|
||||
/// Response future for [`Route`].
|
||||
pub struct RouteFuture<B> {
|
||||
pub struct RouteFuture<B, E> {
|
||||
#[pin]
|
||||
future: Oneshot<
|
||||
CloneBoxService<Request<B>, Response<BoxBody>, Infallible>,
|
||||
CloneBoxService<Request<B>, Response<BoxBody>, E>,
|
||||
Request<B>,
|
||||
>
|
||||
>,
|
||||
strip_body: bool,
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> RouteFuture<B> {
|
||||
impl<B, E> RouteFuture<B, E> {
|
||||
pub(crate) fn new(
|
||||
future: Oneshot<CloneBoxService<Request<B>, Response<BoxBody>, Infallible>, Request<B>>,
|
||||
future: Oneshot<CloneBoxService<Request<B>, Response<BoxBody>, E>, Request<B>>,
|
||||
) -> Self {
|
||||
RouteFuture { future }
|
||||
RouteFuture {
|
||||
future,
|
||||
strip_body: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn strip_body(mut self, strip_body: bool) -> Self {
|
||||
self.strip_body = strip_body;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> Future for RouteFuture<B> {
|
||||
type Output = Result<Response<BoxBody>, Infallible>;
|
||||
impl<B, E> Future for RouteFuture<B, E> {
|
||||
type Output = Result<Response<BoxBody>, E>;
|
||||
|
||||
#[inline]
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
self.project().future.poll(cx)
|
||||
let strip_body = self.strip_body;
|
||||
|
||||
match self.project().future.poll(cx) {
|
||||
Poll::Ready(Ok(res)) => {
|
||||
if strip_body {
|
||||
Poll::Ready(Ok(res.map(|_| box_body(Empty::new()))))
|
||||
} else {
|
||||
Poll::Ready(Ok(res))
|
||||
}
|
||||
}
|
||||
Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,559 +0,0 @@
|
|||
//! 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<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::{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::{
|
||||
fmt,
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tower::{util::Oneshot, 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, MethodNotAllowed<S::Error>, B>
|
||||
where
|
||||
S: Service<Request<B>> + Clone,
|
||||
{
|
||||
on(MethodFilter::all(), svc)
|
||||
}
|
||||
|
||||
/// Route `DELETE` requests to the given service.
|
||||
///
|
||||
/// See [`get`] for an example.
|
||||
pub fn delete<S, B>(svc: S) -> MethodRouter<S, MethodNotAllowed<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_routing 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, MethodNotAllowed<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, MethodNotAllowed<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, MethodNotAllowed<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, MethodNotAllowed<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, MethodNotAllowed<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, MethodNotAllowed<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, MethodNotAllowed<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_routing 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, MethodNotAllowed<S::Error>, B>
|
||||
where
|
||||
S: Service<Request<B>> + Clone,
|
||||
{
|
||||
MethodRouter {
|
||||
method,
|
||||
svc,
|
||||
fallback: MethodNotAllowed::new(),
|
||||
_request_body: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Service`] that accepts requests based on a [`MethodFilter`] and allows
|
||||
/// chaining additional services.
|
||||
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> fmt::Debug for MethodRouter<S, F, B>
|
||||
where
|
||||
S: fmt::Debug,
|
||||
F: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("MethodRouter")
|
||||
.field("method", &self.method)
|
||||
.field("svc", &self.svc)
|
||||
.field("fallback", &self.fallback)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
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 `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_routing 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_routing 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 + '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>;
|
||||
|
||||
#[inline]
|
||||
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 + '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::test_helpers::*;
|
||||
|
||||
assert_send::<MethodRouter<(), (), NotSendSync>>();
|
||||
assert_sync::<MethodRouter<(), (), NotSendSync>>();
|
||||
}
|
|
@ -4,6 +4,7 @@ use tower::ServiceExt;
|
|||
|
||||
mod for_handlers {
|
||||
use super::*;
|
||||
use headers::HeaderMap;
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_handles_head() {
|
||||
|
@ -38,14 +39,14 @@ mod for_handlers {
|
|||
|
||||
mod for_services {
|
||||
use super::*;
|
||||
use crate::routing::service_method_routing::get;
|
||||
use crate::routing::get_service;
|
||||
use http::header::HeaderValue;
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_handles_head() {
|
||||
let app = Router::new().route(
|
||||
"/",
|
||||
get(service_fn(|_req: Request<Body>| async move {
|
||||
get_service(service_fn(|_req: Request<Body>| async move {
|
||||
let res = Response::builder()
|
||||
.header("x-some-header", "foobar".parse::<HeaderValue>().unwrap())
|
||||
.body(Body::from("you shouldn't see this"))
|
||||
|
|
|
@ -196,18 +196,18 @@ async fn many_ors() {
|
|||
|
||||
#[tokio::test]
|
||||
async fn services() {
|
||||
use crate::routing::service_method_routing::get;
|
||||
use crate::routing::get_service;
|
||||
|
||||
let app = Router::new()
|
||||
.route(
|
||||
"/foo",
|
||||
get(service_fn(|_: Request<Body>| async {
|
||||
get_service(service_fn(|_: Request<Body>| async {
|
||||
Ok::<_, Infallible>(Response::new(Body::empty()))
|
||||
})),
|
||||
)
|
||||
.merge(Router::new().route(
|
||||
"/bar",
|
||||
get(service_fn(|_: Request<Body>| async {
|
||||
get_service(service_fn(|_: Request<Body>| async {
|
||||
Ok::<_, Infallible>(Response::new(Body::empty()))
|
||||
})),
|
||||
));
|
||||
|
|
|
@ -3,12 +3,12 @@ use crate::{
|
|||
extract::{self, Path},
|
||||
handler::Handler,
|
||||
response::IntoResponse,
|
||||
routing::{any, delete, get, on, patch, post, service_method_routing as service, MethodFilter},
|
||||
routing::{delete, get, get_service, on, on_service, patch, patch_service, post, MethodFilter},
|
||||
test_helpers::*,
|
||||
BoxError, Json, Router,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use http::{header::HeaderMap, Method, Request, Response, StatusCode, Uri};
|
||||
use http::{Method, Request, Response, StatusCode, Uri};
|
||||
use hyper::Body;
|
||||
use serde::Deserialize;
|
||||
use serde_json::{json, Value};
|
||||
|
@ -135,20 +135,20 @@ async fn routing_between_services() {
|
|||
let app = Router::new()
|
||||
.route(
|
||||
"/one",
|
||||
service::get(service_fn(|_: Request<Body>| async {
|
||||
get_service(service_fn(|_: Request<Body>| async {
|
||||
Ok::<_, Infallible>(Response::new(Body::from("one get")))
|
||||
}))
|
||||
.post(service_fn(|_: Request<Body>| async {
|
||||
.post_service(service_fn(|_: Request<Body>| async {
|
||||
Ok::<_, Infallible>(Response::new(Body::from("one post")))
|
||||
}))
|
||||
.on(
|
||||
.on_service(
|
||||
MethodFilter::PUT,
|
||||
service_fn(|_: Request<Body>| async {
|
||||
Ok::<_, Infallible>(Response::new(Body::from("one put")))
|
||||
}),
|
||||
),
|
||||
)
|
||||
.route("/two", service::on(MethodFilter::GET, any(handle)));
|
||||
.route("/two", on_service(MethodFilter::GET, handle.into_service()));
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
||||
|
@ -202,7 +202,7 @@ async fn service_in_bottom() {
|
|||
Ok(Response::new(hyper::Body::empty()))
|
||||
}
|
||||
|
||||
let app = Router::new().route("/", service::get(service_fn(handler)));
|
||||
let app = Router::new().route("/", get_service(service_fn(handler)));
|
||||
|
||||
TestClient::new(app);
|
||||
}
|
||||
|
@ -248,8 +248,8 @@ async fn wrong_method_service() {
|
|||
}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", service::get(Svc).post(Svc))
|
||||
.route("/foo", service::patch(Svc));
|
||||
.route("/", get_service(Svc).post_service(Svc))
|
||||
.route("/foo", patch_service(Svc));
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
||||
|
@ -505,17 +505,6 @@ async fn route_layer() {
|
|||
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic(
|
||||
expected = "Invalid route: insertion failed due to conflict with previously registered route: /foo"
|
||||
)]
|
||||
async fn conflicting_route() {
|
||||
let app = Router::new()
|
||||
.route("/foo", get(|| async {}))
|
||||
.route("/foo", get(|| async {}));
|
||||
TestClient::new(app);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic(
|
||||
expected = "Invalid route: insertion failed due to conflict with previously registered route: /*axum_nest. Note that `nest(\"/\", _)` conflicts with all routes. Use `Router::fallback` instead"
|
||||
|
@ -537,3 +526,43 @@ async fn good_error_message_if_using_nest_root_when_merging() {
|
|||
let app = one.merge(two);
|
||||
TestClient::new(app);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn different_methods_added_in_different_routes() {
|
||||
let app = Router::new()
|
||||
.route("/", get(|| async { "GET" }))
|
||||
.route("/", post(|| async { "POST" }));
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
||||
let res = client.get("/").send().await;
|
||||
let body = res.text().await;
|
||||
assert_eq!(body, "GET");
|
||||
|
||||
let res = client.post("/").send().await;
|
||||
let body = res.text().await;
|
||||
assert_eq!(body, "POST");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn different_methods_added_in_different_routes_deeply_nested() {
|
||||
let app = Router::new()
|
||||
.route("/foo/bar/baz", get(|| async { "GET" }))
|
||||
.nest(
|
||||
"/foo",
|
||||
Router::new().nest(
|
||||
"/bar",
|
||||
Router::new().route("/baz", post(|| async { "POST" })),
|
||||
),
|
||||
);
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
||||
let res = client.get("/foo/bar/baz").send().await;
|
||||
let body = res.text().await;
|
||||
assert_eq!(body, "GET");
|
||||
|
||||
let res = client.post("/foo/bar/baz").send().await;
|
||||
let body = res.text().await;
|
||||
assert_eq!(body, "POST");
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use tower_http::services::ServeDir;
|
||||
|
||||
use super::*;
|
||||
use crate::{body::box_body, error_handling::HandleErrorExt, extract::Extension};
|
||||
use crate::{body::box_body, extract::Extension};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -167,7 +169,7 @@ async fn nested_service_sees_stripped_uri() {
|
|||
async fn nest_static_file_server() {
|
||||
let app = Router::new().nest(
|
||||
"/static",
|
||||
service::get(tower_http::services::ServeDir::new(".")).handle_error(|error| {
|
||||
get_service(ServeDir::new(".")).handle_error(|error| {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Unhandled internal error: {}", error),
|
||||
|
|
|
@ -5,11 +5,10 @@
|
|||
//! ```
|
||||
|
||||
use axum::{
|
||||
error_handling::HandleErrorExt,
|
||||
extract::TypedHeader,
|
||||
http::StatusCode,
|
||||
response::sse::{Event, Sse},
|
||||
routing::{get, service_method_routing as service},
|
||||
routing::{get, get_service},
|
||||
Router,
|
||||
};
|
||||
use futures::stream::{self, Stream};
|
||||
|
@ -26,7 +25,7 @@ async fn main() {
|
|||
tracing_subscriber::fmt::init();
|
||||
|
||||
let static_files_service =
|
||||
service::get(ServeDir::new("examples/sse/assets").append_index_html_on_directories(true))
|
||||
get_service(ServeDir::new("examples/sse/assets").append_index_html_on_directories(true))
|
||||
.handle_error(|error: std::io::Error| {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
|
|
|
@ -4,10 +4,7 @@
|
|||
//! cargo run -p example-static-file-server
|
||||
//! ```
|
||||
|
||||
use axum::{
|
||||
error_handling::HandleErrorExt, http::StatusCode, routing::service_method_routing as service,
|
||||
Router,
|
||||
};
|
||||
use axum::{http::StatusCode, routing::get_service, Router};
|
||||
use std::net::SocketAddr;
|
||||
use tower_http::{services::ServeDir, trace::TraceLayer};
|
||||
|
||||
|
@ -25,7 +22,7 @@ async fn main() {
|
|||
let app = Router::new()
|
||||
.nest(
|
||||
"/static",
|
||||
service::get(ServeDir::new(".")).handle_error(|error: std::io::Error| {
|
||||
get_service(ServeDir::new(".")).handle_error(|error: std::io::Error| {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Unhandled internal error: {}", error),
|
||||
|
|
|
@ -7,14 +7,13 @@
|
|||
//! ```
|
||||
|
||||
use axum::{
|
||||
error_handling::HandleErrorExt,
|
||||
extract::{
|
||||
ws::{Message, WebSocket, WebSocketUpgrade},
|
||||
TypedHeader,
|
||||
},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
routing::{get, service_method_routing as service},
|
||||
routing::{get, get_service},
|
||||
Router,
|
||||
};
|
||||
use std::net::SocketAddr;
|
||||
|
@ -34,7 +33,7 @@ async fn main() {
|
|||
// build our application with some routes
|
||||
let app = Router::new()
|
||||
.fallback(
|
||||
service::get(
|
||||
get_service(
|
||||
ServeDir::new("examples/websockets/assets").append_index_html_on_directories(true),
|
||||
)
|
||||
.handle_error(|error: std::io::Error| {
|
||||
|
|
Loading…
Add table
Reference in a new issue