Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
File diff suppressed because one or more lines are too long
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "c59208c86fded335cd85e388030ad59347b0e5ae"
},
"path_in_vcs": "axum"
}
File diff suppressed because it is too large Load Diff
+1932
View File
File diff suppressed because it is too large Load Diff
+465
View File
@@ -0,0 +1,465 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.80"
name = "axum"
version = "0.8.9"
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "HTTP routing and request handling library that focuses on ergonomics and modularity"
homepage = "https://github.com/tokio-rs/axum"
readme = "README.md"
keywords = [
"http",
"web",
"routing",
]
categories = [
"asynchronous",
"network-programming",
"web-programming::http-server",
]
license = "MIT"
repository = "https://github.com/tokio-rs/axum"
[package.metadata.docs.rs]
all-features = true
[package.metadata.playground]
features = [
"http1",
"http2",
"json",
"multipart",
"ws",
]
[package.metadata.cargo-public-api-crates]
allowed = [
"axum_core",
"axum_macros",
"futures_core",
"futures_sink",
"futures_util",
"pin_project_lite",
"tower_layer",
"tower_service",
"bytes",
"http",
"http_body",
"serde_core",
"tokio",
"reqwest",
]
[features]
__private = [
"tokio",
"http1",
"dep:reqwest",
]
__private_docs = [
"axum-core/__private_docs",
"tower/full",
"dep:serde",
"dep:tower-http",
]
default = [
"form",
"http1",
"json",
"matched-path",
"original-uri",
"query",
"tokio",
"tower-log",
"tracing",
]
form = [
"dep:form_urlencoded",
"dep:serde_urlencoded",
"dep:serde_path_to_error",
]
http1 = [
"dep:hyper",
"hyper?/http1",
"hyper-util?/http1",
]
http2 = [
"dep:hyper",
"hyper?/http2",
"hyper-util?/http2",
]
json = [
"dep:serde_json",
"dep:serde_path_to_error",
]
macros = ["dep:axum-macros"]
matched-path = []
multipart = ["dep:multer"]
original-uri = []
query = [
"dep:form_urlencoded",
"dep:serde_urlencoded",
"dep:serde_path_to_error",
]
tokio = [
"dep:hyper-util",
"dep:tokio",
"tokio/net",
"tokio/rt",
"tower/make",
"tokio/macros",
]
tower-log = ["tower/log"]
tracing = [
"dep:tracing",
"axum-core/tracing",
]
ws = [
"dep:hyper",
"tokio",
"dep:tokio-tungstenite",
"dep:sha1",
"dep:base64",
]
[lib]
name = "axum"
path = "src/lib.rs"
[[test]]
name = "panic_location"
path = "tests/panic_location.rs"
[[bench]]
name = "benches"
path = "benches/benches.rs"
harness = false
[dependencies.axum-core]
version = "0.5.5"
[dependencies.axum-macros]
version = "0.5.1"
optional = true
[dependencies.base64]
version = "0.22.1"
optional = true
[dependencies.bytes]
version = "1.0"
[dependencies.form_urlencoded]
version = "1.1.0"
optional = true
[dependencies.futures-util]
version = "0.3"
features = ["alloc"]
default-features = false
[dependencies.http]
version = "1.0.0"
[dependencies.http-body]
version = "1.0.0"
[dependencies.http-body-util]
version = "0.1.0"
[dependencies.hyper]
version = "1.1.0"
optional = true
[dependencies.hyper-util]
version = "0.1.3"
features = [
"tokio",
"server",
"service",
]
optional = true
[dependencies.itoa]
version = "1.0.5"
[dependencies.matchit]
version = "=0.8.4"
[dependencies.memchr]
version = "2.4.1"
[dependencies.mime]
version = "0.3.16"
[dependencies.multer]
version = "3.0.0"
optional = true
[dependencies.percent-encoding]
version = "2.1"
[dependencies.pin-project-lite]
version = "0.2.7"
[dependencies.reqwest]
version = "0.12"
features = [
"json",
"stream",
"multipart",
]
optional = true
default-features = false
[dependencies.serde]
version = "1.0.211"
optional = true
[dependencies.serde_core]
version = "1.0.221"
[dependencies.serde_json]
version = "1.0"
features = ["raw_value"]
optional = true
[dependencies.serde_path_to_error]
version = "0.1.8"
optional = true
[dependencies.serde_urlencoded]
version = "0.7"
optional = true
[dependencies.sha1]
version = "0.10"
optional = true
[dependencies.sync_wrapper]
version = "1.0.0"
[dependencies.tokio]
version = "1.44"
features = ["time"]
optional = true
package = "tokio"
[dependencies.tokio-tungstenite]
version = "0.29.0"
optional = true
[dependencies.tower]
version = "0.5.2"
features = ["util"]
default-features = false
[dependencies.tower-http]
version = "0.6.8"
features = [
"add-extension",
"auth",
"catch-panic",
"compression-br",
"compression-deflate",
"compression-gzip",
"cors",
"decompression-br",
"decompression-deflate",
"decompression-gzip",
"follow-redirect",
"fs",
"limit",
"map-request-body",
"map-response-body",
"metrics",
"normalize-path",
"propagate-header",
"redirect",
"request-id",
"sensitive-headers",
"set-header",
"set-status",
"timeout",
"trace",
"util",
"validate-request",
]
optional = true
[dependencies.tower-layer]
version = "0.3.2"
[dependencies.tower-service]
version = "0.3"
[dependencies.tracing]
version = "0.1"
optional = true
default-features = false
[dev-dependencies.anyhow]
version = "1.0"
[dev-dependencies.hyper]
version = "1.1.0"
features = ["client"]
[dev-dependencies.quickcheck]
version = "1.0"
[dev-dependencies.quickcheck_macros]
version = "1.0"
[dev-dependencies.reqwest]
version = "0.12"
features = [
"json",
"stream",
"multipart",
]
default-features = false
[dev-dependencies.serde]
version = "1.0.221"
features = ["derive"]
[dev-dependencies.serde_json]
version = "1.0"
features = ["raw_value"]
[dev-dependencies.time]
version = "0.3"
features = ["serde-human-readable"]
[dev-dependencies.tokio]
version = "1.44.2"
features = [
"macros",
"rt",
"rt-multi-thread",
"net",
"test-util",
]
package = "tokio"
[dev-dependencies.tokio-stream]
version = "0.1"
[dev-dependencies.tokio-tungstenite]
version = "0.29.0"
[dev-dependencies.tower]
version = "0.5.2"
features = [
"util",
"timeout",
"limit",
"load-shed",
"steer",
"filter",
]
package = "tower"
[dev-dependencies.tower-http]
version = "0.6.8"
features = [
"add-extension",
"auth",
"catch-panic",
"compression-br",
"compression-deflate",
"compression-gzip",
"cors",
"decompression-br",
"decompression-deflate",
"decompression-gzip",
"follow-redirect",
"fs",
"limit",
"map-request-body",
"map-response-body",
"metrics",
"normalize-path",
"propagate-header",
"redirect",
"request-id",
"sensitive-headers",
"set-header",
"set-status",
"timeout",
"trace",
"util",
"validate-request",
]
[dev-dependencies.tracing]
version = "0.1"
[dev-dependencies.tracing-subscriber]
version = "0.3"
features = ["json"]
[dev-dependencies.uuid]
version = "1.0"
features = [
"serde",
"v4",
]
[lints.clippy]
await_holding_lock = "warn"
dbg_macro = "warn"
empty_enums = "warn"
enum_glob_use = "warn"
exit = "warn"
filter_map_next = "warn"
fn_params_excessive_bools = "warn"
if_let_mutex = "warn"
imprecise_flops = "warn"
inefficient_to_string = "warn"
linkedlist = "warn"
lossy_float_literal = "warn"
macro_use_imports = "warn"
match_wildcard_for_single_variants = "warn"
mem_forget = "warn"
must_use_candidate = "warn"
needless_borrow = "warn"
needless_continue = "warn"
option_option = "warn"
rest_pat_in_fully_bound_structs = "warn"
return_self_not_must_use = "warn"
str_to_string = "warn"
suboptimal_flops = "warn"
todo = "warn"
type_complexity = "allow"
uninlined_format_args = "warn"
unnested_or_patterns = "warn"
unused_self = "warn"
verbose_file_reads = "warn"
[lints.rust]
missing_debug_implementations = "warn"
missing_docs = "warn"
unreachable_pub = "warn"
unsafe_code = "forbid"
[lints.rust.rust_2018_idioms]
level = "warn"
priority = -1
+233
View File
@@ -0,0 +1,233 @@
[package]
name = "axum"
version = "0.8.9" # remember to bump the version that axum-extra depends on
categories = ["asynchronous", "network-programming", "web-programming::http-server"]
description = "HTTP routing and request handling library that focuses on ergonomics and modularity"
edition = "2021"
rust-version = { workspace = true }
homepage = "https://github.com/tokio-rs/axum"
keywords = ["http", "web", "routing"]
license = "MIT"
readme = "README.md"
repository = "https://github.com/tokio-rs/axum"
[features]
default = [
"form",
"http1",
"json",
"matched-path",
"original-uri",
"query",
"tokio",
"tower-log",
"tracing",
]
form = ["dep:form_urlencoded", "dep:serde_urlencoded", "dep:serde_path_to_error"]
http1 = ["dep:hyper", "hyper?/http1", "hyper-util?/http1"]
http2 = ["dep:hyper", "hyper?/http2", "hyper-util?/http2"]
json = ["dep:serde_json", "dep:serde_path_to_error"]
macros = ["dep:axum-macros"]
matched-path = []
multipart = ["dep:multer"]
original-uri = []
query = ["dep:form_urlencoded", "dep:serde_urlencoded", "dep:serde_path_to_error"]
tokio = ["dep:hyper-util", "dep:tokio", "tokio/net", "tokio/rt", "tower/make", "tokio/macros"]
tower-log = ["tower/log"]
tracing = ["dep:tracing", "axum-core/tracing"]
ws = ["dep:hyper", "tokio", "dep:tokio-tungstenite", "dep:sha1", "dep:base64"]
__private_docs = [
# We re-export some docs from axum-core via #[doc(inline)],
# but they need the same sort of treatment as below to be complete
"axum-core/__private_docs",
# Enables upstream things linked to in docs
"tower/full",
"dep:serde",
"dep:tower-http",
]
# This feature is used to enable private test helper usage
# in `axum-core` and `axum-extra`.
__private = ["tokio", "http1", "dep:reqwest"]
[dependencies]
axum-core = { path = "../axum-core", version = "0.5.5" }
bytes = "1.0"
futures-util = { version = "0.3", default-features = false, features = ["alloc"] }
http = "1.0.0"
http-body = "1.0.0"
http-body-util = "0.1.0"
itoa = "1.0.5"
matchit = "=0.8.4"
memchr = "2.4.1"
mime = "0.3.16"
percent-encoding = "2.1"
pin-project-lite = "0.2.7"
serde_core = "1.0.221"
sync_wrapper = "1.0.0"
tower = { version = "0.5.2", default-features = false, features = ["util"] }
tower-layer = "0.3.2"
tower-service = "0.3"
# optional dependencies
axum-macros = { path = "../axum-macros", version = "0.5.1", optional = true }
base64 = { version = "0.22.1", optional = true }
form_urlencoded = { version = "1.1.0", optional = true }
hyper = { version = "1.1.0", optional = true }
hyper-util = { version = "0.1.3", features = ["tokio", "server", "service"], optional = true }
multer = { version = "3.0.0", optional = true }
reqwest = { version = "0.12", optional = true, default-features = false, features = ["json", "stream", "multipart"] }
serde_json = { version = "1.0", features = ["raw_value"], optional = true }
serde_path_to_error = { version = "0.1.8", optional = true }
serde_urlencoded = { version = "0.7", optional = true }
sha1 = { version = "0.10", optional = true }
tokio = { package = "tokio", version = "1.44", features = ["time"], optional = true }
tokio-tungstenite = { version = "0.29.0", optional = true }
tracing = { version = "0.1", default-features = false, optional = true }
# doc dependencies
serde = { version = "1.0.211", optional = true }
[dependencies.tower-http]
version = "0.6.8"
optional = true
features = [
# all tower-http features except (de)?compression-zstd which doesn't
# build on `--target armv5te-unknown-linux-musleabi`
"add-extension",
"auth",
"catch-panic",
"compression-br",
"compression-deflate",
"compression-gzip",
"cors",
"decompression-br",
"decompression-deflate",
"decompression-gzip",
"follow-redirect",
"fs",
"limit",
"map-request-body",
"map-response-body",
"metrics",
"normalize-path",
"propagate-header",
"redirect",
"request-id",
"sensitive-headers",
"set-header",
"set-status",
"timeout",
"trace",
"util",
"validate-request",
]
[dev-dependencies]
anyhow = "1.0"
axum-extra = { path = "../axum-extra", features = ["typed-header"] }
axum-macros = { path = "../axum-macros", features = ["__private"] }
hyper = { version = "1.1.0", features = ["client"] }
quickcheck = "1.0"
quickcheck_macros = "1.0"
reqwest = { version = "0.12", default-features = false, features = ["json", "stream", "multipart"] }
serde = { version = "1.0.221", features = ["derive"] }
serde_json = { version = "1.0", features = ["raw_value"] }
time = { version = "0.3", features = ["serde-human-readable"] }
tokio = { package = "tokio", version = "1.44.2", features = ["macros", "rt", "rt-multi-thread", "net", "test-util"] }
tokio-stream = "0.1"
tokio-tungstenite = "0.29.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["json"] }
uuid = { version = "1.0", features = ["serde", "v4"] }
[dev-dependencies.tower]
package = "tower"
version = "0.5.2"
features = [
"util",
"timeout",
"limit",
"load-shed",
"steer",
"filter",
]
[dev-dependencies.tower-http]
version = "0.6.8"
features = [
# all tower-http features except (de)?compression-zstd which doesn't
# build on `--target armv5te-unknown-linux-musleabi`
"add-extension",
"auth",
"catch-panic",
"compression-br",
"compression-deflate",
"compression-gzip",
"cors",
"decompression-br",
"decompression-deflate",
"decompression-gzip",
"follow-redirect",
"fs",
"limit",
"map-request-body",
"map-response-body",
"metrics",
"normalize-path",
"propagate-header",
"redirect",
"request-id",
"sensitive-headers",
"set-header",
"set-status",
"timeout",
"trace",
"util",
"validate-request",
]
[lints]
workspace = true
[package.metadata.docs.rs]
all-features = true
[package.metadata.playground]
features = [
"http1",
"http2",
"json",
"multipart",
"ws",
]
[package.metadata.cargo-public-api-crates]
allowed = [
# our crates
"axum_core",
"axum_macros",
# not 1.0
"futures_core",
"futures_sink",
"futures_util",
"pin_project_lite",
"tower_layer",
"tower_service",
# >=1.0
"bytes",
"http",
"http_body",
"serde_core",
"tokio",
# for the `__private` feature
"reqwest",
]
[[bench]]
name = "benches"
harness = false
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2019 axum Contributors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+154
View File
@@ -0,0 +1,154 @@
# axum
`axum` is an HTTP routing and request-handling library that focuses on ergonomics and modularity.
[![Build status](https://github.com/tokio-rs/axum/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/tokio-rs/axum/actions/workflows/CI.yml)
[![Crates.io](https://img.shields.io/crates/v/axum)](https://crates.io/crates/axum)
[![Documentation](https://docs.rs/axum/badge.svg)][docs]
More information about this crate can be found in the [crate documentation][docs].
## High level features
- Route requests to handlers with a macro free API.
- Declaratively parse requests using extractors.
- Simple and predictable error handling model.
- Generate responses with minimal boilerplate.
- Take full advantage of the [`tower`] and [`tower-http`] ecosystem of
middleware, services, and utilities.
In particular the last point is what sets `axum` apart from other libraries / frameworks.
`axum` doesn't have its own middleware system but instead uses
[`tower::Service`]. This means `axum` gets timeouts, tracing, compression,
authorization, and more, for free. It also enables you to share middleware with
applications written using [`hyper`] or [`tonic`].
## Usage example
```rust
use axum::{
routing::{get, post},
http::StatusCode,
Json, Router,
};
use serde::{Deserialize, Serialize};
#[tokio::main]
async fn main() {
// initialize tracing
tracing_subscriber::fmt::init();
// build our application with a route
let app = Router::new()
// `GET /` goes to `root`
.route("/", get(root))
// `POST /users` goes to `create_user`
.route("/users", post(create_user));
// run our app with hyper, listening globally on port 3000
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
// basic handler that responds with a static string
async fn root() -> &'static str {
"Hello, World!"
}
async fn create_user(
// this argument tells axum to parse the request body
// as JSON into a `CreateUser` type
Json(payload): Json<CreateUser>,
) -> (StatusCode, Json<User>) {
// insert your application logic here
let user = User {
id: 1337,
username: payload.username,
};
// this will be converted into a JSON response
// with a status code of `201 Created`
(StatusCode::CREATED, Json(user))
}
// the input to our `create_user` handler
#[derive(Deserialize)]
struct CreateUser {
username: String,
}
// the output to our `create_user` handler
#[derive(Serialize)]
struct User {
id: u64,
username: String,
}
```
You can find this [example][readme-example] as well as other example projects in
the [example directory][examples].
See the [crate documentation][docs] for way more examples.
## Performance
`axum` is a relatively thin layer on top of [`hyper`] and adds very little
overhead. So `axum`'s performance is comparable to [`hyper`]. You can find
benchmarks [here](https://github.com/programatik29/rust-web-benchmarks) and
[here](https://web-frameworks-benchmark.netlify.app/result?l=rust).
## Safety
This crate uses `#![forbid(unsafe_code)]` to ensure everything is implemented in
100% safe Rust.
## Minimum supported Rust version
axum's MSRV is 1.80.
## Examples
The [examples] folder contains various examples of how to use `axum`. The
[docs] also provide lots of code snippets and examples. For full-fledged examples, check out community-maintained [showcases] or [tutorials].
## Getting Help
In the `axum`'s repo we also have a [number of examples][examples] showing how
to put everything together. Community-maintained [showcases] and [tutorials] also demonstrate how to use `axum` for real-world applications. You're also welcome to ask in the [Discord channel][chat] or open a [discussion] with your question.
## Community projects
See [here][ecosystem] for a list of community maintained crates and projects
built with `axum`.
## Contributing
🎈 Thanks for your help improving the project! We are so happy to have
you! We have a [contributing guide][contributing] to help you get involved in the
`axum` project.
## License
This project is licensed under the [MIT license][license].
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in `axum` by you, shall be licensed as MIT, without any
additional terms or conditions.
[readme-example]: https://github.com/tokio-rs/axum/tree/main/examples/readme
[examples]: https://github.com/tokio-rs/axum/tree/main/examples
[docs]: https://docs.rs/axum
[`tower`]: https://crates.io/crates/tower
[`hyper`]: https://crates.io/crates/hyper
[`tower-http`]: https://crates.io/crates/tower-http
[`tonic`]: https://crates.io/crates/tonic
[contributing]: https://github.com/tokio-rs/axum/blob/main/CONTRIBUTING.md
[chat]: https://discord.gg/tokio
[discussion]: https://github.com/tokio-rs/axum/discussions/new?category=q-a
[`tower::Service`]: https://docs.rs/tower/latest/tower/trait.Service.html
[ecosystem]: https://github.com/tokio-rs/axum/blob/main/ECOSYSTEM.md
[showcases]: https://github.com/tokio-rs/axum/blob/main/ECOSYSTEM.md#project-showcase
[tutorials]: https://github.com/tokio-rs/axum/blob/main/ECOSYSTEM.md#tutorials
[license]: https://github.com/tokio-rs/axum/blob/main/axum/LICENSE
+250
View File
@@ -0,0 +1,250 @@
#![allow(missing_docs)]
use axum::{
extract::State,
routing::{get, post},
Extension, Json, Router,
};
use serde::{Deserialize, Serialize};
use std::{
future::IntoFuture,
io::BufRead,
process::{Command, Stdio},
};
fn main() {
if on_ci() {
install_rewrk();
} else {
ensure_rewrk_is_installed();
}
benchmark("minimal").run(Router::new);
benchmark("basic")
.path("/a/b/c")
.run(|| Router::new().route("/a/b/c", get(|| async { "Hello, World!" })));
benchmark("basic-merge").path("/a/b/c").run(|| {
let inner = Router::new().route("/a/b/c", get(|| async { "Hello, World!" }));
Router::new().merge(inner)
});
benchmark("basic-nest").path("/a/b/c").run(|| {
let c = Router::new().route("/c", get(|| async { "Hello, World!" }));
let b = Router::new().nest("/b", c);
Router::new().nest("/a", b)
});
benchmark("routing").path("/foo/bar/baz").run(|| {
let mut app = Router::new();
for a in 0..10 {
for b in 0..10 {
for c in 0..10 {
app = app.route(&format!("/foo-{a}/bar-{b}/baz-{c}"), get(|| async {}));
}
}
}
app.route("/foo/bar/baz", get(|| async {}))
});
benchmark("receive-json")
.method("post")
.headers(&[("content-type", "application/json")])
.body(r#"{"n": 123, "s": "hi there", "b": false}"#)
.run(|| Router::new().route("/", post(|_: Json<Payload>| async {})));
benchmark("send-json").run(|| {
Router::new().route(
"/",
get(|| async {
Json(Payload {
n: 123,
s: "hi there".to_owned(),
b: false,
})
}),
)
});
let state = AppState {
_string: "aaaaaaaaaaaaaaaaaa".to_owned(),
_vec: Vec::from([
"aaaaaaaaaaaaaaaaaa".to_owned(),
"bbbbbbbbbbbbbbbbbb".to_owned(),
"cccccccccccccccccc".to_owned(),
]),
};
benchmark("extension").run(|| {
Router::new()
.route("/", get(|_: Extension<AppState>| async {}))
.layer(Extension(state.clone()))
});
benchmark("state").run(|| {
Router::new()
.route("/", get(|_: State<AppState>| async {}))
.with_state(state.clone())
});
}
#[derive(Clone)]
struct AppState {
_string: String,
_vec: Vec<String>,
}
#[derive(Deserialize, Serialize)]
struct Payload {
n: u32,
s: String,
b: bool,
}
fn benchmark(name: &'static str) -> BenchmarkBuilder {
BenchmarkBuilder {
name,
path: None,
method: None,
headers: None,
body: None,
}
}
struct BenchmarkBuilder {
name: &'static str,
path: Option<&'static str>,
method: Option<&'static str>,
headers: Option<&'static [(&'static str, &'static str)]>,
body: Option<&'static str>,
}
macro_rules! config_method {
($name:ident, $ty:ty) => {
fn $name(mut self, $name: $ty) -> Self {
self.$name = Some($name);
self
}
};
}
impl BenchmarkBuilder {
config_method!(path, &'static str);
config_method!(method, &'static str);
config_method!(headers, &'static [(&'static str, &'static str)]);
config_method!(body, &'static str);
fn run<F>(self, f: F)
where
F: FnOnce() -> Router<()>,
{
// support only running some benchmarks with
// ```
// cargo bench -- routing send-json
// ```
let args = std::env::args().collect::<Vec<_>>();
if args.len() != 1 {
let names = &args[1..args.len() - 1];
if !names.is_empty() && !names.contains(&self.name.to_owned()) {
return;
}
}
let app = f();
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let listener = rt
.block_on(tokio::net::TcpListener::bind("0.0.0.0:0"))
.unwrap();
let addr = listener.local_addr().unwrap();
std::thread::spawn(move || {
rt.block_on(axum::serve(listener, app).into_future())
.unwrap();
});
let mut cmd = Command::new("rewrk");
cmd.stdout(Stdio::piped());
cmd.arg("--host");
cmd.arg(format!("http://{addr}{}", self.path.unwrap_or("")));
cmd.args(["--connections", "10"]);
cmd.args(["--threads", "10"]);
if on_ci() {
// don't slow down CI by running the benchmarks for too long
// but do run them for a bit
cmd.args(["--duration", "1s"]);
} else {
cmd.args(["--duration", "10s"]);
}
if let Some(method) = self.method {
cmd.args(["--method", method]);
}
for (key, value) in self.headers.into_iter().flatten() {
cmd.arg("--header");
cmd.arg(format!("{key}: {value}"));
}
if let Some(body) = self.body {
cmd.args(["--body", body]);
}
eprintln!("Running {:?} benchmark", self.name);
// indent output from `rewrk` so it's easier to read when running multiple benchmarks
let mut child = cmd.spawn().unwrap();
let stdout = child.stdout.take().unwrap();
let stdout = std::io::BufReader::new(stdout);
for line in stdout.lines() {
let line = line.unwrap();
println!(" {line}");
}
let status = child.wait().unwrap();
if !status.success() {
eprintln!("`rewrk` command failed");
std::process::exit(status.code().unwrap());
}
}
}
fn install_rewrk() {
println!("installing rewrk");
let mut cmd = Command::new("cargo");
cmd.args([
"install",
"rewrk",
"--git",
"https://github.com/ChillFish8/rewrk.git",
]);
let status = cmd
.status()
.unwrap_or_else(|_| panic!("failed to install rewrk"));
if !status.success() {
panic!("failed to install rewrk");
}
}
fn ensure_rewrk_is_installed() {
let mut cmd = Command::new("rewrk");
cmd.arg("--help");
cmd.stdout(Stdio::null());
cmd.stderr(Stdio::null());
cmd.status().unwrap_or_else(|_| {
panic!("rewrk is not installed. See https://github.com/lnx-search/rewrk")
});
}
fn on_ci() -> bool {
std::env::var("GITHUB_ACTIONS").is_ok()
}
+54
View File
@@ -0,0 +1,54 @@
//! HTTP body utilities.
#[doc(no_inline)]
pub use http_body::Body as HttpBody;
#[doc(no_inline)]
pub use bytes::Bytes;
#[doc(inline)]
pub use axum_core::body::{Body, BodyDataStream};
use http_body_util::{BodyExt, Limited};
/// Converts [`Body`] into [`Bytes`] and limits the maximum size of the body.
///
/// # Example
///
/// ```rust
/// use axum::body::{to_bytes, Body};
///
/// # async fn foo() -> Result<(), axum_core::Error> {
/// let body = Body::from(vec![1, 2, 3]);
/// // Use `usize::MAX` if you don't care about the maximum size.
/// let bytes = to_bytes(body, usize::MAX).await?;
/// assert_eq!(&bytes[..], &[1, 2, 3]);
/// # Ok(())
/// # }
/// ```
///
/// You can detect if the limit was hit by checking the source of the error:
///
/// ```rust
/// use axum::body::{to_bytes, Body};
/// use http_body_util::LengthLimitError;
///
/// # #[tokio::main]
/// # async fn main() {
/// let body = Body::from(vec![1, 2, 3]);
/// match to_bytes(body, 1).await {
/// Ok(_bytes) => panic!("should have hit the limit"),
/// Err(err) => {
/// let source = std::error::Error::source(&err).unwrap();
/// assert!(source.is::<LengthLimitError>());
/// }
/// }
/// # }
/// ```
pub async fn to_bytes(body: Body, limit: usize) -> Result<Bytes, axum_core::Error> {
Limited::new(body, limit)
.collect()
.await
.map(|col| col.to_bytes())
.map_err(axum_core::Error::new)
}
+178
View File
@@ -0,0 +1,178 @@
use std::{convert::Infallible, fmt};
use crate::extract::Request;
use tower::Service;
use crate::{
handler::Handler,
routing::{future::RouteFuture, Route},
Router,
};
pub(crate) struct BoxedIntoRoute<S, E>(Box<dyn ErasedIntoRoute<S, E>>);
impl<S> BoxedIntoRoute<S, Infallible>
where
S: Clone + Send + Sync + 'static,
{
pub(crate) fn from_handler<H, T>(handler: H) -> Self
where
H: Handler<T, S>,
T: 'static,
{
Self(Box::new(MakeErasedHandler {
handler,
into_route: |handler, state| Route::new(Handler::with_state(handler, state)),
}))
}
}
impl<S, E> BoxedIntoRoute<S, E> {
pub(crate) fn map<F, E2>(self, f: F) -> BoxedIntoRoute<S, E2>
where
S: 'static,
E: 'static,
F: FnOnce(Route<E>) -> Route<E2> + Clone + Send + Sync + 'static,
E2: 'static,
{
BoxedIntoRoute(Box::new(Map {
inner: self.0,
layer: Box::new(f),
}))
}
pub(crate) fn into_route(self, state: S) -> Route<E> {
self.0.into_route(state)
}
}
impl<S, E> Clone for BoxedIntoRoute<S, E> {
fn clone(&self) -> Self {
Self(self.0.clone_box())
}
}
impl<S, E> fmt::Debug for BoxedIntoRoute<S, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("BoxedIntoRoute").finish()
}
}
pub(crate) trait ErasedIntoRoute<S, E>: Send + Sync {
fn clone_box(&self) -> Box<dyn ErasedIntoRoute<S, E>>;
fn into_route(self: Box<Self>, state: S) -> Route<E>;
#[allow(dead_code)]
fn call_with_state(self: Box<Self>, request: Request, state: S) -> RouteFuture<E>;
}
pub(crate) struct MakeErasedHandler<H, S> {
pub(crate) handler: H,
pub(crate) into_route: fn(H, S) -> Route,
}
impl<H, S> ErasedIntoRoute<S, Infallible> for MakeErasedHandler<H, S>
where
H: Clone + Send + Sync + 'static,
S: 'static,
{
fn clone_box(&self) -> Box<dyn ErasedIntoRoute<S, Infallible>> {
Box::new(self.clone())
}
fn into_route(self: Box<Self>, state: S) -> Route {
(self.into_route)(self.handler, state)
}
fn call_with_state(self: Box<Self>, request: Request, state: S) -> RouteFuture<Infallible> {
self.into_route(state).call(request)
}
}
impl<H, S> Clone for MakeErasedHandler<H, S>
where
H: Clone,
{
fn clone(&self) -> Self {
Self {
handler: self.handler.clone(),
into_route: self.into_route,
}
}
}
#[allow(dead_code)]
pub(crate) struct MakeErasedRouter<S> {
pub(crate) router: Router<S>,
pub(crate) into_route: fn(Router<S>, S) -> Route,
}
impl<S> ErasedIntoRoute<S, Infallible> for MakeErasedRouter<S>
where
S: Clone + Send + Sync + 'static,
{
fn clone_box(&self) -> Box<dyn ErasedIntoRoute<S, Infallible>> {
Box::new(self.clone())
}
fn into_route(self: Box<Self>, state: S) -> Route {
(self.into_route)(self.router, state)
}
fn call_with_state(self: Box<Self>, request: Request, state: S) -> RouteFuture<Infallible> {
self.router.call_with_state(request, state)
}
}
impl<S> Clone for MakeErasedRouter<S>
where
S: Clone,
{
fn clone(&self) -> Self {
Self {
router: self.router.clone(),
into_route: self.into_route,
}
}
}
pub(crate) struct Map<S, E, E2> {
pub(crate) inner: Box<dyn ErasedIntoRoute<S, E>>,
pub(crate) layer: Box<dyn LayerFn<E, E2>>,
}
impl<S, E, E2> ErasedIntoRoute<S, E2> for Map<S, E, E2>
where
S: 'static,
E: 'static,
E2: 'static,
{
fn clone_box(&self) -> Box<dyn ErasedIntoRoute<S, E2>> {
Box::new(Self {
inner: self.inner.clone_box(),
layer: self.layer.clone_box(),
})
}
fn into_route(self: Box<Self>, state: S) -> Route<E2> {
(self.layer)(self.inner.into_route(state))
}
fn call_with_state(self: Box<Self>, request: Request, state: S) -> RouteFuture<E2> {
(self.layer)(self.inner.into_route(state)).call(request)
}
}
pub(crate) trait LayerFn<E, E2>: FnOnce(Route<E>) -> Route<E2> + Send + Sync {
fn clone_box(&self) -> Box<dyn LayerFn<E, E2>>;
}
impl<F, E, E2> LayerFn<E, E2> for F
where
F: FnOnce(Route<E>) -> Route<E2> + Clone + Send + Sync + 'static,
{
fn clone_box(&self) -> Box<dyn LayerFn<E, E2>> {
Box::new(self.clone())
}
}
@@ -0,0 +1,39 @@
## Debugging handler type errors
For a function to be used as a handler it must implement the [`Handler`] trait.
axum provides blanket implementations for functions that:
- Are `async fn`s.
- Take no more than 16 arguments that all implement `Send`.
- All except the last argument implement [`FromRequestParts`].
- The last argument implements [`FromRequest`].
- Returns something that implements [`IntoResponse`].
- If a closure is used it must implement `Clone + Send` and be
`'static`.
- Returns a future that is `Send`. The most common way to accidentally make a
future `!Send` is to hold a `!Send` type across an await.
Unfortunately Rust gives poor error messages if you try to use a function
that doesn't quite match what's required by [`Handler`].
You might get an error like this:
```not_rust
error[E0277]: the trait bound `fn(bool) -> impl Future {handler}: Handler<_, _>` is not satisfied
--> src/main.rs:13:44
|
13 | let app = Router::new().route("/", get(handler));
| ^^^^^^^ the trait `Handler<_, _>` is not implemented for `fn(bool) -> impl Future {handler}`
|
::: axum/src/handler/mod.rs:116:8
|
116 | H: Handler<T, B>,
| ------------- required by this bound in `axum::routing::get`
```
This error doesn't tell you _why_ your function doesn't implement
[`Handler`]. It's possible to improve the error with the [`debug_handler`]
proc-macro from the [axum-macros] crate.
[axum-macros]: https://docs.rs/axum-macros
[`debug_handler`]: https://docs.rs/axum-macros/latest/axum_macros/attr.debug_handler.html
+179
View File
@@ -0,0 +1,179 @@
Error handling model and utilities
# axum's error handling model
axum is based on [`tower::Service`] which bundles errors through its associated
`Error` type. If you have a [`Service`] that produces an error and that error
makes it all the way up to hyper, the connection will be terminated _without_
sending a response. This is generally not desirable so axum makes sure you
always produce a response by relying on the type system.
axum does this by requiring all services have [`Infallible`] as their error
type. `Infallible` is the error type for errors that can never happen.
This means if you define a handler like:
```rust
use axum::http::StatusCode;
async fn handler() -> Result<String, StatusCode> {
# todo!()
// ...
}
```
While it looks like it might fail with a `StatusCode` this actually isn't an
"error". If this handler returns `Err(some_status_code)` that will still be
converted into a [`Response`] and sent back to the client. This is done
through `StatusCode`'s [`IntoResponse`] implementation.
It doesn't matter whether you return `Err(StatusCode::NOT_FOUND)` or
`Err(StatusCode::INTERNAL_SERVER_ERROR)`. These are not considered errors in
axum.
Instead of a direct `StatusCode`, it makes sense to use intermediate error type
that can ultimately be converted to `Response`. This allows using `?` operator
in handlers. See those examples:
* [`anyhow-error-response`][anyhow] for generic boxed errors
* [`error-handling`][error-handling] for application-specific detailed errors
[anyhow]: https://github.com/tokio-rs/axum/blob/main/examples/anyhow-error-response/src/main.rs
[error-handling]: https://github.com/tokio-rs/axum/blob/main/examples/error-handling/src/main.rs
This also applies to extractors. If an extractor doesn't match the request the
request will be rejected and a response will be returned without calling your
handler. See [`extract`](crate::extract) to learn more about handling extractor
failures.
# Routing to fallible services
You generally don't have to think about errors if you're only using async
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.
```rust
use axum::{
Router,
body::Body,
http::{Request, Response, StatusCode},
error_handling::HandleError,
};
async fn thing_that_might_fail() -> Result<(), anyhow::Error> {
# Ok(())
// ...
}
// this service might fail with `anyhow::Error`
let some_fallible_service = tower::service_fn(|_req| async {
thing_that_might_fail().await?;
Ok::<_, anyhow::Error>(Response::new(Body::empty()))
});
let app = Router::new().route_service(
"/",
// 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`.
HandleError::new(some_fallible_service, handle_anyhow_error),
);
// handle errors by converting them into something that implements
// `IntoResponse`
async fn handle_anyhow_error(err: anyhow::Error) -> (StatusCode, String) {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {err}"),
)
}
# let _: Router = app;
```
# Applying fallible middleware
Similarly axum requires you to handle errors from middleware. That is done with
[`HandleErrorLayer`]:
```rust
use axum::{
Router,
BoxError,
routing::get,
http::StatusCode,
error_handling::HandleErrorLayer,
};
use std::time::Duration;
use tower::ServiceBuilder;
let app = Router::new()
.route("/", get(|| async {}))
.layer(
ServiceBuilder::new()
// `timeout` will produce an error if the handler takes
// too long so we must handle those
.layer(HandleErrorLayer::new(handle_timeout_error))
.timeout(Duration::from_secs(30))
);
async fn handle_timeout_error(err: BoxError) -> (StatusCode, String) {
if err.is::<tower::timeout::error::Elapsed>() {
(
StatusCode::REQUEST_TIMEOUT,
"Request took too long".to_string(),
)
} else {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Unhandled internal error: {err}"),
)
}
}
# let _: Router = app;
```
# Running extractors for error handling
`HandleErrorLayer` also supports running extractors:
```rust
use axum::{
Router,
BoxError,
routing::get,
http::{StatusCode, Method, Uri},
error_handling::HandleErrorLayer,
};
use std::time::Duration;
use tower::ServiceBuilder;
let app = Router::new()
.route("/", get(|| async {}))
.layer(
ServiceBuilder::new()
// `timeout` will produce an error if the handler takes
// too long so we must handle those
.layer(HandleErrorLayer::new(handle_timeout_error))
.timeout(Duration::from_secs(30))
);
async fn handle_timeout_error(
// `Method` and `Uri` are extractors so they can be used here
method: Method,
uri: Uri,
// the last argument must be the error itself
err: BoxError,
) -> (StatusCode, String) {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("`{method} {uri}` failed with {err}"),
)
}
# let _: Router = app;
```
[`tower::Service`]: `tower::Service`
[`Infallible`]: std::convert::Infallible
[`Response`]: crate::response::Response
[`IntoResponse`]: crate::response::IntoResponse
+689
View File
@@ -0,0 +1,689 @@
Types and traits for extracting data from requests.
# Intro
A handler function is an async function that takes any number of
"extractors" as arguments. An extractor is a type that implements
[`FromRequest`] or [`FromRequestParts`].
For example, [`Json`] is an extractor that consumes the request body and
deserializes it as JSON into some target type:
```rust,no_run
use axum::{
extract::Json,
routing::post,
handler::Handler,
Router,
};
use serde::Deserialize;
#[derive(Deserialize)]
struct CreateUser {
email: String,
password: String,
}
async fn create_user(Json(payload): Json<CreateUser>) {
// ...
}
let app = Router::new().route("/users", post(create_user));
# let _: Router = app;
```
# Common extractors
Some commonly used extractors are:
```rust,no_run
use axum::{
extract::{Request, Json, Path, Extension, Query},
routing::post,
http::header::HeaderMap,
body::{Bytes, Body},
Router,
};
use serde_json::Value;
use std::collections::HashMap;
// `Path` gives you the path parameters and deserializes them. See its docs for
// more details
async fn path(Path(user_id): Path<u32>) {}
// `Query` gives you the query parameters and deserializes them.
async fn query(Query(params): Query<HashMap<String, String>>) {}
// `HeaderMap` gives you all the headers
async fn headers(headers: HeaderMap) {}
// `String` consumes the request body and ensures it is valid utf-8
async fn string(body: String) {}
// `Bytes` gives you the raw request body
async fn bytes(body: Bytes) {}
// We've already seen `Json` for parsing the request body as json
async fn json(Json(payload): Json<Value>) {}
// `Request` gives you the whole request for maximum control
async fn request(request: Request) {}
// `Extension` extracts data from "request extensions"
// This is commonly used to share state with handlers
async fn extension(Extension(state): Extension<State>) {}
#[derive(Clone)]
struct State { /* ... */ }
let app = Router::new()
.route("/path/{user_id}", post(path))
.route("/query", post(query))
.route("/string", post(string))
.route("/bytes", post(bytes))
.route("/json", post(json))
.route("/request", post(request))
.route("/extension", post(extension));
# let _: Router = app;
```
# Applying multiple extractors
You can also apply multiple extractors:
```rust,no_run
use axum::{
extract::{Path, Query},
routing::get,
Router,
};
use uuid::Uuid;
use serde::Deserialize;
let app = Router::new().route("/users/{id}/things", get(get_user_things));
#[derive(Deserialize)]
struct Pagination {
page: usize,
per_page: usize,
}
async fn get_user_things(
Path(user_id): Path<Uuid>,
Query(pagination): Query<Pagination>,
) {
// ...
}
# let _: Router = app;
```
# The order of extractors
Extractors always run in the order of the function parameters that is from
left to right.
The request body is an asynchronous stream that can only be consumed once.
Therefore you can only have one extractor that consumes the request body. axum
enforces this by requiring such extractors to be the _last_ argument your
handler takes.
For example
```rust
use axum::{extract::State, http::{Method, HeaderMap}};
#
# #[derive(Clone)]
# struct AppState {
# }
async fn handler(
// `Method` and `HeaderMap` don't consume the request body so they can
// put anywhere in the argument list (but before `body`)
method: Method,
headers: HeaderMap,
// `State` is also an extractor so it needs to be before `body`
State(state): State<AppState>,
// `String` consumes the request body and thus must be the last extractor
body: String,
) {
// ...
}
#
# let _: axum::routing::MethodRouter<AppState> = axum::routing::get(handler);
```
We get a compile error if `String` isn't the last extractor:
```rust,compile_fail
use axum::http::Method;
async fn handler(
// this doesn't work since `String` must be the last argument
body: String,
method: Method,
) {
// ...
}
#
# let _: axum::routing::MethodRouter = axum::routing::get(handler);
```
This also means you cannot consume the request body twice:
```rust,compile_fail
use axum::Json;
use serde::Deserialize;
#[derive(Deserialize)]
struct Payload {}
async fn handler(
// `String` and `Json` both consume the request body
// so they cannot both be used
string_body: String,
json_body: Json<Payload>,
) {
// ...
}
#
# let _: axum::routing::MethodRouter = axum::routing::get(handler);
```
axum enforces this by requiring the last extractor implements [`FromRequest`]
and all others implement [`FromRequestParts`].
# Handling extractor rejections
If you want to handle the case of an extractor failing within a specific
handler, you can wrap it in `Result`, with the error being the rejection type
of the extractor:
```rust,no_run
use axum::{
extract::{Json, rejection::JsonRejection},
routing::post,
Router,
};
use serde_json::Value;
async fn create_user(payload: Result<Json<Value>, JsonRejection>) {
match payload {
Ok(payload) => {
// We got a valid JSON payload
}
Err(JsonRejection::MissingJsonContentType(_)) => {
// Request didn't have `Content-Type: application/json`
// header
}
Err(JsonRejection::JsonDataError(_)) => {
// Couldn't deserialize the body into the target type
}
Err(JsonRejection::JsonSyntaxError(_)) => {
// Syntax error in the body
}
Err(JsonRejection::BytesRejection(_)) => {
// Failed to extract the request body
}
Err(_) => {
// `JsonRejection` is marked `#[non_exhaustive]` so match must
// include a catch-all case.
}
}
}
let app = Router::new().route("/users", post(create_user));
# let _: Router = app;
```
# Optional extractors
Some extractors implement [`OptionalFromRequestParts`] in addition to
[`FromRequestParts`], or [`OptionalFromRequest`] in addition to [`FromRequest`].
These extractors can be used inside of `Option`. It depends on the particular
`OptionalFromRequestParts` or `OptionalFromRequest` implementation what this
does: For example for `TypedHeader` from axum-extra, you get `None` if the
header you're trying to extract is not part of the request, but if the header
is present and fails to parse, the request is rejected.
```rust,no_run
use axum::{routing::post, Router};
use axum_extra::{headers::UserAgent, TypedHeader};
use serde_json::Value;
async fn foo(user_agent: Option<TypedHeader<UserAgent>>) {
if let Some(TypedHeader(user_agent)) = user_agent {
// The client sent a user agent
} else {
// No user agent header
}
}
let app = Router::new().route("/foo", post(foo));
# let _: Router = app;
```
# Customizing extractor responses
If an extractor fails it will return a response with the error and your
handler will not be called. To customize the error response you have two
options:
1. Use `Result<T, T::Rejection>` as your extractor like shown in
["Handling extractor rejections"](#handling-extractor-rejections).
This works well if you're only using the extractor in a single handler.
2. Create your own extractor that in its [`FromRequest`] implementation calls
one of axum's built in extractors but returns a different response for
rejections. See the [customize-extractor-error] example for more details.
# Accessing inner errors
axum's built-in extractors don't directly expose the inner error. This gives us
more flexibility and allows us to change internal implementations without
breaking the public API.
For example that means while [`Json`] is implemented using [`serde_json`] it
doesn't directly expose the [`serde_json::Error`] that's contained in
[`JsonRejection::JsonDataError`]. However it is still possible to access via
methods from [`std::error::Error`]:
```rust
use std::error::Error;
use axum::{
extract::{Json, rejection::JsonRejection},
response::IntoResponse,
http::StatusCode,
};
use serde_json::{json, Value};
async fn handler(
result: Result<Json<Value>, JsonRejection>,
) -> Result<Json<Value>, (StatusCode, String)> {
match result {
// if the client sent valid JSON then we're good
Ok(Json(payload)) => Ok(Json(json!({ "payload": payload }))),
Err(err) => match err {
JsonRejection::JsonDataError(err) => {
Err(serde_json_error_response(err))
}
JsonRejection::JsonSyntaxError(err) => {
Err(serde_json_error_response(err))
}
// handle other rejections from the `Json` extractor
JsonRejection::MissingJsonContentType(_) => Err((
StatusCode::BAD_REQUEST,
"Missing `Content-Type: application/json` header".to_string(),
)),
JsonRejection::BytesRejection(_) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
"Failed to buffer request body".to_string(),
)),
// we must provide a catch-all case since `JsonRejection` is marked
// `#[non_exhaustive]`
_ => Err((
StatusCode::INTERNAL_SERVER_ERROR,
"Unknown error".to_string(),
)),
},
}
}
// attempt to extract the inner `serde_path_to_error::Error<serde_json::Error>`,
// if that succeeds we can provide a more specific error.
//
// `Json` uses `serde_path_to_error` so the error will be wrapped in `serde_path_to_error::Error`.
fn serde_json_error_response<E>(err: E) -> (StatusCode, String)
where
E: Error + 'static,
{
if let Some(err) = find_error_source::<serde_path_to_error::Error<serde_json::Error>>(&err) {
let serde_json_err = err.inner();
(
StatusCode::BAD_REQUEST,
format!(
"Invalid JSON at line {} column {}",
serde_json_err.line(),
serde_json_err.column()
),
)
} else {
(StatusCode::BAD_REQUEST, "Unknown error".to_string())
}
}
// attempt to downcast `err` into a `T` and if that fails recursively try and
// downcast `err`'s source
fn find_error_source<'a, T>(err: &'a (dyn Error + 'static)) -> Option<&'a T>
where
T: Error + 'static,
{
if let Some(err) = err.downcast_ref::<T>() {
Some(err)
} else if let Some(source) = err.source() {
find_error_source(source)
} else {
None
}
}
#
# #[tokio::main]
# async fn main() {
# use axum::extract::FromRequest;
#
# let req = axum::http::Request::builder()
# .header("content-type", "application/json")
# .body(axum::body::Body::from("{"))
# .unwrap();
#
# let err = match Json::<serde_json::Value>::from_request(req, &()).await.unwrap_err() {
# JsonRejection::JsonSyntaxError(err) => err,
# _ => panic!(),
# };
#
# let (_, body) = serde_json_error_response(err);
# assert_eq!(body, "Invalid JSON at line 1 column 1");
# }
```
Note that while this approach works it might break in the future if axum changes
its implementation to use a different error type internally. Such changes might
happen without major breaking versions.
# Defining custom extractors
You can also define your own extractors by implementing either
[`FromRequestParts`] or [`FromRequest`].
## Implementing `FromRequestParts`
Implement `FromRequestParts` if your extractor doesn't need access to the
request body:
```rust,no_run
use axum::{
extract::FromRequestParts,
routing::get,
Router,
http::{
StatusCode,
header::{HeaderValue, USER_AGENT},
request::Parts,
},
};
struct ExtractUserAgent(HeaderValue);
impl<S> FromRequestParts<S> for ExtractUserAgent
where
S: Send + Sync,
{
type Rejection = (StatusCode, &'static str);
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
if let Some(user_agent) = parts.headers.get(USER_AGENT) {
Ok(ExtractUserAgent(user_agent.clone()))
} else {
Err((StatusCode::BAD_REQUEST, "`User-Agent` header is missing"))
}
}
}
async fn handler(ExtractUserAgent(user_agent): ExtractUserAgent) {
// ...
}
let app = Router::new().route("/foo", get(handler));
# let _: Router = app;
```
## Implementing `FromRequest`
If your extractor needs to consume the request body you must implement [`FromRequest`]
```rust,no_run
use axum::{
extract::{Request, FromRequest},
response::{Response, IntoResponse},
body::{Bytes, Body},
routing::get,
Router,
http::{
StatusCode,
header::{HeaderValue, USER_AGENT},
},
};
struct ValidatedBody(Bytes);
impl<S> FromRequest<S> for ValidatedBody
where
Bytes: FromRequest<S>,
S: Send + Sync,
{
type Rejection = Response;
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
let body = Bytes::from_request(req, state)
.await
.map_err(IntoResponse::into_response)?;
// do validation...
Ok(Self(body))
}
}
async fn handler(ValidatedBody(body): ValidatedBody) {
// ...
}
let app = Router::new().route("/foo", get(handler));
# let _: Router = app;
```
## Cannot implement both `FromRequest` and `FromRequestParts`
Note that you will make your extractor unusable by implementing both
`FromRequest` and `FromRequestParts` directly for the same type, unless it is
wrapping another extractor:
```rust,compile_fail
use axum::{
Router,
routing::get,
extract::{FromRequest, Request, FromRequestParts},
http::request::Parts,
body::Body,
};
use std::convert::Infallible;
// Some extractor that doesn't wrap another extractor
struct MyExtractor;
// `MyExtractor` implements both `FromRequest`
impl<S> FromRequest<S> for MyExtractor
where
S: Send + Sync,
{
type Rejection = Infallible;
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
// ...
# todo!()
}
}
// and `FromRequestParts`
impl<S> FromRequestParts<S> for MyExtractor
where
S: Send + Sync,
{
type Rejection = Infallible;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
// ...
# todo!()
}
}
let app = Router::new().route(
"/",
// This fails when we go to actually use `MyExtractor` in a handler function.
// This is due to a limit in Rust's type system.
//
// The workaround is to implement either `FromRequest` or `FromRequestParts`
// but not both, if your extractor doesn't wrap another extractor.
//
// See "Wrapping extractors" for how to wrap other extractors.
get(|_: MyExtractor| async {}),
);
# let _: Router = app;
```
# Accessing other extractors in `FromRequest` or `FromRequestParts` implementations
When defining custom extractors you often need to access another extractor
in your implementation.
```rust
use axum::{
extract::{Extension, FromRequestParts},
http::{StatusCode, HeaderMap, request::Parts},
response::{IntoResponse, Response},
routing::get,
Router,
};
#[derive(Clone)]
struct State {
// ...
}
struct AuthenticatedUser {
// ...
}
impl<S> FromRequestParts<S> for AuthenticatedUser
where
S: Send + Sync,
{
type Rejection = Response;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
// You can either call them directly...
let headers = HeaderMap::from_request_parts(parts, state)
.await
.map_err(|err| match err {})?;
// ... or use `extract` / `extract_with_state` from `RequestExt` / `RequestPartsExt`
use axum::RequestPartsExt;
let Extension(state) = parts.extract::<Extension<State>>()
.await
.map_err(|err| err.into_response())?;
unimplemented!("actually perform the authorization")
}
}
async fn handler(user: AuthenticatedUser) {
// ...
}
let state = State { /* ... */ };
let app = Router::new().route("/", get(handler)).layer(Extension(state));
# let _: Router = app;
```
# Request body limits
For security reasons, [`Bytes`] will, by default, not accept bodies larger than
2MB. This also applies to extractors that uses [`Bytes`] internally such as
`String`, [`Json`], and [`Form`].
For more details, including how to disable this limit, see [`DefaultBodyLimit`].
# Wrapping extractors
If you want to write an extractor that generically wraps another extractor
(that may or may not consume the request body) you should implement both
[`FromRequest`] and [`FromRequestParts`]:
```rust
use axum::{
Router,
body::Body,
routing::get,
extract::{Request, FromRequest, FromRequestParts},
http::{HeaderMap, request::Parts},
};
use std::time::{Instant, Duration};
// an extractor that wraps another and measures how long time it takes to run
struct Timing<E> {
extractor: E,
duration: Duration,
}
// we must implement both `FromRequestParts`
impl<S, T> FromRequestParts<S> for Timing<T>
where
S: Send + Sync,
T: FromRequestParts<S>,
{
type Rejection = T::Rejection;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let start = Instant::now();
let extractor = T::from_request_parts(parts, state).await?;
let duration = start.elapsed();
Ok(Timing {
extractor,
duration,
})
}
}
// and `FromRequest`
impl<S, T> FromRequest<S> for Timing<T>
where
S: Send + Sync,
T: FromRequest<S>,
{
type Rejection = T::Rejection;
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
let start = Instant::now();
let extractor = T::from_request(req, state).await?;
let duration = start.elapsed();
Ok(Timing {
extractor,
duration,
})
}
}
async fn handler(
// this uses the `FromRequestParts` impl
_: Timing<HeaderMap>,
// this uses the `FromRequest` impl
_: Timing<String>,
) {}
# let _: axum::routing::MethodRouter = axum::routing::get(handler);
```
# Logging rejections
All built-in extractors will log rejections for easier debugging. To see the
logs, enable the `tracing` feature for axum (enabled by default) and the
`axum::rejection=trace` tracing target, for example with
`RUST_LOG=info,axum::rejection=trace cargo run`.
[axum-extra]: https://docs.rs/axum-extra/latest/axum_extra/extract/index.html
[`body::Body`]: crate::body::Body
[`Bytes`]: crate::body::Bytes
[customize-extractor-error]: https://github.com/tokio-rs/axum/blob/main/examples/customize-extractor-error/src/main.rs
[`HeaderMap`]: https://docs.rs/http/latest/http/header/struct.HeaderMap.html
[`Request`]: https://docs.rs/http/latest/http/struct.Request.html
[`JsonRejection::JsonDataError`]: rejection::JsonRejection::JsonDataError
+8
View File
@@ -0,0 +1,8 @@
In axum a "handler" is an async function that accepts zero or more
["extractors"](crate::extract) as arguments and returns something that
can be converted [into a response](crate::response).
Handlers are where your application logic lives and axum applications are built
by routing between handlers.
[`debug_handler`]: https://docs.rs/axum-macros/latest/axum_macros/attr.debug_handler.html
@@ -0,0 +1,55 @@
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);
let app = Router::new().route("/", handler);
async fn fallback(method: Method, uri: Uri) -> (StatusCode, String) {
(StatusCode::NOT_FOUND, format!("`{method}` not allowed for {uri}"))
}
# let _: Router = app;
```
## 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);
let two = post(|| async {}).fallback(fallback_two);
let method_route = one.merge(two);
async fn fallback_one() -> impl IntoResponse { /* ... */ }
async fn fallback_two() -> impl IntoResponse { /* ... */ }
# let app: axum::Router = axum::Router::new().route("/", method_route);
```
## Setting the `Allow` header
By default `MethodRouter` will set the `Allow` header when returning `405 Method
Not Allowed`. This is also done when the fallback is used unless the response
generated by the fallback already sets the `Allow` header.
This means if you use `fallback` to accept additional methods, you should make
sure you set the `Allow` header correctly.
@@ -0,0 +1,27 @@
Apply a [`tower::Layer`] to all routes in the router.
This can be used to add additional processing to a request for a group
of routes.
Note that the middleware is only applied to existing routes. So you have to
first add your routes (and / or fallback) and then call `layer` afterwards. Additional
routes added after `layer` is called will not have the middleware added.
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 handler() {}
let app = Router::new().route(
"/",
// All requests to `GET /` will be sent through `ConcurrencyLimitLayer`
get(handler).layer(ConcurrencyLimitLayer::new(64)),
);
# let _: Router = app;
```
@@ -0,0 +1,23 @@
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 /
# let _: Router = app;
```
@@ -0,0 +1,32 @@
Apply a [`tower::Layer`] to the router that will only run if the request matches
a route.
Note that the middleware is only applied to existing routes. First add your routes and then call `route_layer`
afterwards. Additional routes added after `route_layer` is called will not have
the middleware added.
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::validate_request::ValidateRequestHeaderLayer;
let app = Router::new().route(
"/foo",
get(|| async {})
.route_layer(ValidateRequestHeaderLayer::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`
# let _: Router = app;
```
+583
View File
@@ -0,0 +1,583 @@
# Intro
axum is unique in that it doesn't have its own bespoke middleware system and
instead integrates with [`tower`]. This means the ecosystem of [`tower`] and
[`tower-http`] middleware all work with axum.
While it's not necessary to fully understand tower to write or use middleware
with axum, having at least a basic understanding of tower's concepts is
recommended. See [tower's guides][tower-guides] for a general introduction.
Reading the documentation for [`tower::ServiceBuilder`] is also recommended.
# Applying middleware
axum allows you to add middleware just about anywhere
- To entire routers with [`Router::layer`] and [`Router::route_layer`].
- To method routers with [`MethodRouter::layer`] and [`MethodRouter::route_layer`].
- To individual handlers with [`Handler::layer`].
## Applying multiple middleware
It's recommended to use [`tower::ServiceBuilder`] to apply multiple middleware at
once, instead of calling `layer` (or `route_layer`) repeatedly:
```rust
use axum::{
routing::get,
Extension,
Router,
};
use tower_http::{trace::TraceLayer};
use tower::ServiceBuilder;
async fn handler() {}
#[derive(Clone)]
struct State {}
let app = Router::new()
.route("/", get(handler))
.layer(
ServiceBuilder::new()
.layer(TraceLayer::new_for_http())
.layer(Extension(State {}))
);
# let _: Router = app;
```
# Commonly used middleware
Some commonly used middleware are:
- [`TraceLayer`](tower_http::trace) for high level tracing/logging.
- [`CorsLayer`](tower_http::cors) for handling CORS.
- [`CompressionLayer`](tower_http::compression) for automatic compression of responses.
- [`RequestIdLayer`](tower_http::request_id) and
[`PropagateRequestIdLayer`](tower_http::request_id) set and propagate request
ids.
- [`TimeoutLayer`](tower_http::timeout::TimeoutLayer) for timeouts.
# Ordering
When you add middleware with [`Router::layer`] (or similar) all previously added
routes will be wrapped in the middleware. Generally speaking, this results in
middleware being executed from bottom to top.
So if you do this:
```rust
use axum::{routing::get, Router};
async fn handler() {}
# let layer_one = axum::Extension(());
# let layer_two = axum::Extension(());
# let layer_three = axum::Extension(());
#
let app = Router::new()
.route("/", get(handler))
.layer(layer_one)
.layer(layer_two)
.layer(layer_three);
# let _: Router = app;
```
Think of the middleware as being layered like an onion where each new layer
wraps all previous layers:
```not_rust
requests
|
v
+----- layer_three -----+
| +---- layer_two ----+ |
| | +-- layer_one --+ | |
| | | | | |
| | | handler | | |
| | | | | |
| | +-- layer_one --+ | |
| +---- layer_two ----+ |
+----- layer_three -----+
|
v
responses
```
That is:
- First `layer_three` receives the request
- It then does its thing and passes the request onto `layer_two`
- Which passes the request onto `layer_one`
- Which passes the request onto `handler` where a response is produced
- That response is then passed to `layer_one`
- Then to `layer_two`
- And finally to `layer_three` where it's returned out of your app
It's a little more complicated in practice because any middleware is free to
return early and not call the next layer, for example if a request cannot be
authorized, but it's a useful mental model to have.
As previously mentioned it's recommended to add multiple middleware using
`tower::ServiceBuilder`, however this impacts ordering:
```rust
use tower::ServiceBuilder;
use axum::{routing::get, Router};
async fn handler() {}
# let layer_one = axum::Extension(());
# let layer_two = axum::Extension(());
# let layer_three = axum::Extension(());
#
let app = Router::new()
.route("/", get(handler))
.layer(
ServiceBuilder::new()
.layer(layer_one)
.layer(layer_two)
.layer(layer_three),
);
# let _: Router = app;
```
`ServiceBuilder` works by composing all layers into one such that they run top
to bottom. So with the previous code `layer_one` would receive the request
first, then `layer_two`, then `layer_three`, then `handler`, and then the
response would bubble back up through `layer_three`, then `layer_two`, and
finally `layer_one`.
Executing middleware top to bottom is generally easier to understand and follow
mentally which is one of the reasons `ServiceBuilder` is recommended.
# Writing middleware
axum offers many ways of writing middleware, at different levels of abstraction
and with different pros and cons.
## `axum::middleware::from_fn`
Use [`axum::middleware::from_fn`] to write your middleware when:
- You're not comfortable with implementing your own futures and would rather use
the familiar `async`/`await` syntax.
- You don't intend to publish your middleware as a crate for others to use.
Middleware written like this are only compatible with axum.
## `axum::middleware::from_extractor`
Use [`axum::middleware::from_extractor`] to write your middleware when:
- You have a type that you sometimes want to use as an extractor and sometimes
as a middleware. If you only need your type as a middleware prefer
[`middleware::from_fn`].
## tower's combinators
tower has several utility combinators that can be used to perform simple
modifications to requests or responses. The most commonly used ones are
- [`ServiceBuilder::map_request`]
- [`ServiceBuilder::map_response`]
- [`ServiceBuilder::then`]
- [`ServiceBuilder::and_then`]
You should use these when
- You want to perform a small ad hoc operation, such as adding a header.
- You don't intend to publish your middleware as a crate for others to use.
## `tower::Service` and `Pin<Box<dyn Future>>`
For maximum control (and a more low level API) you can write your own middleware
by implementing [`tower::Service`]:
Use [`tower::Service`] with `Pin<Box<dyn Future>>` to write your middleware when:
- Your middleware needs to be configurable for example via builder methods on
your [`tower::Layer`] such as [`tower_http::trace::TraceLayer`].
- You do intend to publish your middleware as a crate for others to use.
- You're not comfortable with implementing your own futures.
A decent template for such a middleware could be:
```rust
use axum::{
response::Response,
body::Body,
extract::Request,
};
use futures_util::future::BoxFuture;
use tower::{Service, Layer};
use std::task::{Context, Poll};
#[derive(Clone)]
struct MyLayer;
impl<S> Layer<S> for MyLayer {
type Service = MyMiddleware<S>;
fn layer(&self, inner: S) -> Self::Service {
MyMiddleware { inner }
}
}
#[derive(Clone)]
struct MyMiddleware<S> {
inner: S,
}
impl<S> Service<Request> for MyMiddleware<S>
where
S: Service<Request, Response = Response> + Send + 'static,
S::Future: Send + 'static,
{
type Response = S::Response;
type Error = S::Error;
// `BoxFuture` is a type alias for `Pin<Box<dyn Future + Send + 'a>>`
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, request: Request) -> Self::Future {
let future = self.inner.call(request);
Box::pin(async move {
let response: Response = future.await?;
Ok(response)
})
}
}
```
Note that your error type being defined as `S::Error` means that your middleware typically _returns no errors_. As a principle always try to return a response and try not to bail out with a custom error type. For example, if a 3rd party library you are using inside your new middleware returns its own specialized error type, try to convert it to some reasonable response and return `Ok` with that response.
If you choose to implement a custom error type such as `type Error = BoxError` (a boxed opaque error), or any other error type that is not `Infallible`, you must use a `HandleErrorLayer`, here is an example using a `ServiceBuilder`:
```ignore
ServiceBuilder::new()
.layer(HandleErrorLayer::new(|_: BoxError| async {
// because axum uses infallible errors, you must handle your custom error type from your middleware here
StatusCode::BAD_REQUEST
}))
.layer(
// <your actual layer which DOES return an error>
);
```
## `tower::Service` and custom futures
If you're comfortable implementing your own futures (or want to learn it) and
need as much control as possible then using `tower::Service` without boxed
futures is the way to go.
Use [`tower::Service`] with manual futures to write your middleware when:
- You want your middleware to have the lowest possible overhead.
- Your middleware needs to be configurable for example via builder methods on
your [`tower::Layer`] such as [`tower_http::trace::TraceLayer`].
- You do intend to publish your middleware as a crate for others to use, perhaps
as part of tower-http.
- You're comfortable with implementing your own futures, or want to learn how
the lower levels of async Rust works.
tower's ["Building a middleware from scratch"][tower-from-scratch-guide]
guide is a good place to learn how to do this.
# Error handling for middleware
axum's error handling model requires handlers to always return a response.
However middleware is one possible way to introduce errors into an application.
If hyper receives an error the connection will be closed without sending a
response. Thus axum requires those errors to be handled gracefully:
```rust
use axum::{
routing::get,
error_handling::HandleErrorLayer,
http::StatusCode,
BoxError,
Router,
};
use tower::{ServiceBuilder, timeout::TimeoutLayer};
use std::time::Duration;
async fn handler() {}
let app = Router::new()
.route("/", get(handler))
.layer(
ServiceBuilder::new()
// this middleware goes above `TimeoutLayer` because it will receive
// errors returned by `TimeoutLayer`
.layer(HandleErrorLayer::new(|_: BoxError| async {
StatusCode::REQUEST_TIMEOUT
}))
.layer(TimeoutLayer::new(Duration::from_secs(10)))
);
# let _: Router = app;
```
See [`error_handling`](crate::error_handling) for more details on axum's error
handling model.
# Routing to services/middleware 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 won't 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][tower::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);
# let _: Router = app;
```
However when applying middleware around your whole application in this way
you have to take care that errors are still being handled 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.
# Accessing state in middleware
How to make state available to middleware depends on how the middleware is
written.
## Accessing state in `axum::middleware::from_fn`
Use [`axum::middleware::from_fn_with_state`](crate::middleware::from_fn_with_state).
## Accessing state in custom `tower::Layer`s
```rust
use axum::{
Router,
routing::get,
middleware::{self, Next},
response::Response,
extract::{State, Request},
};
use tower::{Layer, Service};
use std::task::{Context, Poll};
#[derive(Clone)]
struct AppState {}
#[derive(Clone)]
struct MyLayer {
state: AppState,
}
impl<S> Layer<S> for MyLayer {
type Service = MyService<S>;
fn layer(&self, inner: S) -> Self::Service {
MyService {
inner,
state: self.state.clone(),
}
}
}
#[derive(Clone)]
struct MyService<S> {
inner: S,
state: AppState,
}
impl<S, B> Service<Request<B>> for MyService<S>
where
S: Service<Request<B>>,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request<B>) -> Self::Future {
// Do something with `self.state`.
//
// See `axum::RequestExt` for how to run extractors directly from
// a `Request`.
self.inner.call(req)
}
}
async fn handler(_: State<AppState>) {}
let state = AppState {};
let app = Router::new()
.route("/", get(handler))
.layer(MyLayer { state: state.clone() })
.with_state(state);
# let _: axum::Router = app;
```
# Passing state from middleware to handlers
State can be passed from middleware to handlers using [request extensions]:
```rust
use axum::{
Router,
http::StatusCode,
routing::get,
response::{IntoResponse, Response},
middleware::{self, Next},
extract::{Request, Extension},
};
#[derive(Clone)]
struct CurrentUser { /* ... */ }
async fn auth(mut req: Request, next: Next) -> Result<Response, StatusCode> {
let auth_header = req.headers()
.get(http::header::AUTHORIZATION)
.and_then(|header| header.to_str().ok());
let auth_header = if let Some(auth_header) = auth_header {
auth_header
} else {
return Err(StatusCode::UNAUTHORIZED);
};
if let Some(current_user) = authorize_current_user(auth_header).await {
// insert the current user into a request extension so the handler can
// extract it
req.extensions_mut().insert(current_user);
Ok(next.run(req).await)
} else {
Err(StatusCode::UNAUTHORIZED)
}
}
async fn authorize_current_user(auth_token: &str) -> Option<CurrentUser> {
// ...
# unimplemented!()
}
async fn handler(
// extract the current user, set by the middleware
Extension(current_user): Extension<CurrentUser>,
) {
// ...
}
let app = Router::new()
.route("/", get(handler))
.route_layer(middleware::from_fn(auth));
# let _: Router = app;
```
[Response extensions] can also be used but note that request extensions are not
automatically moved to response extensions. You need to manually do that for the
extensions you need.
# Rewriting request URI in middleware
Middleware added with [`Router::layer`] will run after routing. That means it
cannot be used to run middleware that rewrites the request URI. By the time the
middleware runs the routing is already done.
The workaround is to wrap the middleware around the entire `Router` (this works
because `Router` implements [`Service`]):
```rust
use tower::Layer;
use axum::{
Router,
ServiceExt, // for `into_make_service`
response::Response,
middleware::Next,
extract::Request,
};
fn rewrite_request_uri<B>(req: Request<B>) -> Request<B> {
// ...
# req
}
// this can be any `tower::Layer`
let middleware = tower::util::MapRequestLayer::new(rewrite_request_uri);
let app = Router::new();
// apply the layer around the whole `Router`
// this way the middleware will run before `Router` receives the request
let app_with_middleware = middleware.layer(app);
# async {
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app_with_middleware.into_make_service()).await.unwrap();
# };
```
[`tower`]: https://crates.io/crates/tower
[`tower-http`]: https://crates.io/crates/tower-http
[tower-guides]: https://github.com/tower-rs/tower/tree/master/guides
[`axum::middleware::from_fn`]: fn@crate::middleware::from_fn
[`middleware::from_fn`]: fn@crate::middleware::from_fn
[tower-from-scratch-guide]: https://github.com/tower-rs/tower/blob/master/guides/building-a-middleware-from-scratch.md
[`ServiceBuilder::map_request`]: tower::ServiceBuilder::map_request
[`ServiceBuilder::map_response`]: tower::ServiceBuilder::map_response
[`ServiceBuilder::then`]: tower::ServiceBuilder::then
[`ServiceBuilder::and_then`]: tower::ServiceBuilder::and_then
[`axum::middleware::from_extractor`]: fn@crate::middleware::from_extractor
[`Handler::layer`]: crate::handler::Handler::layer
[`Router::layer`]: crate::routing::Router::layer
[`MethodRouter::layer`]: crate::routing::MethodRouter::layer
[`Router::route_layer`]: crate::routing::Router::route_layer
[`MethodRouter::route_layer`]: crate::routing::MethodRouter::route_layer
[request extensions]: https://docs.rs/http/latest/http/request/struct.Request.html#method.extensions
[Response extensions]: https://docs.rs/http/latest/http/response/struct.Response.html#method.extensions
[`State`]: crate::extract::State
[`Service`]: tower::Service
+321
View File
@@ -0,0 +1,321 @@
Types and traits for generating responses.
# Building responses
Anything that implements [`IntoResponse`] can be returned from a handler. axum
provides implementations for common types:
```rust,no_run
use axum::{
Json,
response::{Html, IntoResponse},
http::{StatusCode, Uri, header::{self, HeaderMap, HeaderName}},
};
// `()` gives an empty response
async fn empty() {}
// String will get a `text/plain; charset=utf-8` content-type
async fn plain_text(uri: Uri) -> String {
format!("Hi from {}", uri.path())
}
// Bytes will get a `application/octet-stream` content-type
async fn bytes() -> Vec<u8> {
vec![1, 2, 3, 4]
}
// `Json` will get a `application/json` content-type and work with anything that
// implements `serde::Serialize`
async fn json() -> Json<Vec<String>> {
Json(vec!["foo".to_owned(), "bar".to_owned()])
}
// `Html` will get a `text/html` content-type
async fn html() -> Html<&'static str> {
Html("<p>Hello, World!</p>")
}
// `StatusCode` gives an empty response with that status code
async fn status() -> StatusCode {
StatusCode::NOT_FOUND
}
// `HeaderMap` gives an empty response with some headers
async fn headers() -> HeaderMap {
let mut headers = HeaderMap::new();
headers.insert(header::SERVER, "axum".parse().unwrap());
headers
}
// An array of tuples also gives headers
async fn array_headers() -> [(HeaderName, &'static str); 2] {
[
(header::SERVER, "axum"),
(header::CONTENT_TYPE, "text/plain")
]
}
// Use `impl IntoResponse` to avoid writing the whole type
async fn impl_trait() -> impl IntoResponse {
[
(header::SERVER, "axum"),
(header::CONTENT_TYPE, "text/plain")
]
}
```
Additionally you can return tuples to build more complex responses from
individual parts.
```rust,no_run
use axum::{
Json,
response::IntoResponse,
http::{StatusCode, HeaderMap, Uri, header},
extract::Extension,
};
// `(StatusCode, impl IntoResponse)` will override the status code of the response
async fn with_status(uri: Uri) -> (StatusCode, String) {
(StatusCode::NOT_FOUND, format!("Not Found: {}", uri.path()))
}
// Use `impl IntoResponse` to avoid having to type the whole type
async fn impl_trait(uri: Uri) -> impl IntoResponse {
(StatusCode::NOT_FOUND, format!("Not Found: {}", uri.path()))
}
// `(HeaderMap, impl IntoResponse)` to add additional headers
async fn with_headers() -> impl IntoResponse {
let mut headers = HeaderMap::new();
headers.insert(header::CONTENT_TYPE, "text/plain".parse().unwrap());
(headers, "foo")
}
// Or an array of tuples to more easily build the headers
async fn with_array_headers() -> impl IntoResponse {
([(header::CONTENT_TYPE, "text/plain")], "foo")
}
// Use string keys for custom headers
async fn with_array_headers_custom() -> impl IntoResponse {
([("x-custom", "custom")], "foo")
}
// `(StatusCode, headers, impl IntoResponse)` to set status and add headers
// `headers` can be either a `HeaderMap` or an array of tuples
async fn with_status_and_array_headers() -> impl IntoResponse {
(
StatusCode::NOT_FOUND,
[(header::CONTENT_TYPE, "text/plain")],
"foo",
)
}
// `(Extension<_>, impl IntoResponse)` to set response extensions
async fn with_status_extensions() -> impl IntoResponse {
(
Extension(Foo("foo")),
"foo",
)
}
#[derive(Clone)]
struct Foo(&'static str);
// Or mix and match all the things
async fn all_the_things(uri: Uri) -> impl IntoResponse {
let mut header_map = HeaderMap::new();
if uri.path() == "/" {
header_map.insert(header::SERVER, "axum".parse().unwrap());
}
(
// set status code
StatusCode::NOT_FOUND,
// headers with an array
[("x-custom", "custom")],
// some extensions
Extension(Foo("foo")),
Extension(Foo("bar")),
// more headers, built dynamically
header_map,
// and finally the body
"foo",
)
}
```
In general you can return tuples like:
- `(StatusCode, impl IntoResponse)`
- `(Parts, impl IntoResponse)`
- `(Response<()>, impl IntoResponse)`
- `(T1, .., Tn, impl IntoResponse)` where `T1` to `Tn` all implement [`IntoResponseParts`].
- `(StatusCode, T1, .., Tn, impl IntoResponse)` where `T1` to `Tn` all implement [`IntoResponseParts`].
- `(Parts, T1, .., Tn, impl IntoResponse)` where `T1` to `Tn` all implement [`IntoResponseParts`].
- `(Response<()>, T1, .., Tn, impl IntoResponse)` where `T1` to `Tn` all implement [`IntoResponseParts`].
This means you cannot accidentally override the status or body as [`IntoResponseParts`] only allows
setting headers and extensions.
Use [`Response`] for more low level control:
```rust,no_run
use axum::{
Json,
response::{IntoResponse, Response},
body::Body,
http::StatusCode,
};
async fn response() -> Response {
Response::builder()
.status(StatusCode::NOT_FOUND)
.header("x-foo", "custom header")
.body(Body::from("not found"))
.unwrap()
}
```
# Returning different response types
If you need to return multiple response types, and `Result<T, E>` isn't appropriate, you can call
`.into_response()` to turn things into `axum::response::Response`:
```rust
use axum::{
response::{IntoResponse, Redirect, Response},
http::StatusCode,
};
async fn handle() -> Response {
if something() {
"All good!".into_response()
} else if something_else() {
(
StatusCode::INTERNAL_SERVER_ERROR,
"Something went wrong...",
).into_response()
} else {
Redirect::to("/").into_response()
}
}
fn something() -> bool {
// ...
# true
}
fn something_else() -> bool {
// ...
# true
}
```
# Regarding `impl IntoResponse`
You can use `impl IntoResponse` as the return type from handlers to avoid
typing large types. For example
```rust
use axum::http::StatusCode;
async fn handler() -> (StatusCode, [(&'static str, &'static str); 1], &'static str) {
(StatusCode::OK, [("x-foo", "bar")], "Hello, World!")
}
```
Becomes easier using `impl IntoResponse`:
```rust
use axum::{http::StatusCode, response::IntoResponse};
async fn impl_into_response() -> impl IntoResponse {
(StatusCode::OK, [("x-foo", "bar")], "Hello, World!")
}
```
However `impl IntoResponse` has a few limitations. Firstly it can only be used
to return a single type:
```rust,compile_fail
use axum::{http::StatusCode, response::IntoResponse};
async fn handler() -> impl IntoResponse {
if check_something() {
StatusCode::NOT_FOUND
} else {
"Hello, World!"
}
}
fn check_something() -> bool {
# false
// ...
}
```
This function returns either a `StatusCode` or a `&'static str` which `impl
Trait` doesn't allow.
Secondly `impl IntoResponse` can lead to type inference issues when used with
`Result` and `?`:
```rust,compile_fail
use axum::{http::StatusCode, response::IntoResponse};
async fn handler() -> impl IntoResponse {
create_thing()?;
Ok(StatusCode::CREATED)
}
fn create_thing() -> Result<(), StatusCode> {
# Ok(())
// ...
}
```
This is because `?` supports using the [`From`] trait to convert to a different
error type but it doesn't know which type to convert to, because we only
specified `impl IntoResponse` as the return type.
`Result<impl IntoResponse, impl IntoResponse>` doesn't always work either:
```rust,compile_fail
use axum::{http::StatusCode, response::IntoResponse};
async fn handler() -> Result<impl IntoResponse, impl IntoResponse> {
create_thing()?;
Ok(StatusCode::CREATED)
}
fn create_thing() -> Result<(), StatusCode> {
# Ok(())
// ...
}
```
The solution is to use a concrete error type, such as `Result<impl IntoResponse, StatusCode>`:
```rust
use axum::{http::StatusCode, response::IntoResponse};
async fn handler() -> Result<impl IntoResponse, StatusCode> {
create_thing()?;
Ok(StatusCode::CREATED)
}
fn create_thing() -> Result<(), StatusCode> {
# Ok(())
// ...
}
```
Because of this it is generally not recommended to use `impl IntoResponse`
unless you're familiar with the details of how `impl Trait` works.
[`IntoResponse`]: crate::response::IntoResponse
[`IntoResponseParts`]: crate::response::IntoResponseParts
[`StatusCode`]: http::StatusCode
+61
View File
@@ -0,0 +1,61 @@
Add a fallback [`Handler`] 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, Uri},
};
let app = Router::new()
.route("/foo", get(|| async { /* ... */ }))
.fallback(fallback);
async fn fallback(uri: Uri) -> (StatusCode, String) {
(StatusCode::NOT_FOUND, format!("No route for {uri}"))
}
# let _: Router = app;
```
Fallbacks only apply to routes that aren't matched by anything in the
router. If a handler is matched by a request but returns 404 the
fallback is not called. Note that this applies to [`MethodRouter`]s too: if the
request hits a valid path but the [`MethodRouter`] does not have an appropriate
method handler installed, the fallback is not called (use
[`MethodRouter::fallback`] for this purpose instead).
# Handling all requests without other routes
Using `Router::new().fallback(...)` to accept all request regardless of path or
method, if you don't have other routes, isn't optimal:
```rust
use axum::Router;
async fn handler() {}
let app = Router::new().fallback(handler);
# async {
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
# };
```
Running the handler directly is faster since it avoids the overhead of routing:
```rust
use axum::handler::HandlerWithoutStateExt;
async fn handler() {}
# async {
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, handler.into_make_service()).await.unwrap();
# };
```
@@ -0,0 +1,73 @@
Convert this router into a [`MakeService`], that will store `C`'s
associated `ConnectInfo` in a request extension such that [`ConnectInfo`]
can extract it.
This enables extracting things like the client's remote address.
Extracting [`std::net::SocketAddr`] is supported out of the box:
```rust
use axum::{
extract::ConnectInfo,
routing::get,
Router,
};
use std::net::SocketAddr;
let app = Router::new().route("/", get(handler));
async fn handler(ConnectInfo(addr): ConnectInfo<SocketAddr>) -> String {
format!("Hello {addr}")
}
# async {
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>()).await.unwrap();
# };
```
You can implement custom a [`Connected`] like so:
```rust
use axum::{
extract::connect_info::{ConnectInfo, Connected},
routing::get,
serve::IncomingStream,
Router,
};
use tokio::net::TcpListener;
let app = Router::new().route("/", get(handler));
async fn handler(
ConnectInfo(my_connect_info): ConnectInfo<MyConnectInfo>,
) -> String {
format!("Hello {my_connect_info:?}")
}
#[derive(Clone, Debug)]
struct MyConnectInfo {
// ...
}
impl Connected<IncomingStream<'_, TcpListener>> for MyConnectInfo {
fn connect_info(target: IncomingStream<'_, TcpListener>) -> Self {
MyConnectInfo {
// ...
}
}
}
# async {
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app.into_make_service_with_connect_info::<MyConnectInfo>()).await.unwrap();
# };
```
See the [unix domain socket example][uds] for an example of how to use
this to collect UDS connection info.
[`MakeService`]: tower::make::MakeService
[`Connected`]: crate::extract::connect_info::Connected
[`ConnectInfo`]: crate::extract::connect_info::ConnectInfo
[uds]: https://github.com/tokio-rs/axum/blob/main/examples/unix-domain-socket/src/main.rs
+67
View File
@@ -0,0 +1,67 @@
Apply a [`tower::Layer`] to all routes in the router.
This can be used to add additional processing to a request for a group
of routes.
Note that the middleware is only applied to existing routes. So you have to
first add your routes (and / or fallback) and then call `layer` afterwards. Additional
routes added after `layer` is called will not have the middleware added.
If you want to add middleware to a single handler you can either use
[`MethodRouter::layer`] or [`Handler::layer`].
# Example
Adding the [`tower_http::trace::TraceLayer`]:
```rust
use axum::{routing::get, Router};
use tower_http::trace::TraceLayer;
let app = Router::new()
.route("/foo", get(|| async {}))
.route("/bar", get(|| async {}))
.layer(TraceLayer::new_for_http());
# let _: Router = app;
```
If you need to write your own middleware see ["Writing
middleware"](crate::middleware#writing-middleware) for the different options.
If you only want middleware on some routes you can use [`Router::merge`]:
```rust
use axum::{routing::get, Router};
use tower_http::{trace::TraceLayer, compression::CompressionLayer};
let with_tracing = Router::new()
.route("/foo", get(|| async {}))
.layer(TraceLayer::new_for_http());
let with_compression = Router::new()
.route("/bar", get(|| async {}))
.layer(CompressionLayer::new());
// Merge everything into one `Router`
let app = Router::new()
.merge(with_tracing)
.merge(with_compression);
# let _: Router = app;
```
# Multiple middleware
It's recommended to use [`tower::ServiceBuilder`] when applying multiple
middleware. See [`middleware`](crate::middleware) for more details.
# Runs after routing
Middleware added with this method will run _after_ routing and thus cannot be
used to rewrite the request URI. See ["Rewriting request URI in
middleware"](crate::middleware#rewriting-request-uri-in-middleware) for more
details and a workaround.
# Error handling
See [`middleware`](crate::middleware) for details on how error handling impacts
middleware.
+80
View File
@@ -0,0 +1,80 @@
Merge the paths and fallbacks of two routers into a single [`Router`].
This is useful for breaking apps into smaller pieces and combining them
into one.
```rust
use axum::{
routing::get,
Router,
};
#
# async fn users_list() {}
# async fn users_show() {}
# async fn teams_list() {}
// define some routes separately
let user_routes = Router::new()
.route("/users", get(users_list))
.route("/users/{id}", get(users_show));
let team_routes = Router::new()
.route("/teams", get(teams_list));
// combine them into one
let app = Router::new()
.merge(user_routes)
.merge(team_routes);
// could also do `user_routes.merge(team_routes)`
// Our app now accepts
// - GET /users
// - GET /users/{id}
// - GET /teams
# let _: Router = app;
```
# Merging routers with state
When combining [`Router`]s with this method, each [`Router`] must have the
same type of state. If your routers have different types you can use
[`Router::with_state`] to provide the state and make the types match:
```rust
use axum::{
Router,
routing::get,
extract::State,
};
#[derive(Clone)]
struct InnerState {}
#[derive(Clone)]
struct OuterState {}
async fn inner_handler(state: State<InnerState>) {}
let inner_router = Router::new()
.route("/bar", get(inner_handler))
.with_state(InnerState {});
async fn outer_handler(state: State<OuterState>) {}
let app = Router::new()
.route("/", get(outer_handler))
.merge(inner_router)
.with_state(OuterState {});
# let _: axum::Router = app;
```
# Merging routers with fallbacks
When combining [`Router`]s with this method, the [fallback](Router::fallback) is also merged.
However only one of the routers can have a fallback.
# Panics
- If two routers that each have a [fallback](Router::fallback) are merged. This
is because `Router` only allows a single fallback.
@@ -0,0 +1,38 @@
Add a fallback [`Handler`] for the case where a route exists, but the method of the request is not supported.
Sets a fallback on all previously registered [`MethodRouter`]s,
to be called when no matching method handler is set.
```rust,no_run
use axum::{response::IntoResponse, routing::get, Router};
async fn hello_world() -> impl IntoResponse {
"Hello, world!\n"
}
async fn default_fallback() -> impl IntoResponse {
"Default fallback\n"
}
async fn handle_405() -> impl IntoResponse {
"Method not allowed fallback"
}
#[tokio::main]
async fn main() {
let router = Router::new()
.route("/", get(hello_world))
.fallback(default_fallback)
.method_not_allowed_fallback(handle_405);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, router).await.unwrap();
}
```
The fallback only applies if there is a `MethodRouter` registered for a given path,
but the method used in the request is not specified. In the example, a `GET` on
`http://localhost:3000` causes the `hello_world` handler to react, while issuing a
`POST` triggers `handle_405`. Calling an entirely different route, like `http://localhost:3000/hello`
causes `default_fallback` to run.
+194
View File
@@ -0,0 +1,194 @@
Nest a [`Router`] at some path.
This allows you to break your application into smaller pieces and compose
them together.
# Example
```rust
use axum::{
routing::{get, post},
Router,
};
let user_routes = Router::new().route("/{id}", get(|| async {}));
let team_routes = Router::new().route("/", post(|| async {}));
let api_routes = Router::new()
.nest("/users", user_routes)
.nest("/teams", team_routes);
let app = Router::new().nest("/api", api_routes);
// Our app now accepts
// - GET /api/users/{id}
// - POST /api/teams
# let _: Router = app;
```
# How the URI changes
Note that nested routes will not see the original request URI but instead
have the matched prefix stripped. This is necessary for services like static
file serving to work. Use [`OriginalUri`] if you need the original request
URI.
# Captures from outer routes
Take care when using `nest` together with dynamic routes as nesting also
captures from the outer routes:
```rust
use axum::{
extract::Path,
routing::get,
Router,
};
use std::collections::HashMap;
async fn users_get(Path(params): Path<HashMap<String, String>>) {
// Both `version` and `id` were captured even though `users_api` only
// explicitly captures `id`.
let version = params.get("version");
let id = params.get("id");
}
let users_api = Router::new().route("/users/{id}", get(users_get));
let app = Router::new().nest("/{version}/api", users_api);
# let _: Router = app;
```
# Differences from wildcard routes
Nested routes are similar to wildcard routes. The difference is that
wildcard routes still see the whole URI whereas nested routes will have
the prefix stripped:
```rust
use axum::{routing::get, http::Uri, Router};
let nested_router = Router::new()
.route("/", get(|uri: Uri| async {
// `uri` will _not_ contain `/bar`
}));
let app = Router::new()
.route("/foo/{*rest}", get(|uri: Uri| async {
// `uri` will contain `/foo`
}))
.nest("/bar", nested_router);
# let _: Router = app;
```
Additionally, while the wildcard route `/foo/*rest` will not match the
paths `/foo` or `/foo/`, a nested router at `/foo` will match the path `/foo`
(but not `/foo/`), and a nested router at `/foo/` will match the path `/foo/`
(but not `/foo`).
# Fallbacks
If a nested router doesn't have its own fallback then it will inherit the
fallback from the outer router:
```rust
use axum::{routing::get, http::StatusCode, handler::Handler, Router};
async fn fallback() -> (StatusCode, &'static str) {
(StatusCode::NOT_FOUND, "Not Found")
}
let api_routes = Router::new().route("/users", get(|| async {}));
let app = Router::new()
.nest("/api", api_routes)
.fallback(fallback);
# let _: Router = app;
```
Here requests like `GET /api/not-found` will go into `api_routes` but because
it doesn't have a matching route and doesn't have its own fallback it will call
the fallback from the outer router, i.e. the `fallback` function.
If the nested router has its own fallback then the outer fallback will not be
inherited:
```rust
use axum::{
routing::get,
http::StatusCode,
handler::Handler,
Json,
Router,
};
async fn fallback() -> (StatusCode, &'static str) {
(StatusCode::NOT_FOUND, "Not Found")
}
async fn api_fallback() -> (StatusCode, Json<serde_json::Value>) {
(
StatusCode::NOT_FOUND,
Json(serde_json::json!({ "status": "Not Found" })),
)
}
let api_routes = Router::new()
.route("/users", get(|| async {}))
.fallback(api_fallback);
let app = Router::new()
.nest("/api", api_routes)
.fallback(fallback);
# let _: Router = app;
```
Here requests like `GET /api/not-found` will go to `api_fallback`.
# Nesting routers with state
When combining [`Router`]s with this method, each [`Router`] must have the
same type of state. If your routers have different types you can use
[`Router::with_state`] to provide the state and make the types match:
```rust
use axum::{
Router,
routing::get,
extract::State,
};
#[derive(Clone)]
struct InnerState {}
#[derive(Clone)]
struct OuterState {}
async fn inner_handler(state: State<InnerState>) {}
let inner_router = Router::new()
.route("/bar", get(inner_handler))
.with_state(InnerState {});
async fn outer_handler(state: State<OuterState>) {}
let app = Router::new()
.route("/", get(outer_handler))
.nest("/foo", inner_router)
.with_state(OuterState {});
# let _: axum::Router = app;
```
Note that the inner router will still inherit the fallback from the outer
router.
# Panics
- If the route overlaps with another route. See [`Router::route`]
for more details.
- If the route contains a wildcard (`*`).
- If `path` is empty.
[`OriginalUri`]: crate::extract::OriginalUri
[fallbacks]: Router::fallback
+157
View File
@@ -0,0 +1,157 @@
Add another route to the router.
`path` is a string of path segments separated by `/`. Each segment
can be either static, a capture, or a wildcard.
`method_router` is the [`MethodRouter`] that should receive the request if the
path matches `path`. Usually, `method_router` will be a handler wrapped in a method
router like [`get`]. See [`handler`](crate::handler) for more details on handlers.
# Static paths
Examples:
- `/`
- `/foo`
- `/users/123`
If the incoming request matches the path exactly the corresponding service will
be called.
# Captures
Paths can contain segments like `/{key}` which matches any single segment and
will store the value captured at `key`. The value captured can be zero-length
except for in the invalid path `//`.
Examples:
- `/{key}`
- `/users/{id}`
- `/users/{id}/tweets`
Captures can be extracted using [`Path`](crate::extract::Path). See its
documentation for more details.
It is not possible to create segments that only match some types like numbers or
regular expression. You must handle that manually in your handlers.
[`MatchedPath`](crate::extract::MatchedPath) can be used to extract the matched
path rather than the actual path.
# Wildcards
Paths can end in `/{*key}` which matches all segments and will store the segments
captured at `key`.
Examples:
- `/{*key}`
- `/assets/{*path}`
- `/{id}/{repo}/{*tree}`
Note that `/{*key}` doesn't match empty segments. Thus:
- `/{*key}` doesn't match `/` but does match `/a`, `/a/`, etc.
- `/x/{*key}` doesn't match `/x` or `/x/` but does match `/x/a`, `/x/a/`, etc.
Wildcard captures can also be extracted using [`Path`](crate::extract::Path):
```rust
use axum::{
Router,
routing::get,
extract::Path,
};
let app: Router = Router::new().route("/{*key}", get(handler));
async fn handler(Path(path): Path<String>) -> String {
path
}
```
Note that the leading slash is not included, i.e. for the route `/foo/{*rest}` and
the path `/foo/bar/baz` the value of `rest` will be `bar/baz`.
# Accepting multiple methods
To accept multiple methods for the same route you can add all handlers at the
same time:
```rust
use axum::{Router, routing::{get, delete}, extract::Path};
let app = Router::new().route(
"/",
get(get_root).post(post_root).delete(delete_root),
);
async fn get_root() {}
async fn post_root() {}
async fn delete_root() {}
# let _: Router = app;
```
Or you can add them one by one:
```rust
# use axum::Router;
# use axum::routing::{get, post, delete};
#
let app = Router::new()
.route("/", get(get_root))
.route("/", post(post_root))
.route("/", delete(delete_root));
#
# let _: Router = app;
# async fn get_root() {}
# async fn post_root() {}
# async fn delete_root() {}
```
# More examples
```rust
use axum::{Router, routing::{get, delete}, extract::Path};
let app = Router::new()
.route("/", get(root))
.route("/users", get(list_users).post(create_user))
.route("/users/{id}", get(show_user))
.route("/api/{version}/users/{id}/action", delete(do_users_action))
.route("/assets/{*path}", get(serve_asset));
async fn root() {}
async fn list_users() {}
async fn create_user() {}
async fn show_user(Path(id): Path<u64>) {}
async fn do_users_action(Path((version, id)): Path<(String, u64)>) {}
async fn serve_asset(Path(path): Path<String>) {}
# let _: Router = app;
```
# Panics
Panics if the route overlaps with another route:
```rust,should_panic
use axum::{routing::get, Router};
let app = Router::new()
.route("/", get(|| async {}))
.route("/", get(|| async {}));
# let _: Router = app;
```
The static route `/foo` and the dynamic route `/{key}` are not considered to
overlap and `/foo` will take precedence.
Also panics if `path` is empty.
@@ -0,0 +1,35 @@
Apply a [`tower::Layer`] to the router that will only run if the request matches
a route.
Note that the middleware is only applied to existing routes. So you have to
first add your routes (and / or fallback) and then call `route_layer`
afterwards. Additional routes added after `route_layer` is called will not have
the middleware added.
This works similarly to [`Router::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 `404 Not Found` into a
`401 Unauthorized`.
This function will panic if no routes have been declared yet on the router,
since the new layer will have no effect, and this is typically a bug.
In generic code, you can test if that is the case first, by calling [`Router::has_routes`].
# Example
```rust
use axum::{
routing::get,
Router,
};
use tower_http::validate_request::ValidateRequestHeaderLayer;
let app = Router::new()
.route("/foo", get(|| async {}))
.route_layer(ValidateRequestHeaderLayer::bearer("password"));
// `GET /foo` with a valid token will receive `200 OK`
// `GET /foo` with a invalid token will receive `401 Unauthorized`
// `GET /not-found` with a invalid token will receive `404 Not Found`
# let _: Router = app;
```
@@ -0,0 +1,70 @@
Add another route to the router that calls a [`Service`].
# Example
```rust,no_run
use axum::{
Router,
body::Body,
routing::{any_service, get_service},
extract::Request,
http::StatusCode,
error_handling::HandleErrorLayer,
};
use tower_http::services::ServeFile;
use http::Response;
use std::{convert::Infallible, io};
use tower::service_fn;
let app = Router::new()
.route(
// Any request to `/` goes to a service
"/",
// Services whose response body is not `axum::body::BoxBody`
// can be wrapped in `axum::routing::any_service` (or one of the other routing filters)
// to have the response body mapped
any_service(service_fn(|_: Request| async {
let res = Response::new(Body::from("Hi from `GET /`"));
Ok::<_, Infallible>(res)
}))
)
.route_service(
"/foo",
// This service's response body is `axum::body::BoxBody` so
// it can be routed to directly.
service_fn(|req: Request| async move {
let body = Body::from(format!("Hi from `{} /foo`", req.method()));
let res = Response::new(body);
Ok::<_, Infallible>(res)
})
)
.route_service(
// GET `/static/Cargo.toml` goes to a service from tower-http
"/static/Cargo.toml",
ServeFile::new("Cargo.toml"),
);
# let _: Router = app;
```
Routing to arbitrary services in this way has complications for backpressure
([`Service::poll_ready`]). See the [Routing to services and backpressure] module
for more details.
# Panics
Panics for the same reasons as [`Router::route`] or if you attempt to route to a
`Router`:
```rust,should_panic
use axum::{routing::get, Router};
let app = Router::new().route_service(
"/",
Router::new().route("/foo", get(|| async {})),
);
# let _: Router = app;
```
Use [`Router::nest`] instead.
[Routing to services and backpressure]: middleware/index.html#routing-to-servicesmiddleware-and-backpressure
+238
View File
@@ -0,0 +1,238 @@
Provide the state for the router. State passed to this method is global and will be used
for all requests this router receives. That means it is not suitable for holding state derived from a request, such as authorization data extracted in a middleware. Use [`Extension`] instead for such data.
See ["Sharing state with handlers"][sharing-state] for an overview of state patterns,
including when to use `Arc`, how to extract substates with [`FromRef`], and what the
`Router<S>` type parameter means.
[sharing-state]: crate#sharing-state-with-handlers
[`FromRef`]: crate::extract::FromRef
```rust
use axum::{Router, routing::get, extract::State};
#[derive(Clone)]
struct AppState {}
let routes = Router::new()
.route("/", get(|State(state): State<AppState>| async {
// use state
}))
.with_state(AppState {});
# async {
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, routes).await.unwrap();
# };
```
# Returning routers with states from functions
When returning `Router`s from functions, it is generally recommended not to set the
state directly:
```rust
use axum::{Router, routing::get, extract::State};
#[derive(Clone)]
struct AppState {}
// Don't call `Router::with_state` here
fn routes() -> Router<AppState> {
Router::new()
.route("/", get(|_: State<AppState>| async {}))
}
// Instead do it before you run the server
let routes = routes().with_state(AppState {});
# async {
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, routes).await.unwrap();
# };
```
If you do need to provide the state, and you're _not_ nesting/merging the router
into another router, then return `Router` without any type parameters:
```rust
# use axum::{Router, routing::get, extract::State};
# #[derive(Clone)]
# struct AppState {}
#
// Don't return `Router<AppState>`
fn routes(state: AppState) -> Router {
Router::new()
.route("/", get(|_: State<AppState>| async {}))
.with_state(state)
}
let routes = routes(AppState {});
# async {
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, routes).await.unwrap();
# };
```
This is because we can only call `Router::into_make_service` on `Router<()>`,
not `Router<AppState>`. See below for more details about why that is.
Note that the state defaults to `()` so `Router` and `Router<()>` is the same.
If you are nesting/merging the router it is recommended to use a generic state
type on the resulting router:
```rust
# use axum::{Router, routing::get, extract::State};
# #[derive(Clone)]
# struct AppState {}
#
fn routes<S>(state: AppState) -> Router<S> {
Router::new()
.route("/", get(|_: State<AppState>| async {}))
.with_state(state)
}
let routes = Router::new().nest("/api", routes(AppState {}));
# async {
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, routes).await.unwrap();
# };
```
# What `S` in `Router<S>` means
`Router<S>` means a router that is _missing_ a state of type `S` to be able to
handle requests. It does _not_ mean a `Router` that _has_ a state of type `S`.
For example:
```rust
# use axum::{Router, routing::get, extract::State};
# #[derive(Clone)]
# struct AppState {}
#
// A router that _needs_ an `AppState` to handle requests
let router: Router<AppState> = Router::new()
.route("/", get(|_: State<AppState>| async {}));
// Once we call `Router::with_state` the router isn't missing
// the state anymore, because we just provided it
//
// Therefore the router type becomes `Router<()>`, i.e a router
// that is not missing any state
let router: Router<()> = router.with_state(AppState {});
// Only `Router<()>` has the `into_make_service` method.
//
// You cannot call `into_make_service` on a `Router<AppState>`
// because it is still missing an `AppState`.
# async {
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, router).await.unwrap();
# };
```
Perhaps a little counter intuitively, `Router::with_state` doesn't always return a
`Router<()>`. Instead you get to pick what the new missing state type is:
```rust
# use axum::{Router, routing::get, extract::State};
# #[derive(Clone)]
# struct AppState {}
#
let router: Router<AppState> = Router::new()
.route("/", get(|_: State<AppState>| async {}));
// When we call `with_state` we're able to pick what the next missing state type is.
// Here we pick `String`.
let string_router: Router<String> = router.with_state(AppState {});
// That allows us to add new routes that uses `String` as the state type
let string_router = string_router
.route("/needs-string", get(|_: State<String>| async {}));
// Provide the `String` and choose `()` as the new missing state.
let final_router: Router<()> = string_router.with_state("foo".to_owned());
// Since we have a `Router<()>` we can run it.
# async {
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, final_router).await.unwrap();
# };
```
This is why returning `Router<AppState>` after calling `with_state` doesn't
work:
```rust,compile_fail
# use axum::{Router, routing::get, extract::State};
# #[derive(Clone)]
# struct AppState {}
#
// This won't work because we're returning a `Router<AppState>`
// i.e. we're saying we're still missing an `AppState`
fn routes(state: AppState) -> Router<AppState> {
Router::new()
.route("/", get(|_: State<AppState>| async {}))
.with_state(state)
}
let app = routes(AppState {});
// We can only call `Router::into_make_service` on a `Router<()>`
// but `app` is a `Router<AppState>`
# async {
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
# };
```
Instead return `Router<()>` since we have provided all the state needed:
```rust
# use axum::{Router, routing::get, extract::State};
# #[derive(Clone)]
# struct AppState {}
#
// We've provided all the state necessary so return `Router<()>`
fn routes(state: AppState) -> Router<()> {
Router::new()
.route("/", get(|_: State<AppState>| async {}))
.with_state(state)
}
let app = routes(AppState {});
// We can now call `Router::into_make_service`
# async {
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
# };
```
# A note about performance
If you need a `Router` that implements `Service` but you don't need any state (perhaps
you're making a library that uses axum internally) then it is recommended to call this
method before you start serving requests:
```rust
use axum::{Router, routing::get};
let app = Router::new()
.route("/", get(|| async { /* ... */ }))
// even though we don't need any state, call `with_state(())` anyway
.with_state(());
# let _: Router = app;
```
This is not required but it gives axum a chance to update some internals in the router
which may impact performance and reduce allocations.
Note that [`Router::into_make_service`] and [`Router::into_make_service_with_connect_info`]
do this automatically.
[`Extension`]: crate::Extension
@@ -0,0 +1,43 @@
Turn off checks for compatibility with route matching syntax from 0.7.
This allows usage of paths starting with a colon `:` or an asterisk `*` which are otherwise prohibited.
# Example
```rust
use axum::{
routing::get,
Router,
};
let app = Router::<()>::new()
.without_v07_checks()
.route("/:colon", get(|| async {}))
.route("/*asterisk", get(|| async {}));
// Our app now accepts
// - GET /:colon
// - GET /*asterisk
# let _: Router = app;
```
Adding such routes without calling this method first will panic.
```rust,should_panic
use axum::{
routing::get,
Router,
};
// This panics...
let app = Router::<()>::new()
.route("/:colon", get(|| async {}));
```
# Merging
When two routers are merged, v0.7 checks are disabled for route registrations on the resulting router if both of the two routers had them also disabled.
# Nesting
Each router needs to have the checks explicitly disabled. Nesting a router with the checks either enabled or disabled has no effect on the outer router.
+262
View File
@@ -0,0 +1,262 @@
#![doc = include_str!("../docs/error_handling.md")]
use crate::{
extract::FromRequestParts,
http::Request,
response::{IntoResponse, Response},
};
use std::{
convert::Infallible,
fmt,
future::Future,
marker::PhantomData,
task::{Context, Poll},
};
use tower::ServiceExt;
use tower_layer::Layer;
use tower_service::Service;
/// [`Layer`] that applies [`HandleError`] which is a [`Service`] adapter
/// that handles errors by converting them into responses.
///
/// See [module docs](self) for more details on axum's error handling model.
pub struct HandleErrorLayer<F, T> {
f: F,
_extractor: PhantomData<fn() -> T>,
}
impl<F, T> HandleErrorLayer<F, T> {
/// Create a new `HandleErrorLayer`.
pub fn new(f: F) -> Self {
Self {
f,
_extractor: PhantomData,
}
}
}
impl<F, T> Clone for HandleErrorLayer<F, T>
where
F: Clone,
{
fn clone(&self) -> Self {
Self {
f: self.f.clone(),
_extractor: PhantomData,
}
}
}
impl<F, E> fmt::Debug for HandleErrorLayer<F, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HandleErrorLayer")
.field("f", &format_args!("{}", std::any::type_name::<F>()))
.finish()
}
}
impl<S, F, T> Layer<S> for HandleErrorLayer<F, T>
where
F: Clone,
{
type Service = HandleError<S, F, T>;
fn layer(&self, inner: S) -> Self::Service {
HandleError::new(inner, self.f.clone())
}
}
/// A [`Service`] adapter that handles errors by converting them into responses.
///
/// See [module docs](self) for more details on axum's error handling model.
pub struct HandleError<S, F, T> {
inner: S,
f: F,
_extractor: PhantomData<fn() -> T>,
}
impl<S, F, T> HandleError<S, F, T> {
/// Create a new `HandleError`.
pub fn new(inner: S, f: F) -> Self {
Self {
inner,
f,
_extractor: PhantomData,
}
}
}
impl<S, F, T> Clone for HandleError<S, F, T>
where
S: Clone,
F: Clone,
{
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
f: self.f.clone(),
_extractor: PhantomData,
}
}
}
impl<S, F, E> fmt::Debug for HandleError<S, F, E>
where
S: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HandleError")
.field("inner", &self.inner)
.field("f", &format_args!("{}", std::any::type_name::<F>()))
.finish()
}
}
impl<S, F, B, Fut, Res> Service<Request<B>> for HandleError<S, F, ()>
where
S: Service<Request<B>> + Clone + Send + 'static,
S::Response: IntoResponse + Send,
S::Error: Send,
S::Future: Send,
F: FnOnce(S::Error) -> Fut + Clone + Send + 'static,
Fut: Future<Output = Res> + Send,
Res: IntoResponse,
B: Send + 'static,
{
type Response = Response;
type Error = Infallible;
type Future = future::HandleErrorFuture;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<B>) -> Self::Future {
let f = self.f.clone();
let clone = self.inner.clone();
let inner = std::mem::replace(&mut self.inner, clone);
let future = Box::pin(async move {
match inner.oneshot(req).await {
Ok(res) => Ok(res.into_response()),
Err(err) => Ok(f(err).await.into_response()),
}
});
future::HandleErrorFuture { future }
}
}
#[allow(unused_macros)]
macro_rules! impl_service {
( $($ty:ident),* $(,)? ) => {
impl<S, F, B, Res, Fut, $($ty,)*> Service<Request<B>>
for HandleError<S, F, ($($ty,)*)>
where
S: Service<Request<B>> + Clone + Send + 'static,
S::Response: IntoResponse + Send,
S::Error: Send,
S::Future: Send,
F: FnOnce($($ty),*, S::Error) -> Fut + Clone + Send + 'static,
Fut: Future<Output = Res> + Send,
Res: IntoResponse,
$( $ty: FromRequestParts<()> + Send,)*
B: Send + 'static,
{
type Response = Response;
type Error = Infallible;
type Future = future::HandleErrorFuture;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
#[allow(non_snake_case)]
fn call(&mut self, req: Request<B>) -> Self::Future {
let f = self.f.clone();
let clone = self.inner.clone();
let inner = std::mem::replace(&mut self.inner, clone);
let (mut parts, body) = req.into_parts();
let future = Box::pin(async move {
$(
let $ty = match $ty::from_request_parts(&mut parts, &()).await {
Ok(value) => value,
Err(rejection) => return Ok(rejection.into_response()),
};
)*
let req = Request::from_parts(parts, body);
match inner.oneshot(req).await {
Ok(res) => Ok(res.into_response()),
Err(err) => Ok(f($($ty),*, err).await.into_response()),
}
});
future::HandleErrorFuture { future }
}
}
}
}
impl_service!(T1);
impl_service!(T1, T2);
impl_service!(T1, T2, T3);
impl_service!(T1, T2, T3, T4);
impl_service!(T1, T2, T3, T4, T5);
impl_service!(T1, T2, T3, T4, T5, T6);
impl_service!(T1, T2, T3, T4, T5, T6, T7);
impl_service!(T1, T2, T3, T4, T5, T6, T7, T8);
impl_service!(T1, T2, T3, T4, T5, T6, T7, T8, T9);
impl_service!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
impl_service!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
impl_service!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
impl_service!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
impl_service!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14);
impl_service!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15);
impl_service!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16);
pub mod future {
//! Future types.
use crate::response::Response;
use pin_project_lite::pin_project;
use std::{
convert::Infallible,
future::Future,
pin::Pin,
task::{Context, Poll},
};
pin_project! {
/// Response future for [`HandleError`].
pub struct HandleErrorFuture {
#[pin]
pub(super) future: Pin<Box<dyn Future<Output = Result<Response, Infallible>>
+ Send
+ 'static
>>,
}
}
impl Future for HandleErrorFuture {
type Output = Result<Response, Infallible>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.project().future.poll(cx)
}
}
}
#[test]
fn traits() {
use crate::test_helpers::*;
assert_send::<HandleError<(), (), NotSendSync>>();
assert_sync::<HandleError<(), (), NotSendSync>>();
}
+246
View File
@@ -0,0 +1,246 @@
use crate::{extract::rejection::*, response::IntoResponseParts};
use axum_core::extract::OptionalFromRequestParts;
use axum_core::{
extract::FromRequestParts,
response::{IntoResponse, Response, ResponseParts},
};
use http::{request::Parts, Extensions, Request};
use std::{
convert::Infallible,
task::{Context, Poll},
};
use tower_service::Service;
/// Extractor and response for extensions.
///
/// # As extractor
///
/// This is commonly used to share state across handlers.
///
/// ```rust,no_run
/// use axum::{
/// Router,
/// Extension,
/// routing::get,
/// };
/// use std::sync::Arc;
///
/// // Some shared state used throughout our application
/// struct State {
/// // ...
/// }
///
/// async fn handler(state: Extension<Arc<State>>) {
/// // ...
/// }
///
/// let state = Arc::new(State { /* ... */ });
///
/// let app = Router::new().route("/", get(handler))
/// // Add middleware that inserts the state into all incoming request's
/// // extensions.
/// .layer(Extension(state));
/// # let _: Router = app;
/// ```
///
/// If the extension is missing it will reject the request with a `500 Internal
/// Server Error` response. Alternatively, you can use `Option<Extension<T>>` to
/// make the extension extractor optional.
///
/// # As response
///
/// Response extensions can be used to share state with middleware.
///
/// ```rust
/// use axum::{
/// Extension,
/// response::IntoResponse,
/// };
///
/// async fn handler() -> (Extension<Foo>, &'static str) {
/// (
/// Extension(Foo("foo")),
/// "Hello, World!"
/// )
/// }
///
/// #[derive(Clone)]
/// struct Foo(&'static str);
/// ```
#[derive(Debug, Clone, Copy, Default)]
#[must_use]
pub struct Extension<T>(pub T);
impl<T> Extension<T>
where
T: Clone + Send + Sync + 'static,
{
fn from_extensions(extensions: &Extensions) -> Option<Self> {
extensions.get::<T>().cloned().map(Extension)
}
}
impl<T, S> FromRequestParts<S> for Extension<T>
where
T: Clone + Send + Sync + 'static,
S: Send + Sync,
{
type Rejection = ExtensionRejection;
async fn from_request_parts(req: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
Ok(Self::from_extensions(&req.extensions).ok_or_else(|| {
MissingExtension::from_err(format!(
"Extension of type `{}` was not found. Perhaps you forgot to add it? See `axum::Extension`.",
std::any::type_name::<T>()
))
})?)
}
}
impl<T, S> OptionalFromRequestParts<S> for Extension<T>
where
T: Clone + Send + Sync + 'static,
S: Send + Sync,
{
type Rejection = Infallible;
async fn from_request_parts(
req: &mut Parts,
_state: &S,
) -> Result<Option<Self>, Self::Rejection> {
Ok(Self::from_extensions(&req.extensions))
}
}
axum_core::__impl_deref!(Extension);
impl<T> IntoResponseParts for Extension<T>
where
T: Clone + Send + Sync + 'static,
{
type Error = Infallible;
fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {
res.extensions_mut().insert(self.0);
Ok(res)
}
}
impl<T> IntoResponse for Extension<T>
where
T: Clone + Send + Sync + 'static,
{
fn into_response(self) -> Response {
let mut res = ().into_response();
res.extensions_mut().insert(self.0);
res
}
}
impl<S, T> tower_layer::Layer<S> for Extension<T>
where
T: Clone + Send + Sync + 'static,
{
type Service = AddExtension<S, T>;
fn layer(&self, inner: S) -> Self::Service {
AddExtension {
inner,
value: self.0.clone(),
}
}
}
/// Middleware for adding some shareable value to [request extensions].
///
/// See [Passing state from middleware to handlers](index.html#passing-state-from-middleware-to-handlers)
/// for more details.
///
/// [request extensions]: https://docs.rs/http/latest/http/struct.Extensions.html
///
/// If you need a layer to add an extension to every request,
/// use the [Layer](tower::Layer) implementation of [Extension].
#[derive(Clone, Copy, Debug)]
pub struct AddExtension<S, T> {
pub(crate) inner: S,
pub(crate) value: T,
}
impl<ResBody, S, T> Service<Request<ResBody>> for AddExtension<S, T>
where
S: Service<Request<ResBody>>,
T: Clone + Send + Sync + 'static,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
#[inline]
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, mut req: Request<ResBody>) -> Self::Future {
req.extensions_mut().insert(self.value.clone());
self.inner.call(req)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::routing::get;
use crate::test_helpers::TestClient;
use crate::Router;
use http::StatusCode;
#[derive(Clone)]
struct Foo(String);
#[derive(Clone)]
struct Bar(String);
#[crate::test]
async fn extension_extractor() {
async fn requires_foo(Extension(foo): Extension<Foo>) -> String {
foo.0
}
async fn optional_foo(extension: Option<Extension<Foo>>) -> String {
extension.map(|foo| foo.0 .0).unwrap_or("none".to_owned())
}
async fn requires_bar(Extension(bar): Extension<Bar>) -> String {
bar.0
}
async fn optional_bar(extension: Option<Extension<Bar>>) -> String {
extension.map(|bar| bar.0 .0).unwrap_or("none".to_owned())
}
let app = Router::new()
.route("/requires_foo", get(requires_foo))
.route("/optional_foo", get(optional_foo))
.route("/requires_bar", get(requires_bar))
.route("/optional_bar", get(optional_bar))
.layer(Extension(Foo("foo".to_owned())));
let client = TestClient::new(app);
let response = client.get("/requires_foo").await;
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.text().await, "foo");
let response = client.get("/optional_foo").await;
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.text().await, "foo");
let response = client.get("/requires_bar").await;
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
assert_eq!(response.text().await, "Missing request extension: Extension of type `axum::extension::tests::Bar` was not found. Perhaps you forgot to add it? See `axum::Extension`.");
let response = client.get("/optional_bar").await;
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.text().await, "none");
}
}
+356
View File
@@ -0,0 +1,356 @@
//! Extractor for getting connection information from a client.
//!
//! See [`Router::into_make_service_with_connect_info`] for more details.
//!
//! [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info
use crate::extension::AddExtension;
use super::{Extension, FromRequestParts};
use http::request::Parts;
use std::{
convert::Infallible,
fmt,
future::ready,
marker::PhantomData,
net::SocketAddr,
task::{Context, Poll},
};
use tower_layer::Layer;
use tower_service::Service;
/// A [`MakeService`] created from a router.
///
/// See [`Router::into_make_service_with_connect_info`] for more details.
///
/// [`MakeService`]: tower::make::MakeService
/// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info
pub struct IntoMakeServiceWithConnectInfo<S, C> {
svc: S,
_connect_info: PhantomData<fn() -> C>,
}
#[test]
fn traits() {
use crate::test_helpers::*;
assert_send::<IntoMakeServiceWithConnectInfo<(), NotSendSync>>();
}
impl<S, C> IntoMakeServiceWithConnectInfo<S, C> {
pub(crate) fn new(svc: S) -> Self {
Self {
svc,
_connect_info: PhantomData,
}
}
}
impl<S, C> fmt::Debug for IntoMakeServiceWithConnectInfo<S, C>
where
S: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("IntoMakeServiceWithConnectInfo")
.field("svc", &self.svc)
.finish()
}
}
impl<S, C> Clone for IntoMakeServiceWithConnectInfo<S, C>
where
S: Clone,
{
fn clone(&self) -> Self {
Self {
svc: self.svc.clone(),
_connect_info: PhantomData,
}
}
}
/// Trait that connected IO resources implement and use to produce information
/// about the connection.
///
/// The goal for this trait is to allow users to implement custom IO types that
/// can still provide the same connection metadata.
///
/// See [`Router::into_make_service_with_connect_info`] for more details.
///
/// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info
pub trait Connected<T>: Clone + Send + Sync + 'static {
/// Create type holding information about the connection.
fn connect_info(stream: T) -> Self;
}
#[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))]
const _: () = {
use crate::serve;
use tokio::net::TcpListener;
impl Connected<serve::IncomingStream<'_, TcpListener>> for SocketAddr {
fn connect_info(stream: serve::IncomingStream<'_, TcpListener>) -> Self {
*stream.remote_addr()
}
}
impl<'a, L, F> Connected<serve::IncomingStream<'a, serve::TapIo<L, F>>> for L::Addr
where
L: serve::Listener,
L::Addr: Clone + Sync + 'static,
F: FnMut(&mut L::Io) + Send + 'static,
{
fn connect_info(stream: serve::IncomingStream<'a, serve::TapIo<L, F>>) -> Self {
stream.remote_addr().clone()
}
}
};
impl Connected<SocketAddr> for SocketAddr {
fn connect_info(remote_addr: SocketAddr) -> Self {
remote_addr
}
}
impl<S, C, T> Service<T> for IntoMakeServiceWithConnectInfo<S, C>
where
S: Clone,
C: Connected<T>,
{
type Response = AddExtension<S, ConnectInfo<C>>;
type Error = Infallible;
type Future = ResponseFuture<S, C>;
#[inline]
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, target: T) -> Self::Future {
let connect_info = ConnectInfo(C::connect_info(target));
let svc = Extension(connect_info).layer(self.svc.clone());
ResponseFuture::new(ready(Ok(svc)))
}
}
opaque_future! {
/// Response future for [`IntoMakeServiceWithConnectInfo`].
pub type ResponseFuture<S, C> =
std::future::Ready<Result<AddExtension<S, ConnectInfo<C>>, Infallible>>;
}
/// Extractor for getting connection information produced by a [`Connected`].
///
/// Note this extractor requires you to use
/// [`Router::into_make_service_with_connect_info`] to run your app
/// otherwise it will fail at runtime.
///
/// See [`Router::into_make_service_with_connect_info`] for more details.
///
/// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info
#[derive(Clone, Copy, Debug)]
pub struct ConnectInfo<T>(pub T);
impl<S, T> FromRequestParts<S> for ConnectInfo<T>
where
S: Send + Sync,
T: Clone + Send + Sync + 'static,
{
type Rejection = <Extension<Self> as FromRequestParts<S>>::Rejection;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
match Extension::<Self>::from_request_parts(parts, state).await {
Ok(Extension(connect_info)) => Ok(connect_info),
Err(err) => match parts.extensions.get::<MockConnectInfo<T>>() {
Some(MockConnectInfo(connect_info)) => Ok(Self(connect_info.clone())),
None => Err(err),
},
}
}
}
axum_core::__impl_deref!(ConnectInfo);
/// Middleware used to mock [`ConnectInfo`] during tests.
///
/// If you're accidentally using [`MockConnectInfo`] and
/// [`Router::into_make_service_with_connect_info`] at the same time then
/// [`Router::into_make_service_with_connect_info`] takes precedence.
///
/// # Example
///
/// ```
/// use axum::{
/// Router,
/// extract::connect_info::{MockConnectInfo, ConnectInfo},
/// body::Body,
/// routing::get,
/// http::{Request, StatusCode},
/// };
/// use std::net::SocketAddr;
/// use tower::ServiceExt;
///
/// async fn handler(ConnectInfo(addr): ConnectInfo<SocketAddr>) {}
///
/// // this router you can run with `app.into_make_service_with_connect_info::<SocketAddr>()`
/// fn app() -> Router {
/// Router::new().route("/", get(handler))
/// }
///
/// // use this router for tests
/// fn test_app() -> Router {
/// app().layer(MockConnectInfo(SocketAddr::from(([0, 0, 0, 0], 1337))))
/// }
///
/// // #[tokio::test]
/// async fn some_test() {
/// let app = test_app();
///
/// let request = Request::new(Body::empty());
/// let response = app.oneshot(request).await.unwrap();
/// assert_eq!(response.status(), StatusCode::OK);
/// }
/// #
/// # #[tokio::main]
/// # async fn main() {
/// # some_test().await;
/// # }
/// ```
///
/// [`Router::into_make_service_with_connect_info`]: crate::Router::into_make_service_with_connect_info
#[derive(Clone, Copy, Debug)]
pub struct MockConnectInfo<T>(pub T);
impl<S, T> Layer<S> for MockConnectInfo<T>
where
T: Clone + Send + Sync + 'static,
{
type Service = <Extension<Self> as Layer<S>>::Service;
fn layer(&self, inner: S) -> Self::Service {
Extension(self.clone()).layer(inner)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{routing::get, serve::IncomingStream, test_helpers::TestClient, Router};
use tokio::net::TcpListener;
#[crate::test]
async fn socket_addr() {
async fn handler(ConnectInfo(addr): ConnectInfo<SocketAddr>) -> String {
format!("{addr}")
}
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let (tx, rx) = tokio::sync::oneshot::channel();
tokio::spawn(async move {
let app = Router::new().route("/", get(handler));
tx.send(()).unwrap();
crate::serve(
listener,
app.into_make_service_with_connect_info::<SocketAddr>(),
)
.await
.unwrap();
});
rx.await.unwrap();
let client = reqwest::Client::new();
let res = client.get(format!("http://{addr}")).send().await.unwrap();
let body = res.text().await.unwrap();
assert!(body.starts_with("127.0.0.1:"));
}
#[crate::test]
async fn custom() {
#[derive(Clone, Debug)]
struct MyConnectInfo {
value: &'static str,
}
impl Connected<IncomingStream<'_, TcpListener>> for MyConnectInfo {
fn connect_info(_target: IncomingStream<'_, TcpListener>) -> Self {
Self {
value: "it worked!",
}
}
}
async fn handler(ConnectInfo(addr): ConnectInfo<MyConnectInfo>) -> &'static str {
addr.value
}
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let (tx, rx) = tokio::sync::oneshot::channel();
tokio::spawn(async move {
let app = Router::new().route("/", get(handler));
tx.send(()).unwrap();
crate::serve(
listener,
app.into_make_service_with_connect_info::<MyConnectInfo>(),
)
.await
.unwrap();
});
rx.await.unwrap();
let client = reqwest::Client::new();
let res = client.get(format!("http://{addr}")).send().await.unwrap();
let body = res.text().await.unwrap();
assert_eq!(body, "it worked!");
}
#[crate::test]
async fn mock_connect_info() {
async fn handler(ConnectInfo(addr): ConnectInfo<SocketAddr>) -> String {
format!("{addr}")
}
let app = Router::new()
.route("/", get(handler))
.layer(MockConnectInfo(SocketAddr::from(([0, 0, 0, 0], 1337))));
let client = TestClient::new(app);
let res = client.get("/").await;
let body = res.text().await;
assert!(body.starts_with("0.0.0.0:1337"));
}
#[crate::test]
async fn both_mock_and_real_connect_info() {
async fn handler(ConnectInfo(addr): ConnectInfo<SocketAddr>) -> String {
format!("{addr}")
}
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move {
let app = Router::new()
.route("/", get(handler))
.layer(MockConnectInfo(SocketAddr::from(([0, 0, 0, 0], 1337))));
crate::serve(
listener,
app.into_make_service_with_connect_info::<SocketAddr>(),
)
.await
.unwrap();
});
let client = reqwest::Client::new();
let res = client.get(format!("http://{addr}")).send().await.unwrap();
let body = res.text().await.unwrap();
assert!(body.starts_with("127.0.0.1:"));
}
}
+408
View File
@@ -0,0 +1,408 @@
use super::{rejection::*, FromRequestParts};
use crate::routing::{RouteId, NEST_TAIL_PARAM_CAPTURE};
use axum_core::extract::OptionalFromRequestParts;
use http::request::Parts;
use std::{collections::HashMap, convert::Infallible, sync::Arc};
/// Access the path in the router that matches the request.
///
/// ```
/// use axum::{
/// Router,
/// extract::MatchedPath,
/// routing::get,
/// };
///
/// let app = Router::new().route(
/// "/users/{id}",
/// get(|path: MatchedPath| async move {
/// let path = path.as_str();
/// // `path` will be "/users/{id}"
/// })
/// );
/// # let _: Router = app;
/// ```
///
/// # Accessing `MatchedPath` via extensions
///
/// `MatchedPath` can also be accessed from middleware via request extensions.
///
/// This is useful for example with [`Trace`](tower_http::trace::Trace) to
/// create a span that contains the matched path:
///
/// ```
/// use axum::{
/// Router,
/// extract::{Request, MatchedPath},
/// routing::get,
/// };
/// use tower_http::trace::TraceLayer;
///
/// let app = Router::new()
/// .route("/users/{id}", get(|| async { /* ... */ }))
/// .layer(
/// TraceLayer::new_for_http().make_span_with(|req: &Request<_>| {
/// let path = if let Some(path) = req.extensions().get::<MatchedPath>() {
/// path.as_str()
/// } else {
/// req.uri().path()
/// };
/// tracing::info_span!("http-request", %path)
/// }),
/// );
/// # let _: Router = app;
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "matched-path")))]
#[derive(Clone, Debug)]
pub struct MatchedPath(pub(crate) Arc<str>);
impl MatchedPath {
/// Returns a `str` representation of the path.
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl<S> FromRequestParts<S> for MatchedPath
where
S: Send + Sync,
{
type Rejection = MatchedPathRejection;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
let matched_path = parts
.extensions
.get::<Self>()
.ok_or(MatchedPathRejection::MatchedPathMissing(MatchedPathMissing))?
.clone();
Ok(matched_path)
}
}
impl<S> OptionalFromRequestParts<S> for MatchedPath
where
S: Send + Sync,
{
type Rejection = Infallible;
async fn from_request_parts(
parts: &mut Parts,
_state: &S,
) -> Result<Option<Self>, Self::Rejection> {
Ok(parts.extensions.get::<Self>().cloned())
}
}
#[derive(Clone, Debug)]
struct MatchedNestedPath(Arc<str>);
pub(crate) fn set_matched_path_for_request(
id: RouteId,
route_id_to_path: &HashMap<RouteId, Arc<str>>,
extensions: &mut http::Extensions,
) {
let matched_path = if let Some(matched_path) = route_id_to_path.get(&id) {
matched_path
} else {
#[cfg(debug_assertions)]
panic!("should always have a matched path for a route id");
#[cfg(not(debug_assertions))]
return;
};
let matched_path = append_nested_matched_path(matched_path, extensions);
if matched_path.ends_with(NEST_TAIL_PARAM_CAPTURE) {
extensions.insert(MatchedNestedPath(matched_path));
debug_assert!(extensions.remove::<MatchedPath>().is_none());
} else {
extensions.insert(MatchedPath(matched_path));
extensions.remove::<MatchedNestedPath>();
}
}
// a previous `MatchedPath` might exist if we're inside a nested Router
fn append_nested_matched_path(matched_path: &Arc<str>, extensions: &http::Extensions) -> Arc<str> {
if let Some(previous) = extensions
.get::<MatchedPath>()
.map(|matched_path| matched_path.as_str())
.or_else(|| Some(&extensions.get::<MatchedNestedPath>()?.0))
{
let previous = previous
.strip_suffix(NEST_TAIL_PARAM_CAPTURE)
.unwrap_or(previous);
let matched_path = format!("{previous}{matched_path}");
matched_path.into()
} else {
Arc::clone(matched_path)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
extract::Request,
handler::HandlerWithoutStateExt,
middleware::map_request,
routing::{any, get},
test_helpers::*,
Router,
};
use http::StatusCode;
#[crate::test]
async fn extracting_on_handler() {
let app = Router::new().route(
"/{a}",
get(|path: MatchedPath| async move { path.as_str().to_owned() }),
);
let client = TestClient::new(app);
let res = client.get("/foo").await;
assert_eq!(res.text().await, "/{a}");
}
#[crate::test]
async fn extracting_on_handler_in_nested_router() {
let app = Router::new().nest(
"/{a}",
Router::new().route(
"/{b}",
get(|path: MatchedPath| async move { path.as_str().to_owned() }),
),
);
let client = TestClient::new(app);
let res = client.get("/foo/bar").await;
assert_eq!(res.text().await, "/{a}/{b}");
}
#[crate::test]
async fn extracting_on_handler_in_deeply_nested_router() {
let app = Router::new().nest(
"/{a}",
Router::new().nest(
"/{b}",
Router::new().route(
"/{c}",
get(|path: MatchedPath| async move { path.as_str().to_owned() }),
),
),
);
let client = TestClient::new(app);
let res = client.get("/foo/bar/baz").await;
assert_eq!(res.text().await, "/{a}/{b}/{c}");
}
#[crate::test]
async fn cannot_extract_nested_matched_path_in_middleware() {
async fn extract_matched_path<B>(
matched_path: Option<MatchedPath>,
req: Request<B>,
) -> Request<B> {
assert!(matched_path.is_none());
req
}
let app = Router::new()
.nest_service("/{a}", Router::new().route("/{b}", get(|| async move {})))
.layer(map_request(extract_matched_path));
let client = TestClient::new(app);
let res = client.get("/foo/bar").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn can_extract_nested_matched_path_in_middleware_using_nest() {
async fn extract_matched_path<B>(
matched_path: Option<MatchedPath>,
req: Request<B>,
) -> Request<B> {
assert_eq!(matched_path.unwrap().as_str(), "/{a}/{b}");
req
}
let app = Router::new()
.nest("/{a}", Router::new().route("/{b}", get(|| async move {})))
.layer(map_request(extract_matched_path));
let client = TestClient::new(app);
let res = client.get("/foo/bar").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn cannot_extract_nested_matched_path_in_middleware_via_extension() {
async fn assert_no_matched_path<B>(req: Request<B>) -> Request<B> {
assert!(req.extensions().get::<MatchedPath>().is_none());
req
}
let app = Router::new()
.nest_service("/{a}", Router::new().route("/{b}", get(|| async move {})))
.layer(map_request(assert_no_matched_path));
let client = TestClient::new(app);
let res = client.get("/foo/bar").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
async fn can_extract_nested_matched_path_in_middleware_via_extension_using_nest() {
async fn assert_matched_path<B>(req: Request<B>) -> Request<B> {
assert!(req.extensions().get::<MatchedPath>().is_some());
req
}
let app = Router::new()
.nest("/{a}", Router::new().route("/{b}", get(|| async move {})))
.layer(map_request(assert_matched_path));
let client = TestClient::new(app);
let res = client.get("/foo/bar").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn can_extract_nested_matched_path_in_middleware_on_nested_router() {
async fn extract_matched_path<B>(matched_path: MatchedPath, req: Request<B>) -> Request<B> {
assert_eq!(matched_path.as_str(), "/{a}/{b}");
req
}
let app = Router::new().nest(
"/{a}",
Router::new()
.route("/{b}", get(|| async move {}))
.layer(map_request(extract_matched_path)),
);
let client = TestClient::new(app);
let res = client.get("/foo/bar").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn can_extract_nested_matched_path_in_middleware_on_nested_router_via_extension() {
async fn extract_matched_path<B>(req: Request<B>) -> Request<B> {
let matched_path = req.extensions().get::<MatchedPath>().unwrap();
assert_eq!(matched_path.as_str(), "/{a}/{b}");
req
}
let app = Router::new().nest(
"/{a}",
Router::new()
.route("/{b}", get(|| async move {}))
.layer(map_request(extract_matched_path)),
);
let client = TestClient::new(app);
let res = client.get("/foo/bar").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn extracting_on_nested_handler() {
async fn handler(path: Option<MatchedPath>) {
assert!(path.is_none());
}
let app = Router::new().nest_service("/{a}", handler.into_service());
let client = TestClient::new(app);
let res = client.get("/foo/bar").await;
assert_eq!(res.status(), StatusCode::OK);
}
// https://github.com/tokio-rs/axum/issues/1579
#[crate::test]
async fn doesnt_panic_if_router_called_from_wildcard_route() {
use tower::ServiceExt;
let app = Router::new().route(
"/{*path}",
any(|req: Request| {
Router::new()
.nest("/foo", Router::new().route("/bar", get(|| async {})))
.oneshot(req)
}),
);
let client = TestClient::new(app);
let res = client.get("/foo/bar").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn cant_extract_in_fallback() {
async fn handler(path: Option<MatchedPath>, req: Request) {
assert!(path.is_none());
assert!(req.extensions().get::<MatchedPath>().is_none());
}
let app = Router::new().fallback(handler);
let client = TestClient::new(app);
let res = client.get("/foo/bar").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn matching_colon() {
let app = Router::new().without_v07_checks().route(
"/:foo",
get(|path: MatchedPath| async move { path.as_str().to_owned() }),
);
let client = TestClient::new(app);
let res = client.get("/:foo").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "/:foo");
let res = client.get("/:bar").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
let res = client.get("/foo").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[crate::test]
async fn matching_asterisk() {
let app = Router::new().without_v07_checks().route(
"/*foo",
get(|path: MatchedPath| async move { path.as_str().to_owned() }),
);
let client = TestClient::new(app);
let res = client.get("/*foo").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "/*foo");
let res = client.get("/*bar").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
let res = client.get("/foo").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
}
+113
View File
@@ -0,0 +1,113 @@
#![doc = include_str!("../docs/extract.md")]
use http::header::{self, HeaderMap};
#[cfg(feature = "tokio")]
pub mod connect_info;
pub mod path;
pub mod rejection;
#[cfg(feature = "ws")]
pub mod ws;
pub(crate) mod nested_path;
#[cfg(feature = "original-uri")]
mod original_uri;
mod raw_form;
mod raw_query;
mod state;
#[doc(inline)]
pub use axum_core::extract::{
DefaultBodyLimit, FromRef, FromRequest, FromRequestParts, OptionalFromRequest,
OptionalFromRequestParts, Request,
};
#[cfg(feature = "macros")]
pub use axum_macros::{FromRef, FromRequest, FromRequestParts};
#[doc(inline)]
pub use self::{
nested_path::NestedPath,
path::{Path, RawPathParams},
raw_form::RawForm,
raw_query::RawQuery,
state::State,
};
#[doc(inline)]
#[cfg(feature = "tokio")]
pub use self::connect_info::ConnectInfo;
#[doc(no_inline)]
#[cfg(feature = "json")]
pub use crate::Json;
#[doc(no_inline)]
pub use crate::Extension;
#[cfg(feature = "form")]
#[doc(no_inline)]
pub use crate::form::Form;
#[cfg(feature = "matched-path")]
pub(crate) mod matched_path;
#[cfg(feature = "matched-path")]
#[doc(inline)]
pub use self::matched_path::MatchedPath;
#[cfg(feature = "multipart")]
pub mod multipart;
#[cfg(feature = "multipart")]
#[doc(inline)]
pub use self::multipart::Multipart;
#[cfg(feature = "query")]
mod query;
#[cfg(feature = "query")]
#[doc(inline)]
pub use self::query::Query;
#[cfg(feature = "original-uri")]
#[doc(inline)]
pub use self::original_uri::OriginalUri;
#[cfg(feature = "ws")]
#[doc(inline)]
pub use self::ws::WebSocketUpgrade;
// this is duplicated in `axum-extra/src/extract/form.rs`
pub(super) fn has_content_type(headers: &HeaderMap, expected_content_type: &mime::Mime) -> bool {
let content_type = if let Some(content_type) = headers.get(header::CONTENT_TYPE) {
content_type
} else {
return false;
};
let content_type = if let Ok(content_type) = content_type.to_str() {
content_type
} else {
return false;
};
content_type.starts_with(expected_content_type.as_ref())
}
#[cfg(test)]
mod tests {
use crate::{routing::get, test_helpers::*, Router};
#[crate::test]
async fn consume_body() {
let app = Router::new().route("/", get(|body: String| async { body }));
let client = TestClient::new(app);
let res = client.get("/").body("foo").await;
let body = res.text().await;
assert_eq!(body, "foo");
}
}
+459
View File
@@ -0,0 +1,459 @@
//! Extractor that parses `multipart/form-data` requests commonly used with file uploads.
//!
//! See [`Multipart`] for more details.
use super::{FromRequest, Request};
use crate::body::Bytes;
use axum_core::{
__composite_rejection as composite_rejection, __define_rejection as define_rejection,
extract::OptionalFromRequest,
response::{IntoResponse, Response},
RequestExt,
};
use futures_util::stream::Stream;
use http::{
header::{HeaderMap, CONTENT_TYPE},
StatusCode,
};
use std::{
error::Error,
fmt,
pin::Pin,
task::{Context, Poll},
};
/// Extractor that parses `multipart/form-data` requests (commonly used with file uploads).
///
/// ⚠️ Since extracting multipart form data from the request requires consuming the body, the
/// `Multipart` extractor must be *last* if there are multiple extractors in a handler.
/// See ["the order of extractors"][order-of-extractors]
///
/// [order-of-extractors]: crate::extract#the-order-of-extractors
///
/// # Example
///
/// ```rust,no_run
/// use axum::{
/// extract::Multipart,
/// routing::post,
/// Router,
/// };
/// use futures_util::stream::StreamExt;
///
/// async fn upload(mut multipart: Multipart) {
/// while let Some(mut field) = multipart.next_field().await.unwrap() {
/// let name = field.name().unwrap().to_string();
/// let data = field.bytes().await.unwrap();
///
/// println!("Length of `{}` is {} bytes", name, data.len());
/// }
/// }
///
/// let app = Router::new().route("/upload", post(upload));
/// # let _: Router = app;
/// ```
///
/// # Large Files
///
/// For security reasons, by default, `Multipart` limits the request body size to 2MB.
/// See [`DefaultBodyLimit`][default-body-limit] for how to configure this limit.
///
/// [default-body-limit]: crate::extract::DefaultBodyLimit
#[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
#[derive(Debug)]
pub struct Multipart {
inner: multer::Multipart<'static>,
}
impl<S> FromRequest<S> for Multipart
where
S: Send + Sync,
{
type Rejection = MultipartRejection;
async fn from_request(req: Request, _state: &S) -> Result<Self, Self::Rejection> {
let boundary = content_type_str(req.headers())
.and_then(|content_type| multer::parse_boundary(content_type).ok())
.ok_or(InvalidBoundary)?;
let stream = req.with_limited_body().into_body();
let multipart = multer::Multipart::new(stream.into_data_stream(), boundary);
Ok(Self { inner: multipart })
}
}
impl<S> OptionalFromRequest<S> for Multipart
where
S: Send + Sync,
{
type Rejection = MultipartRejection;
async fn from_request(req: Request, _state: &S) -> Result<Option<Self>, Self::Rejection> {
let Some(content_type) = content_type_str(req.headers()) else {
return Ok(None);
};
match multer::parse_boundary(content_type) {
Ok(boundary) => {
let stream = req.with_limited_body().into_body();
let multipart = multer::Multipart::new(stream.into_data_stream(), boundary);
Ok(Some(Self { inner: multipart }))
}
Err(multer::Error::NoMultipart) => Ok(None),
Err(_) => Err(MultipartRejection::InvalidBoundary(InvalidBoundary)),
}
}
}
impl Multipart {
/// Yields the next [`Field`] if available.
pub async fn next_field(&mut self) -> Result<Option<Field<'_>>, MultipartError> {
let field = self
.inner
.next_field()
.await
.map_err(MultipartError::from_multer)?;
if let Some(field) = field {
Ok(Some(Field {
inner: field,
_multipart: self,
}))
} else {
Ok(None)
}
}
}
/// A single field in a multipart stream.
#[derive(Debug)]
pub struct Field<'a> {
inner: multer::Field<'static>,
// multer requires there to only be one live `multer::Field` at any point. This enforces that
// statically, which multer does not do, it returns an error instead.
_multipart: &'a mut Multipart,
}
impl Stream for Field<'_> {
type Item = Result<Bytes, MultipartError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Pin::new(&mut self.inner)
.poll_next(cx)
.map_err(MultipartError::from_multer)
}
}
impl Field<'_> {
/// The field name found in the
/// [`Content-Disposition`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition)
/// header.
#[must_use]
pub fn name(&self) -> Option<&str> {
self.inner.name()
}
/// The file name found in the
/// [`Content-Disposition`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition)
/// header.
#[must_use]
pub fn file_name(&self) -> Option<&str> {
self.inner.file_name()
}
/// Get the [content type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) of the field.
#[must_use]
pub fn content_type(&self) -> Option<&str> {
self.inner.content_type().map(|m| m.as_ref())
}
/// Get a map of headers as [`HeaderMap`].
#[must_use]
pub fn headers(&self) -> &HeaderMap {
self.inner.headers()
}
/// Get the full data of the field as [`Bytes`].
pub async fn bytes(self) -> Result<Bytes, MultipartError> {
self.inner
.bytes()
.await
.map_err(MultipartError::from_multer)
}
/// Get the full field data as text.
pub async fn text(self) -> Result<String, MultipartError> {
self.inner.text().await.map_err(MultipartError::from_multer)
}
/// Stream a chunk of the field data.
///
/// When the field data has been exhausted, this will return [`None`].
///
/// Note this does the same thing as `Field`'s [`Stream`] implementation.
///
/// # Example
///
/// ```
/// use axum::{
/// extract::Multipart,
/// routing::post,
/// response::IntoResponse,
/// http::StatusCode,
/// Router,
/// };
///
/// async fn upload(mut multipart: Multipart) -> Result<(), (StatusCode, String)> {
/// while let Some(mut field) = multipart
/// .next_field()
/// .await
/// .map_err(|err| (StatusCode::BAD_REQUEST, err.to_string()))?
/// {
/// while let Some(chunk) = field
/// .chunk()
/// .await
/// .map_err(|err| (StatusCode::BAD_REQUEST, err.to_string()))?
/// {
/// println!("received {} bytes", chunk.len());
/// }
/// }
///
/// Ok(())
/// }
///
/// let app = Router::new().route("/upload", post(upload));
/// # let _: Router = app;
/// ```
pub async fn chunk(&mut self) -> Result<Option<Bytes>, MultipartError> {
self.inner
.chunk()
.await
.map_err(MultipartError::from_multer)
}
}
/// Errors associated with parsing `multipart/form-data` requests.
#[derive(Debug)]
pub struct MultipartError {
source: multer::Error,
}
impl MultipartError {
fn from_multer(multer: multer::Error) -> Self {
Self { source: multer }
}
/// Get the response body text used for this rejection.
#[must_use]
pub fn body_text(&self) -> String {
if is_body_limit_error(&self.source) {
"Request payload is too large".to_owned()
} else {
self.source.to_string()
}
}
/// Get the status code used for this rejection.
#[must_use]
pub fn status(&self) -> http::StatusCode {
status_code_from_multer_error(&self.source)
}
}
fn status_code_from_multer_error(err: &multer::Error) -> StatusCode {
match err {
multer::Error::UnknownField { .. }
| multer::Error::IncompleteFieldData { .. }
| multer::Error::IncompleteHeaders
| multer::Error::ReadHeaderFailed(..)
| multer::Error::DecodeHeaderName { .. }
| multer::Error::DecodeContentType(..)
| multer::Error::NoBoundary
| multer::Error::DecodeHeaderValue { .. }
| multer::Error::NoMultipart
| multer::Error::IncompleteStream => StatusCode::BAD_REQUEST,
multer::Error::FieldSizeExceeded { .. } | multer::Error::StreamSizeExceeded { .. } => {
StatusCode::PAYLOAD_TOO_LARGE
}
multer::Error::StreamReadFailed(err) => {
if let Some(err) = err.downcast_ref::<multer::Error>() {
return status_code_from_multer_error(err);
}
if err
.downcast_ref::<crate::Error>()
.and_then(|err| err.source())
.and_then(|err| err.downcast_ref::<http_body_util::LengthLimitError>())
.is_some()
{
return StatusCode::PAYLOAD_TOO_LARGE;
}
StatusCode::INTERNAL_SERVER_ERROR
}
_ => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn is_body_limit_error(err: &multer::Error) -> bool {
match err {
multer::Error::FieldSizeExceeded { .. } | multer::Error::StreamSizeExceeded { .. } => true,
multer::Error::StreamReadFailed(err) => {
if let Some(err) = err.downcast_ref::<multer::Error>() {
return is_body_limit_error(err);
}
err.downcast_ref::<crate::Error>()
.and_then(|err| err.source())
.and_then(|err| err.downcast_ref::<http_body_util::LengthLimitError>())
.is_some()
}
_ => false,
}
}
impl fmt::Display for MultipartError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Error parsing `multipart/form-data` request")
}
}
impl std::error::Error for MultipartError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.source)
}
}
impl IntoResponse for MultipartError {
fn into_response(self) -> Response {
let body = self.body_text();
axum_core::__log_rejection!(
rejection_type = Self,
body_text = body,
status = self.status(),
);
(self.status(), body).into_response()
}
}
fn content_type_str(headers: &HeaderMap) -> Option<&str> {
headers.get(CONTENT_TYPE)?.to_str().ok()
}
composite_rejection! {
/// Rejection used for [`Multipart`].
///
/// Contains one variant for each way the [`Multipart`] extractor can fail.
pub enum MultipartRejection {
InvalidBoundary,
}
}
define_rejection! {
#[status = BAD_REQUEST]
#[body = "Invalid `boundary` for `multipart/form-data` request"]
/// Rejection type used if the `boundary` in a `multipart/form-data` is
/// missing or invalid.
pub struct InvalidBoundary;
}
#[cfg(test)]
mod tests {
use axum_core::extract::DefaultBodyLimit;
use super::*;
use crate::{routing::post, test_helpers::*, Router};
#[crate::test]
async fn content_type_with_encoding() {
const BYTES: &[u8] = "<!doctype html><title>🦀</title>".as_bytes();
const FILE_NAME: &str = "index.html";
const CONTENT_TYPE: &str = "text/html; charset=utf-8";
async fn handle(mut multipart: Multipart) -> impl IntoResponse {
let field = multipart.next_field().await.unwrap().unwrap();
assert_eq!(field.file_name().unwrap(), FILE_NAME);
assert_eq!(field.content_type().unwrap(), CONTENT_TYPE);
assert_eq!(field.headers()["foo"], "bar");
assert_eq!(field.bytes().await.unwrap(), BYTES);
assert!(multipart.next_field().await.unwrap().is_none());
}
let app = Router::new().route("/", post(handle));
let client = TestClient::new(app);
let form = reqwest::multipart::Form::new().part(
"file",
reqwest::multipart::Part::bytes(BYTES)
.file_name(FILE_NAME)
.mime_str(CONTENT_TYPE)
.unwrap()
.headers(reqwest::header::HeaderMap::from_iter([(
reqwest::header::HeaderName::from_static("foo"),
reqwest::header::HeaderValue::from_static("bar"),
)])),
);
client.post("/").multipart(form).await;
}
// No need for this to be a #[test], we just want to make sure it compiles
fn _multipart_from_request_limited() {
async fn handler(_: Multipart) {}
let _app: Router = Router::new()
.route("/", post(handler))
.layer(tower_http::limit::RequestBodyLimitLayer::new(1024));
}
#[crate::test]
async fn body_too_large() {
const BYTES: &[u8] = "<!doctype html><title>🦀</title>".as_bytes();
async fn handle(mut multipart: Multipart) -> Result<(), MultipartError> {
while let Some(field) = multipart.next_field().await? {
field.bytes().await?;
}
Ok(())
}
let app = Router::new()
.route("/", post(handle))
.layer(DefaultBodyLimit::max(BYTES.len() - 1));
let client = TestClient::new(app);
let form =
reqwest::multipart::Form::new().part("file", reqwest::multipart::Part::bytes(BYTES));
let res = client.post("/").multipart(form).await;
assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE);
assert_eq!(res.text().await, "Request payload is too large");
}
#[crate::test]
async fn optional_multipart() {
const BYTES: &[u8] = "<!doctype html><title>🦀</title>".as_bytes();
async fn handle(multipart: Option<Multipart>) -> Result<StatusCode, MultipartError> {
if let Some(mut multipart) = multipart {
while let Some(field) = multipart.next_field().await? {
field.bytes().await?;
}
Ok(StatusCode::OK)
} else {
Ok(StatusCode::NO_CONTENT)
}
}
let app = Router::new().route("/", post(handle));
let client = TestClient::new(app);
let form =
reqwest::multipart::Form::new().part("file", reqwest::multipart::Part::bytes(BYTES));
let res = client.post("/").multipart(form).await;
assert_eq!(res.status(), StatusCode::OK);
let res = client.post("/").await;
assert_eq!(res.status(), StatusCode::NO_CONTENT);
}
}
+229
View File
@@ -0,0 +1,229 @@
use std::{
sync::Arc,
task::{Context, Poll},
};
use crate::extract::Request;
use axum_core::extract::FromRequestParts;
use http::request::Parts;
use tower_layer::{layer_fn, Layer};
use tower_service::Service;
use super::rejection::NestedPathRejection;
/// Access the path the matched the route is nested at.
///
/// This can for example be used when doing redirects.
///
/// # Example
///
/// ```
/// use axum::{
/// Router,
/// extract::NestedPath,
/// routing::get,
/// };
///
/// let api = Router::new().route(
/// "/users",
/// get(|path: NestedPath| async move {
/// // `path` will be "/api" because that's what this
/// // router is nested at when we build `app`
/// let path = path.as_str();
/// })
/// );
///
/// let app = Router::new().nest("/api", api);
/// # let _: Router = app;
/// ```
#[derive(Debug, Clone)]
pub struct NestedPath(Arc<str>);
impl NestedPath {
/// Returns a `str` representation of the path.
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
#[diagnostic::do_not_recommend] // pretty niche type
impl<S> FromRequestParts<S> for NestedPath
where
S: Send + Sync,
{
type Rejection = NestedPathRejection;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
match parts.extensions.get::<Self>() {
Some(nested_path) => Ok(nested_path.clone()),
None => Err(NestedPathRejection),
}
}
}
#[derive(Clone)]
pub(crate) struct SetNestedPath<S> {
inner: S,
path: Arc<str>,
}
impl<S> SetNestedPath<S> {
pub(crate) fn layer(path: &str) -> impl Layer<S, Service = Self> + Clone {
let path = Arc::from(path);
layer_fn(move |inner| Self {
inner,
path: Arc::clone(&path),
})
}
}
impl<S, B> Service<Request<B>> for SetNestedPath<S>
where
S: Service<Request<B>>,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
#[inline]
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, mut req: Request<B>) -> Self::Future {
if let Some(prev) = req.extensions_mut().get_mut::<NestedPath>() {
let new_path = if prev.as_str() == "/" {
Arc::clone(&self.path)
} else {
format!("{}{}", prev.as_str().trim_end_matches('/'), self.path).into()
};
prev.0 = new_path;
} else {
req.extensions_mut()
.insert(NestedPath(Arc::clone(&self.path)));
};
self.inner.call(req)
}
}
#[cfg(test)]
mod tests {
use axum_core::response::Response;
use http::StatusCode;
use crate::{
extract::{NestedPath, Request},
middleware::{from_fn, Next},
routing::get,
test_helpers::*,
Router,
};
#[crate::test]
async fn one_level_of_nesting() {
let api = Router::new().route(
"/users",
get(|nested_path: NestedPath| {
assert_eq!(nested_path.as_str(), "/api");
async {}
}),
);
let app = Router::new().nest("/api", api);
let client = TestClient::new(app);
let res = client.get("/api/users").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn one_level_of_nesting_with_trailing_slash() {
let api = Router::new().route(
"/users",
get(|nested_path: NestedPath| {
assert_eq!(nested_path.as_str(), "/api/");
async {}
}),
);
let app = Router::new().nest("/api/", api);
let client = TestClient::new(app);
let res = client.get("/api/users").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn two_levels_of_nesting() {
let api = Router::new().route(
"/users",
get(|nested_path: NestedPath| {
assert_eq!(nested_path.as_str(), "/api/v2");
async {}
}),
);
let app = Router::new().nest("/api", Router::new().nest("/v2", api));
let client = TestClient::new(app);
let res = client.get("/api/v2/users").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn two_levels_of_nesting_with_trailing_slash() {
let api = Router::new().route(
"/users",
get(|nested_path: NestedPath| {
assert_eq!(nested_path.as_str(), "/api/v2");
async {}
}),
);
let app = Router::new().nest("/api/", Router::new().nest("/v2", api));
let client = TestClient::new(app);
let res = client.get("/api/v2/users").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn in_fallbacks() {
let api = Router::new().fallback(get(|nested_path: NestedPath| {
assert_eq!(nested_path.as_str(), "/api");
async {}
}));
let app = Router::new().nest("/api", api);
let client = TestClient::new(app);
let res = client.get("/api/doesnt-exist").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn in_middleware() {
async fn middleware(nested_path: NestedPath, req: Request, next: Next) -> Response {
assert_eq!(nested_path.as_str(), "/api");
next.run(req).await
}
let api = Router::new()
.route("/users", get(|| async {}))
.layer(from_fn(middleware));
let app = Router::new().nest("/api", api);
let client = TestClient::new(app);
let res = client.get("/api/users").await;
assert_eq!(res.status(), StatusCode::OK);
}
}
+85
View File
@@ -0,0 +1,85 @@
use super::{Extension, FromRequestParts};
use http::{request::Parts, Uri};
use std::convert::Infallible;
/// Extractor that gets the original request URI regardless of nesting.
///
/// This is necessary since [`Uri`](http::Uri), when used as an extractor, will
/// have the prefix stripped if used in a nested service.
///
/// # Example
///
/// ```
/// use axum::{
/// routing::get,
/// Router,
/// extract::OriginalUri,
/// http::Uri
/// };
///
/// let api_routes = Router::new()
/// .route(
/// "/users",
/// get(|uri: Uri, OriginalUri(original_uri): OriginalUri| async {
/// // `uri` is `/users`
/// // `original_uri` is `/api/users`
/// }),
/// );
///
/// let app = Router::new().nest("/api", api_routes);
/// # let _: Router = app;
/// ```
///
/// # Extracting via request extensions
///
/// `OriginalUri` can also be accessed from middleware via request extensions.
/// This is useful for example with [`Trace`](tower_http::trace::Trace) to
/// create a span that contains the full path, if your service might be nested:
///
/// ```
/// use axum::{
/// Router,
/// extract::OriginalUri,
/// http::Request,
/// routing::get,
/// };
/// use tower_http::trace::TraceLayer;
///
/// let api_routes = Router::new()
/// .route("/users/{id}", get(|| async { /* ... */ }))
/// .layer(
/// TraceLayer::new_for_http().make_span_with(|req: &Request<_>| {
/// let path = if let Some(path) = req.extensions().get::<OriginalUri>() {
/// // This will include `/api`
/// path.0.path().to_owned()
/// } else {
/// // The `OriginalUri` extension will always be present if using
/// // `Router` unless another extractor or middleware has removed it
/// req.uri().path().to_owned()
/// };
/// tracing::info_span!("http-request", %path)
/// }),
/// );
///
/// let app = Router::new().nest("/api", api_routes);
/// # let _: Router = app;
/// ```
#[derive(Debug, Clone)]
pub struct OriginalUri(pub Uri);
impl<S> FromRequestParts<S> for OriginalUri
where
S: Send + Sync,
{
type Rejection = Infallible;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let uri = Extension::<Self>::from_request_parts(parts, state)
.await
.unwrap_or_else(|_| Extension(OriginalUri(parts.uri.clone())))
.0;
Ok(uri)
}
}
axum_core::__impl_deref!(OriginalUri: Uri);
+979
View File
@@ -0,0 +1,979 @@
use super::{ErrorKind, PathDeserializationError};
use crate::util::PercentDecodedStr;
use serde_core::{
de::{self, DeserializeSeed, EnumAccess, Error, MapAccess, SeqAccess, VariantAccess, Visitor},
forward_to_deserialize_any, Deserializer,
};
use std::{any::type_name, sync::Arc};
macro_rules! unsupported_type {
($trait_fn:ident) => {
fn $trait_fn<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
Err(PathDeserializationError::unsupported_type(type_name::<
V::Value,
>()))
}
};
}
macro_rules! parse_single_value {
($trait_fn:ident, $visit_fn:ident, $ty:literal) => {
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
if self.url_params.len() != 1 {
return Err(PathDeserializationError::wrong_number_of_parameters()
.got(self.url_params.len())
.expected(1));
}
let value = self.url_params[0].1.parse().map_err(|_| {
PathDeserializationError::new(ErrorKind::ParseError {
value: self.url_params[0].1.as_str().to_owned(),
expected_type: $ty,
})
})?;
visitor.$visit_fn(value)
}
};
}
pub(crate) struct PathDeserializer<'de> {
url_params: &'de [(Arc<str>, PercentDecodedStr)],
}
impl<'de> PathDeserializer<'de> {
#[inline]
pub(crate) fn new(url_params: &'de [(Arc<str>, PercentDecodedStr)]) -> Self {
PathDeserializer { url_params }
}
}
impl<'de> Deserializer<'de> for PathDeserializer<'de> {
type Error = PathDeserializationError;
unsupported_type!(deserialize_bytes);
unsupported_type!(deserialize_option);
unsupported_type!(deserialize_identifier);
unsupported_type!(deserialize_ignored_any);
parse_single_value!(deserialize_bool, visit_bool, "bool");
parse_single_value!(deserialize_i8, visit_i8, "i8");
parse_single_value!(deserialize_i16, visit_i16, "i16");
parse_single_value!(deserialize_i32, visit_i32, "i32");
parse_single_value!(deserialize_i64, visit_i64, "i64");
parse_single_value!(deserialize_i128, visit_i128, "i128");
parse_single_value!(deserialize_u8, visit_u8, "u8");
parse_single_value!(deserialize_u16, visit_u16, "u16");
parse_single_value!(deserialize_u32, visit_u32, "u32");
parse_single_value!(deserialize_u64, visit_u64, "u64");
parse_single_value!(deserialize_u128, visit_u128, "u128");
parse_single_value!(deserialize_f32, visit_f32, "f32");
parse_single_value!(deserialize_f64, visit_f64, "f64");
parse_single_value!(deserialize_string, visit_string, "String");
parse_single_value!(deserialize_byte_buf, visit_string, "String");
parse_single_value!(deserialize_char, visit_char, "char");
fn deserialize_any<V>(self, v: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
self.deserialize_str(v)
}
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
if self.url_params.len() != 1 {
return Err(PathDeserializationError::wrong_number_of_parameters()
.got(self.url_params.len())
.expected(1));
}
let key = &self.url_params[0].0;
let value = &self.url_params[0].1;
visitor
.visit_borrowed_str(value)
.map_err(|e: PathDeserializationError| {
if let ErrorKind::Message(message) = &e.kind {
PathDeserializationError::new(ErrorKind::DeserializeError {
key: key.to_string(),
value: value.as_str().to_owned(),
message: message.to_owned(),
})
} else {
e
}
})
}
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_unit()
}
fn deserialize_unit_struct<V>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_unit()
}
fn deserialize_newtype_struct<V>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_newtype_struct(self)
}
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_seq(SeqDeserializer {
params: self.url_params,
idx: 0,
})
}
fn deserialize_tuple<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
if self.url_params.len() != len {
return Err(PathDeserializationError::wrong_number_of_parameters()
.got(self.url_params.len())
.expected(len));
}
visitor.visit_seq(SeqDeserializer {
params: self.url_params,
idx: 0,
})
}
fn deserialize_tuple_struct<V>(
self,
_name: &'static str,
len: usize,
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
if self.url_params.len() != len {
return Err(PathDeserializationError::wrong_number_of_parameters()
.got(self.url_params.len())
.expected(len));
}
visitor.visit_seq(SeqDeserializer {
params: self.url_params,
idx: 0,
})
}
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_map(MapDeserializer {
params: self.url_params,
value: None,
key: None,
})
}
fn deserialize_struct<V>(
self,
_name: &'static str,
_fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
self.deserialize_map(visitor)
}
fn deserialize_enum<V>(
self,
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
if self.url_params.len() != 1 {
return Err(PathDeserializationError::wrong_number_of_parameters()
.got(self.url_params.len())
.expected(1));
}
visitor.visit_enum(EnumDeserializer {
value: &self.url_params[0].1,
})
}
}
struct MapDeserializer<'de> {
params: &'de [(Arc<str>, PercentDecodedStr)],
key: Option<KeyOrIdx<'de>>,
value: Option<&'de PercentDecodedStr>,
}
impl<'de> MapAccess<'de> for MapDeserializer<'de> {
type Error = PathDeserializationError;
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
where
K: DeserializeSeed<'de>,
{
match self.params.split_first() {
Some(((key, value), tail)) => {
self.value = Some(value);
self.params = tail;
self.key = Some(KeyOrIdx::Key(key));
seed.deserialize(KeyDeserializer { key }).map(Some)
}
None => Ok(None),
}
}
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>
where
V: DeserializeSeed<'de>,
{
match self.value.take() {
Some(value) => seed.deserialize(ValueDeserializer {
key: self.key.take(),
value,
}),
None => Err(PathDeserializationError::custom("value is missing")),
}
}
}
struct KeyDeserializer<'de> {
key: &'de str,
}
macro_rules! parse_key {
($trait_fn:ident) => {
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_str(&self.key)
}
};
}
impl<'de> Deserializer<'de> for KeyDeserializer<'de> {
type Error = PathDeserializationError;
parse_key!(deserialize_identifier);
parse_key!(deserialize_str);
parse_key!(deserialize_string);
fn deserialize_any<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
Err(PathDeserializationError::custom("Unexpected key type"))
}
forward_to_deserialize_any! {
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char bytes
byte_buf option unit unit_struct seq tuple
tuple_struct map newtype_struct struct enum ignored_any
}
}
macro_rules! parse_value {
($trait_fn:ident, $visit_fn:ident, $ty:literal) => {
fn $trait_fn<V>(mut self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
let v = self.value.parse().map_err(|_| {
if let Some(key) = self.key.take() {
let kind = match key {
KeyOrIdx::Key(key) => ErrorKind::ParseErrorAtKey {
key: key.to_owned(),
value: self.value.as_str().to_owned(),
expected_type: $ty,
},
KeyOrIdx::Idx { idx: index, key: _ } => ErrorKind::ParseErrorAtIndex {
index,
value: self.value.as_str().to_owned(),
expected_type: $ty,
},
};
PathDeserializationError::new(kind)
} else {
PathDeserializationError::new(ErrorKind::ParseError {
value: self.value.as_str().to_owned(),
expected_type: $ty,
})
}
})?;
visitor.$visit_fn(v)
}
};
}
#[derive(Debug)]
struct ValueDeserializer<'de> {
key: Option<KeyOrIdx<'de>>,
value: &'de PercentDecodedStr,
}
impl<'de> Deserializer<'de> for ValueDeserializer<'de> {
type Error = PathDeserializationError;
unsupported_type!(deserialize_map);
unsupported_type!(deserialize_identifier);
parse_value!(deserialize_bool, visit_bool, "bool");
parse_value!(deserialize_i8, visit_i8, "i8");
parse_value!(deserialize_i16, visit_i16, "i16");
parse_value!(deserialize_i32, visit_i32, "i32");
parse_value!(deserialize_i64, visit_i64, "i64");
parse_value!(deserialize_i128, visit_i128, "i128");
parse_value!(deserialize_u8, visit_u8, "u8");
parse_value!(deserialize_u16, visit_u16, "u16");
parse_value!(deserialize_u32, visit_u32, "u32");
parse_value!(deserialize_u64, visit_u64, "u64");
parse_value!(deserialize_u128, visit_u128, "u128");
parse_value!(deserialize_f32, visit_f32, "f32");
parse_value!(deserialize_f64, visit_f64, "f64");
parse_value!(deserialize_string, visit_string, "String");
parse_value!(deserialize_byte_buf, visit_string, "String");
parse_value!(deserialize_char, visit_char, "char");
fn deserialize_any<V>(self, v: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
self.deserialize_str(v)
}
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor
.visit_borrowed_str(self.value)
.map_err(|e: PathDeserializationError| {
if let (ErrorKind::Message(message), Some(key)) = (&e.kind, self.key.as_ref()) {
PathDeserializationError::new(ErrorKind::DeserializeError {
key: key.key().to_owned(),
value: self.value.as_str().to_owned(),
message: message.to_owned(),
})
} else {
e
}
})
}
fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_borrowed_bytes(self.value.as_bytes())
}
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_some(self)
}
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_unit()
}
fn deserialize_unit_struct<V>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_unit()
}
fn deserialize_newtype_struct<V>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_newtype_struct(self)
}
fn deserialize_tuple<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
struct PairDeserializer<'de> {
key: Option<KeyOrIdx<'de>>,
value: Option<&'de PercentDecodedStr>,
}
impl<'de> SeqAccess<'de> for PairDeserializer<'de> {
type Error = PathDeserializationError;
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error>
where
T: DeserializeSeed<'de>,
{
match self.key.take() {
Some(KeyOrIdx::Idx { idx: _, key }) => {
return seed.deserialize(KeyDeserializer { key }).map(Some);
}
Some(KeyOrIdx::Key(_)) => {
return Err(PathDeserializationError::custom(
"array types are not supported",
));
}
None => {}
};
self.value
.take()
.map(|value| seed.deserialize(ValueDeserializer { key: None, value }))
.transpose()
}
}
if len == 2 {
match self.key {
Some(key) => visitor.visit_seq(PairDeserializer {
key: Some(key),
value: Some(self.value),
}),
// `self.key` is only `None` when deserializing maps so `deserialize_seq`
// wouldn't be called for that
None => unreachable!(),
}
} else {
Err(PathDeserializationError::unsupported_type(type_name::<
V::Value,
>()))
}
}
fn deserialize_seq<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
Err(PathDeserializationError::unsupported_type(type_name::<
V::Value,
>()))
}
fn deserialize_tuple_struct<V>(
self,
_name: &'static str,
_len: usize,
_visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
Err(PathDeserializationError::unsupported_type(type_name::<
V::Value,
>()))
}
fn deserialize_struct<V>(
self,
_name: &'static str,
_fields: &'static [&'static str],
_visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
Err(PathDeserializationError::unsupported_type(type_name::<
V::Value,
>()))
}
fn deserialize_enum<V>(
self,
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_enum(EnumDeserializer { value: self.value })
}
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_unit()
}
}
struct EnumDeserializer<'de> {
value: &'de str,
}
impl<'de> EnumAccess<'de> for EnumDeserializer<'de> {
type Error = PathDeserializationError;
type Variant = UnitVariant;
fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error>
where
V: de::DeserializeSeed<'de>,
{
Ok((
seed.deserialize(KeyDeserializer { key: self.value })?,
UnitVariant,
))
}
}
struct UnitVariant;
impl<'de> VariantAccess<'de> for UnitVariant {
type Error = PathDeserializationError;
fn unit_variant(self) -> Result<(), Self::Error> {
Ok(())
}
fn newtype_variant_seed<T>(self, _seed: T) -> Result<T::Value, Self::Error>
where
T: DeserializeSeed<'de>,
{
Err(PathDeserializationError::unsupported_type(
"newtype enum variant",
))
}
fn tuple_variant<V>(self, _len: usize, _visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
Err(PathDeserializationError::unsupported_type(
"tuple enum variant",
))
}
fn struct_variant<V>(
self,
_fields: &'static [&'static str],
_visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
Err(PathDeserializationError::unsupported_type(
"struct enum variant",
))
}
}
struct SeqDeserializer<'de> {
params: &'de [(Arc<str>, PercentDecodedStr)],
idx: usize,
}
impl<'de> SeqAccess<'de> for SeqDeserializer<'de> {
type Error = PathDeserializationError;
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error>
where
T: DeserializeSeed<'de>,
{
match self.params.split_first() {
Some(((key, value), tail)) => {
self.params = tail;
let idx = self.idx;
self.idx += 1;
Ok(Some(seed.deserialize(ValueDeserializer {
key: Some(KeyOrIdx::Idx { idx, key }),
value,
})?))
}
None => Ok(None),
}
}
}
#[derive(Debug, Clone)]
enum KeyOrIdx<'de> {
Key(&'de str),
Idx { idx: usize, key: &'de str },
}
impl<'de> KeyOrIdx<'de> {
fn key(&self) -> &'de str {
match &self {
Self::Key(key) => key,
Self::Idx { key, .. } => key,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Deserialize;
use std::collections::HashMap;
#[derive(Debug, Deserialize, Eq, PartialEq)]
enum MyEnum {
A,
B,
#[serde(rename = "c")]
C,
}
#[derive(Debug, Deserialize, Eq, PartialEq)]
struct Struct {
c: String,
b: bool,
a: i32,
}
fn create_url_params<I, K, V>(values: I) -> Vec<(Arc<str>, PercentDecodedStr)>
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<str>,
V: AsRef<str>,
{
values
.into_iter()
.map(|(k, v)| (Arc::from(k.as_ref()), PercentDecodedStr::new(v).unwrap()))
.collect()
}
macro_rules! check_single_value {
($ty:ty, $value_str:literal, $value:expr) => {
#[allow(clippy::bool_assert_comparison)]
{
let url_params = create_url_params(vec![("value", $value_str)]);
let deserializer = PathDeserializer::new(&url_params);
assert_eq!(<$ty>::deserialize(deserializer).unwrap(), $value);
}
};
}
#[test]
fn test_parse_single_value() {
check_single_value!(bool, "true", true);
check_single_value!(bool, "false", false);
check_single_value!(i8, "-123", -123);
check_single_value!(i16, "-123", -123);
check_single_value!(i32, "-123", -123);
check_single_value!(i64, "-123", -123);
check_single_value!(i128, "123", 123);
check_single_value!(u8, "123", 123);
check_single_value!(u16, "123", 123);
check_single_value!(u32, "123", 123);
check_single_value!(u64, "123", 123);
check_single_value!(u128, "123", 123);
check_single_value!(f32, "123", 123.0);
check_single_value!(f64, "123", 123.0);
check_single_value!(String, "abc", "abc");
check_single_value!(String, "one%20two", "one two");
check_single_value!(&str, "abc", "abc");
check_single_value!(&str, "one%20two", "one two");
check_single_value!(char, "a", 'a');
let url_params = create_url_params(vec![("a", "B")]);
assert_eq!(
MyEnum::deserialize(PathDeserializer::new(&url_params)).unwrap(),
MyEnum::B
);
let url_params = create_url_params(vec![("a", "1"), ("b", "2")]);
let error_kind = i32::deserialize(PathDeserializer::new(&url_params))
.unwrap_err()
.kind;
assert!(matches!(
error_kind,
ErrorKind::WrongNumberOfParameters {
expected: 1,
got: 2
}
));
}
#[test]
fn test_parse_seq() {
let url_params = create_url_params(vec![("a", "1"), ("b", "true"), ("c", "abc")]);
assert_eq!(
<(i32, bool, String)>::deserialize(PathDeserializer::new(&url_params)).unwrap(),
(1, true, "abc".to_owned())
);
#[derive(Debug, Deserialize, Eq, PartialEq)]
struct TupleStruct(i32, bool, String);
assert_eq!(
TupleStruct::deserialize(PathDeserializer::new(&url_params)).unwrap(),
TupleStruct(1, true, "abc".to_owned())
);
let url_params = create_url_params(vec![("a", "1"), ("b", "2"), ("c", "3")]);
assert_eq!(
<Vec<i32>>::deserialize(PathDeserializer::new(&url_params)).unwrap(),
vec![1, 2, 3]
);
let url_params = create_url_params(vec![("a", "c"), ("a", "B")]);
assert_eq!(
<Vec<MyEnum>>::deserialize(PathDeserializer::new(&url_params)).unwrap(),
vec![MyEnum::C, MyEnum::B]
);
}
#[test]
fn test_parse_seq_tuple_string_string() {
let url_params = create_url_params(vec![("a", "foo"), ("b", "bar")]);
assert_eq!(
<Vec<(String, String)>>::deserialize(PathDeserializer::new(&url_params)).unwrap(),
vec![
("a".to_owned(), "foo".to_owned()),
("b".to_owned(), "bar".to_owned())
]
);
}
#[test]
fn test_parse_seq_tuple_string_parse() {
let url_params = create_url_params(vec![("a", "1"), ("b", "2")]);
assert_eq!(
<Vec<(String, u32)>>::deserialize(PathDeserializer::new(&url_params)).unwrap(),
vec![("a".to_owned(), 1), ("b".to_owned(), 2)]
);
}
#[test]
fn test_parse_struct() {
let url_params = create_url_params(vec![("a", "1"), ("b", "true"), ("c", "abc")]);
assert_eq!(
Struct::deserialize(PathDeserializer::new(&url_params)).unwrap(),
Struct {
c: "abc".to_owned(),
b: true,
a: 1,
}
);
}
#[test]
fn test_parse_struct_ignoring_additional_fields() {
let url_params = create_url_params(vec![
("a", "1"),
("b", "true"),
("c", "abc"),
("d", "false"),
]);
assert_eq!(
Struct::deserialize(PathDeserializer::new(&url_params)).unwrap(),
Struct {
c: "abc".to_owned(),
b: true,
a: 1,
}
);
}
#[test]
fn test_parse_map() {
let url_params = create_url_params(vec![("a", "1"), ("b", "true"), ("c", "abc")]);
assert_eq!(
<HashMap<String, String>>::deserialize(PathDeserializer::new(&url_params)).unwrap(),
[("a", "1"), ("b", "true"), ("c", "abc")]
.iter()
.map(|(key, value)| ((*key).to_owned(), (*value).to_owned()))
.collect()
);
}
macro_rules! test_parse_error {
(
$params:expr,
$ty:ty,
$expected_error_kind:expr $(,)?
) => {
let url_params = create_url_params($params);
let actual_error_kind = <$ty>::deserialize(PathDeserializer::new(&url_params))
.unwrap_err()
.kind;
assert_eq!(actual_error_kind, $expected_error_kind);
};
}
#[test]
fn test_parse_tuple_too_many_fields() {
test_parse_error!(
vec![("a", "abc"), ("b", "true"), ("c", "1"), ("d", "false"),],
(&str, bool, u32),
ErrorKind::WrongNumberOfParameters {
got: 4,
expected: 3,
}
);
}
#[test]
fn test_wrong_number_of_parameters_error() {
test_parse_error!(
vec![("a", "1")],
(u32, u32),
ErrorKind::WrongNumberOfParameters {
got: 1,
expected: 2,
}
);
}
#[test]
fn test_parse_error_at_key_error() {
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct Params {
a: u32,
}
test_parse_error!(
vec![("a", "false")],
Params,
ErrorKind::ParseErrorAtKey {
key: "a".to_owned(),
value: "false".to_owned(),
expected_type: "u32",
}
);
}
#[test]
fn test_parse_error_at_key_error_multiple() {
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct Params {
a: u32,
b: u32,
}
test_parse_error!(
vec![("a", "false")],
Params,
ErrorKind::ParseErrorAtKey {
key: "a".to_owned(),
value: "false".to_owned(),
expected_type: "u32",
}
);
}
#[test]
fn test_parse_error_at_index_error() {
test_parse_error!(
vec![("a", "false"), ("b", "true")],
(bool, u32),
ErrorKind::ParseErrorAtIndex {
index: 1,
value: "true".to_owned(),
expected_type: "u32",
}
);
}
#[test]
fn test_parse_error_error() {
test_parse_error!(
vec![("a", "false")],
u32,
ErrorKind::ParseError {
value: "false".to_owned(),
expected_type: "u32",
}
);
}
#[test]
fn test_unsupported_type_error_nested_data_structure() {
test_parse_error!(
vec![("a", "false")],
Vec<Vec<u32>>,
ErrorKind::UnsupportedType {
name: "alloc::vec::Vec<u32>",
}
);
}
#[test]
fn test_parse_seq_tuple_unsupported_key_type() {
test_parse_error!(
vec![("a", "false")],
Vec<(u32, String)>,
ErrorKind::Message("Unexpected key type".to_owned())
);
}
#[test]
fn test_parse_seq_wrong_tuple_length() {
test_parse_error!(
vec![("a", "false")],
Vec<(String, String, String)>,
ErrorKind::UnsupportedType {
name: "(alloc::string::String, alloc::string::String, alloc::string::String)",
}
);
}
#[test]
fn test_parse_seq_seq() {
test_parse_error!(
vec![("a", "false")],
Vec<Vec<String>>,
ErrorKind::UnsupportedType {
name: "alloc::vec::Vec<alloc::string::String>",
}
);
}
#[test]
fn test_deserialize_key_value() {
test_parse_error!(
vec![("id", "123123-123-123123")],
uuid::Uuid,
ErrorKind::DeserializeError {
key: "id".to_owned(),
value: "123123-123-123123".to_owned(),
message: "UUID parsing failed: invalid group count: expected 5, found 3".to_owned(),
}
);
}
}
File diff suppressed because it is too large Load Diff
+205
View File
@@ -0,0 +1,205 @@
use super::{rejection::*, FromRequestParts};
use http::{request::Parts, Uri};
use serde_core::de::DeserializeOwned;
/// Extractor that deserializes query strings into some type.
///
/// `T` is expected to implement [`serde::Deserialize`].
///
/// # Examples
///
/// ```rust,no_run
/// use axum::{
/// extract::Query,
/// routing::get,
/// Router,
/// };
/// use serde::Deserialize;
///
/// #[derive(Deserialize)]
/// struct Pagination {
/// page: usize,
/// per_page: usize,
/// }
///
/// // This will parse query strings like `?page=2&per_page=30` into `Pagination`
/// // structs.
/// async fn list_things(pagination: Query<Pagination>) {
/// let pagination: Pagination = pagination.0;
///
/// // ...
/// }
///
/// let app = Router::new().route("/list_things", get(list_things));
/// # let _: Router = app;
/// ```
///
/// If the query string cannot be parsed it will reject the request with a `400
/// Bad Request` response.
///
/// For handling values being empty vs missing see the [query-params-with-empty-strings][example]
/// example.
///
/// [example]: https://github.com/tokio-rs/axum/blob/v0.8.x/examples/query-params-with-empty-strings/src/main.rs
///
/// For handling multiple values for the same query parameter, in a `?foo=1&foo=2&foo=3`
/// fashion, use [`axum_extra::extract::Query`] instead.
///
/// [`axum_extra::extract::Query`]: https://docs.rs/axum-extra/latest/axum_extra/extract/struct.Query.html
#[cfg_attr(docsrs, doc(cfg(feature = "query")))]
#[derive(Debug, Clone, Copy, Default)]
pub struct Query<T>(pub T);
impl<T, S> FromRequestParts<S> for Query<T>
where
T: DeserializeOwned,
S: Send + Sync,
{
type Rejection = QueryRejection;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
Self::try_from_uri(&parts.uri)
}
}
impl<T> Query<T>
where
T: DeserializeOwned,
{
/// Attempts to construct a [`Query`] from a reference to a [`Uri`].
///
/// # Example
/// ```
/// use axum::extract::Query;
/// use http::Uri;
/// use serde::Deserialize;
///
/// #[derive(Deserialize)]
/// struct ExampleParams {
/// foo: String,
/// bar: u32,
/// }
///
/// let uri: Uri = "http://example.com/path?foo=hello&bar=42".parse().unwrap();
/// let result: Query<ExampleParams> = Query::try_from_uri(&uri).unwrap();
/// assert_eq!(result.foo, String::from("hello"));
/// assert_eq!(result.bar, 42);
/// ```
pub fn try_from_uri(value: &Uri) -> Result<Self, QueryRejection> {
let query = value.query().unwrap_or_default();
let deserializer =
serde_urlencoded::Deserializer::new(form_urlencoded::parse(query.as_bytes()));
let params = serde_path_to_error::deserialize(deserializer)
.map_err(FailedToDeserializeQueryString::from_err)?;
Ok(Query(params))
}
}
axum_core::__impl_deref!(Query);
#[cfg(test)]
mod tests {
use crate::{routing::get, test_helpers::TestClient, Router};
use super::*;
use axum_core::{body::Body, extract::FromRequest};
use http::{Request, StatusCode};
use serde::Deserialize;
use std::fmt::Debug;
async fn check<T>(uri: impl AsRef<str>, value: T)
where
T: DeserializeOwned + PartialEq + Debug,
{
let req = Request::builder()
.uri(uri.as_ref())
.body(Body::empty())
.unwrap();
assert_eq!(Query::<T>::from_request(req, &()).await.unwrap().0, value);
}
#[crate::test]
async fn test_query() {
#[derive(Debug, PartialEq, Deserialize)]
struct Pagination {
size: Option<u64>,
page: Option<u64>,
}
check(
"http://example.com/test",
Pagination {
size: None,
page: None,
},
)
.await;
check(
"http://example.com/test?size=10",
Pagination {
size: Some(10),
page: None,
},
)
.await;
check(
"http://example.com/test?size=10&page=20",
Pagination {
size: Some(10),
page: Some(20),
},
)
.await;
}
#[crate::test]
async fn correct_rejection_status_code() {
#[derive(Deserialize)]
#[allow(dead_code)]
struct Params {
n: i32,
}
async fn handler(_: Query<Params>) {}
let app = Router::new().route("/", get(handler));
let client = TestClient::new(app);
let res = client.get("/?n=hi").await;
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
assert_eq!(
res.text().await,
"Failed to deserialize query string: n: invalid digit found in string"
);
}
#[test]
fn test_try_from_uri() {
#[derive(Deserialize)]
struct TestQueryParams {
foo: String,
bar: u32,
}
let uri: Uri = "http://example.com/path?foo=hello&bar=42".parse().unwrap();
let result: Query<TestQueryParams> = Query::try_from_uri(&uri).unwrap();
assert_eq!(result.foo, String::from("hello"));
assert_eq!(result.bar, 42);
}
#[test]
fn test_try_from_uri_with_invalid_query() {
#[derive(Deserialize)]
struct TestQueryParams {
_foo: String,
_bar: u32,
}
let uri: Uri = "http://example.com/path?foo=hello&bar=invalid"
.parse()
.unwrap();
let result: Result<Query<TestQueryParams>, _> = Query::try_from_uri(&uri);
assert!(result.is_err());
}
}
+104
View File
@@ -0,0 +1,104 @@
use axum_core::extract::{FromRequest, Request};
use bytes::Bytes;
use http::Method;
use super::{
has_content_type,
rejection::{InvalidFormContentType, RawFormRejection},
};
/// Extractor that extracts raw form requests.
///
/// For `GET` requests it will extract the raw query. For other methods it extracts the raw
/// `application/x-www-form-urlencoded` encoded request body.
///
/// # Example
///
/// ```rust,no_run
/// use axum::{
/// extract::RawForm,
/// routing::get,
/// Router
/// };
///
/// async fn handler(RawForm(form): RawForm) {}
///
/// let app = Router::new().route("/", get(handler));
/// # let _: Router = app;
/// ```
#[derive(Debug)]
pub struct RawForm(pub Bytes);
impl<S> FromRequest<S> for RawForm
where
S: Send + Sync,
{
type Rejection = RawFormRejection;
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
if req.method() == Method::GET {
if let Some(query) = req.uri().query() {
return Ok(Self(Bytes::copy_from_slice(query.as_bytes())));
}
Ok(Self(Bytes::new()))
} else {
if !has_content_type(req.headers(), &mime::APPLICATION_WWW_FORM_URLENCODED) {
return Err(InvalidFormContentType.into());
}
Ok(Self(Bytes::from_request(req, state).await?))
}
}
}
#[cfg(test)]
mod tests {
use axum_core::body::Body;
use http::{header::CONTENT_TYPE, Request};
use super::{InvalidFormContentType, RawForm, RawFormRejection};
use crate::extract::FromRequest;
async fn check_query(uri: &str, value: &[u8]) {
let req = Request::builder().uri(uri).body(Body::empty()).unwrap();
assert_eq!(RawForm::from_request(req, &()).await.unwrap().0, value);
}
async fn check_body(body: &'static [u8]) {
let req = Request::post("http://example.com/test")
.header(CONTENT_TYPE, mime::APPLICATION_WWW_FORM_URLENCODED.as_ref())
.body(Body::from(body))
.unwrap();
assert_eq!(RawForm::from_request(req, &()).await.unwrap().0, body);
}
#[crate::test]
async fn test_from_query() {
check_query("http://example.com/test", b"").await;
check_query("http://example.com/test?page=0&size=10", b"page=0&size=10").await;
}
#[crate::test]
async fn test_from_body() {
check_body(b"").await;
check_body(b"username=user&password=secure%20password").await;
}
#[crate::test]
async fn test_incorrect_content_type() {
let req = Request::post("http://example.com/test")
.body(Body::from("page=0&size=10"))
.unwrap();
assert!(matches!(
RawForm::from_request(req, &()).await.unwrap_err(),
RawFormRejection::InvalidFormContentType(InvalidFormContentType)
))
}
}
+37
View File
@@ -0,0 +1,37 @@
use super::FromRequestParts;
use http::request::Parts;
use std::convert::Infallible;
/// Extractor that extracts the raw query string, without parsing it.
///
/// # Example
///
/// ```rust,no_run
/// use axum::{
/// extract::RawQuery,
/// routing::get,
/// Router,
/// };
/// use futures_util::StreamExt;
///
/// async fn handler(RawQuery(query): RawQuery) {
/// // ...
/// }
///
/// let app = Router::new().route("/users", get(handler));
/// # let _: Router = app;
/// ```
#[derive(Debug)]
pub struct RawQuery(pub Option<String>);
impl<S> FromRequestParts<S> for RawQuery
where
S: Send + Sync,
{
type Rejection = Infallible;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
let query = parts.uri.query().map(|query| query.to_owned());
Ok(Self(query))
}
}
+200
View File
@@ -0,0 +1,200 @@
//! Rejection response types.
use axum_core::__composite_rejection as composite_rejection;
use axum_core::__define_rejection as define_rejection;
pub use crate::extract::path::{FailedToDeserializePathParams, InvalidUtf8InPathParam};
pub use axum_core::extract::rejection::*;
#[cfg(feature = "json")]
define_rejection! {
#[status = UNPROCESSABLE_ENTITY]
#[body = "Failed to deserialize the JSON body into the target type"]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
/// Rejection type for [`Json`](super::Json).
///
/// This rejection is used if the request body is syntactically valid JSON but couldn't be
/// deserialized into the target type.
pub struct JsonDataError(Error);
}
#[cfg(feature = "json")]
define_rejection! {
#[status = BAD_REQUEST]
#[body = "Failed to parse the request body as JSON"]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
/// Rejection type for [`Json`](super::Json).
///
/// This rejection is used if the request body didn't contain syntactically valid JSON.
pub struct JsonSyntaxError(Error);
}
#[cfg(feature = "json")]
define_rejection! {
#[status = UNSUPPORTED_MEDIA_TYPE]
#[body = "Expected request with `Content-Type: application/json`"]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
/// Rejection type for [`Json`](super::Json) used if the `Content-Type`
/// header is missing.
pub struct MissingJsonContentType;
}
define_rejection! {
#[status = INTERNAL_SERVER_ERROR]
#[body = "Missing request extension"]
/// Rejection type for [`Extension`](super::Extension) if an expected
/// request extension was not found.
pub struct MissingExtension(Error);
}
define_rejection! {
#[status = INTERNAL_SERVER_ERROR]
#[body = "No paths parameters found for matched route"]
/// Rejection type used if axum's internal representation of path parameters
/// is missing. This is commonly caused by extracting `Request<_>`. `Path`
/// must be extracted first.
pub struct MissingPathParams;
}
define_rejection! {
#[status = UNSUPPORTED_MEDIA_TYPE]
#[body = "Form requests must have `Content-Type: application/x-www-form-urlencoded`"]
/// Rejection type for [`Form`](super::Form) or [`RawForm`](super::RawForm)
/// used if the `Content-Type` header is missing
/// or its value is not `application/x-www-form-urlencoded`.
pub struct InvalidFormContentType;
}
define_rejection! {
#[status = BAD_REQUEST]
#[body = "Failed to deserialize form"]
/// Rejection type used if the [`Form`](super::Form) extractor is unable to
/// deserialize the form into the target type.
pub struct FailedToDeserializeForm(Error);
}
define_rejection! {
#[status = UNPROCESSABLE_ENTITY]
#[body = "Failed to deserialize form body"]
/// Rejection type used if the [`Form`](super::Form) extractor is unable to
/// deserialize the form body into the target type.
pub struct FailedToDeserializeFormBody(Error);
}
define_rejection! {
#[status = BAD_REQUEST]
#[body = "Failed to deserialize query string"]
/// Rejection type used if the [`Query`](super::Query) extractor is unable to
/// deserialize the query string into the target type.
pub struct FailedToDeserializeQueryString(Error);
}
composite_rejection! {
/// Rejection used for [`Query`](super::Query).
///
/// Contains one variant for each way the [`Query`](super::Query) extractor
/// can fail.
pub enum QueryRejection {
FailedToDeserializeQueryString,
}
}
composite_rejection! {
/// Rejection used for [`Form`](super::Form).
///
/// Contains one variant for each way the [`Form`](super::Form) extractor
/// can fail.
pub enum FormRejection {
InvalidFormContentType,
FailedToDeserializeForm,
FailedToDeserializeFormBody,
BytesRejection,
}
}
composite_rejection! {
/// Rejection used for [`RawForm`](super::RawForm).
///
/// Contains one variant for each way the [`RawForm`](super::RawForm) extractor
/// can fail.
pub enum RawFormRejection {
InvalidFormContentType,
BytesRejection,
}
}
#[cfg(feature = "json")]
composite_rejection! {
/// Rejection used for [`Json`](super::Json).
///
/// Contains one variant for each way the [`Json`](super::Json) extractor
/// can fail.
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
pub enum JsonRejection {
JsonDataError,
JsonSyntaxError,
MissingJsonContentType,
BytesRejection,
}
}
composite_rejection! {
/// Rejection used for [`Extension`](super::Extension).
///
/// Contains one variant for each way the [`Extension`](super::Extension) extractor
/// can fail.
pub enum ExtensionRejection {
MissingExtension,
}
}
composite_rejection! {
/// Rejection used for [`Path`](super::Path).
///
/// Contains one variant for each way the [`Path`](super::Path) extractor
/// can fail.
pub enum PathRejection {
FailedToDeserializePathParams,
MissingPathParams,
}
}
composite_rejection! {
/// Rejection used for [`RawPathParams`](super::RawPathParams).
///
/// Contains one variant for each way the [`RawPathParams`](super::RawPathParams) extractor
/// can fail.
pub enum RawPathParamsRejection {
InvalidUtf8InPathParam,
MissingPathParams,
}
}
#[cfg(feature = "matched-path")]
define_rejection! {
#[status = INTERNAL_SERVER_ERROR]
#[body = "No matched path found"]
/// Rejection if no matched path could be found.
///
/// See [`MatchedPath`](super::MatchedPath) for more details.
#[cfg_attr(docsrs, doc(cfg(feature = "matched-path")))]
pub struct MatchedPathMissing;
}
#[cfg(feature = "matched-path")]
composite_rejection! {
/// Rejection used for [`MatchedPath`](super::MatchedPath).
#[cfg_attr(docsrs, doc(cfg(feature = "matched-path")))]
pub enum MatchedPathRejection {
MatchedPathMissing,
}
}
define_rejection! {
#[status = INTERNAL_SERVER_ERROR]
#[body = "The matched route is not nested"]
/// Rejection type for [`NestedPath`](super::NestedPath).
///
/// This rejection is used if the matched route wasn't nested.
pub struct NestedPathRejection;
}
+331
View File
@@ -0,0 +1,331 @@
use axum_core::extract::{FromRef, FromRequestParts};
use http::request::Parts;
use std::{
convert::Infallible,
ops::{Deref, DerefMut},
};
/// Extractor for state.
///
/// See ["Sharing state with handlers"][sharing-state] for an overview of all approaches to
/// sharing state, including when to use `Arc`, how `FromRef` substates work, and what the
/// `Router<S>` type parameter means.
///
/// See ["Accessing state in middleware"][state-from-middleware] for how to
/// access state in middleware.
///
/// State is global and used in every request a router with state receives.
/// For accessing data derived from requests, such as authorization data, see [`Extension`].
///
/// [sharing-state]: crate#sharing-state-with-handlers
/// [state-from-middleware]: crate::middleware#accessing-state-in-middleware
/// [`Extension`]: crate::Extension
///
/// # With `Router`
///
/// ```
/// use axum::{Router, routing::get, extract::State};
///
/// // the application state
/// //
/// // here you can put configuration, database connection pools, or whatever
/// // state you need
/// #[derive(Clone)]
/// struct AppState {}
///
/// let state = AppState {};
///
/// // create a `Router` that holds our state
/// let app = Router::new()
/// .route("/", get(handler))
/// // provide the state so the router can access it
/// .with_state(state);
///
/// async fn handler(
/// // access the state via the `State` extractor
/// // extracting a state of the wrong type results in a compile error
/// State(state): State<AppState>,
/// ) {
/// // use `state`...
/// }
/// # let _: axum::Router = app;
/// ```
///
/// Note that `State` is an extractor, so be sure to put it before any body
/// extractors, see ["the order of extractors"][order-of-extractors].
///
/// [order-of-extractors]: crate::extract#the-order-of-extractors
///
/// ## Combining stateful routers
///
/// Multiple [`Router`]s can be combined with [`Router::nest`] or [`Router::merge`]
/// When combining [`Router`]s with one of these methods, the [`Router`]s must have
/// the same state type. Generally, this can be inferred automatically:
///
/// ```
/// use axum::{Router, routing::get, extract::State};
///
/// #[derive(Clone)]
/// struct AppState {}
///
/// let state = AppState {};
///
/// // create a `Router` that will be nested within another
/// let api = Router::new()
/// .route("/posts", get(posts_handler));
///
/// let app = Router::new()
/// .nest("/api", api)
/// .with_state(state);
///
/// async fn posts_handler(State(state): State<AppState>) {
/// // use `state`...
/// }
/// # let _: axum::Router = app;
/// ```
///
/// However, if you are composing [`Router`]s that are defined in separate scopes,
/// you may need to annotate the [`State`] type explicitly:
///
/// ```
/// use axum::{Router, routing::get, extract::State};
///
/// #[derive(Clone)]
/// struct AppState {}
///
/// fn make_app() -> Router {
/// let state = AppState {};
///
/// Router::new()
/// .nest("/api", make_api())
/// .with_state(state) // the outer Router's state is inferred
/// }
///
/// // the inner Router must specify its state type to compose with the
/// // outer router
/// fn make_api() -> Router<AppState> {
/// Router::new()
/// .route("/posts", get(posts_handler))
/// }
///
/// async fn posts_handler(State(state): State<AppState>) {
/// // use `state`...
/// }
/// # let _: axum::Router = make_app();
/// ```
///
/// In short, a [`Router`]'s generic state type defaults to `()`
/// (no state) unless [`Router::with_state`] is called or the value
/// of the generic type is given explicitly.
///
/// [`Router`]: crate::Router
/// [`Router::merge`]: crate::Router::merge
/// [`Router::nest`]: crate::Router::nest
/// [`Router::with_state`]: crate::Router::with_state
///
/// # With `MethodRouter`
///
/// ```
/// use axum::{routing::get, extract::State};
///
/// #[derive(Clone)]
/// struct AppState {}
///
/// let state = AppState {};
///
/// let method_router_with_state = get(handler)
/// // provide the state so the handler can access it
/// .with_state(state);
/// # let _: axum::routing::MethodRouter = method_router_with_state;
///
/// async fn handler(State(state): State<AppState>) {
/// // use `state`...
/// }
/// ```
///
/// # With `Handler`
///
/// ```
/// use axum::{routing::get, handler::Handler, extract::State};
///
/// #[derive(Clone)]
/// struct AppState {}
///
/// let state = AppState {};
///
/// async fn handler(State(state): State<AppState>) {
/// // use `state`...
/// }
///
/// // provide the state so the handler can access it
/// let handler_with_state = handler.with_state(state);
///
/// # async {
/// let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
/// axum::serve(listener, handler_with_state.into_make_service()).await.unwrap();
/// # };
/// ```
///
/// # Substates
///
/// [`State`] only allows a single state type but you can use [`FromRef`] to extract "substates":
///
/// ```
/// use axum::{Router, routing::get, extract::{State, FromRef}};
///
/// // the application state
/// #[derive(Clone)]
/// struct AppState {
/// // that holds some api specific state
/// api_state: ApiState,
/// }
///
/// // the api specific state
/// #[derive(Clone)]
/// struct ApiState {}
///
/// // support converting an `AppState` in an `ApiState`
/// impl FromRef<AppState> for ApiState {
/// fn from_ref(app_state: &AppState) -> ApiState {
/// app_state.api_state.clone()
/// }
/// }
///
/// let state = AppState {
/// api_state: ApiState {},
/// };
///
/// let app = Router::new()
/// .route("/", get(handler))
/// .route("/api/users", get(api_users))
/// .with_state(state);
///
/// async fn api_users(
/// // access the api specific state
/// State(api_state): State<ApiState>,
/// ) {
/// }
///
/// async fn handler(
/// // we can still access to top level state
/// State(state): State<AppState>,
/// ) {
/// }
/// # let _: axum::Router = app;
/// ```
///
/// For convenience `FromRef` can also be derived using `#[derive(FromRef)]`.
///
/// # For library authors
///
/// If you're writing a library that has an extractor that needs state, this is the recommended way
/// to do it:
///
/// ```rust
/// use axum_core::extract::{FromRequestParts, FromRef};
/// use http::request::Parts;
/// use std::convert::Infallible;
///
/// // the extractor your library provides
/// struct MyLibraryExtractor;
///
/// impl<S> FromRequestParts<S> for MyLibraryExtractor
/// where
/// // keep `S` generic but require that it can produce a `MyLibraryState`
/// // this means users will have to implement `FromRef<UserState> for MyLibraryState`
/// MyLibraryState: FromRef<S>,
/// S: Send + Sync,
/// {
/// type Rejection = Infallible;
///
/// async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
/// // get a `MyLibraryState` from a reference to the state
/// let state = MyLibraryState::from_ref(state);
///
/// // ...
/// # todo!()
/// }
/// }
///
/// // the state your library needs
/// struct MyLibraryState {
/// // ...
/// }
/// ```
///
/// # Shared mutable state
///
/// [As state is global within a `Router`][global] you can't directly get a mutable reference to
/// the state.
///
/// The most basic solution is to use an `Arc<Mutex<_>>`. Which kind of mutex you need depends on
/// your use case. See [the tokio docs] for more details.
///
/// Note that holding a locked `std::sync::Mutex` across `.await` points will result in `!Send`
/// futures which are incompatible with axum. If you need to hold a mutex across `.await` points,
/// consider using a `tokio::sync::Mutex` instead.
///
/// ## Example
///
/// ```
/// use axum::{Router, routing::get, extract::State};
/// use std::sync::{Arc, Mutex};
///
/// #[derive(Clone)]
/// struct AppState {
/// data: Arc<Mutex<String>>,
/// }
///
/// async fn handler(State(state): State<AppState>) {
/// {
/// let mut data = state.data.lock().expect("mutex was poisoned");
/// *data = "updated foo".to_owned();
/// }
///
/// // ...
/// }
///
/// let state = AppState {
/// data: Arc::new(Mutex::new("foo".to_owned())),
/// };
///
/// let app = Router::new()
/// .route("/", get(handler))
/// .with_state(state);
/// # let _: Router = app;
/// ```
///
/// [global]: crate::Router::with_state
/// [the tokio docs]: https://docs.rs/tokio/1.25.0/tokio/sync/struct.Mutex.html#which-kind-of-mutex-should-you-use
#[derive(Debug, Default, Clone, Copy)]
pub struct State<S>(pub S);
impl<OuterState, InnerState> FromRequestParts<OuterState> for State<InnerState>
where
InnerState: FromRef<OuterState>,
OuterState: Send + Sync,
{
type Rejection = Infallible;
async fn from_request_parts(
_parts: &mut Parts,
state: &OuterState,
) -> Result<Self, Self::Rejection> {
let inner_state = InnerState::from_ref(state);
Ok(Self(inner_state))
}
}
impl<S> Deref for State<S> {
type Target = S;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<S> DerefMut for State<S> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
File diff suppressed because it is too large Load Diff
+277
View File
@@ -0,0 +1,277 @@
use crate::extract::Request;
use crate::extract::{rejection::*, FromRequest, RawForm};
use axum_core::response::{IntoResponse, Response};
use axum_core::RequestExt;
use http::header::CONTENT_TYPE;
use http::StatusCode;
use serde_core::{de::DeserializeOwned, Serialize};
/// URL encoded extractor and response.
///
/// # As extractor
///
/// If used as an extractor, `Form` will deserialize form data from the request,
/// specifically:
///
/// - If the request has a method of `GET` or `HEAD`, the form data will be read
/// from the query string (same as with [`Query`])
/// - If the request has a different method, the form will be read from the body
/// of the request. It must have a `content-type` of
/// `application/x-www-form-urlencoded` for this to work. If you want to parse
/// `multipart/form-data` request bodies, use [`Multipart`] instead.
///
/// This matches how HTML forms are sent by browsers by default.
/// In both cases, the inner type `T` must implement [`serde::Deserialize`].
///
/// ⚠️ Since parsing form data might require consuming the request body, the `Form` extractor must be
/// *last* if there are multiple extractors in a handler. See ["the order of
/// extractors"][order-of-extractors]
///
/// [order-of-extractors]: crate::extract#the-order-of-extractors
///
/// ```rust
/// use axum::Form;
/// use serde::Deserialize;
///
/// #[derive(Deserialize)]
/// struct SignUp {
/// username: String,
/// password: String,
/// }
///
/// async fn accept_form(Form(sign_up): Form<SignUp>) {
/// // ...
/// }
/// ```
///
/// # As response
///
/// `Form` can also be used to encode any type that implements
/// [`serde::Serialize`] as `application/x-www-form-urlencoded`
///
/// ```rust
/// use axum::Form;
/// use serde::Serialize;
///
/// #[derive(Serialize)]
/// struct Payload {
/// value: String,
/// }
///
/// async fn handler() -> Form<Payload> {
/// Form(Payload { value: "foo".to_owned() })
/// }
/// ```
///
/// [`Query`]: crate::extract::Query
/// [`Multipart`]: crate::extract::Multipart
#[cfg_attr(docsrs, doc(cfg(feature = "form")))]
#[derive(Debug, Clone, Copy, Default)]
#[must_use]
pub struct Form<T>(pub T);
impl<T, S> FromRequest<S> for Form<T>
where
T: DeserializeOwned,
S: Send + Sync,
{
type Rejection = FormRejection;
async fn from_request(req: Request, _state: &S) -> Result<Self, Self::Rejection> {
let is_get_or_head =
req.method() == http::Method::GET || req.method() == http::Method::HEAD;
match req.extract().await {
Ok(RawForm(bytes)) => {
let deserializer =
serde_urlencoded::Deserializer::new(form_urlencoded::parse(&bytes));
let value = serde_path_to_error::deserialize(deserializer).map_err(
|err| -> FormRejection {
if is_get_or_head {
FailedToDeserializeForm::from_err(err).into()
} else {
FailedToDeserializeFormBody::from_err(err).into()
}
},
)?;
Ok(Form(value))
}
Err(RawFormRejection::BytesRejection(r)) => Err(FormRejection::BytesRejection(r)),
Err(RawFormRejection::InvalidFormContentType(r)) => {
Err(FormRejection::InvalidFormContentType(r))
}
}
}
}
impl<T> IntoResponse for Form<T>
where
T: Serialize,
{
fn into_response(self) -> Response {
// Extracted into separate fn so it's only compiled once for all T.
fn make_response(ser_result: Result<String, serde_urlencoded::ser::Error>) -> Response {
match ser_result {
Ok(body) => (
[(CONTENT_TYPE, mime::APPLICATION_WWW_FORM_URLENCODED.as_ref())],
body,
)
.into_response(),
Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response(),
}
}
make_response(serde_urlencoded::to_string(&self.0))
}
}
axum_core::__impl_deref!(Form);
#[cfg(test)]
mod tests {
use crate::{
routing::{on, MethodFilter},
test_helpers::TestClient,
Router,
};
use super::*;
use axum_core::body::Body;
use http::{Method, Request};
use mime::APPLICATION_WWW_FORM_URLENCODED;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Pagination {
size: Option<u64>,
page: Option<u64>,
}
async fn check_query<T: DeserializeOwned + PartialEq + Debug>(uri: impl AsRef<str>, value: T) {
let req = Request::builder()
.uri(uri.as_ref())
.body(Body::empty())
.unwrap();
assert_eq!(Form::<T>::from_request(req, &()).await.unwrap().0, value);
}
async fn check_body<T: Serialize + DeserializeOwned + PartialEq + Debug>(value: T) {
let req = Request::builder()
.uri("http://example.com/test")
.method(Method::POST)
.header(CONTENT_TYPE, APPLICATION_WWW_FORM_URLENCODED.as_ref())
.body(Body::from(serde_urlencoded::to_string(&value).unwrap()))
.unwrap();
assert_eq!(Form::<T>::from_request(req, &()).await.unwrap().0, value);
}
#[crate::test]
async fn test_form_query() {
check_query(
"http://example.com/test",
Pagination {
size: None,
page: None,
},
)
.await;
check_query(
"http://example.com/test?size=10",
Pagination {
size: Some(10),
page: None,
},
)
.await;
check_query(
"http://example.com/test?size=10&page=20",
Pagination {
size: Some(10),
page: Some(20),
},
)
.await;
}
#[crate::test]
async fn test_form_body() {
check_body(Pagination {
size: None,
page: None,
})
.await;
check_body(Pagination {
size: Some(10),
page: None,
})
.await;
check_body(Pagination {
size: Some(10),
page: Some(20),
})
.await;
}
#[crate::test]
async fn test_incorrect_content_type() {
let req = Request::builder()
.uri("http://example.com/test")
.method(Method::POST)
.header(CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
.body(Body::from(
serde_urlencoded::to_string(&Pagination {
size: Some(10),
page: None,
})
.unwrap(),
))
.unwrap();
assert!(matches!(
Form::<Pagination>::from_request(req, &())
.await
.unwrap_err(),
FormRejection::InvalidFormContentType(InvalidFormContentType)
));
}
#[tokio::test]
async fn deserialize_error_status_codes() {
#[allow(dead_code)]
#[derive(Deserialize)]
struct Payload {
a: i32,
}
let app = Router::new().route(
"/",
on(
MethodFilter::GET.or(MethodFilter::POST),
|_: Form<Payload>| async {},
),
);
let client = TestClient::new(app);
let res = client.get("/?a=false").await;
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
assert_eq!(
res.text().await,
"Failed to deserialize form: a: invalid digit found in string"
);
let res = client
.post("/")
.header(CONTENT_TYPE, APPLICATION_WWW_FORM_URLENCODED.as_ref())
.body("a=false")
.await;
assert_eq!(res.status(), StatusCode::UNPROCESSABLE_ENTITY);
assert_eq!(
res.text().await,
"Failed to deserialize form body: a: invalid digit found in string"
);
}
}
+52
View File
@@ -0,0 +1,52 @@
//! Handler future types.
use crate::response::Response;
use axum_core::extract::Request;
use futures_util::future::Map;
use pin_project_lite::pin_project;
use std::{convert::Infallible, future::Future, pin::Pin, task::Context};
use tower::util::Oneshot;
use tower_service::Service;
opaque_future! {
/// The response future for [`IntoService`](super::IntoService).
pub type IntoServiceFuture<F> =
Map<
F,
fn(Response) -> Result<Response, Infallible>,
>;
}
pin_project! {
/// The response future for [`Layered`](super::Layered).
pub struct LayeredFuture<S>
where
S: Service<Request>,
{
#[pin]
inner: Map<Oneshot<S, Request>, fn(Result<S::Response, S::Error>) -> Response>,
}
}
impl<S> LayeredFuture<S>
where
S: Service<Request>,
{
pub(super) fn new(
inner: Map<Oneshot<S, Request>, fn(Result<S::Response, S::Error>) -> Response>,
) -> Self {
Self { inner }
}
}
impl<S> Future for LayeredFuture<S>
where
S: Service<Request>,
{
type Output = Response;
#[inline]
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> std::task::Poll<Self::Output> {
self.project().inner.poll(cx)
}
}
+447
View File
@@ -0,0 +1,447 @@
//! Async functions that can be used to handle requests.
//!
#![doc = include_str!("../docs/handlers_intro.md")]
//!
//! Some examples of handlers:
//!
//! ```rust
//! use axum::{body::Bytes, http::StatusCode};
//!
//! // Handler that immediately returns an empty `200 OK` response.
//! async fn unit_handler() {}
//!
//! // Handler that immediately returns a `200 OK` response with a plain text
//! // body.
//! async fn string_handler() -> String {
//! "Hello, World!".to_string()
//! }
//!
//! // Handler that buffers the request body and returns it.
//! //
//! // This works because `Bytes` implements `FromRequest`
//! // and therefore can be used as an extractor.
//! //
//! // `String` and `StatusCode` both implement `IntoResponse` and
//! // therefore `Result<String, StatusCode>` also implements `IntoResponse`
//! async fn echo(body: Bytes) -> Result<String, StatusCode> {
//! if let Ok(string) = String::from_utf8(body.to_vec()) {
//! Ok(string)
//! } else {
//! Err(StatusCode::BAD_REQUEST)
//! }
//! }
//! ```
//!
//! Instead of a direct `StatusCode`, it makes sense to use intermediate error type
//! that can ultimately be converted to `Response`. This allows using `?` operator
//! in handlers. See those examples:
//!
//! * [`anyhow-error-response`][anyhow] for generic boxed errors
//! * [`error-handling`][error-handling] for application-specific detailed errors
//!
//! [anyhow]: https://github.com/tokio-rs/axum/blob/main/examples/anyhow-error-response/src/main.rs
//! [error-handling]: https://github.com/tokio-rs/axum/blob/main/examples/error-handling/src/main.rs
//!
#![doc = include_str!("../docs/debugging_handler_type_errors.md")]
#[cfg(feature = "tokio")]
use crate::extract::connect_info::IntoMakeServiceWithConnectInfo;
use crate::{
extract::{FromRequest, FromRequestParts, Request},
response::{IntoResponse, Response},
routing::IntoMakeService,
};
use std::{convert::Infallible, fmt, future::Future, marker::PhantomData, pin::Pin};
use tower::ServiceExt;
use tower_layer::Layer;
use tower_service::Service;
pub mod future;
mod service;
pub use self::service::HandlerService;
/// Trait for async functions that can be used to handle requests.
///
/// You shouldn't need to depend on this trait directly. It is automatically
/// implemented to closures of the right types.
///
/// See the [module docs](crate::handler) for more details.
///
/// # Converting `Handler`s into [`Service`]s
///
/// To convert `Handler`s into [`Service`]s you have to call either
/// [`HandlerWithoutStateExt::into_service`] or [`Handler::with_state`]:
///
/// ```
/// use tower::Service;
/// use axum::{
/// extract::{State, Request},
/// body::Body,
/// handler::{HandlerWithoutStateExt, Handler},
/// };
///
/// // this handler doesn't require any state
/// async fn one() {}
/// // so it can be converted to a service with `HandlerWithoutStateExt::into_service`
/// assert_service(one.into_service());
///
/// // this handler requires state
/// async fn two(_: State<String>) {}
/// // so we have to provide it
/// let handler_with_state = two.with_state(String::new());
/// // which gives us a `Service`
/// assert_service(handler_with_state);
///
/// // helper to check that a value implements `Service`
/// fn assert_service<S>(service: S)
/// where
/// S: Service<Request>,
/// {}
/// ```
#[doc = include_str!("../docs/debugging_handler_type_errors.md")]
///
/// # Handlers that aren't functions
///
/// The `Handler` trait is also implemented for `T: IntoResponse`. That allows easily returning
/// fixed data for routes:
///
/// ```
/// use axum::{
/// Router,
/// routing::{get, post},
/// Json,
/// http::StatusCode,
/// };
/// use serde_json::json;
///
/// let app = Router::new()
/// // respond with a fixed string
/// .route("/", get("Hello, World!"))
/// // or return some mock data
/// .route("/users", post((
/// StatusCode::CREATED,
/// Json(json!({ "id": 1, "username": "alice" })),
/// )));
/// # let _: Router = app;
/// ```
///
/// # About type parameter `T`
///
/// **Generally you shouldn't need to worry about `T`**; when calling methods such as
/// [`post`](crate::routing::method_routing::post) it will be automatically inferred and this is
/// the intended way for this parameter to be provided in application code.
///
/// If you are implementing your own methods that accept implementations of `Handler` as
/// arguments, then the following may be useful:
///
/// The type parameter `T` is a workaround for trait coherence rules, allowing us to
/// write blanket implementations of `Handler` over many types of handler functions
/// with different numbers of arguments, without the compiler forbidding us from doing
/// so because one type `F` can in theory implement both `Fn(A) -> X` and `Fn(A, B) -> Y`.
/// `T` is a placeholder taking on a representation of the parameters of the handler function,
/// as well as other similar 'coherence rule workaround' discriminators,
/// allowing us to select one function signature to use as a `Handler`.
#[diagnostic::on_unimplemented(
note = "Consider using `#[axum::debug_handler]` to improve the error message"
)]
pub trait Handler<T, S>: Clone + Send + Sync + Sized + 'static {
/// The type of future calling this handler returns.
type Future: Future<Output = Response> + Send + 'static;
/// Call the handler with the given request.
fn call(self, req: Request, state: S) -> Self::Future;
/// Apply a [`tower::Layer`] to the handler.
///
/// All requests to the handler will be processed by the layer's
/// corresponding middleware.
///
/// This can be used to add additional processing to a request for a single
/// handler.
///
/// Note this differs from [`routing::Router::layer`](crate::routing::Router::layer)
/// which adds a middleware to a group of routes.
///
/// If you're applying middleware that produces errors you have to handle the errors
/// so they're converted into responses. You can learn more about doing that
/// [here](crate::error_handling).
///
/// # Example
///
/// Adding the [`tower::limit::ConcurrencyLimit`] middleware to a handler
/// can be done like so:
///
/// ```rust
/// use axum::{
/// routing::get,
/// handler::Handler,
/// Router,
/// };
/// use tower::limit::{ConcurrencyLimitLayer, ConcurrencyLimit};
///
/// async fn handler() { /* ... */ }
///
/// let layered_handler = handler.layer(ConcurrencyLimitLayer::new(64));
/// let app = Router::new().route("/", get(layered_handler));
/// # let _: Router = app;
/// ```
fn layer<L>(self, layer: L) -> Layered<L, Self, T, S>
where
L: Layer<HandlerService<Self, T, S>> + Clone,
L::Service: Service<Request>,
{
Layered {
layer,
handler: self,
_marker: PhantomData,
}
}
/// Convert the handler into a [`Service`] by providing the state
fn with_state(self, state: S) -> HandlerService<Self, T, S> {
HandlerService::new(self, state)
}
}
#[diagnostic::do_not_recommend]
impl<F, Fut, Res, S> Handler<((),), S> for F
where
F: FnOnce() -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = Res> + Send,
Res: IntoResponse,
{
type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
fn call(self, _req: Request, _state: S) -> Self::Future {
Box::pin(async move { self().await.into_response() })
}
}
macro_rules! impl_handler {
(
[$($ty:ident),*], $last:ident
) => {
#[diagnostic::do_not_recommend]
#[allow(non_snake_case, unused_mut)]
impl<F, Fut, S, Res, M, $($ty,)* $last> Handler<(M, $($ty,)* $last,), S> for F
where
F: FnOnce($($ty,)* $last,) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = Res> + Send,
S: Send + Sync + 'static,
Res: IntoResponse,
$( $ty: FromRequestParts<S> + Send, )*
$last: FromRequest<S, M> + Send,
{
type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
fn call(self, req: Request, state: S) -> Self::Future {
let (mut parts, body) = req.into_parts();
Box::pin(async move {
$(
let $ty = match $ty::from_request_parts(&mut parts, &state).await {
Ok(value) => value,
Err(rejection) => return rejection.into_response(),
};
)*
let req = Request::from_parts(parts, body);
let $last = match $last::from_request(req, &state).await {
Ok(value) => value,
Err(rejection) => return rejection.into_response(),
};
self($($ty,)* $last,).await.into_response()
})
}
}
};
}
all_the_tuples!(impl_handler);
mod private {
// Marker type for `impl<T: IntoResponse> Handler for T`
#[allow(missing_debug_implementations)]
pub enum IntoResponseHandler {}
}
#[diagnostic::do_not_recommend]
impl<T, S> Handler<private::IntoResponseHandler, S> for T
where
T: IntoResponse + Clone + Send + Sync + 'static,
{
type Future = std::future::Ready<Response>;
fn call(self, _req: Request, _state: S) -> Self::Future {
std::future::ready(self.into_response())
}
}
/// A [`Service`] created from a [`Handler`] by applying a Tower middleware.
///
/// Created with [`Handler::layer`]. See that method for more details.
pub struct Layered<L, H, T, S> {
layer: L,
handler: H,
_marker: PhantomData<fn() -> (T, S)>,
}
impl<L, H, T, S> fmt::Debug for Layered<L, H, T, S>
where
L: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Layered")
.field("layer", &self.layer)
.finish()
}
}
impl<L, H, T, S> Clone for Layered<L, H, T, S>
where
L: Clone,
H: Clone,
{
fn clone(&self) -> Self {
Self {
layer: self.layer.clone(),
handler: self.handler.clone(),
_marker: PhantomData,
}
}
}
#[diagnostic::do_not_recommend]
impl<H, S, T, L> Handler<T, S> for Layered<L, H, T, S>
where
L: Layer<HandlerService<H, T, S>> + Clone + Send + Sync + 'static,
H: Handler<T, S>,
L::Service: Service<Request, Error = Infallible> + Clone + Send + 'static,
<L::Service as Service<Request>>::Response: IntoResponse,
<L::Service as Service<Request>>::Future: Send,
T: 'static,
S: 'static,
{
type Future = future::LayeredFuture<L::Service>;
fn call(self, req: Request, state: S) -> Self::Future {
use futures_util::future::{FutureExt, Map};
let svc = self.handler.with_state(state);
let svc = self.layer.layer(svc);
let future: Map<
_,
fn(
Result<
<L::Service as Service<Request>>::Response,
<L::Service as Service<Request>>::Error,
>,
) -> _,
> = svc.oneshot(req).map(|result| match result {
Ok(res) => res.into_response(),
Err(err) => match err {},
});
future::LayeredFuture::new(future)
}
}
/// Extension trait for [`Handler`]s that don't have state.
///
/// This provides convenience methods to convert the [`Handler`] into a [`Service`] or [`MakeService`].
///
/// [`MakeService`]: tower::make::MakeService
pub trait HandlerWithoutStateExt<T>: Handler<T, ()> {
/// Convert the handler into a [`Service`] and no state.
fn into_service(self) -> HandlerService<Self, T, ()>;
/// Convert the handler into a [`MakeService`] and no state.
///
/// See [`HandlerService::into_make_service`] for more details.
///
/// [`MakeService`]: tower::make::MakeService
fn into_make_service(self) -> IntoMakeService<HandlerService<Self, T, ()>>;
/// Convert the handler into a [`MakeService`] which stores information
/// about the incoming connection and has no state.
///
/// See [`HandlerService::into_make_service_with_connect_info`] for more details.
///
/// [`MakeService`]: tower::make::MakeService
#[cfg(feature = "tokio")]
fn into_make_service_with_connect_info<C>(
self,
) -> IntoMakeServiceWithConnectInfo<HandlerService<Self, T, ()>, C>;
}
impl<H, T> HandlerWithoutStateExt<T> for H
where
H: Handler<T, ()>,
{
fn into_service(self) -> HandlerService<Self, T, ()> {
self.with_state(())
}
fn into_make_service(self) -> IntoMakeService<HandlerService<Self, T, ()>> {
self.into_service().into_make_service()
}
#[cfg(feature = "tokio")]
fn into_make_service_with_connect_info<C>(
self,
) -> IntoMakeServiceWithConnectInfo<HandlerService<Self, T, ()>, C> {
self.into_service().into_make_service_with_connect_info()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{extract::State, test_helpers::*};
use axum_core::body::Body;
use http::StatusCode;
use std::time::Duration;
use tower_http::{
limit::RequestBodyLimitLayer, map_request_body::MapRequestBodyLayer,
map_response_body::MapResponseBodyLayer, timeout::TimeoutLayer,
};
#[crate::test]
async fn handler_into_service() {
async fn handle(body: String) -> impl IntoResponse {
format!("you said: {body}")
}
let client = TestClient::new(handle.into_service());
let res = client.post("/").body("hi there!").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "you said: hi there!");
}
#[crate::test]
async fn with_layer_that_changes_request_body_and_state() {
async fn handle(State(state): State<&'static str>) -> &'static str {
state
}
let svc = handle
.layer((
RequestBodyLimitLayer::new(1024),
TimeoutLayer::with_status_code(
StatusCode::REQUEST_TIMEOUT,
Duration::from_secs(10),
),
MapResponseBodyLayer::new(Body::new),
))
.layer(MapRequestBodyLayer::new(Body::new))
.with_state("foo");
let client = TestClient::new(svc);
let res = client.get("/").await;
assert_eq!(res.text().await, "foo");
}
}
+203
View File
@@ -0,0 +1,203 @@
use super::Handler;
use crate::body::{Body, Bytes, HttpBody};
#[cfg(feature = "tokio")]
use crate::extract::connect_info::IntoMakeServiceWithConnectInfo;
use crate::response::Response;
use crate::routing::IntoMakeService;
use crate::BoxError;
use http::Request;
use std::{
convert::Infallible,
fmt,
marker::PhantomData,
task::{Context, Poll},
};
use tower_service::Service;
/// An adapter that makes a [`Handler`] into a [`Service`].
///
/// Created with [`Handler::with_state`] or [`HandlerWithoutStateExt::into_service`].
///
/// [`HandlerWithoutStateExt::into_service`]: super::HandlerWithoutStateExt::into_service
pub struct HandlerService<H, T, S> {
handler: H,
state: S,
_marker: PhantomData<fn() -> T>,
}
impl<H, T, S> HandlerService<H, T, S> {
/// Get a reference to the state.
pub fn state(&self) -> &S {
&self.state
}
/// Convert the handler into a [`MakeService`].
///
/// This allows you to serve a single handler if you don't need any routing:
///
/// ```rust
/// use axum::{
/// handler::Handler,
/// extract::State,
/// http::{Uri, Method},
/// response::IntoResponse,
/// };
/// use std::net::SocketAddr;
///
/// #[derive(Clone)]
/// struct AppState {}
///
/// async fn handler(State(state): State<AppState>) {
/// // ...
/// }
///
/// let app = handler.with_state(AppState {});
///
/// # async {
/// let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
/// axum::serve(listener, app.into_make_service()).await.unwrap();
/// # };
/// ```
///
/// [`MakeService`]: tower::make::MakeService
pub fn into_make_service(self) -> IntoMakeService<HandlerService<H, T, S>> {
IntoMakeService::new(self)
}
/// Convert the handler into a [`MakeService`] which stores information
/// about the incoming connection.
///
/// See [`Router::into_make_service_with_connect_info`] for more details.
///
/// ```rust
/// use axum::{
/// handler::Handler,
/// response::IntoResponse,
/// extract::{ConnectInfo, State},
/// };
/// use std::net::SocketAddr;
///
/// #[derive(Clone)]
/// struct AppState {};
///
/// async fn handler(
/// ConnectInfo(addr): ConnectInfo<SocketAddr>,
/// State(state): State<AppState>,
/// ) -> String {
/// format!("Hello {addr}")
/// }
///
/// let app = handler.with_state(AppState {});
///
/// # async {
/// let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
/// axum::serve(
/// listener,
/// app.into_make_service_with_connect_info::<SocketAddr>(),
/// ).await.unwrap();
/// # };
/// ```
///
/// [`MakeService`]: tower::make::MakeService
/// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info
#[cfg(feature = "tokio")]
pub fn into_make_service_with_connect_info<C>(
self,
) -> IntoMakeServiceWithConnectInfo<HandlerService<H, T, S>, C> {
IntoMakeServiceWithConnectInfo::new(self)
}
}
#[test]
fn traits() {
use crate::test_helpers::*;
assert_send::<HandlerService<(), NotSendSync, ()>>();
assert_sync::<HandlerService<(), NotSendSync, ()>>();
}
impl<H, T, S> HandlerService<H, T, S> {
pub(super) fn new(handler: H, state: S) -> Self {
Self {
handler,
state,
_marker: PhantomData,
}
}
}
impl<H, T, S> fmt::Debug for HandlerService<H, T, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("IntoService").finish_non_exhaustive()
}
}
impl<H, T, S> Clone for HandlerService<H, T, S>
where
H: Clone,
S: Clone,
{
fn clone(&self) -> Self {
Self {
handler: self.handler.clone(),
state: self.state.clone(),
_marker: PhantomData,
}
}
}
impl<H, T, S, B> Service<Request<B>> for HandlerService<H, T, S>
where
H: Handler<T, S> + Clone + Send + 'static,
B: HttpBody<Data = Bytes> + Send + 'static,
B::Error: Into<BoxError>,
S: Clone + Send + Sync,
{
type Response = Response;
type Error = Infallible;
type Future = super::future::IntoServiceFuture<H::Future>;
#[inline]
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
// `IntoService` can only be constructed from async functions which are always ready, or
// from `Layered` which buffers in `<Layered as Handler>::call` and is therefore
// also always ready.
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<B>) -> Self::Future {
use futures_util::future::FutureExt;
let req = req.map(Body::new);
let handler = self.handler.clone();
let future = Handler::call(handler, req, self.state.clone());
let future = future.map(Ok as _);
super::future::IntoServiceFuture::new(future)
}
}
// for `axum::serve(listener, handler)`
#[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))]
const _: () = {
use crate::serve;
impl<H, T, S, L> Service<serve::IncomingStream<'_, L>> for HandlerService<H, T, S>
where
H: Clone,
S: Clone,
L: serve::Listener,
{
type Response = Self;
type Error = Infallible;
type Future = std::future::Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _req: serve::IncomingStream<'_, L>) -> Self::Future {
std::future::ready(Ok(self.clone()))
}
}
};
+376
View File
@@ -0,0 +1,376 @@
use crate::extract::Request;
use crate::extract::{rejection::*, FromRequest};
use axum_core::extract::OptionalFromRequest;
use axum_core::response::{IntoResponse, Response};
use bytes::{BufMut, Bytes, BytesMut};
use http::{
header::{self, HeaderMap, HeaderValue},
StatusCode,
};
use serde_core::{de::DeserializeOwned, Serialize};
/// JSON Extractor / Response.
///
/// When used as an extractor, it can deserialize request bodies into some type that
/// implements [`serde::de::DeserializeOwned`]. The request will be rejected (and a [`JsonRejection`] will
/// be returned) if:
///
/// - The request doesn't have a `Content-Type: application/json` (or similar) header.
/// - The body doesn't contain syntactically valid JSON.
/// - The body contains syntactically valid JSON, but it couldn't be deserialized into the target type.
/// - Buffering the request body fails.
///
/// ⚠️ Since parsing JSON requires consuming the request body, the `Json` extractor must be
/// *last* if there are multiple extractors in a handler.
/// See ["the order of extractors"][order-of-extractors]
///
/// [order-of-extractors]: crate::extract#the-order-of-extractors
///
/// See [`JsonRejection`] for more details.
///
/// # Extractor example
///
/// ```rust,no_run
/// use axum::{
/// extract,
/// routing::post,
/// Router,
/// };
/// use serde::Deserialize;
///
/// #[derive(Deserialize)]
/// struct CreateUser {
/// email: String,
/// password: String,
/// }
///
/// async fn create_user(extract::Json(payload): extract::Json<CreateUser>) {
/// // payload is a `CreateUser`
/// }
///
/// let app = Router::new().route("/users", post(create_user));
/// # let _: Router = app;
/// ```
///
/// When used as a response, it can serialize any type that implements [`serde::Serialize`] to
/// `JSON`, and will automatically set `Content-Type: application/json` header.
///
/// If the [`Serialize`] implementation decides to fail
/// or if a map with non-string keys is used,
/// a 500 response will be issued
/// whose body is the error message in UTF-8.
///
/// # Response example
///
/// ```
/// use axum::{
/// extract::Path,
/// routing::get,
/// Router,
/// Json,
/// };
/// use serde::Serialize;
/// use uuid::Uuid;
///
/// #[derive(Serialize)]
/// struct User {
/// id: Uuid,
/// username: String,
/// }
///
/// async fn get_user(Path(user_id) : Path<Uuid>) -> Json<User> {
/// let user = find_user(user_id).await;
/// Json(user)
/// }
///
/// async fn find_user(user_id: Uuid) -> User {
/// // ...
/// # unimplemented!()
/// }
///
/// let app = Router::new().route("/users/{id}", get(get_user));
/// # let _: Router = app;
/// ```
#[derive(Debug, Clone, Copy, Default)]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
#[must_use]
pub struct Json<T>(pub T);
impl<T, S> FromRequest<S> for Json<T>
where
T: DeserializeOwned,
S: Send + Sync,
{
type Rejection = JsonRejection;
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
if !json_content_type(req.headers()) {
return Err(MissingJsonContentType.into());
}
let bytes = Bytes::from_request(req, state).await?;
Self::from_bytes(&bytes)
}
}
impl<T, S> OptionalFromRequest<S> for Json<T>
where
T: DeserializeOwned,
S: Send + Sync,
{
type Rejection = JsonRejection;
async fn from_request(req: Request, state: &S) -> Result<Option<Self>, Self::Rejection> {
let headers = req.headers();
if headers.get(header::CONTENT_TYPE).is_some() {
if json_content_type(headers) {
let bytes = Bytes::from_request(req, state).await?;
Ok(Some(Self::from_bytes(&bytes)?))
} else {
Err(MissingJsonContentType.into())
}
} else {
Ok(None)
}
}
}
fn json_content_type(headers: &HeaderMap) -> bool {
let Some(content_type) = headers.get(header::CONTENT_TYPE) else {
return false;
};
let Ok(content_type) = content_type.to_str() else {
return false;
};
let Ok(mime) = content_type.parse::<mime::Mime>() else {
return false;
};
let is_json_content_type = mime.type_() == "application"
&& (mime.subtype() == "json" || mime.suffix().is_some_and(|name| name == "json"));
is_json_content_type
}
axum_core::__impl_deref!(Json);
impl<T> From<T> for Json<T> {
fn from(inner: T) -> Self {
Self(inner)
}
}
impl<T> Json<T>
where
T: DeserializeOwned,
{
/// Construct a `Json<T>` from a byte slice. Most users should prefer to use the `FromRequest` impl
/// but special cases may require first extracting a `Request` into `Bytes` then optionally
/// constructing a `Json<T>`.
pub fn from_bytes(bytes: &[u8]) -> Result<Self, JsonRejection> {
// Extracted into separate fn so it's only compiled once for all T.
fn make_rejection(err: serde_path_to_error::Error<serde_json::Error>) -> JsonRejection {
match err.inner().classify() {
serde_json::error::Category::Data => JsonDataError::from_err(err).into(),
serde_json::error::Category::Syntax | serde_json::error::Category::Eof => {
JsonSyntaxError::from_err(err).into()
}
serde_json::error::Category::Io => {
if cfg!(debug_assertions) {
// we don't use `serde_json::from_reader` and instead always buffer
// bodies first, so we shouldn't encounter any IO errors
unreachable!()
} else {
JsonSyntaxError::from_err(err).into()
}
}
}
}
let mut deserializer = serde_json::Deserializer::from_slice(bytes);
serde_path_to_error::deserialize(&mut deserializer)
.map_err(make_rejection)
.and_then(|value| {
deserializer
.end()
.map(|()| Self(value))
.map_err(|err| JsonSyntaxError::from_err(err).into())
})
}
}
impl<T> IntoResponse for Json<T>
where
T: Serialize,
{
fn into_response(self) -> Response {
// Extracted into separate fn so it's only compiled once for all T.
fn make_response(buf: BytesMut, ser_result: serde_json::Result<()>) -> Response {
match ser_result {
Ok(()) => (
[(
header::CONTENT_TYPE,
HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
)],
buf.freeze(),
)
.into_response(),
Err(err) => (
StatusCode::INTERNAL_SERVER_ERROR,
[(
header::CONTENT_TYPE,
HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
)],
err.to_string(),
)
.into_response(),
}
}
// Use a small initial capacity of 128 bytes like serde_json::to_vec
// https://docs.rs/serde_json/1.0.82/src/serde_json/ser.rs.html#2189
let mut buf = BytesMut::with_capacity(128).writer();
let res = serde_json::to_writer(&mut buf, &self.0);
make_response(buf.into_inner(), res)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{routing::post, test_helpers::*, Router};
use serde::Deserialize;
use serde_json::{json, Value};
#[crate::test]
async fn deserialize_body() {
#[derive(Debug, Deserialize)]
struct Input {
foo: String,
}
let app = Router::new().route("/", post(|input: Json<Input>| async { input.0.foo }));
let client = TestClient::new(app);
let res = client.post("/").json(&json!({ "foo": "bar" })).await;
let body = res.text().await;
assert_eq!(body, "bar");
}
#[crate::test]
async fn consume_body_to_json_requires_json_content_type() {
#[derive(Debug, Deserialize)]
struct Input {
foo: String,
}
let app = Router::new().route("/", post(|input: Json<Input>| async { input.0.foo }));
let client = TestClient::new(app);
let res = client.post("/").body(r#"{ "foo": "bar" }"#).await;
let status = res.status();
assert_eq!(status, StatusCode::UNSUPPORTED_MEDIA_TYPE);
}
#[crate::test]
async fn json_content_types() {
async fn valid_json_content_type(content_type: &str) -> bool {
println!("testing {content_type:?}");
let app = Router::new().route("/", post(|Json(_): Json<Value>| async {}));
let res = TestClient::new(app)
.post("/")
.header("content-type", content_type)
.body("{}")
.await;
res.status() == StatusCode::OK
}
assert!(valid_json_content_type("application/json").await);
assert!(valid_json_content_type("application/json; charset=utf-8").await);
assert!(valid_json_content_type("application/json;charset=utf-8").await);
assert!(valid_json_content_type("application/cloudevents+json").await);
assert!(!valid_json_content_type("text/json").await);
}
#[crate::test]
async fn invalid_json_syntax() {
let app = Router::new().route("/", post(|_: Json<serde_json::Value>| async {}));
let client = TestClient::new(app);
let res = client
.post("/")
.body("{")
.header("content-type", "application/json")
.await;
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
}
#[crate::test]
async fn extra_chars_after_valid_json_syntax() {
#[derive(Debug, Deserialize)]
struct Input {
foo: String,
}
let app = Router::new().route("/", post(|input: Json<Input>| async { input.0.foo }));
let client = TestClient::new(app);
let res = client
.post("/")
.body(r#"{ "foo": "bar" } baz "#)
.header("content-type", "application/json")
.await;
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
let body_text = res.text().await;
assert_eq!(
body_text,
"Failed to parse the request body as JSON: trailing characters at line 1 column 18"
);
}
#[derive(Deserialize)]
struct Foo {
#[allow(dead_code)]
a: i32,
#[allow(dead_code)]
b: Vec<Bar>,
}
#[derive(Deserialize)]
struct Bar {
#[allow(dead_code)]
x: i32,
#[allow(dead_code)]
y: i32,
}
#[crate::test]
async fn invalid_json_data() {
let app = Router::new().route("/", post(|_: Json<Foo>| async {}));
let client = TestClient::new(app);
let res = client
.post("/")
.body("{\"a\": 1, \"b\": [{\"x\": 2}]}")
.header("content-type", "application/json")
.await;
assert_eq!(res.status(), StatusCode::UNPROCESSABLE_ENTITY);
let body_text = res.text().await;
assert_eq!(
body_text,
"Failed to deserialize the JSON body into the target type: b[0]: missing field `y` at line 1 column 23"
);
}
}
+535
View File
@@ -0,0 +1,535 @@
//! axum is an HTTP routing and request-handling library that focuses on ergonomics and modularity.
//!
//! # High-level features
//!
//! - Route requests to handlers with a macro-free API.
//! - Declaratively parse requests using extractors.
//! - Simple and predictable error handling model.
//! - Generate responses with minimal boilerplate.
//! - Take full advantage of the [`tower`] and [`tower-http`] ecosystem of
//! middleware, services, and utilities.
//!
//! In particular, the last point is what sets `axum` apart from other libraries / frameworks.
//! `axum` doesn't have its own middleware system but instead uses
//! [`tower::Service`]. This means `axum` gets timeouts, tracing, compression,
//! authorization, and more, for free. It also enables you to share middleware with
//! applications written using [`hyper`] or [`tonic`].
//!
//! # Compatibility
//!
//! axum is designed to work with [tokio] and [hyper]. Runtime and
//! transport layer independence is not a goal, at least for the time being.
//!
//! # Example
//!
//! The "Hello, World!" of axum is:
//!
//! ```rust,no_run
//! use axum::{
//! routing::get,
//! Router,
//! };
//!
//! #[tokio::main]
//! async fn main() {
//! // build our application with a single route
//! let app = Router::new().route("/", get(|| async { "Hello, World!" }));
//!
//! // run our app with hyper, listening globally on port 3000
//! let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
//! axum::serve(listener, app).await.unwrap();
//! }
//! ```
//!
//! Note using `#[tokio::main]` requires you enable tokio's `macros` and `rt-multi-thread` features
//! or just `full` to enable all features (`cargo add tokio --features macros,rt-multi-thread`).
//!
//! # Routing
//!
//! [`Router`] is used to set up which paths go to which services:
//!
//! ```rust
//! use axum::{Router, routing::get};
//!
//! // our router
//! let app = Router::new()
//! .route("/", get(root))
//! .route("/foo", get(get_foo).post(post_foo))
//! .route("/foo/bar", get(foo_bar));
//!
//! // which calls one of these handlers
//! async fn root() {}
//! async fn get_foo() {}
//! async fn post_foo() {}
//! async fn foo_bar() {}
//! # let _: Router = app;
//! ```
//!
//! See [`Router`] for more details on routing.
//!
//! # Handlers
//!
#![doc = include_str!("docs/handlers_intro.md")]
//!
//! See [`handler`](crate::handler) for more details on handlers.
//!
//! # Extractors
//!
//! An extractor is a type that implements [`FromRequest`] or [`FromRequestParts`]. Extractors are
//! how you pick apart the incoming request to get the parts your handler needs.
//!
//! ```rust
//! use axum::extract::{Path, Query, Json};
//! use std::collections::HashMap;
//!
//! // `Path` gives you the path parameters and deserializes them.
//! async fn path(Path(user_id): Path<u32>) {}
//!
//! // `Query` gives you the query parameters and deserializes them.
//! async fn query(Query(params): Query<HashMap<String, String>>) {}
//!
//! // Buffer the request body and deserialize it as JSON into a
//! // `serde_json::Value`. `Json` supports any type that implements
//! // `serde::Deserialize`.
//! async fn json(Json(payload): Json<serde_json::Value>) {}
//! ```
//!
//! See [`extract`](crate::extract) for more details on extractors.
//!
//! # Responses
//!
//! Anything that implements [`IntoResponse`] can be returned from handlers.
//!
//! ```rust,no_run
//! use axum::{
//! body::Body,
//! routing::get,
//! response::Json,
//! Router,
//! };
//! use serde_json::{Value, json};
//!
//! // `&'static str` becomes a `200 OK` with `content-type: text/plain; charset=utf-8`
//! async fn plain_text() -> &'static str {
//! "foo"
//! }
//!
//! // `Json` gives a content-type of `application/json` and works with any type
//! // that implements `serde::Serialize`
//! async fn json() -> Json<Value> {
//! Json(json!({ "data": 42 }))
//! }
//!
//! let app = Router::new()
//! .route("/plain_text", get(plain_text))
//! .route("/json", get(json));
//! # let _: Router = app;
//! ```
//!
//! See [`response`](crate::response) for more details on building responses.
//!
//! # Error handling
//!
//! axum aims to have a simple and predictable error handling model. That means
//! it is simple to convert errors into responses and you are guaranteed that
//! all errors are handled.
//!
//! See [`error_handling`] for more details on axum's
//! error handling model and how to handle errors gracefully.
//!
//! # Middleware
//!
//! There are several different ways to write middleware for axum. See
//! [`middleware`] for more details.
//!
//! # Sharing state with handlers
//!
//! It is common to share some state between handlers. For example, a
//! pool of database connections or clients to other services may need to
//! be shared.
//!
//! The four most common ways of doing that are:
//!
//! - Using the [`State`] extractor
//! - Using request extensions
//! - Using closure captures
//! - Using task-local variables
//!
//! ## Using the [`State`] extractor
//!
//! ```rust,no_run
//! use axum::{
//! extract::State,
//! routing::get,
//! Router,
//! };
//! use std::sync::Arc;
//!
//! struct AppState {
//! // ...
//! }
//!
//! let shared_state = Arc::new(AppState { /* ... */ });
//!
//! let app = Router::new()
//! .route("/", get(handler))
//! .with_state(shared_state);
//!
//! async fn handler(
//! State(state): State<Arc<AppState>>,
//! ) {
//! // ...
//! }
//! # let _: Router = app;
//! ```
//!
//! State is cloned for every request. Wrapping your state in `Arc` makes those
//! clones cheap. If all fields are already cheap to clone (for example, each field
//! is itself an `Arc` or a copy type), you can `#[derive(Clone)]` directly on the
//! struct instead.
//!
//! ### Substates with `FromRef`
//!
//! When a handler only needs part of the application state, use [`FromRef`] to extract
//! a substate. Implement the trait manually, or derive it with `#[derive(FromRef)]`
//! (requires the `macros` feature):
//!
//! ```rust
//! use axum::{Router, routing::get, extract::{State, FromRef}};
//!
//! #[derive(Clone)]
//! struct AppState {
//! api_state: ApiState,
//! }
//!
//! #[derive(Clone)]
//! struct ApiState {}
//!
//! // Teach axum how to produce an `ApiState` from a reference to `AppState`.
//! impl FromRef<AppState> for ApiState {
//! fn from_ref(app_state: &AppState) -> ApiState {
//! app_state.api_state.clone()
//! }
//! }
//!
//! let app = Router::new()
//! .route("/", get(handler))
//! .with_state(AppState { api_state: ApiState {} });
//!
//! // This handler receives only the `ApiState` slice; it never sees `AppState`.
//! async fn handler(State(api_state): State<ApiState>) {}
//! # let _: Router = app;
//! ```
//!
//! ### The `Router<S>` type parameter
//!
//! `Router<S>` when `S` is not `()` means a router that is _missing_ a state of type `S`. Calling
//! [`.with_state(s)`][Router::with_state] provides that state and typically produces a
//! `Router<()>`, which is the only form that can be passed to [`serve()`]. See
//! [`Router::with_state`] for a full explanation.
//!
//! You should prefer using [`State`] if possible since it's more type safe. The downside is that
//! it's less dynamic than task-local variables and request extensions.
//!
//! See [`State`] for more details about accessing state.
//!
//! ## Using request extensions
//!
//! Another way to share state with handlers is using [`Extension`] as
//! layer and extractor:
//!
//! ```rust,no_run
//! use axum::{
//! extract::Extension,
//! routing::get,
//! Router,
//! };
//! use std::sync::Arc;
//!
//! struct AppState {
//! // ...
//! }
//!
//! let shared_state = Arc::new(AppState { /* ... */ });
//!
//! let app = Router::new()
//! .route("/", get(handler))
//! .layer(Extension(shared_state));
//!
//! async fn handler(
//! Extension(state): Extension<Arc<AppState>>,
//! ) {
//! // ...
//! }
//! # let _: Router = app;
//! ```
//!
//! The downside to this approach is that you'll get runtime errors
//! (specifically a `500 Internal Server Error` response) if you try and extract
//! an extension that doesn't exist, perhaps because you forgot to add the
//! middleware or because you're extracting the wrong type.
//!
//! ## Using closure captures
//!
//! State can also be passed directly to handlers using closure captures:
//!
//! ```rust,no_run
//! use axum::{
//! Json,
//! extract::{Extension, Path},
//! routing::{get, post},
//! Router,
//! };
//! use std::sync::Arc;
//! use serde::Deserialize;
//!
//! struct AppState {
//! // ...
//! }
//!
//! let shared_state = Arc::new(AppState { /* ... */ });
//!
//! let app = Router::new()
//! .route(
//! "/users",
//! post({
//! let shared_state = Arc::clone(&shared_state);
//! move |body| create_user(body, shared_state)
//! }),
//! )
//! .route(
//! "/users/{id}",
//! get({
//! let shared_state = Arc::clone(&shared_state);
//! move |path| get_user(path, shared_state)
//! }),
//! );
//!
//! async fn get_user(Path(user_id): Path<String>, state: Arc<AppState>) {
//! // ...
//! }
//!
//! async fn create_user(Json(payload): Json<CreateUserPayload>, state: Arc<AppState>) {
//! // ...
//! }
//!
//! #[derive(Deserialize)]
//! struct CreateUserPayload {
//! // ...
//! }
//! # let _: Router = app;
//! ```
//!
//! The downside to this approach is that it's the most verbose approach.
//!
//! ## Using task-local variables
//!
//! This also allows to share state with `IntoResponse` implementations:
//!
//! ```rust,no_run
//! use axum::{
//! extract::Request,
//! http::{header, StatusCode},
//! middleware::{self, Next},
//! response::{IntoResponse, Response},
//! routing::get,
//! Router,
//! };
//! use tokio::task_local;
//!
//! #[derive(Clone)]
//! struct CurrentUser {
//! name: String,
//! }
//! task_local! {
//! pub static USER: CurrentUser;
//! }
//!
//! async fn auth(req: Request, next: Next) -> Result<Response, StatusCode> {
//! let auth_header = req
//! .headers()
//! .get(header::AUTHORIZATION)
//! .and_then(|header| header.to_str().ok())
//! .ok_or(StatusCode::UNAUTHORIZED)?;
//! if let Some(current_user) = authorize_current_user(auth_header).await {
//! // State is setup here in the middleware
//! Ok(USER.scope(current_user, next.run(req)).await)
//! } else {
//! Err(StatusCode::UNAUTHORIZED)
//! }
//! }
//! async fn authorize_current_user(auth_token: &str) -> Option<CurrentUser> {
//! Some(CurrentUser {
//! name: auth_token.to_string(),
//! })
//! }
//!
//! struct UserResponse;
//!
//! impl IntoResponse for UserResponse {
//! fn into_response(self) -> Response {
//! // State is accessed here in the IntoResponse implementation
//! let current_user = USER.with(|u| u.clone());
//! (StatusCode::OK, current_user.name).into_response()
//! }
//! }
//!
//! async fn handler() -> UserResponse {
//! UserResponse
//! }
//!
//! let app: Router = Router::new()
//! .route("/", get(handler))
//! .route_layer(middleware::from_fn(auth));
//! ```
//!
//! The main downside to this approach is that it only works when the async executor being used
//! has the concept of task-local variables. The example above uses
//! [tokio's `task_local` macro](https://docs.rs/tokio/1/tokio/macro.task_local.html).
//! smol does not yet offer equivalent functionality at the time of writing (see
//! [this GitHub issue](https://github.com/smol-rs/async-executor/issues/139)).
//!
//! # Building integrations for axum
//!
//! Libraries authors that want to provide [`FromRequest`], [`FromRequestParts`], or
//! [`IntoResponse`] implementations should depend on the [`axum-core`] crate, instead of `axum` if
//! possible. [`axum-core`] contains core types and traits and is less likely to receive breaking
//! changes.
//!
//! # Required dependencies
//!
//! To use axum there are a few dependencies you have to pull in as well:
//!
//! ```toml
//! [dependencies]
//! axum = "<latest-version>"
//! tokio = { version = "<latest-version>", features = ["full"] }
//! tower = "<latest-version>"
//! ```
//!
//! The `"full"` feature for tokio isn't necessary but it's the easiest way to get started.
//!
//! Tower isn't strictly necessary either but helpful for testing. See the
//! testing example in the repo to learn more about testing axum apps.
//!
//! # Examples
//!
//! The axum repo contains [a number of examples][examples] that show how to put all the
//! pieces together.
//!
//! # Feature flags
//!
//! axum uses a set of [feature flags] to reduce the amount of compiled and
//! optional dependencies.
//!
//! The following optional features are available:
//!
//! Name | Description | Default?
//! ---|---|---
//! `http1` | Enables hyper's `http1` feature | <span role="img" aria-label="Default feature">✔</span>
//! `http2` | Enables hyper's `http2` feature |
//! `json` | Enables the [`Json`] type and some similar convenience functionality | <span role="img" aria-label="Default feature">✔</span>
//! `macros` | Enables optional utility macros |
//! `matched-path` | Enables capturing of every request's router path and the [`MatchedPath`] extractor | <span role="img" aria-label="Default feature">✔</span>
//! `multipart` | Enables parsing `multipart/form-data` requests with [`Multipart`] |
//! `original-uri` | Enables capturing of every request's original URI and the [`OriginalUri`] extractor | <span role="img" aria-label="Default feature">✔</span>
//! `tokio` | Enables `tokio` as a dependency and `axum::serve`, `SSE` and `extract::connect_info` types. | <span role="img" aria-label="Default feature">✔</span>
//! `tower-log` | Enables `tower`'s `log` feature | <span role="img" aria-label="Default feature">✔</span>
//! `tracing` | Log rejections from built-in extractors | <span role="img" aria-label="Default feature">✔</span>
//! `ws` | Enables WebSockets support via [`extract::ws`] |
//! `form` | Enables the `Form` extractor | <span role="img" aria-label="Default feature">✔</span>
//! `query` | Enables the `Query` extractor | <span role="img" aria-label="Default feature">✔</span>
//!
//! [`MatchedPath`]: crate::extract::MatchedPath
//! [`Multipart`]: crate::extract::Multipart
//! [`OriginalUri`]: crate::extract::OriginalUri
//! [`tower`]: https://crates.io/crates/tower
//! [`tower-http`]: https://crates.io/crates/tower-http
//! [`tokio`]: http://crates.io/crates/tokio
//! [`hyper`]: http://crates.io/crates/hyper
//! [`tonic`]: http://crates.io/crates/tonic
//! [feature flags]: https://doc.rust-lang.org/cargo/reference/features.html#the-features-section
//! [`IntoResponse`]: crate::response::IntoResponse
//! [`Timeout`]: tower::timeout::Timeout
//! [examples]: https://github.com/tokio-rs/axum/tree/main/examples
//! [`Router::merge`]: crate::routing::Router::merge
//! [`Service`]: tower::Service
//! [`Service::poll_ready`]: tower::Service::poll_ready
//! [`Service`'s]: tower::Service
//! [`tower::Service`]: tower::Service
//! [tower-guides]: https://github.com/tower-rs/tower/tree/master/guides
//! [`Uuid`]: https://docs.rs/uuid/latest/uuid/
//! [`FromRequest`]: crate::extract::FromRequest
//! [`FromRequestParts`]: crate::extract::FromRequestParts
//! [`HeaderMap`]: http::header::HeaderMap
//! [`Request`]: http::Request
//! [customize-extractor-error]: https://github.com/tokio-rs/axum/blob/main/examples/customize-extractor-error/src/main.rs
//! [axum-macros]: https://docs.rs/axum-macros
//! [`debug_handler`]: https://docs.rs/axum-macros/latest/axum_macros/attr.debug_handler.html
//! [`Handler`]: crate::handler::Handler
//! [`Infallible`]: std::convert::Infallible
//! [load shed]: tower::load_shed
//! [`axum-core`]: http://crates.io/crates/axum-core
//! [`State`]: crate::extract::State
//! [`FromRef`]: crate::extract::FromRef
//! [`Router::with_state`]: crate::routing::Router::with_state
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(test, allow(clippy::float_cmp))]
#![cfg_attr(not(test), warn(clippy::print_stdout, clippy::dbg_macro))]
#[macro_use]
pub(crate) mod macros;
mod boxed;
mod extension;
#[cfg(feature = "form")]
mod form;
#[cfg(feature = "json")]
mod json;
mod service_ext;
mod util;
pub mod body;
pub mod error_handling;
pub mod extract;
pub mod handler;
pub mod middleware;
pub mod response;
pub mod routing;
#[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))]
pub mod serve;
#[cfg(any(test, feature = "__private"))]
#[allow(missing_docs, missing_debug_implementations, clippy::print_stdout)]
pub mod test_helpers;
#[doc(no_inline)]
pub use http;
#[doc(inline)]
pub use self::extension::Extension;
#[doc(inline)]
#[cfg(feature = "json")]
pub use self::json::Json;
#[doc(inline)]
pub use self::routing::Router;
#[doc(inline)]
#[cfg(feature = "form")]
pub use self::form::Form;
#[doc(inline)]
pub use axum_core::{BoxError, Error, RequestExt, RequestPartsExt};
#[cfg(feature = "macros")]
pub use axum_macros::{debug_handler, debug_middleware};
#[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))]
#[doc(inline)]
pub use self::serve::serve;
pub use self::service_ext::ServiceExt;
#[cfg(test)]
use axum_macros::__private_axum_test as test;
+96
View File
@@ -0,0 +1,96 @@
//! Internal macros
macro_rules! opaque_future {
($(#[$m:meta])* pub type $name:ident = $actual:ty;) => {
opaque_future! {
$(#[$m])*
pub type $name<> = $actual;
}
};
($(#[$m:meta])* pub type $name:ident<$($param:ident),*> = $actual:ty;) => {
pin_project_lite::pin_project! {
$(#[$m])*
pub struct $name<$($param),*> {
#[pin] future: $actual,
}
}
impl<$($param),*> $name<$($param),*> {
pub(crate) fn new(future: $actual) -> Self {
Self { future }
}
}
impl<$($param),*> std::fmt::Debug for $name<$($param),*> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(stringify!($name)).finish_non_exhaustive()
}
}
impl<$($param),*> std::future::Future for $name<$($param),*>
where
$actual: std::future::Future,
{
type Output = <$actual as std::future::Future>::Output;
#[inline]
fn poll(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
self.project().future.poll(cx)
}
}
};
}
#[rustfmt::skip]
macro_rules! all_the_tuples {
($name:ident) => {
$name!([], T1);
$name!([T1], T2);
$name!([T1, T2], T3);
$name!([T1, T2, T3], T4);
$name!([T1, T2, T3, T4], T5);
$name!([T1, T2, T3, T4, T5], T6);
$name!([T1, T2, T3, T4, T5, T6], T7);
$name!([T1, T2, T3, T4, T5, T6, T7], T8);
$name!([T1, T2, T3, T4, T5, T6, T7, T8], T9);
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9], T10);
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T11);
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], T12);
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], T13);
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], T14);
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], T15);
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], T16);
};
}
#[cfg(feature = "tracing")]
#[allow(unused_macros)]
macro_rules! trace {
($($tt:tt)*) => {
tracing::trace!($($tt)*)
}
}
#[cfg(feature = "tracing")]
#[allow(unused_macros)]
macro_rules! error {
($($tt:tt)*) => {
tracing::error!($($tt)*)
};
}
#[cfg(not(feature = "tracing"))]
#[allow(unused_macros)]
macro_rules! trace {
($($tt:tt)*) => {};
}
#[cfg(not(feature = "tracing"))]
#[allow(unused_macros)]
macro_rules! error {
($($tt:tt)*) => {};
}
@@ -0,0 +1,386 @@
use crate::{
extract::FromRequestParts,
response::{IntoResponse, Response},
};
use futures_util::future::BoxFuture;
use http::Request;
use pin_project_lite::pin_project;
use std::{
fmt,
future::Future,
marker::PhantomData,
pin::Pin,
task::{ready, Context, Poll},
};
use tower_layer::Layer;
use tower_service::Service;
/// Create a middleware from an extractor.
///
/// If the extractor succeeds the value will be discarded and the inner service
/// will be called. If the extractor fails the rejection will be returned and
/// the inner service will _not_ be called.
///
/// This can be used to perform validation of requests if the validation doesn't
/// produce any useful output, and run the extractor for several handlers
/// without repeating it in the function signature.
///
/// Note that if the extractor consumes the request body, as `String` or
/// [`Bytes`] does, an empty body will be left in its place. Thus won't be
/// accessible to subsequent extractors or handlers.
///
/// # Example
///
/// ```rust
/// use axum::{
/// extract::FromRequestParts,
/// middleware::from_extractor,
/// routing::{get, post},
/// Router,
/// http::{header, StatusCode, request::Parts},
/// };
///
/// // An extractor that performs authorization.
/// struct RequireAuth;
///
/// impl<S> FromRequestParts<S> for RequireAuth
/// where
/// S: Send + Sync,
/// {
/// type Rejection = StatusCode;
///
/// async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
/// let auth_header = parts
/// .headers
/// .get(header::AUTHORIZATION)
/// .and_then(|value| value.to_str().ok());
///
/// match auth_header {
/// Some(auth_header) if token_is_valid(auth_header) => {
/// Ok(Self)
/// }
/// _ => Err(StatusCode::UNAUTHORIZED),
/// }
/// }
/// }
///
/// fn token_is_valid(token: &str) -> bool {
/// // ...
/// # false
/// }
///
/// async fn handler() {
/// // If we get here the request has been authorized
/// }
///
/// async fn other_handler() {
/// // If we get here the request has been authorized
/// }
///
/// let app = Router::new()
/// .route("/", get(handler))
/// .route("/foo", post(other_handler))
/// // The extractor will run before all routes
/// .route_layer(from_extractor::<RequireAuth>());
/// # let _: Router = app;
/// ```
///
/// [`Bytes`]: bytes::Bytes
pub fn from_extractor<E>() -> FromExtractorLayer<E, ()> {
from_extractor_with_state(())
}
/// Create a middleware from an extractor with the given state.
///
/// See [`State`](crate::extract::State) for more details about accessing state.
pub fn from_extractor_with_state<E, S>(state: S) -> FromExtractorLayer<E, S> {
FromExtractorLayer {
state,
_marker: PhantomData,
}
}
/// [`Layer`] that applies [`FromExtractor`] that runs an extractor and
/// discards the value.
///
/// See [`from_extractor`] for more details.
///
/// [`Layer`]: tower::Layer
#[must_use]
pub struct FromExtractorLayer<E, S> {
state: S,
_marker: PhantomData<fn() -> E>,
}
impl<E, S> Clone for FromExtractorLayer<E, S>
where
S: Clone,
{
fn clone(&self) -> Self {
Self {
state: self.state.clone(),
_marker: PhantomData,
}
}
}
impl<E, S> fmt::Debug for FromExtractorLayer<E, S>
where
S: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FromExtractorLayer")
.field("state", &self.state)
.field("extractor", &format_args!("{}", std::any::type_name::<E>()))
.finish()
}
}
impl<E, T, S> Layer<T> for FromExtractorLayer<E, S>
where
S: Clone,
{
type Service = FromExtractor<T, E, S>;
fn layer(&self, inner: T) -> Self::Service {
FromExtractor {
inner,
state: self.state.clone(),
_extractor: PhantomData,
}
}
}
/// Middleware that runs an extractor and discards the value.
///
/// See [`from_extractor`] for more details.
pub struct FromExtractor<T, E, S> {
inner: T,
state: S,
_extractor: PhantomData<fn() -> E>,
}
#[test]
fn traits() {
use crate::test_helpers::*;
assert_send::<FromExtractor<(), NotSendSync, ()>>();
assert_sync::<FromExtractor<(), NotSendSync, ()>>();
}
impl<T, E, S> Clone for FromExtractor<T, E, S>
where
T: Clone,
S: Clone,
{
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
state: self.state.clone(),
_extractor: PhantomData,
}
}
}
impl<T, E, S> fmt::Debug for FromExtractor<T, E, S>
where
T: fmt::Debug,
S: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FromExtractor")
.field("inner", &self.inner)
.field("state", &self.state)
.field("extractor", &format_args!("{}", std::any::type_name::<E>()))
.finish()
}
}
impl<T, E, B, S> Service<Request<B>> for FromExtractor<T, E, S>
where
E: FromRequestParts<S> + 'static,
B: Send + 'static,
T: Service<Request<B>> + Clone,
T::Response: IntoResponse,
S: Clone + Send + Sync + 'static,
{
type Response = Response;
type Error = T::Error;
type Future = ResponseFuture<B, T, E, S>;
#[inline]
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request<B>) -> Self::Future {
let state = self.state.clone();
let (mut parts, body) = req.into_parts();
let extract_future = Box::pin(async move {
let extracted = E::from_request_parts(&mut parts, &state).await;
let req = Request::from_parts(parts, body);
(req, extracted)
});
ResponseFuture {
state: State::Extracting {
future: extract_future,
},
svc: Some(self.inner.clone()),
}
}
}
pin_project! {
/// Response future for [`FromExtractor`].
#[allow(missing_debug_implementations)]
pub struct ResponseFuture<B, T, E, S>
where
E: FromRequestParts<S>,
T: Service<Request<B>>,
{
#[pin]
state: State<B, T, E, S>,
svc: Option<T>,
}
}
pin_project! {
#[project = StateProj]
enum State<B, T, E, S>
where
E: FromRequestParts<S>,
T: Service<Request<B>>,
{
Extracting {
future: BoxFuture<'static, (Request<B>, Result<E, E::Rejection>)>,
},
Call { #[pin] future: T::Future },
}
}
impl<B, T, E, S> Future for ResponseFuture<B, T, E, S>
where
E: FromRequestParts<S>,
T: Service<Request<B>>,
T::Response: IntoResponse,
{
type Output = Result<Response, T::Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
loop {
let mut this = self.as_mut().project();
let new_state = match this.state.as_mut().project() {
StateProj::Extracting { future } => {
let (req, extracted) = ready!(future.as_mut().poll(cx));
match extracted {
Ok(_) => {
let mut svc = this.svc.take().expect("future polled after completion");
let future = svc.call(req);
State::Call { future }
}
Err(err) => {
let res = err.into_response();
return Poll::Ready(Ok(res));
}
}
}
StateProj::Call { future } => {
return future
.poll(cx)
.map(|result| result.map(IntoResponse::into_response));
}
};
this.state.set(new_state);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{handler::Handler, routing::get, test_helpers::*, Router};
use axum_core::extract::FromRef;
use http::{header, request::Parts, StatusCode};
use tower_http::limit::RequestBodyLimitLayer;
#[crate::test]
async fn test_from_extractor() {
#[derive(Clone)]
struct Secret(&'static str);
struct RequireAuth;
impl<S> FromRequestParts<S> for RequireAuth
where
S: Send + Sync,
Secret: FromRef<S>,
{
type Rejection = StatusCode;
async fn from_request_parts(
parts: &mut Parts,
state: &S,
) -> Result<Self, Self::Rejection> {
let Secret(secret) = Secret::from_ref(state);
if let Some(auth) = parts
.headers
.get(header::AUTHORIZATION)
.and_then(|v| v.to_str().ok())
{
if auth == secret {
return Ok(Self);
}
}
Err(StatusCode::UNAUTHORIZED)
}
}
async fn handler() {}
let state = Secret("secret");
let app = Router::new().route(
"/",
get(handler.layer(from_extractor_with_state::<RequireAuth, _>(state))),
);
let client = TestClient::new(app);
let res = client.get("/").await;
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
let res = client
.get("/")
.header(http::header::AUTHORIZATION, "secret")
.await;
assert_eq!(res.status(), StatusCode::OK);
}
// just needs to compile
#[allow(dead_code)]
fn works_with_request_body_limit() {
struct MyExtractor;
impl<S> FromRequestParts<S> for MyExtractor
where
S: Send + Sync,
{
type Rejection = std::convert::Infallible;
async fn from_request_parts(
_parts: &mut Parts,
_state: &S,
) -> Result<Self, Self::Rejection> {
unimplemented!()
}
}
let _: Router = Router::new()
.layer(from_extractor::<MyExtractor>())
.layer(RequestBodyLimitLayer::new(1));
}
}
+418
View File
@@ -0,0 +1,418 @@
use axum_core::extract::{FromRequest, FromRequestParts, Request};
use futures_util::future::BoxFuture;
use std::{
any::type_name,
convert::Infallible,
fmt,
future::Future,
marker::PhantomData,
pin::Pin,
task::{Context, Poll},
};
use tower::util::BoxCloneSyncService;
use tower_layer::Layer;
use tower_service::Service;
use crate::{
response::{IntoResponse, Response},
util::MapIntoResponse,
};
/// Create a middleware from an async function.
///
/// `from_fn` requires the function given to
///
/// 1. Be an `async fn`.
/// 2. Take zero or more [`FromRequestParts`] extractors.
/// 3. Take exactly one [`FromRequest`] extractor as the second to last argument.
/// 4. Take [`Next`](Next) as the last argument.
/// 5. Return something that implements [`IntoResponse`].
///
/// Note that this function doesn't support extracting [`State`]. For that, use [`from_fn_with_state`].
///
/// # Example
///
/// ```rust
/// use axum::{
/// Router,
/// http,
/// routing::get,
/// response::Response,
/// middleware::{self, Next},
/// extract::Request,
/// };
///
/// async fn my_middleware(
/// request: Request,
/// next: Next,
/// ) -> Response {
/// // do something with `request`...
///
/// let response = next.run(request).await;
///
/// // do something with `response`...
///
/// response
/// }
///
/// let app = Router::new()
/// .route("/", get(|| async { /* ... */ }))
/// .layer(middleware::from_fn(my_middleware));
/// # let app: Router = app;
/// ```
///
/// # Running extractors
///
/// ```rust
/// use axum::{
/// Router,
/// extract::Request,
/// http::{StatusCode, HeaderMap},
/// middleware::{self, Next},
/// response::Response,
/// routing::get,
/// };
///
/// async fn auth(
/// // run the `HeaderMap` extractor
/// headers: HeaderMap,
/// // you can also add more extractors here but the last
/// // extractor must implement `FromRequest` which
/// // `Request` does
/// request: Request,
/// next: Next,
/// ) -> Result<Response, StatusCode> {
/// match get_token(&headers) {
/// Some(token) if token_is_valid(token) => {
/// let response = next.run(request).await;
/// Ok(response)
/// }
/// _ => {
/// Err(StatusCode::UNAUTHORIZED)
/// }
/// }
/// }
///
/// fn get_token(headers: &HeaderMap) -> Option<&str> {
/// // ...
/// # None
/// }
///
/// fn token_is_valid(token: &str) -> bool {
/// // ...
/// # false
/// }
///
/// let app = Router::new()
/// .route("/", get(|| async { /* ... */ }))
/// .route_layer(middleware::from_fn(auth));
/// # let app: Router = app;
/// ```
///
/// [extractors]: crate::extract::FromRequest
/// [`State`]: crate::extract::State
pub fn from_fn<F, T>(f: F) -> FromFnLayer<F, (), T> {
from_fn_with_state((), f)
}
/// Create a middleware from an async function with the given state.
///
/// For the requirements for the function supplied see [`from_fn`].
///
/// See [`State`](crate::extract::State) for more details about accessing state.
///
/// # Example
///
/// ```rust
/// use axum::{
/// Router,
/// http::StatusCode,
/// routing::get,
/// response::{IntoResponse, Response},
/// middleware::{self, Next},
/// extract::{Request, State},
/// };
///
/// #[derive(Clone)]
/// struct AppState { /* ... */ }
///
/// async fn my_middleware(
/// State(state): State<AppState>,
/// // you can add more extractors here but the last
/// // extractor must implement `FromRequest` which
/// // `Request` does
/// request: Request,
/// next: Next,
/// ) -> Response {
/// // do something with `request`...
///
/// let response = next.run(request).await;
///
/// // do something with `response`...
///
/// response
/// }
///
/// let state = AppState { /* ... */ };
///
/// let app = Router::new()
/// .route("/", get(|| async { /* ... */ }))
/// .route_layer(middleware::from_fn_with_state(state.clone(), my_middleware))
/// .with_state(state);
/// # let _: axum::Router = app;
/// ```
pub fn from_fn_with_state<F, S, T>(state: S, f: F) -> FromFnLayer<F, S, T> {
FromFnLayer {
f,
state,
_extractor: PhantomData,
}
}
/// A [`tower::Layer`] from an async function.
///
/// [`tower::Layer`] is used to apply middleware to [`Router`](crate::Router)'s.
///
/// Created with [`from_fn`] or [`from_fn_with_state`]. See those functions for more details.
#[must_use]
pub struct FromFnLayer<F, S, T> {
f: F,
state: S,
_extractor: PhantomData<fn() -> T>,
}
impl<F, S, T> Clone for FromFnLayer<F, S, T>
where
F: Clone,
S: Clone,
{
fn clone(&self) -> Self {
Self {
f: self.f.clone(),
state: self.state.clone(),
_extractor: self._extractor,
}
}
}
impl<S, I, F, T> Layer<I> for FromFnLayer<F, S, T>
where
F: Clone,
S: Clone,
{
type Service = FromFn<F, S, I, T>;
fn layer(&self, inner: I) -> Self::Service {
FromFn {
f: self.f.clone(),
state: self.state.clone(),
inner,
_extractor: PhantomData,
}
}
}
impl<F, S, T> fmt::Debug for FromFnLayer<F, S, T>
where
S: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FromFnLayer")
// Write out the type name, without quoting it as `&type_name::<F>()` would
.field("f", &format_args!("{}", type_name::<F>()))
.field("state", &self.state)
.finish()
}
}
/// A middleware created from an async function.
///
/// Created with [`from_fn`] or [`from_fn_with_state`]. See those functions for more details.
pub struct FromFn<F, S, I, T> {
f: F,
inner: I,
state: S,
_extractor: PhantomData<fn() -> T>,
}
impl<F, S, I, T> Clone for FromFn<F, S, I, T>
where
F: Clone,
I: Clone,
S: Clone,
{
fn clone(&self) -> Self {
Self {
f: self.f.clone(),
inner: self.inner.clone(),
state: self.state.clone(),
_extractor: self._extractor,
}
}
}
macro_rules! impl_service {
(
[$($ty:ident),*], $last:ident
) => {
#[allow(non_snake_case, unused_mut)]
impl<F, Fut, Out, S, I, $($ty,)* $last> Service<Request> for FromFn<F, S, I, ($($ty,)* $last,)>
where
F: FnMut($($ty,)* $last, Next) -> Fut + Clone + Send + 'static,
$( $ty: FromRequestParts<S> + Send, )*
$last: FromRequest<S> + Send,
Fut: Future<Output = Out> + Send + 'static,
Out: IntoResponse + 'static,
I: Service<Request, Error = Infallible>
+ Clone
+ Send
+ Sync
+ 'static,
I::Response: IntoResponse,
I::Future: Send + 'static,
S: Clone + Send + Sync + 'static,
{
type Response = Response;
type Error = Infallible;
type Future = ResponseFuture;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request) -> Self::Future {
let not_ready_inner = self.inner.clone();
let ready_inner = std::mem::replace(&mut self.inner, not_ready_inner);
let mut f = self.f.clone();
let state = self.state.clone();
let (mut parts, body) = req.into_parts();
let future = Box::pin(async move {
$(
let $ty = match $ty::from_request_parts(&mut parts, &state).await {
Ok(value) => value,
Err(rejection) => return rejection.into_response(),
};
)*
let req = Request::from_parts(parts, body);
let $last = match $last::from_request(req, &state).await {
Ok(value) => value,
Err(rejection) => return rejection.into_response(),
};
let inner = BoxCloneSyncService::new(MapIntoResponse::new(ready_inner));
let next = Next { inner };
f($($ty,)* $last, next).await.into_response()
});
ResponseFuture {
inner: future
}
}
}
};
}
all_the_tuples!(impl_service);
impl<F, S, I, T> fmt::Debug for FromFn<F, S, I, T>
where
S: fmt::Debug,
I: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FromFnLayer")
.field("f", &format_args!("{}", type_name::<F>()))
.field("inner", &self.inner)
.field("state", &self.state)
.finish()
}
}
/// The remainder of a middleware stack, including the handler.
#[derive(Debug, Clone)]
pub struct Next {
inner: BoxCloneSyncService<Request, Response, Infallible>,
}
impl Next {
/// Execute the remaining middleware stack.
pub async fn run(mut self, req: Request) -> Response {
match self.inner.call(req).await {
Ok(res) => res,
Err(err) => match err {},
}
}
}
impl Service<Request> for Next {
type Response = Response;
type Error = Infallible;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request) -> Self::Future {
self.inner.call(req)
}
}
/// Response future for [`FromFn`].
pub struct ResponseFuture {
inner: BoxFuture<'static, Response>,
}
impl Future for ResponseFuture {
type Output = Result<Response, Infallible>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.inner.as_mut().poll(cx).map(Ok)
}
}
impl fmt::Debug for ResponseFuture {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ResponseFuture").finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{body::Body, routing::get, Router};
use http::{HeaderMap, StatusCode};
use http_body_util::BodyExt;
use tower::ServiceExt;
#[crate::test]
async fn basic() {
async fn insert_header(mut req: Request, next: Next) -> impl IntoResponse {
req.headers_mut()
.insert("x-axum-test", "ok".parse().unwrap());
next.run(req).await
}
async fn handle(headers: HeaderMap) -> String {
headers["x-axum-test"].to_str().unwrap().to_owned()
}
let app = Router::new()
.route("/", get(handle))
.layer(from_fn(insert_header));
let res = app
.oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
let body = res.collect().await.unwrap().to_bytes();
assert_eq!(&body[..], b"ok");
}
}
+439
View File
@@ -0,0 +1,439 @@
use crate::body::{Body, Bytes, HttpBody};
use crate::response::{IntoResponse, Response};
use crate::BoxError;
use axum_core::extract::{FromRequest, FromRequestParts};
use futures_util::future::BoxFuture;
use http::Request;
use std::{
any::type_name,
convert::Infallible,
fmt,
future::Future,
marker::PhantomData,
pin::Pin,
task::{Context, Poll},
};
use tower_layer::Layer;
use tower_service::Service;
/// Create a middleware from an async function that transforms a request.
///
/// This differs from [`tower::util::MapRequest`] in that it allows you to easily run axum-specific
/// extractors.
///
/// # Example
///
/// ```
/// use axum::{
/// Router,
/// routing::get,
/// middleware::map_request,
/// http::Request,
/// };
///
/// async fn set_header<B>(mut request: Request<B>) -> Request<B> {
/// request.headers_mut().insert("x-foo", "foo".parse().unwrap());
/// request
/// }
///
/// async fn handler<B>(request: Request<B>) {
/// // `request` will have an `x-foo` header
/// }
///
/// let app = Router::new()
/// .route("/", get(handler))
/// .layer(map_request(set_header));
/// # let _: Router = app;
/// ```
///
/// # Rejecting the request
///
/// The function given to `map_request` is allowed to also return a `Result` which can be used to
/// reject the request and return a response immediately, without calling the remaining
/// middleware.
///
/// Specifically the valid return types are:
///
/// - `Request<B>`
/// - `Result<Request<B>, E> where E: IntoResponse`
///
/// ```
/// use axum::{
/// Router,
/// http::{Request, StatusCode},
/// routing::get,
/// middleware::map_request,
/// };
///
/// async fn auth<B>(request: Request<B>) -> Result<Request<B>, StatusCode> {
/// let auth_header = request.headers()
/// .get(http::header::AUTHORIZATION)
/// .and_then(|header| header.to_str().ok());
///
/// match auth_header {
/// Some(auth_header) if token_is_valid(auth_header) => Ok(request),
/// _ => Err(StatusCode::UNAUTHORIZED),
/// }
/// }
///
/// fn token_is_valid(token: &str) -> bool {
/// // ...
/// # false
/// }
///
/// let app = Router::new()
/// .route("/", get(|| async { /* ... */ }))
/// .route_layer(map_request(auth));
/// # let app: Router = app;
/// ```
///
/// # Running extractors
///
/// ```
/// use axum::{
/// Router,
/// routing::get,
/// middleware::map_request,
/// extract::Path,
/// http::Request,
/// };
/// use std::collections::HashMap;
///
/// async fn log_path_params<B>(
/// Path(path_params): Path<HashMap<String, String>>,
/// request: Request<B>,
/// ) -> Request<B> {
/// tracing::debug!(?path_params);
/// request
/// }
///
/// let app = Router::new()
/// .route("/", get(|| async { /* ... */ }))
/// .layer(map_request(log_path_params));
/// # let _: Router = app;
/// ```
///
/// Note that to access state you must use either [`map_request_with_state`].
pub fn map_request<F, T>(f: F) -> MapRequestLayer<F, (), T> {
map_request_with_state((), f)
}
/// Create a middleware from an async function that transforms a request, with the given state.
///
/// See [`State`](crate::extract::State) for more details about accessing state.
///
/// # Example
///
/// ```rust
/// use axum::{
/// Router,
/// http::{Request, StatusCode},
/// routing::get,
/// response::IntoResponse,
/// middleware::map_request_with_state,
/// extract::State,
/// };
///
/// #[derive(Clone)]
/// struct AppState { /* ... */ }
///
/// async fn my_middleware<B>(
/// State(state): State<AppState>,
/// // you can add more extractors here but the last
/// // extractor must implement `FromRequest` which
/// // `Request` does
/// request: Request<B>,
/// ) -> Request<B> {
/// // do something with `state` and `request`...
/// request
/// }
///
/// let state = AppState { /* ... */ };
///
/// let app = Router::new()
/// .route("/", get(|| async { /* ... */ }))
/// .route_layer(map_request_with_state(state.clone(), my_middleware))
/// .with_state(state);
/// # let _: axum::Router = app;
/// ```
pub fn map_request_with_state<F, S, T>(state: S, f: F) -> MapRequestLayer<F, S, T> {
MapRequestLayer {
f,
state,
_extractor: PhantomData,
}
}
/// A [`tower::Layer`] from an async function that transforms a request.
///
/// Created with [`map_request`]. See that function for more details.
#[must_use]
pub struct MapRequestLayer<F, S, T> {
f: F,
state: S,
_extractor: PhantomData<fn() -> T>,
}
impl<F, S, T> Clone for MapRequestLayer<F, S, T>
where
F: Clone,
S: Clone,
{
fn clone(&self) -> Self {
Self {
f: self.f.clone(),
state: self.state.clone(),
_extractor: self._extractor,
}
}
}
impl<S, I, F, T> Layer<I> for MapRequestLayer<F, S, T>
where
F: Clone,
S: Clone,
{
type Service = MapRequest<F, S, I, T>;
fn layer(&self, inner: I) -> Self::Service {
MapRequest {
f: self.f.clone(),
state: self.state.clone(),
inner,
_extractor: PhantomData,
}
}
}
impl<F, S, T> fmt::Debug for MapRequestLayer<F, S, T>
where
S: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MapRequestLayer")
// Write out the type name, without quoting it as `&type_name::<F>()` would
.field("f", &format_args!("{}", type_name::<F>()))
.field("state", &self.state)
.finish()
}
}
/// A middleware created from an async function that transforms a request.
///
/// Created with [`map_request`]. See that function for more details.
pub struct MapRequest<F, S, I, T> {
f: F,
inner: I,
state: S,
_extractor: PhantomData<fn() -> T>,
}
impl<F, S, I, T> Clone for MapRequest<F, S, I, T>
where
F: Clone,
I: Clone,
S: Clone,
{
fn clone(&self) -> Self {
Self {
f: self.f.clone(),
inner: self.inner.clone(),
state: self.state.clone(),
_extractor: self._extractor,
}
}
}
macro_rules! impl_service {
(
[$($ty:ident),*], $last:ident
) => {
#[allow(non_snake_case, unused_mut)]
impl<F, Fut, S, I, B, $($ty,)* $last> Service<Request<B>> for MapRequest<F, S, I, ($($ty,)* $last,)>
where
F: FnMut($($ty,)* $last) -> Fut + Clone + Send + 'static,
$( $ty: FromRequestParts<S> + Send, )*
$last: FromRequest<S> + Send,
Fut: Future + Send + 'static,
Fut::Output: IntoMapRequestResult<B> + Send + 'static,
I: Service<Request<B>, Error = Infallible>
+ Clone
+ Send
+ 'static,
I::Response: IntoResponse,
I::Future: Send + 'static,
B: HttpBody<Data = Bytes> + Send + 'static,
B::Error: Into<BoxError>,
S: Clone + Send + Sync + 'static,
{
type Response = Response;
type Error = Infallible;
type Future = ResponseFuture;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request<B>) -> Self::Future {
let req = req.map(Body::new);
let not_ready_inner = self.inner.clone();
let mut ready_inner = std::mem::replace(&mut self.inner, not_ready_inner);
let mut f = self.f.clone();
let state = self.state.clone();
let (mut parts, body) = req.into_parts();
let future = Box::pin(async move {
$(
let $ty = match $ty::from_request_parts(&mut parts, &state).await {
Ok(value) => value,
Err(rejection) => return rejection.into_response(),
};
)*
let req = Request::from_parts(parts, body);
let $last = match $last::from_request(req, &state).await {
Ok(value) => value,
Err(rejection) => return rejection.into_response(),
};
match f($($ty,)* $last).await.into_map_request_result() {
Ok(req) => {
ready_inner.call(req).await.into_response()
}
Err(res) => {
res
}
}
});
ResponseFuture {
inner: future
}
}
}
};
}
all_the_tuples!(impl_service);
impl<F, S, I, T> fmt::Debug for MapRequest<F, S, I, T>
where
S: fmt::Debug,
I: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MapRequest")
.field("f", &format_args!("{}", type_name::<F>()))
.field("inner", &self.inner)
.field("state", &self.state)
.finish()
}
}
/// Response future for [`MapRequest`].
pub struct ResponseFuture {
inner: BoxFuture<'static, Response>,
}
impl Future for ResponseFuture {
type Output = Result<Response, Infallible>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.inner.as_mut().poll(cx).map(Ok)
}
}
impl fmt::Debug for ResponseFuture {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ResponseFuture").finish()
}
}
mod private {
use crate::{http::Request, response::IntoResponse};
pub trait Sealed<B> {}
impl<B, E> Sealed<B> for Result<Request<B>, E> where E: IntoResponse {}
impl<B> Sealed<B> for Request<B> {}
}
/// Trait implemented by types that can be returned from [`map_request`],
/// [`map_request_with_state`].
///
/// This trait is sealed such that it cannot be implemented outside this crate.
pub trait IntoMapRequestResult<B>: private::Sealed<B> {
/// Perform the conversion.
#[allow(clippy::result_large_err)]
fn into_map_request_result(self) -> Result<Request<B>, Response>;
}
impl<B, E> IntoMapRequestResult<B> for Result<Request<B>, E>
where
E: IntoResponse,
{
fn into_map_request_result(self) -> Result<Request<B>, Response> {
self.map_err(IntoResponse::into_response)
}
}
impl<B> IntoMapRequestResult<B> for Request<B> {
fn into_map_request_result(self) -> Result<Request<B>, Response> {
Ok(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{routing::get, test_helpers::TestClient, Router};
use http::{HeaderMap, StatusCode};
#[crate::test]
async fn works() {
async fn add_header<B>(mut req: Request<B>) -> Request<B> {
req.headers_mut().insert("x-foo", "foo".parse().unwrap());
req
}
async fn handler(headers: HeaderMap) -> Response {
headers["x-foo"]
.to_str()
.unwrap()
.to_owned()
.into_response()
}
let app = Router::new()
.route("/", get(handler))
.layer(map_request(add_header));
let client = TestClient::new(app);
let res = client.get("/").await;
assert_eq!(res.text().await, "foo");
}
#[crate::test]
async fn works_for_short_circutting() {
async fn add_header<B>(_req: Request<B>) -> Result<Request<B>, (StatusCode, &'static str)> {
Err((StatusCode::INTERNAL_SERVER_ERROR, "something went wrong"))
}
async fn handler(_headers: HeaderMap) -> Response {
unreachable!()
}
let app = Router::new()
.route("/", get(handler))
.layer(map_request(add_header));
let client = TestClient::new(app);
let res = client.get("/").await;
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
assert_eq!(res.text().await, "something went wrong");
}
}
+363
View File
@@ -0,0 +1,363 @@
use crate::response::{IntoResponse, Response};
use axum_core::extract::FromRequestParts;
use futures_util::future::BoxFuture;
use http::Request;
use std::{
any::type_name,
convert::Infallible,
fmt,
future::Future,
marker::PhantomData,
pin::Pin,
task::{Context, Poll},
};
use tower_layer::Layer;
use tower_service::Service;
/// Create a middleware from an async function that transforms a response.
///
/// This differs from [`tower::util::MapResponse`] in that it allows you to easily run axum-specific
/// extractors.
///
/// # Example
///
/// ```
/// use axum::{
/// Router,
/// routing::get,
/// middleware::map_response,
/// response::Response,
/// };
///
/// async fn set_header<B>(mut response: Response<B>) -> Response<B> {
/// response.headers_mut().insert("x-foo", "foo".parse().unwrap());
/// response
/// }
///
/// let app = Router::new()
/// .route("/", get(|| async { /* ... */ }))
/// .layer(map_response(set_header));
/// # let _: Router = app;
/// ```
///
/// # Running extractors
///
/// It is also possible to run extractors that implement [`FromRequestParts`]. These will be run
/// before calling the handler.
///
/// ```
/// use axum::{
/// Router,
/// routing::get,
/// middleware::map_response,
/// extract::Path,
/// response::Response,
/// };
/// use std::collections::HashMap;
///
/// async fn log_path_params<B>(
/// Path(path_params): Path<HashMap<String, String>>,
/// response: Response<B>,
/// ) -> Response<B> {
/// tracing::debug!(?path_params);
/// response
/// }
///
/// let app = Router::new()
/// .route("/", get(|| async { /* ... */ }))
/// .layer(map_response(log_path_params));
/// # let _: Router = app;
/// ```
///
/// Note that to access state you must use either [`map_response_with_state`].
///
/// # Returning any `impl IntoResponse`
///
/// It is also possible to return anything that implements [`IntoResponse`]
///
/// ```
/// use axum::{
/// Router,
/// routing::get,
/// middleware::map_response,
/// response::{Response, IntoResponse},
/// };
/// use std::collections::HashMap;
///
/// async fn set_header(response: Response) -> impl IntoResponse {
/// (
/// [("x-foo", "foo")],
/// response,
/// )
/// }
///
/// let app = Router::new()
/// .route("/", get(|| async { /* ... */ }))
/// .layer(map_response(set_header));
/// # let _: Router = app;
/// ```
pub fn map_response<F, T>(f: F) -> MapResponseLayer<F, (), T> {
map_response_with_state((), f)
}
/// Create a middleware from an async function that transforms a response, with the given state.
///
/// See [`State`](crate::extract::State) for more details about accessing state.
///
/// # Example
///
/// ```rust
/// use axum::{
/// Router,
/// http::StatusCode,
/// routing::get,
/// response::Response,
/// middleware::map_response_with_state,
/// extract::State,
/// };
///
/// #[derive(Clone)]
/// struct AppState { /* ... */ }
///
/// async fn my_middleware<B>(
/// State(state): State<AppState>,
/// // you can add more extractors here but they must
/// // all implement `FromRequestParts`
/// // `FromRequest` is not allowed
/// response: Response<B>,
/// ) -> Response<B> {
/// // do something with `state` and `response`...
/// response
/// }
///
/// let state = AppState { /* ... */ };
///
/// let app = Router::new()
/// .route("/", get(|| async { /* ... */ }))
/// .route_layer(map_response_with_state(state.clone(), my_middleware))
/// .with_state(state);
/// # let _: axum::Router = app;
/// ```
pub fn map_response_with_state<F, S, T>(state: S, f: F) -> MapResponseLayer<F, S, T> {
MapResponseLayer {
f,
state,
_extractor: PhantomData,
}
}
/// A [`tower::Layer`] from an async function that transforms a response.
///
/// Created with [`map_response`]. See that function for more details.
#[must_use]
pub struct MapResponseLayer<F, S, T> {
f: F,
state: S,
_extractor: PhantomData<fn() -> T>,
}
impl<F, S, T> Clone for MapResponseLayer<F, S, T>
where
F: Clone,
S: Clone,
{
fn clone(&self) -> Self {
Self {
f: self.f.clone(),
state: self.state.clone(),
_extractor: self._extractor,
}
}
}
impl<S, I, F, T> Layer<I> for MapResponseLayer<F, S, T>
where
F: Clone,
S: Clone,
{
type Service = MapResponse<F, S, I, T>;
fn layer(&self, inner: I) -> Self::Service {
MapResponse {
f: self.f.clone(),
state: self.state.clone(),
inner,
_extractor: PhantomData,
}
}
}
impl<F, S, T> fmt::Debug for MapResponseLayer<F, S, T>
where
S: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MapResponseLayer")
// Write out the type name, without quoting it as `&type_name::<F>()` would
.field("f", &format_args!("{}", type_name::<F>()))
.field("state", &self.state)
.finish()
}
}
/// A middleware created from an async function that transforms a response.
///
/// Created with [`map_response`]. See that function for more details.
pub struct MapResponse<F, S, I, T> {
f: F,
inner: I,
state: S,
_extractor: PhantomData<fn() -> T>,
}
impl<F, S, I, T> Clone for MapResponse<F, S, I, T>
where
F: Clone,
I: Clone,
S: Clone,
{
fn clone(&self) -> Self {
Self {
f: self.f.clone(),
inner: self.inner.clone(),
state: self.state.clone(),
_extractor: self._extractor,
}
}
}
macro_rules! impl_service {
(
$($ty:ident),*
) => {
#[allow(non_snake_case, unused_mut)]
impl<F, Fut, S, I, B, ResBody, $($ty,)*> Service<Request<B>> for MapResponse<F, S, I, ($($ty,)*)>
where
F: FnMut($($ty,)* Response<ResBody>) -> Fut + Clone + Send + 'static,
$( $ty: FromRequestParts<S> + Send, )*
Fut: Future + Send + 'static,
Fut::Output: IntoResponse + Send + 'static,
I: Service<Request<B>, Response = Response<ResBody>, Error = Infallible>
+ Clone
+ Send
+ 'static,
I::Future: Send + 'static,
B: Send + 'static,
ResBody: Send + 'static,
S: Clone + Send + Sync + 'static,
{
type Response = Response;
type Error = Infallible;
type Future = ResponseFuture;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request<B>) -> Self::Future {
let not_ready_inner = self.inner.clone();
let mut ready_inner = std::mem::replace(&mut self.inner, not_ready_inner);
let mut f = self.f.clone();
let _state = self.state.clone();
let (mut parts, body) = req.into_parts();
let future = Box::pin(async move {
$(
let $ty = match $ty::from_request_parts(&mut parts, &_state).await {
Ok(value) => value,
Err(rejection) => return rejection.into_response(),
};
)*
let req = Request::from_parts(parts, body);
match ready_inner.call(req).await {
Ok(res) => {
f($($ty,)* res).await.into_response()
}
Err(err) => match err {}
}
});
ResponseFuture {
inner: future
}
}
}
};
}
impl_service!();
impl_service!(T1);
impl_service!(T1, T2);
impl_service!(T1, T2, T3);
impl_service!(T1, T2, T3, T4);
impl_service!(T1, T2, T3, T4, T5);
impl_service!(T1, T2, T3, T4, T5, T6);
impl_service!(T1, T2, T3, T4, T5, T6, T7);
impl_service!(T1, T2, T3, T4, T5, T6, T7, T8);
impl_service!(T1, T2, T3, T4, T5, T6, T7, T8, T9);
impl_service!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
impl_service!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
impl_service!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
impl_service!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
impl_service!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14);
impl_service!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15);
impl_service!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16);
impl<F, S, I, T> fmt::Debug for MapResponse<F, S, I, T>
where
S: fmt::Debug,
I: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MapResponse")
.field("f", &format_args!("{}", type_name::<F>()))
.field("inner", &self.inner)
.field("state", &self.state)
.finish()
}
}
/// Response future for [`MapResponse`].
pub struct ResponseFuture {
inner: BoxFuture<'static, Response>,
}
impl Future for ResponseFuture {
type Output = Result<Response, Infallible>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.inner.as_mut().poll(cx).map(Ok)
}
}
impl fmt::Debug for ResponseFuture {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ResponseFuture").finish()
}
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
use crate::{test_helpers::TestClient, Router};
#[crate::test]
async fn works() {
async fn add_header<B>(mut res: Response<B>) -> Response<B> {
res.headers_mut().insert("x-foo", "foo".parse().unwrap());
res
}
let app = Router::new().layer(map_response(add_header));
let client = TestClient::new(app);
let res = client.get("/").await;
assert_eq!(res.headers()["x-foo"], "foo");
}
}
+33
View File
@@ -0,0 +1,33 @@
//! Utilities for writing middleware
//!
#![doc = include_str!("../docs/middleware.md")]
mod from_extractor;
mod from_fn;
mod map_request;
mod map_response;
mod response_axum_body;
pub use self::from_extractor::{
from_extractor, from_extractor_with_state, FromExtractor, FromExtractorLayer,
};
pub use self::from_fn::{from_fn, from_fn_with_state, FromFn, FromFnLayer, Next};
pub use self::map_request::{
map_request, map_request_with_state, IntoMapRequestResult, MapRequest, MapRequestLayer,
};
pub use self::map_response::{
map_response, map_response_with_state, MapResponse, MapResponseLayer,
};
pub use self::response_axum_body::{
ResponseAxumBody, ResponseAxumBodyFuture, ResponseAxumBodyLayer,
};
pub use crate::extension::AddExtension;
pub mod future {
//! Future types.
pub use super::from_extractor::ResponseFuture as FromExtractorResponseFuture;
pub use super::from_fn::ResponseFuture as FromFnResponseFuture;
pub use super::map_request::ResponseFuture as MapRequestResponseFuture;
pub use super::map_response::ResponseFuture as MapResponseResponseFuture;
}
@@ -0,0 +1,76 @@
use std::{
error::Error,
future::Future,
pin::Pin,
task::{ready, Context, Poll},
};
use axum_core::{body::Body, response::Response};
use bytes::Bytes;
use http_body::Body as HttpBody;
use pin_project_lite::pin_project;
use tower::{Layer, Service};
/// Layer that transforms the Response body to [`crate::body::Body`].
///
/// This is useful when another layer maps the body to some other type to convert it back.
#[derive(Debug, Clone)]
pub struct ResponseAxumBodyLayer;
impl<S> Layer<S> for ResponseAxumBodyLayer {
type Service = ResponseAxumBody<S>;
fn layer(&self, inner: S) -> Self::Service {
ResponseAxumBody::<S>(inner)
}
}
/// Service generated by [`ResponseAxumBodyLayer`].
#[derive(Debug, Clone)]
pub struct ResponseAxumBody<S>(S);
impl<S, Request, ResBody> Service<Request> for ResponseAxumBody<S>
where
S: Service<Request, Response = Response<ResBody>>,
ResBody: HttpBody<Data = Bytes> + Send + 'static,
<ResBody as HttpBody>::Error: Error + Send + Sync,
{
type Response = Response;
type Error = S::Error;
type Future = ResponseAxumBodyFuture<S::Future>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.poll_ready(cx)
}
fn call(&mut self, req: Request) -> Self::Future {
ResponseAxumBodyFuture {
inner: self.0.call(req),
}
}
}
pin_project! {
/// Response future for [`ResponseAxumBody`].
pub struct ResponseAxumBodyFuture<Fut> {
#[pin]
inner: Fut,
}
}
impl<Fut, ResBody, E> Future for ResponseAxumBodyFuture<Fut>
where
Fut: Future<Output = Result<Response<ResBody>, E>>,
ResBody: HttpBody<Data = Bytes> + Send + 'static,
<ResBody as HttpBody>::Error: Error + Send + Sync,
{
type Output = Result<Response<Body>, E>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
let res = ready!(this.inner.poll(cx)?);
Poll::Ready(Ok(res.map(Body::new)))
}
}
+257
View File
@@ -0,0 +1,257 @@
#![doc = include_str!("../docs/response.md")]
use http::{header, HeaderValue, StatusCode};
mod redirect;
pub mod sse;
#[doc(no_inline)]
#[cfg(feature = "json")]
pub use crate::Json;
#[cfg(feature = "form")]
#[doc(no_inline)]
pub use crate::form::Form;
#[doc(no_inline)]
pub use crate::Extension;
#[doc(inline)]
pub use axum_core::response::{
AppendHeaders, ErrorResponse, IntoResponse, IntoResponseParts, Response, ResponseParts, Result,
};
#[doc(inline)]
pub use self::redirect::Redirect;
#[doc(inline)]
pub use sse::Sse;
/// An HTML response.
///
/// Will automatically get `Content-Type: text/html`.
#[derive(Clone, Copy, Debug)]
#[must_use]
pub struct Html<T>(pub T);
impl<T> IntoResponse for Html<T>
where
T: IntoResponse,
{
fn into_response(self) -> Response {
(
[(
header::CONTENT_TYPE,
HeaderValue::from_static(mime::TEXT_HTML_UTF_8.as_ref()),
)],
self.0,
)
.into_response()
}
}
impl<T> From<T> for Html<T> {
fn from(inner: T) -> Self {
Self(inner)
}
}
/// An empty response with 204 No Content status.
///
/// Due to historical and implementation reasons, the `IntoResponse` implementation of `()`
/// (unit type) returns an empty response with 200 [`StatusCode::OK`] status.
/// If you specifically want a 204 [`StatusCode::NO_CONTENT`] status, you can use either `StatusCode` type
/// directly, or this shortcut struct for self-documentation.
///
/// ```
/// use axum::{extract::Path, response::NoContent};
///
/// async fn delete_user(Path(user): Path<String>) -> Result<NoContent, String> {
/// // ...access database...
/// # drop(user);
/// Ok(NoContent)
/// }
/// ```
#[derive(Debug, Clone, Copy)]
pub struct NoContent;
impl IntoResponse for NoContent {
fn into_response(self) -> Response {
StatusCode::NO_CONTENT.into_response()
}
}
#[cfg(test)]
mod tests {
use crate::extract::Extension;
use crate::{routing::get, Router};
use axum_core::response::IntoResponse;
use http::HeaderMap;
use http::{StatusCode, Uri};
// just needs to compile
#[allow(dead_code)]
fn impl_trait_result_works() {
async fn impl_trait_ok() -> Result<impl IntoResponse, ()> {
Ok(())
}
async fn impl_trait_err() -> Result<(), impl IntoResponse> {
Err(())
}
async fn impl_trait_both(uri: Uri) -> Result<impl IntoResponse, impl IntoResponse> {
if uri.path() == "/" {
Ok(())
} else {
Err(())
}
}
async fn impl_trait(uri: Uri) -> impl IntoResponse {
if uri.path() == "/" {
Ok(())
} else {
Err(())
}
}
_ = Router::<()>::new()
.route("/", get(impl_trait_ok))
.route("/", get(impl_trait_err))
.route("/", get(impl_trait_both))
.route("/", get(impl_trait));
}
// just needs to compile
#[allow(dead_code)]
fn tuple_responses() {
async fn status() -> impl IntoResponse {
StatusCode::OK
}
async fn status_headermap() -> impl IntoResponse {
(StatusCode::OK, HeaderMap::new())
}
async fn status_header_array() -> impl IntoResponse {
(StatusCode::OK, [("content-type", "text/plain")])
}
async fn status_headermap_body() -> impl IntoResponse {
(StatusCode::OK, HeaderMap::new(), String::new())
}
async fn status_header_array_body() -> impl IntoResponse {
(
StatusCode::OK,
[("content-type", "text/plain")],
String::new(),
)
}
async fn status_headermap_impl_into_response() -> impl IntoResponse {
(StatusCode::OK, HeaderMap::new(), impl_into_response())
}
async fn status_header_array_impl_into_response() -> impl IntoResponse {
(
StatusCode::OK,
[("content-type", "text/plain")],
impl_into_response(),
)
}
fn impl_into_response() -> impl IntoResponse {}
async fn status_header_array_extension_body() -> impl IntoResponse {
(
StatusCode::OK,
[("content-type", "text/plain")],
Extension(1),
String::new(),
)
}
async fn status_header_array_extension_mixed_body() -> impl IntoResponse {
(
StatusCode::OK,
[("content-type", "text/plain")],
Extension(1),
HeaderMap::new(),
String::new(),
)
}
//
async fn headermap() -> impl IntoResponse {
HeaderMap::new()
}
async fn header_array() -> impl IntoResponse {
[("content-type", "text/plain")]
}
async fn headermap_body() -> impl IntoResponse {
(HeaderMap::new(), String::new())
}
async fn header_array_body() -> impl IntoResponse {
([("content-type", "text/plain")], String::new())
}
async fn headermap_impl_into_response() -> impl IntoResponse {
(HeaderMap::new(), impl_into_response())
}
async fn header_array_impl_into_response() -> impl IntoResponse {
([("content-type", "text/plain")], impl_into_response())
}
async fn header_array_extension_body() -> impl IntoResponse {
(
[("content-type", "text/plain")],
Extension(1),
String::new(),
)
}
async fn header_array_extension_mixed_body() -> impl IntoResponse {
(
[("content-type", "text/plain")],
Extension(1),
HeaderMap::new(),
String::new(),
)
}
_ = Router::<()>::new()
.route("/", get(status))
.route("/", get(status_headermap))
.route("/", get(status_header_array))
.route("/", get(status_headermap_body))
.route("/", get(status_header_array_body))
.route("/", get(status_headermap_impl_into_response))
.route("/", get(status_header_array_impl_into_response))
.route("/", get(status_header_array_extension_body))
.route("/", get(status_header_array_extension_mixed_body))
.route("/", get(headermap))
.route("/", get(header_array))
.route("/", get(headermap_body))
.route("/", get(header_array_body))
.route("/", get(headermap_impl_into_response))
.route("/", get(header_array_impl_into_response))
.route("/", get(header_array_extension_body))
.route("/", get(header_array_extension_mixed_body));
}
#[test]
fn no_content() {
assert_eq!(
super::NoContent.into_response().status(),
StatusCode::NO_CONTENT,
)
}
}
+138
View File
@@ -0,0 +1,138 @@
use axum_core::response::{IntoResponse, Response};
use http::{header::LOCATION, HeaderValue, StatusCode};
/// Response that redirects the request to another location.
///
/// # Example
///
/// ```rust
/// use axum::{
/// routing::get,
/// response::Redirect,
/// Router,
/// };
///
/// let app = Router::new()
/// .route("/old", get(|| async { Redirect::permanent("/new") }))
/// .route("/new", get(|| async { "Hello!" }));
/// # let _: Router = app;
/// ```
#[must_use = "needs to be returned from a handler or otherwise turned into a Response to be useful"]
#[derive(Debug, Clone)]
pub struct Redirect {
status_code: StatusCode,
location: String,
}
impl Redirect {
/// Create a new [`Redirect`] that uses a [`303 See Other`][mdn] status code.
///
/// This redirect instructs the client to change the method to GET for the subsequent request
/// to the given `uri`, which is useful after successful form submission, file upload or when
/// you generally don't want the redirected-to page to observe the original request method and
/// body (if non-empty). If you want to preserve the request method and body,
/// [`Redirect::temporary`] should be used instead.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303
pub fn to(uri: &str) -> Self {
Self::with_status_code(StatusCode::SEE_OTHER, uri)
}
/// Create a new [`Redirect`] that uses a [`307 Temporary Redirect`][mdn] status code.
///
/// This has the same behavior as [`Redirect::to`], except it will preserve the original HTTP
/// method and body.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307
pub fn temporary(uri: &str) -> Self {
Self::with_status_code(StatusCode::TEMPORARY_REDIRECT, uri)
}
/// Create a new [`Redirect`] that uses a [`308 Permanent Redirect`][mdn] status code.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308
pub fn permanent(uri: &str) -> Self {
Self::with_status_code(StatusCode::PERMANENT_REDIRECT, uri)
}
/// Returns the HTTP status code of the `Redirect`.
#[must_use]
pub fn status_code(&self) -> StatusCode {
self.status_code
}
/// Returns the `Redirect`'s URI.
#[must_use]
pub fn location(&self) -> &str {
&self.location
}
// This is intentionally not public since other kinds of redirects might not
// use the `Location` header, namely `304 Not Modified`.
//
// We're open to adding more constructors upon request, if they make sense :)
fn with_status_code(status_code: StatusCode, uri: &str) -> Self {
assert!(
status_code.is_redirection(),
"not a redirection status code"
);
Self {
status_code,
location: uri.to_owned(),
}
}
}
impl IntoResponse for Redirect {
fn into_response(self) -> Response {
match HeaderValue::try_from(self.location) {
Ok(location) => (self.status_code, [(LOCATION, location)]).into_response(),
Err(error) => (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(),
}
}
}
#[cfg(test)]
mod tests {
use super::Redirect;
use axum_core::response::IntoResponse;
use http::StatusCode;
const EXAMPLE_URL: &str = "https://example.com";
// Tests to make sure Redirect has the correct status codes
// based on the way it was constructed.
#[test]
fn correct_status() {
assert_eq!(
StatusCode::SEE_OTHER,
Redirect::to(EXAMPLE_URL).status_code()
);
assert_eq!(
StatusCode::TEMPORARY_REDIRECT,
Redirect::temporary(EXAMPLE_URL).status_code()
);
assert_eq!(
StatusCode::PERMANENT_REDIRECT,
Redirect::permanent(EXAMPLE_URL).status_code()
);
}
#[test]
fn correct_location() {
assert_eq!(EXAMPLE_URL, Redirect::permanent(EXAMPLE_URL).location());
assert_eq!("/redirect", Redirect::permanent("/redirect").location())
}
#[test]
fn test_internal_error() {
let response = Redirect::permanent("Axum is awesome, \n but newlines aren't allowed :(")
.into_response();
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
}
+834
View File
@@ -0,0 +1,834 @@
//! Server-Sent Events (SSE) responses.
//!
//! # Example
//!
//! ```
//! use axum::{
//! Router,
//! routing::get,
//! response::sse::{Event, KeepAlive, Sse},
//! };
//! use std::{time::Duration, convert::Infallible};
//! use tokio_stream::StreamExt as _ ;
//! use futures_util::stream::{self, Stream};
//!
//! let app = Router::new().route("/sse", get(sse_handler));
//!
//! async fn sse_handler() -> Sse<impl Stream<Item = Result<Event, Infallible>>> {
//! // A `Stream` that repeats an event every second
//! let stream = stream::repeat_with(|| Event::default().data("hi!"))
//! .map(Ok)
//! .throttle(Duration::from_secs(1));
//!
//! Sse::new(stream).keep_alive(KeepAlive::default())
//! }
//! # let _: Router = app;
//! ```
use crate::{
body::{Bytes, HttpBody},
BoxError,
};
use axum_core::{
body::Body,
response::{IntoResponse, Response},
};
use bytes::{BufMut, BytesMut};
use futures_util::stream::{Stream, TryStream};
use http_body::Frame;
use pin_project_lite::pin_project;
use std::{
fmt::{self, Write as _},
io::Write as _,
mem,
pin::Pin,
task::{ready, Context, Poll},
time::Duration,
};
use sync_wrapper::SyncWrapper;
/// An SSE response
#[derive(Clone)]
#[must_use]
pub struct Sse<S> {
stream: S,
}
impl<S> Sse<S> {
/// Create a new [`Sse`] response that will respond with the given stream of
/// [`Event`]s.
///
/// See the [module docs](self) for more details.
pub fn new(stream: S) -> Self
where
S: TryStream<Ok = Event> + Send + 'static,
S::Error: Into<BoxError>,
{
Sse { stream }
}
/// Configure the interval between keep-alive messages.
///
/// Defaults to no keep-alive messages.
#[cfg(feature = "tokio")]
pub fn keep_alive(self, keep_alive: KeepAlive) -> Sse<KeepAliveStream<S>> {
Sse {
stream: KeepAliveStream::new(keep_alive, self.stream),
}
}
}
impl<S> fmt::Debug for Sse<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Sse")
.field("stream", &format_args!("{}", std::any::type_name::<S>()))
.finish()
}
}
impl<S, E> IntoResponse for Sse<S>
where
S: Stream<Item = Result<Event, E>> + Send + 'static,
E: Into<BoxError>,
{
fn into_response(self) -> Response {
(
[
(http::header::CONTENT_TYPE, mime::TEXT_EVENT_STREAM.as_ref()),
(http::header::CACHE_CONTROL, "no-cache"),
],
Body::new(SseBody {
event_stream: SyncWrapper::new(self.stream),
}),
)
.into_response()
}
}
pin_project! {
struct SseBody<S> {
#[pin]
event_stream: SyncWrapper<S>,
}
}
impl<S, E> HttpBody for SseBody<S>
where
S: Stream<Item = Result<Event, E>>,
{
type Data = Bytes;
type Error = E;
fn poll_frame(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
let this = self.project();
match ready!(this.event_stream.get_pin_mut().poll_next(cx)) {
Some(Ok(event)) => Poll::Ready(Some(Ok(Frame::data(event.finalize())))),
Some(Err(error)) => Poll::Ready(Some(Err(error))),
None => Poll::Ready(None),
}
}
}
/// The state of an event's buffer.
///
/// This type allows creating events in a `const` context
/// by using a finalized buffer.
///
/// While the buffer is active, more bytes can be written to it.
/// Once finalized, it's immutable and cheap to clone.
/// The buffer is active during the event building, but eventually
/// becomes finalized to send http body frames as [`Bytes`].
#[derive(Debug, Clone)]
enum Buffer {
Active(BytesMut),
Finalized(Bytes),
}
impl Buffer {
/// Returns a mutable reference to the internal buffer.
///
/// If the buffer was finalized, this method creates
/// a new active buffer with the previous contents.
fn as_mut(&mut self) -> &mut BytesMut {
match self {
Buffer::Active(bytes_mut) => bytes_mut,
Buffer::Finalized(bytes) => {
*self = Buffer::Active(BytesMut::from(mem::take(bytes)));
match self {
Buffer::Active(bytes_mut) => bytes_mut,
Buffer::Finalized(_) => unreachable!(),
}
}
}
}
}
/// Server-sent event
#[derive(Debug, Clone)]
#[must_use]
pub struct Event {
buffer: Buffer,
flags: EventFlags,
}
/// Expose [`Event`] as a [`std::fmt::Write`]
/// such that any form of data can be written as data safely.
///
/// This also ensures that newline characters `\r` and `\n`
/// correctly trigger a split with a new `data: ` prefix.
///
/// # Panics
///
/// Panics if any `data` has already been written prior to the first write
/// of this [`EventDataWriter`] instance.
#[derive(Debug)]
#[must_use]
pub struct EventDataWriter {
event: Event,
// Indicates if _this_ EventDataWriter has written data,
// this does not say anything about whether or not `event` contains
// data or not.
data_written: bool,
}
impl Event {
/// Default keep-alive event
pub const DEFAULT_KEEP_ALIVE: Self = Self::finalized(Bytes::from_static(b":\n\n"));
const fn finalized(bytes: Bytes) -> Self {
Self {
buffer: Buffer::Finalized(bytes),
flags: EventFlags::from_bits(0),
}
}
/// Use this [`Event`] as a [`EventDataWriter`] to write custom data.
///
/// - [`Self::data`] can be used as a shortcut to write `str` data
/// - [`Self::json_data`] can be used as a shortcut to write `json` data
///
/// Turn it into an [`Event`] again using [`EventDataWriter::into_event`].
pub fn into_data_writer(self) -> EventDataWriter {
EventDataWriter {
event: self,
data_written: false,
}
}
/// Set the event's data data field(s) (`data: <content>`)
///
/// Newlines in `data` will automatically be broken across `data: ` fields.
///
/// This corresponds to [`MessageEvent`'s data field].
///
/// Note that events with an empty data field will be ignored by the browser.
///
/// # Panics
///
/// Panics if any `data` has already been written before.
///
/// [`MessageEvent`'s data field]: https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent/data
pub fn data<T>(self, data: T) -> Self
where
T: AsRef<str>,
{
let mut writer = self.into_data_writer();
let _ = writer.write_str(data.as_ref());
writer.into_event()
}
/// Set the event's data field to a value serialized as unformatted JSON (`data: <content>`).
///
/// This corresponds to [`MessageEvent`'s data field].
///
/// # Panics
///
/// Panics if any `data` has already been written before.
///
/// [`MessageEvent`'s data field]: https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent/data
#[cfg(feature = "json")]
pub fn json_data<T>(self, data: T) -> Result<Self, axum_core::Error>
where
T: serde_core::Serialize,
{
struct JsonWriter<'a>(&'a mut EventDataWriter);
impl std::io::Write for JsonWriter<'_> {
#[inline]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
Ok(self.0.write_buf(buf))
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
let mut writer = self.into_data_writer();
let json_writer = JsonWriter(&mut writer);
serde_json::to_writer(json_writer, &data).map_err(axum_core::Error::new)?;
Ok(writer.into_event())
}
/// Set the event's comment field (`:<comment-text>`).
///
/// This field will be ignored by most SSE clients.
///
/// Unlike other functions, this function can be called multiple times to add many comments.
///
/// # Panics
///
/// Panics if `comment` contains any newlines or carriage returns, as they are not allowed in
/// comments.
pub fn comment<T>(mut self, comment: T) -> Event
where
T: AsRef<str>,
{
self.field("", comment.as_ref());
self
}
/// Set the event's name field (`event:<event-name>`).
///
/// This corresponds to the `type` parameter given when calling `addEventListener` on an
/// [`EventSource`]. For example, `.event("update")` should correspond to
/// `.addEventListener("update", ...)`. If no event type is given, browsers will fire a
/// [`message` event] instead.
///
/// [`EventSource`]: https://developer.mozilla.org/en-US/docs/Web/API/EventSource
/// [`message` event]: https://developer.mozilla.org/en-US/docs/Web/API/EventSource/message_event
///
/// # Panics
///
/// - Panics if `event` contains any newlines or carriage returns.
/// - Panics if this function has already been called on this event.
pub fn event<T>(mut self, event: T) -> Event
where
T: AsRef<str>,
{
if self.flags.contains(EventFlags::HAS_EVENT) {
panic!("Called `Event::event` multiple times");
}
self.flags.insert(EventFlags::HAS_EVENT);
self.field("event", event.as_ref());
self
}
/// Set the event's retry timeout field (`retry: <timeout>`).
///
/// This sets how long clients will wait before reconnecting if they are disconnected from the
/// SSE endpoint. Note that this is just a hint: clients are free to wait for longer if they
/// wish, such as if they implement exponential backoff.
///
/// # Panics
///
/// Panics if this function has already been called on this event.
pub fn retry(mut self, duration: Duration) -> Event {
if self.flags.contains(EventFlags::HAS_RETRY) {
panic!("Called `Event::retry` multiple times");
}
self.flags.insert(EventFlags::HAS_RETRY);
let buffer = self.buffer.as_mut();
buffer.extend_from_slice(b"retry: ");
let secs = duration.as_secs();
let millis = duration.subsec_millis();
if secs > 0 {
// format seconds
buffer.extend_from_slice(itoa::Buffer::new().format(secs).as_bytes());
// pad milliseconds
if millis < 10 {
buffer.extend_from_slice(b"00");
} else if millis < 100 {
buffer.extend_from_slice(b"0");
}
}
// format milliseconds
buffer.extend_from_slice(itoa::Buffer::new().format(millis).as_bytes());
buffer.put_u8(b'\n');
self
}
/// Set the event's identifier field (`id:<identifier>`).
///
/// This corresponds to [`MessageEvent`'s `lastEventId` field]. If no ID is in the event itself,
/// the browser will set that field to the last known message ID, starting with the empty
/// string.
///
/// [`MessageEvent`'s `lastEventId` field]: https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent/lastEventId
///
/// # Panics
///
/// - Panics if `id` contains any newlines, carriage returns or null characters.
/// - Panics if this function has already been called on this event.
pub fn id<T>(mut self, id: T) -> Event
where
T: AsRef<str>,
{
if self.flags.contains(EventFlags::HAS_ID) {
panic!("Called `Event::id` multiple times");
}
self.flags.insert(EventFlags::HAS_ID);
let id = id.as_ref().as_bytes();
assert_eq!(
memchr::memchr(b'\0', id),
None,
"Event ID cannot contain null characters",
);
self.field("id", id);
self
}
fn field(&mut self, name: &str, value: impl AsRef<[u8]>) {
let value = value.as_ref();
assert_eq!(
memchr::memchr2(b'\r', b'\n', value),
None,
"SSE field value cannot contain newlines or carriage returns",
);
let buffer = self.buffer.as_mut();
buffer.extend_from_slice(name.as_bytes());
buffer.put_u8(b':');
buffer.put_u8(b' ');
buffer.extend_from_slice(value);
buffer.put_u8(b'\n');
}
fn finalize(self) -> Bytes {
match self.buffer {
Buffer::Finalized(bytes) => bytes,
Buffer::Active(mut bytes_mut) => {
bytes_mut.put_u8(b'\n');
bytes_mut.freeze()
}
}
}
}
impl EventDataWriter {
/// Consume the [`EventDataWriter`] and return the [`Event`] once again.
///
/// In case any data was written by this instance
/// it will also write the trailing `\n` character.
pub fn into_event(self) -> Event {
let mut event = self.event;
if self.data_written {
let _ = event.buffer.as_mut().write_char('\n');
}
event
}
}
impl EventDataWriter {
// Assumption: underlying writer never returns an error:
// <https://docs.rs/bytes/latest/src/bytes/buf/writer.rs.html#79-82>
fn write_buf(&mut self, buf: &[u8]) -> usize {
if buf.is_empty() {
return 0;
}
let buffer = self.event.buffer.as_mut();
if !std::mem::replace(&mut self.data_written, true) {
if self.event.flags.contains(EventFlags::HAS_DATA) {
panic!("Called `Event::data*` multiple times");
}
let _ = buffer.write_str("data: ");
self.event.flags.insert(EventFlags::HAS_DATA);
}
let mut writer = buffer.writer();
let mut last_split = 0;
for delimiter in memchr::memchr2_iter(b'\n', b'\r', buf) {
let _ = writer.write_all(&buf[last_split..=delimiter]);
let _ = writer.write_all(b"data: ");
last_split = delimiter + 1;
}
let _ = writer.write_all(&buf[last_split..]);
buf.len()
}
}
impl fmt::Write for EventDataWriter {
fn write_str(&mut self, s: &str) -> fmt::Result {
let _ = self.write_buf(s.as_bytes());
Ok(())
}
}
impl Default for Event {
fn default() -> Self {
Self {
buffer: Buffer::Active(BytesMut::new()),
flags: EventFlags::from_bits(0),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
struct EventFlags(u8);
impl EventFlags {
const HAS_DATA: Self = Self::from_bits(0b0001);
const HAS_EVENT: Self = Self::from_bits(0b0010);
const HAS_RETRY: Self = Self::from_bits(0b0100);
const HAS_ID: Self = Self::from_bits(0b1000);
const fn bits(&self) -> u8 {
self.0
}
const fn from_bits(bits: u8) -> Self {
Self(bits)
}
const fn contains(&self, other: Self) -> bool {
self.bits() & other.bits() == other.bits()
}
fn insert(&mut self, other: Self) {
*self = Self::from_bits(self.bits() | other.bits());
}
}
/// Configure the interval between keep-alive messages, the content
/// of each message, and the associated stream.
#[derive(Debug, Clone)]
#[must_use]
pub struct KeepAlive {
event: Event,
max_interval: Duration,
}
impl KeepAlive {
/// Create a new `KeepAlive`.
pub fn new() -> Self {
Self {
event: Event::DEFAULT_KEEP_ALIVE,
max_interval: Duration::from_secs(15),
}
}
/// Customize the interval between keep-alive messages.
///
/// Default is 15 seconds.
pub fn interval(mut self, time: Duration) -> Self {
self.max_interval = time;
self
}
/// Customize the text of the keep-alive message.
///
/// Default is an empty comment.
///
/// # Panics
///
/// Panics if `text` contains any newline or carriage returns, as they are not allowed in SSE
/// comments.
pub fn text<I>(self, text: I) -> Self
where
I: AsRef<str>,
{
self.event(Event::default().comment(text))
}
/// Customize the event of the keep-alive message.
///
/// Default is an empty comment.
///
/// # Panics
///
/// Panics if `event` contains any newline or carriage returns, as they are not allowed in SSE
/// comments.
pub fn event(mut self, event: Event) -> Self {
self.event = Event::finalized(event.finalize());
self
}
}
impl Default for KeepAlive {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "tokio")]
pin_project! {
/// A wrapper around a stream that produces keep-alive events
#[derive(Debug)]
pub struct KeepAliveStream<S> {
#[pin]
alive_timer: tokio::time::Sleep,
#[pin]
inner: S,
keep_alive: KeepAlive,
}
}
#[cfg(feature = "tokio")]
impl<S> KeepAliveStream<S> {
fn new(keep_alive: KeepAlive, inner: S) -> Self {
Self {
alive_timer: tokio::time::sleep(keep_alive.max_interval),
inner,
keep_alive,
}
}
fn reset(self: Pin<&mut Self>) {
let this = self.project();
this.alive_timer
.reset(tokio::time::Instant::now() + this.keep_alive.max_interval);
}
}
#[cfg(feature = "tokio")]
impl<S, E> Stream for KeepAliveStream<S>
where
S: Stream<Item = Result<Event, E>>,
{
type Item = Result<Event, E>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
use std::future::Future;
let mut this = self.as_mut().project();
match this.inner.as_mut().poll_next(cx) {
Poll::Ready(Some(Ok(event))) => {
self.reset();
Poll::Ready(Some(Ok(event)))
}
Poll::Ready(Some(Err(error))) => Poll::Ready(Some(Err(error))),
Poll::Ready(None) => Poll::Ready(None),
Poll::Pending => {
ready!(this.alive_timer.poll(cx));
let event = this.keep_alive.event.clone();
self.reset();
Poll::Ready(Some(Ok(event)))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{routing::get, test_helpers::*, Router};
use futures_util::stream;
use serde_json::value::RawValue;
use std::{collections::HashMap, convert::Infallible};
use tokio_stream::StreamExt as _;
#[test]
fn leading_space_is_not_stripped() {
let no_leading_space = Event::default().data("\tfoobar");
assert_eq!(&*no_leading_space.finalize(), b"data: \tfoobar\n\n");
let leading_space = Event::default().data(" foobar");
assert_eq!(&*leading_space.finalize(), b"data: foobar\n\n");
}
#[test]
fn write_data_writer_str() {
// also confirm that nop writers do nothing :)
let mut writer = Event::default()
.into_data_writer()
.into_event()
.into_data_writer();
writer.write_str("").unwrap();
let mut writer = writer.into_event().into_data_writer();
writer.write_str("").unwrap();
writer.write_str("moon ").unwrap();
writer.write_str("star\nsun").unwrap();
writer.write_str("").unwrap();
writer.write_str("set").unwrap();
writer.write_str("").unwrap();
writer.write_str(" bye\r").unwrap();
let event = writer.into_event();
assert_eq!(
&*event.finalize(),
b"data: moon star\ndata: sunset bye\rdata: \n\n"
);
}
#[test]
fn valid_json_raw_value_chars_handled() {
let json_string = "{\r\"foo\": \n\r\r \"bar\\n\"\n}";
let json_raw_value_event = Event::default()
.json_data(serde_json::from_str::<&RawValue>(json_string).unwrap())
.unwrap();
assert_eq!(
&*json_raw_value_event.finalize(),
b"data: {\rdata: \"foo\": \ndata: \rdata: \rdata: \"bar\\n\"\ndata: }\n\n"
);
}
#[crate::test]
async fn basic() {
let app = Router::new().route(
"/",
get(|| async {
let stream = stream::iter(vec![
Event::default().data("one").comment("this is a comment"),
Event::default()
.json_data(serde_json::json!({ "foo": "bar" }))
.unwrap(),
Event::default()
.event("three")
.retry(Duration::from_secs(30))
.id("unique-id"),
])
.map(Ok::<_, Infallible>);
Sse::new(stream)
}),
);
let client = TestClient::new(app);
let mut stream = client.get("/").await;
assert_eq!(stream.headers()["content-type"], "text/event-stream");
assert_eq!(stream.headers()["cache-control"], "no-cache");
let event_fields = parse_event(&stream.chunk_text().await.unwrap());
assert_eq!(event_fields.get("data").unwrap(), "one");
assert_eq!(event_fields.get("comment").unwrap(), "this is a comment");
let event_fields = parse_event(&stream.chunk_text().await.unwrap());
assert_eq!(event_fields.get("data").unwrap(), "{\"foo\":\"bar\"}");
assert!(!event_fields.contains_key("comment"));
let event_fields = parse_event(&stream.chunk_text().await.unwrap());
assert_eq!(event_fields.get("event").unwrap(), "three");
assert_eq!(event_fields.get("retry").unwrap(), "30000");
assert_eq!(event_fields.get("id").unwrap(), "unique-id");
assert!(!event_fields.contains_key("comment"));
assert!(stream.chunk_text().await.is_none());
}
#[tokio::test(start_paused = true)]
async fn keep_alive() {
const DELAY: Duration = Duration::from_secs(5);
let app = Router::new().route(
"/",
get(|| async {
let stream = stream::repeat_with(|| Event::default().data("msg"))
.map(Ok::<_, Infallible>)
.throttle(DELAY);
Sse::new(stream).keep_alive(
KeepAlive::new()
.interval(Duration::from_secs(1))
.text("keep-alive-text"),
)
}),
);
let client = TestClient::new(app);
let mut stream = client.get("/").await;
for _ in 0..5 {
// first message should be an event
let event_fields = parse_event(&stream.chunk_text().await.unwrap());
assert_eq!(event_fields.get("data").unwrap(), "msg");
// then 4 seconds of keep-alive messages
for _ in 0..4 {
tokio::time::sleep(Duration::from_secs(1)).await;
let event_fields = parse_event(&stream.chunk_text().await.unwrap());
assert_eq!(event_fields.get("comment").unwrap(), "keep-alive-text");
}
}
}
#[tokio::test(start_paused = true)]
async fn keep_alive_ends_when_the_stream_ends() {
const DELAY: Duration = Duration::from_secs(5);
let app = Router::new().route(
"/",
get(|| async {
let stream = stream::repeat_with(|| Event::default().data("msg"))
.map(Ok::<_, Infallible>)
.throttle(DELAY)
.take(2);
Sse::new(stream).keep_alive(
KeepAlive::new()
.interval(Duration::from_secs(1))
.text("keep-alive-text"),
)
}),
);
let client = TestClient::new(app);
let mut stream = client.get("/").await;
// first message should be an event
let event_fields = parse_event(&stream.chunk_text().await.unwrap());
assert_eq!(event_fields.get("data").unwrap(), "msg");
// then 4 seconds of keep-alive messages
for _ in 0..4 {
tokio::time::sleep(Duration::from_secs(1)).await;
let event_fields = parse_event(&stream.chunk_text().await.unwrap());
assert_eq!(event_fields.get("comment").unwrap(), "keep-alive-text");
}
// then the last event
let event_fields = parse_event(&stream.chunk_text().await.unwrap());
assert_eq!(event_fields.get("data").unwrap(), "msg");
// then no more events or keep-alive messages
assert!(stream.chunk_text().await.is_none());
}
fn parse_event(payload: &str) -> HashMap<String, String> {
let mut fields = HashMap::new();
let mut lines = payload.lines().peekable();
while let Some(line) = lines.next() {
if line.is_empty() {
assert!(lines.next().is_none());
break;
}
let (mut key, value) = line.split_once(':').unwrap();
let value = value.trim();
if key.is_empty() {
key = "comment";
}
fields.insert(key.to_owned(), value.to_owned());
}
fields
}
}
+6
View File
@@ -0,0 +1,6 @@
//! Future types.
pub use super::{
into_make_service::IntoMakeServiceFuture,
route::{InfallibleRouteFuture, RouteFuture},
};
@@ -0,0 +1,56 @@
use std::{
convert::Infallible,
future::ready,
task::{Context, Poll},
};
use tower_service::Service;
/// A [`MakeService`] that produces axum router services.
///
/// [`MakeService`]: tower::make::MakeService
#[derive(Debug, Clone)]
pub struct IntoMakeService<S> {
svc: S,
}
impl<S> IntoMakeService<S> {
pub(crate) fn new(svc: S) -> Self {
Self { svc }
}
}
impl<S, T> Service<T> for IntoMakeService<S>
where
S: Clone,
{
type Response = S;
type Error = Infallible;
type Future = IntoMakeServiceFuture<S>;
#[inline]
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _target: T) -> Self::Future {
IntoMakeServiceFuture::new(ready(Ok(self.svc.clone())))
}
}
opaque_future! {
/// Response future for [`IntoMakeService`].
pub type IntoMakeServiceFuture<S> =
std::future::Ready<Result<S, Infallible>>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn traits() {
use crate::test_helpers::*;
assert_send::<IntoMakeService<()>>();
}
}
+165
View File
@@ -0,0 +1,165 @@
use http::Method;
use std::{
fmt,
fmt::{Debug, Formatter},
};
/// A filter that matches one or more HTTP methods.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct MethodFilter(u16);
impl MethodFilter {
/// Match `CONNECT` requests.
///
/// This is useful for implementing HTTP/2's [extended CONNECT method],
/// in which the `:protocol` pseudoheader is read
/// (using [`hyper::ext::Protocol`])
/// and the connection upgraded to a bidirectional byte stream
/// (using [`hyper::upgrade::on`]).
///
/// As seen in the [HTTP Upgrade Token Registry],
/// common uses include WebSockets and proxying UDP or IP –
/// though note that when using [`WebSocketUpgrade`]
/// it's more useful to use [`any`](crate::routing::any)
/// as HTTP/1.1 WebSockets need to support `GET`.
///
/// [extended CONNECT]: https://www.rfc-editor.org/rfc/rfc8441.html#section-4
/// [HTTP Upgrade Token Registry]: https://www.iana.org/assignments/http-upgrade-tokens/http-upgrade-tokens.xhtml
/// [`WebSocketUpgrade`]: crate::extract::WebSocketUpgrade
pub const CONNECT: Self = Self::from_bits(0b0_0000_0001);
/// Match `DELETE` requests.
pub const DELETE: Self = Self::from_bits(0b0_0000_0010);
/// Match `GET` requests.
pub const GET: Self = Self::from_bits(0b0_0000_0100);
/// Match `HEAD` requests.
pub const HEAD: Self = Self::from_bits(0b0_0000_1000);
/// Match `OPTIONS` requests.
pub const OPTIONS: Self = Self::from_bits(0b0_0001_0000);
/// Match `PATCH` requests.
pub const PATCH: Self = Self::from_bits(0b0_0010_0000);
/// Match `POST` requests.
pub const POST: Self = Self::from_bits(0b0_0100_0000);
/// Match `PUT` requests.
pub const PUT: Self = Self::from_bits(0b0_1000_0000);
/// Match `TRACE` requests.
pub const TRACE: Self = Self::from_bits(0b1_0000_0000);
const fn bits(&self) -> u16 {
let bits = self;
bits.0
}
const fn from_bits(bits: u16) -> Self {
Self(bits)
}
pub(crate) const fn contains(&self, other: Self) -> bool {
self.bits() & other.bits() == other.bits()
}
/// Performs the OR operation between the [`MethodFilter`] in `self` with `other`.
#[must_use]
pub const fn or(self, other: Self) -> Self {
Self(self.0 | other.0)
}
}
/// Error type used when converting a [`Method`] to a [`MethodFilter`] fails.
#[derive(Debug)]
pub struct NoMatchingMethodFilter {
method: Method,
}
impl NoMatchingMethodFilter {
/// Get the [`Method`] that couldn't be converted to a [`MethodFilter`].
pub fn method(&self) -> &Method {
&self.method
}
}
impl fmt::Display for NoMatchingMethodFilter {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "no `MethodFilter` for `{}`", self.method.as_str())
}
}
impl std::error::Error for NoMatchingMethodFilter {}
impl TryFrom<Method> for MethodFilter {
type Error = NoMatchingMethodFilter;
fn try_from(m: Method) -> Result<Self, NoMatchingMethodFilter> {
match m {
Method::CONNECT => Ok(MethodFilter::CONNECT),
Method::DELETE => Ok(MethodFilter::DELETE),
Method::GET => Ok(MethodFilter::GET),
Method::HEAD => Ok(MethodFilter::HEAD),
Method::OPTIONS => Ok(MethodFilter::OPTIONS),
Method::PATCH => Ok(MethodFilter::PATCH),
Method::POST => Ok(MethodFilter::POST),
Method::PUT => Ok(MethodFilter::PUT),
Method::TRACE => Ok(MethodFilter::TRACE),
other => Err(NoMatchingMethodFilter { method: other }),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_http_method() {
assert_eq!(
MethodFilter::try_from(Method::CONNECT).unwrap(),
MethodFilter::CONNECT
);
assert_eq!(
MethodFilter::try_from(Method::DELETE).unwrap(),
MethodFilter::DELETE
);
assert_eq!(
MethodFilter::try_from(Method::GET).unwrap(),
MethodFilter::GET
);
assert_eq!(
MethodFilter::try_from(Method::HEAD).unwrap(),
MethodFilter::HEAD
);
assert_eq!(
MethodFilter::try_from(Method::OPTIONS).unwrap(),
MethodFilter::OPTIONS
);
assert_eq!(
MethodFilter::try_from(Method::PATCH).unwrap(),
MethodFilter::PATCH
);
assert_eq!(
MethodFilter::try_from(Method::POST).unwrap(),
MethodFilter::POST
);
assert_eq!(
MethodFilter::try_from(Method::PUT).unwrap(),
MethodFilter::PUT
);
assert_eq!(
MethodFilter::try_from(Method::TRACE).unwrap(),
MethodFilter::TRACE
);
assert!(
MethodFilter::try_from(http::Method::from_bytes(b"CUSTOM").unwrap())
.unwrap_err()
.to_string()
.contains("CUSTOM")
);
}
}
File diff suppressed because it is too large Load Diff
+807
View File
@@ -0,0 +1,807 @@
//! Routing between [`Service`]s and handlers.
use self::{future::RouteFuture, not_found::NotFound, path_router::PathRouter};
#[cfg(feature = "tokio")]
use crate::extract::connect_info::IntoMakeServiceWithConnectInfo;
use crate::{
body::{Body, HttpBody},
boxed::BoxedIntoRoute,
handler::Handler,
util::try_downcast,
};
use axum_core::{
extract::Request,
response::{IntoResponse, Response},
};
use std::{
convert::Infallible,
fmt,
marker::PhantomData,
sync::Arc,
task::{Context, Poll},
};
use tower_layer::Layer;
use tower_service::Service;
pub mod future;
pub mod method_routing;
mod into_make_service;
mod method_filter;
mod not_found;
pub(crate) mod path_router;
mod route;
mod strip_prefix;
pub(crate) mod url_params;
#[cfg(test)]
mod tests;
pub use self::{into_make_service::IntoMakeService, method_filter::MethodFilter, route::Route};
pub use self::method_routing::{
any, any_service, connect, connect_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,
};
macro_rules! panic_on_err {
($expr:expr) => {
match $expr {
Ok(x) => x,
Err(err) => panic!("{err}"),
}
};
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct RouteId(u32);
/// The router type for composing handlers and services.
///
/// `Router<S>` means a router that is _missing_ a state of type `S` to be able
/// to handle requests. Thus, only `Router<()>` (i.e. without missing state) can
/// be passed to [`serve`]. See [`Router::with_state`] for more details.
///
/// [`serve`]: crate::serve()
#[must_use]
pub struct Router<S = ()> {
inner: Arc<RouterInner<S>>,
}
impl<S> Clone for Router<S> {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
}
struct RouterInner<S> {
path_router: PathRouter<S, false>,
fallback_router: PathRouter<S, true>,
default_fallback: bool,
catch_all_fallback: Fallback<S>,
}
impl<S> Default for Router<S>
where
S: Clone + Send + Sync + 'static,
{
fn default() -> Self {
Self::new()
}
}
impl<S> fmt::Debug for Router<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Router")
.field("path_router", &self.inner.path_router)
.field("fallback_router", &self.inner.fallback_router)
.field("default_fallback", &self.inner.default_fallback)
.field("catch_all_fallback", &self.inner.catch_all_fallback)
.finish()
}
}
pub(crate) const NEST_TAIL_PARAM: &str = "__private__axum_nest_tail_param";
#[cfg(feature = "matched-path")]
pub(crate) const NEST_TAIL_PARAM_CAPTURE: &str = "/{*__private__axum_nest_tail_param}";
pub(crate) const FALLBACK_PARAM: &str = "__private__axum_fallback";
pub(crate) const FALLBACK_PARAM_PATH: &str = "/{*__private__axum_fallback}";
macro_rules! map_inner {
( $self_:ident, $inner:pat_param => $expr:expr) => {
#[allow(redundant_semicolons)]
{
let $inner = $self_.into_inner();
Router {
inner: Arc::new($expr),
}
}
};
}
macro_rules! tap_inner {
( $self_:ident, mut $inner:ident => { $($stmt:stmt)* } ) => {
#[allow(redundant_semicolons)]
{
let mut $inner = $self_.into_inner();
$($stmt)*;
Router {
inner: Arc::new($inner),
}
}
};
}
impl<S> Router<S>
where
S: Clone + Send + Sync + 'static,
{
/// Create a new `Router`.
///
/// Unless you add additional routes this will respond with `404 Not Found` to
/// all requests.
pub fn new() -> Self {
Self {
inner: Arc::new(RouterInner {
path_router: Default::default(),
fallback_router: PathRouter::new_fallback(),
default_fallback: true,
catch_all_fallback: Fallback::Default(Route::new(NotFound)),
}),
}
}
fn into_inner(self) -> RouterInner<S> {
match Arc::try_unwrap(self.inner) {
Ok(inner) => inner,
Err(arc) => RouterInner {
path_router: arc.path_router.clone(),
fallback_router: arc.fallback_router.clone(),
default_fallback: arc.default_fallback,
catch_all_fallback: arc.catch_all_fallback.clone(),
},
}
}
#[doc = include_str!("../docs/routing/without_v07_checks.md")]
pub fn without_v07_checks(self) -> Self {
tap_inner!(self, mut this => {
this.path_router.without_v07_checks();
})
}
#[doc = include_str!("../docs/routing/route.md")]
#[track_caller]
pub fn route(self, path: &str, method_router: MethodRouter<S>) -> Self {
tap_inner!(self, mut this => {
panic_on_err!(this.path_router.route(path, method_router));
})
}
#[doc = include_str!("../docs/routing/route_service.md")]
pub fn route_service<T>(self, path: &str, service: T) -> Self
where
T: Service<Request, Error = Infallible> + Clone + Send + Sync + 'static,
T::Response: IntoResponse,
T::Future: Send + 'static,
{
let service = match try_downcast::<Router<S>, _>(service) {
Ok(_) => {
panic!(
"Invalid route: `Router::route_service` cannot be used with `Router`s. \
Use `Router::nest` instead"
);
}
Err(service) => service,
};
tap_inner!(self, mut this => {
panic_on_err!(this.path_router.route_service(path, service));
})
}
#[doc = include_str!("../docs/routing/nest.md")]
#[doc(alias = "scope")] // Some other libs like actix-web use this term
#[track_caller]
pub fn nest(self, path: &str, router: Router<S>) -> Self {
if path.is_empty() || path == "/" {
panic!("Nesting at the root is no longer supported. Use merge instead.");
}
let RouterInner {
path_router,
fallback_router,
default_fallback,
// we don't need to inherit the catch-all fallback. It is only used for CONNECT
// requests with an empty path. If we were to inherit the catch-all fallback
// it would end up matching `/{path}/*` which doesn't match empty paths.
catch_all_fallback: _,
} = router.into_inner();
tap_inner!(self, mut this => {
panic_on_err!(this.path_router.nest(path, path_router));
if !default_fallback {
panic_on_err!(this.fallback_router.nest(path, fallback_router));
}
})
}
/// Like [`nest`](Self::nest), but accepts an arbitrary `Service`.
#[track_caller]
pub fn nest_service<T>(self, path: &str, service: T) -> Self
where
T: Service<Request, Error = Infallible> + Clone + Send + Sync + 'static,
T::Response: IntoResponse,
T::Future: Send + 'static,
{
if path.is_empty() || path == "/" {
panic!("Nesting at the root is no longer supported. Use fallback_service instead.");
}
tap_inner!(self, mut this => {
panic_on_err!(this.path_router.nest_service(path, service));
})
}
#[doc = include_str!("../docs/routing/merge.md")]
#[track_caller]
pub fn merge<R>(self, other: R) -> Self
where
R: Into<Router<S>>,
{
const PANIC_MSG: &str =
"Failed to merge fallbacks. This is a bug in axum. Please file an issue";
let other: Router<S> = other.into();
let RouterInner {
path_router,
fallback_router: mut other_fallback,
default_fallback,
catch_all_fallback,
} = other.into_inner();
map_inner!(self, mut this => {
panic_on_err!(this.path_router.merge(path_router));
match (this.default_fallback, default_fallback) {
// both have the default fallback
// use the one from other
(true, true) => {
this.fallback_router.merge(other_fallback).expect(PANIC_MSG);
}
// this has default fallback, other has a custom fallback
(true, false) => {
this.fallback_router.merge(other_fallback).expect(PANIC_MSG);
this.default_fallback = false;
}
// this has a custom fallback, other has a default
(false, true) => {
let fallback_router = std::mem::take(&mut this.fallback_router);
other_fallback.merge(fallback_router).expect(PANIC_MSG);
this.fallback_router = other_fallback;
}
// both have a custom fallback, not allowed
(false, false) => {
panic!("Cannot merge two `Router`s that both have a fallback")
}
};
this.catch_all_fallback = this
.catch_all_fallback
.merge(catch_all_fallback)
.unwrap_or_else(|| panic!("Cannot merge two `Router`s that both have a fallback"));
this
})
}
#[doc = include_str!("../docs/routing/layer.md")]
pub fn layer<L>(self, layer: L) -> Router<S>
where
L: Layer<Route> + Clone + Send + Sync + 'static,
L::Service: Service<Request> + Clone + Send + Sync + 'static,
<L::Service as Service<Request>>::Response: IntoResponse + 'static,
<L::Service as Service<Request>>::Error: Into<Infallible> + 'static,
<L::Service as Service<Request>>::Future: Send + 'static,
{
map_inner!(self, this => RouterInner {
path_router: this.path_router.layer(layer.clone()),
fallback_router: this.fallback_router.layer(layer.clone()),
default_fallback: this.default_fallback,
catch_all_fallback: this.catch_all_fallback.map(|route| route.layer(layer)),
})
}
#[doc = include_str!("../docs/routing/route_layer.md")]
#[track_caller]
pub fn route_layer<L>(self, layer: L) -> Self
where
L: Layer<Route> + Clone + Send + Sync + 'static,
L::Service: Service<Request> + Clone + Send + Sync + 'static,
<L::Service as Service<Request>>::Response: IntoResponse + 'static,
<L::Service as Service<Request>>::Error: Into<Infallible> + 'static,
<L::Service as Service<Request>>::Future: Send + 'static,
{
map_inner!(self, this => RouterInner {
path_router: this.path_router.route_layer(layer),
fallback_router: this.fallback_router,
default_fallback: this.default_fallback,
catch_all_fallback: this.catch_all_fallback,
})
}
/// True if the router currently has at least one route added.
#[must_use]
pub fn has_routes(&self) -> bool {
self.inner.path_router.has_routes()
}
#[track_caller]
#[doc = include_str!("../docs/routing/fallback.md")]
pub fn fallback<H, T>(self, handler: H) -> Self
where
H: Handler<T, S>,
T: 'static,
{
tap_inner!(self, mut this => {
this.catch_all_fallback =
Fallback::BoxedHandler(BoxedIntoRoute::from_handler(handler.clone()));
})
.fallback_endpoint(Endpoint::MethodRouter(any(handler)))
}
/// Add a fallback [`Service`] to the router.
///
/// See [`Router::fallback`] for more details.
pub fn fallback_service<T>(self, service: T) -> Self
where
T: Service<Request, Error = Infallible> + Clone + Send + Sync + 'static,
T::Response: IntoResponse,
T::Future: Send + 'static,
{
let route = Route::new(service);
tap_inner!(self, mut this => {
this.catch_all_fallback = Fallback::Service(route.clone());
})
.fallback_endpoint(Endpoint::Route(route))
}
#[doc = include_str!("../docs/routing/method_not_allowed_fallback.md")]
pub fn method_not_allowed_fallback<H, T>(self, handler: H) -> Self
where
H: Handler<T, S>,
T: 'static,
{
tap_inner!(self, mut this => {
this.path_router
.method_not_allowed_fallback(handler.clone());
})
}
/// Reset the fallback to its default.
///
/// Useful to merge two routers with fallbacks, as [`merge`] doesn't allow
/// both routers to have an explicit fallback. Use this method to remove the
/// one you want to discard before merging.
///
/// [`merge`]: Self::merge
pub fn reset_fallback(self) -> Self {
tap_inner!(self, mut this => {
this.fallback_router = PathRouter::new_fallback();
this.default_fallback = true;
this.catch_all_fallback = Fallback::Default(Route::new(NotFound));
})
}
fn fallback_endpoint(self, endpoint: Endpoint<S>) -> Self {
tap_inner!(self, mut this => {
this.fallback_router.set_fallback(endpoint);
this.default_fallback = false;
})
}
#[doc = include_str!("../docs/routing/with_state.md")]
pub fn with_state<S2>(self, state: S) -> Router<S2> {
map_inner!(self, this => RouterInner {
path_router: this.path_router.with_state(state.clone()),
fallback_router: this.fallback_router.with_state(state.clone()),
default_fallback: this.default_fallback,
catch_all_fallback: this.catch_all_fallback.with_state(state),
})
}
pub(crate) fn call_with_state(&self, req: Request, state: S) -> RouteFuture<Infallible> {
let (req, state) = match self.inner.path_router.call_with_state(req, state) {
Ok(future) => return future,
Err((req, state)) => (req, state),
};
let (req, state) = match self.inner.fallback_router.call_with_state(req, state) {
Ok(future) => return future,
Err((req, state)) => (req, state),
};
self.inner
.catch_all_fallback
.clone()
.call_with_state(req, state)
}
/// Convert the router into a borrowed [`Service`] with a fixed request body type, to aid type
/// inference.
///
/// In some cases when calling methods from [`tower::ServiceExt`] on a [`Router`] you might get
/// type inference errors along the lines of
///
/// ```not_rust
/// let response = router.ready().await?.call(request).await?;
/// ^^^^^ cannot infer type for type parameter `B`
/// ```
///
/// This happens because `Router` implements [`Service`] with `impl<B> Service<Request<B>> for Router<()>`.
///
/// For example:
///
/// ```compile_fail
/// use axum::{
/// Router,
/// routing::get,
/// http::Request,
/// body::Body,
/// };
/// use tower::{Service, ServiceExt};
///
/// # async fn async_main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut router = Router::new().route("/", get(|| async {}));
/// let request = Request::new(Body::empty());
/// let response = router.ready().await?.call(request).await?;
/// # Ok(())
/// # }
/// ```
///
/// Calling `Router::as_service` fixes that:
///
/// ```
/// use axum::{
/// Router,
/// routing::get,
/// http::Request,
/// body::Body,
/// };
/// use tower::{Service, ServiceExt};
///
/// # async fn async_main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut router = Router::new().route("/", get(|| async {}));
/// let request = Request::new(Body::empty());
/// let response = router.as_service().ready().await?.call(request).await?;
/// # Ok(())
/// # }
/// ```
///
/// This is mainly used when calling `Router` in tests. It shouldn't be necessary when running
/// the `Router` normally via [`Router::into_make_service`].
pub fn as_service<B>(&mut self) -> RouterAsService<'_, B, S> {
RouterAsService {
router: self,
_marker: PhantomData,
}
}
/// Convert the router into an owned [`Service`] with a fixed request body type, to aid type
/// inference.
///
/// This is the same as [`Router::as_service`] instead it returns an owned [`Service`]. See
/// that method for more details.
#[must_use]
pub fn into_service<B>(self) -> RouterIntoService<B, S> {
RouterIntoService {
router: self,
_marker: PhantomData,
}
}
}
impl Router {
/// Convert this router into a [`MakeService`], that is a [`Service`] whose
/// response is another service.
///
/// ```
/// use axum::{
/// routing::get,
/// Router,
/// };
///
/// let app = Router::new().route("/", get(|| async { "Hi!" }));
///
/// # async {
/// let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
/// axum::serve(listener, app).await.unwrap();
/// # };
/// ```
///
/// [`MakeService`]: tower::make::MakeService
#[must_use]
pub fn into_make_service(self) -> IntoMakeService<Self> {
// call `Router::with_state` such that everything is turned into `Route` eagerly
// rather than doing that per request
IntoMakeService::new(self.with_state(()))
}
#[doc = include_str!("../docs/routing/into_make_service_with_connect_info.md")]
#[cfg(feature = "tokio")]
#[must_use]
pub fn into_make_service_with_connect_info<C>(self) -> IntoMakeServiceWithConnectInfo<Self, C> {
// call `Router::with_state` such that everything is turned into `Route` eagerly
// rather than doing that per request
IntoMakeServiceWithConnectInfo::new(self.with_state(()))
}
}
// for `axum::serve(listener, router)`
#[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))]
const _: () = {
use crate::serve;
impl<L> Service<serve::IncomingStream<'_, L>> for Router<()>
where
L: serve::Listener,
{
type Response = Self;
type Error = Infallible;
type Future = std::future::Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _req: serve::IncomingStream<'_, L>) -> Self::Future {
// call `Router::with_state` such that everything is turned into `Route` eagerly
// rather than doing that per request
std::future::ready(Ok(self.clone().with_state(())))
}
}
};
impl<B> Service<Request<B>> for Router<()>
where
B: HttpBody<Data = bytes::Bytes> + Send + 'static,
B::Error: Into<axum_core::BoxError>,
{
type Response = Response;
type Error = Infallible;
type Future = RouteFuture<Infallible>;
#[inline]
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
#[inline]
fn call(&mut self, req: Request<B>) -> Self::Future {
let req = req.map(Body::new);
self.call_with_state(req, ())
}
}
/// A [`Router`] converted into a borrowed [`Service`] with a fixed body type.
///
/// See [`Router::as_service`] for more details.
pub struct RouterAsService<'a, B, S = ()> {
router: &'a mut Router<S>,
_marker: PhantomData<fn(B)>,
}
impl<B> Service<Request<B>> for RouterAsService<'_, B, ()>
where
B: HttpBody<Data = bytes::Bytes> + Send + 'static,
B::Error: Into<axum_core::BoxError>,
{
type Response = Response;
type Error = Infallible;
type Future = RouteFuture<Infallible>;
#[inline]
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
<Router as Service<Request<B>>>::poll_ready(self.router, cx)
}
#[inline]
fn call(&mut self, req: Request<B>) -> Self::Future {
self.router.call(req)
}
}
impl<B, S> fmt::Debug for RouterAsService<'_, B, S>
where
S: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RouterAsService")
.field("router", &self.router)
.finish()
}
}
/// A [`Router`] converted into an owned [`Service`] with a fixed body type.
///
/// See [`Router::into_service`] for more details.
pub struct RouterIntoService<B, S = ()> {
router: Router<S>,
_marker: PhantomData<fn(B)>,
}
impl<B, S> Clone for RouterIntoService<B, S>
where
Router<S>: Clone,
{
fn clone(&self) -> Self {
Self {
router: self.router.clone(),
_marker: PhantomData,
}
}
}
impl<B> Service<Request<B>> for RouterIntoService<B, ()>
where
B: HttpBody<Data = bytes::Bytes> + Send + 'static,
B::Error: Into<axum_core::BoxError>,
{
type Response = Response;
type Error = Infallible;
type Future = RouteFuture<Infallible>;
#[inline]
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
<Router as Service<Request<B>>>::poll_ready(&mut self.router, cx)
}
#[inline]
fn call(&mut self, req: Request<B>) -> Self::Future {
self.router.call(req)
}
}
impl<B, S> fmt::Debug for RouterIntoService<B, S>
where
S: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RouterIntoService")
.field("router", &self.router)
.finish()
}
}
enum Fallback<S, E = Infallible> {
Default(Route<E>),
Service(Route<E>),
BoxedHandler(BoxedIntoRoute<S, E>),
}
impl<S, E> Fallback<S, E>
where
S: Clone,
{
fn merge(self, other: Self) -> Option<Self> {
match (self, other) {
(Self::Default(_), pick @ Self::Default(_)) => Some(pick),
(Self::Default(_), pick) | (pick, Self::Default(_)) => Some(pick),
_ => None,
}
}
fn map<F, E2>(self, f: F) -> Fallback<S, E2>
where
S: 'static,
E: 'static,
F: FnOnce(Route<E>) -> Route<E2> + Clone + Send + Sync + 'static,
E2: 'static,
{
match self {
Self::Default(route) => Fallback::Default(f(route)),
Self::Service(route) => Fallback::Service(f(route)),
Self::BoxedHandler(handler) => Fallback::BoxedHandler(handler.map(f)),
}
}
fn with_state<S2>(self, state: S) -> Fallback<S2, E> {
match self {
Fallback::Default(route) => Fallback::Default(route),
Fallback::Service(route) => Fallback::Service(route),
Fallback::BoxedHandler(handler) => Fallback::Service(handler.into_route(state)),
}
}
fn call_with_state(self, req: Request, state: S) -> RouteFuture<E> {
match self {
Fallback::Default(route) | Fallback::Service(route) => route.oneshot_inner_owned(req),
Fallback::BoxedHandler(handler) => {
let route = handler.clone().into_route(state);
route.oneshot_inner_owned(req)
}
}
}
}
impl<S, E> Clone for Fallback<S, E> {
fn clone(&self) -> Self {
match self {
Self::Default(inner) => Self::Default(inner.clone()),
Self::Service(inner) => Self::Service(inner.clone()),
Self::BoxedHandler(inner) => Self::BoxedHandler(inner.clone()),
}
}
}
impl<S, E> fmt::Debug for Fallback<S, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Default(inner) => f.debug_tuple("Default").field(inner).finish(),
Self::Service(inner) => f.debug_tuple("Service").field(inner).finish(),
Self::BoxedHandler(_) => f.debug_tuple("BoxedHandler").finish(),
}
}
}
#[allow(clippy::large_enum_variant)]
enum Endpoint<S> {
MethodRouter(MethodRouter<S>),
Route(Route),
}
impl<S> Endpoint<S>
where
S: Clone + Send + Sync + 'static,
{
fn layer<L>(self, layer: L) -> Endpoint<S>
where
L: Layer<Route> + Clone + Send + Sync + 'static,
L::Service: Service<Request> + Clone + Send + Sync + 'static,
<L::Service as Service<Request>>::Response: IntoResponse + 'static,
<L::Service as Service<Request>>::Error: Into<Infallible> + 'static,
<L::Service as Service<Request>>::Future: Send + 'static,
{
match self {
Endpoint::MethodRouter(method_router) => {
Endpoint::MethodRouter(method_router.layer(layer))
}
Endpoint::Route(route) => Endpoint::Route(route.layer(layer)),
}
}
}
impl<S> Clone for Endpoint<S> {
fn clone(&self) -> Self {
match self {
Self::MethodRouter(inner) => Self::MethodRouter(inner.clone()),
Self::Route(inner) => Self::Route(inner.clone()),
}
}
}
impl<S> fmt::Debug for Endpoint<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MethodRouter(method_router) => {
f.debug_tuple("MethodRouter").field(method_router).finish()
}
Self::Route(route) => f.debug_tuple("Route").field(route).finish(),
}
}
}
#[test]
fn traits() {
use crate::test_helpers::*;
assert_send::<Router<()>>();
assert_sync::<Router<()>>();
assert_send::<RouterAsService<'static, Body, ()>>();
assert_sync::<RouterAsService<'static, Body, ()>>();
assert_send::<RouterIntoService<Body, ()>>();
assert_sync::<RouterIntoService<Body, ()>>();
}
+34
View File
@@ -0,0 +1,34 @@
use crate::response::Response;
use axum_core::response::IntoResponse;
use http::{Request, StatusCode};
use std::{
convert::Infallible,
future::ready,
task::{Context, Poll},
};
use tower_service::Service;
/// A [`Service`] that responds with `404 Not Found` to all requests.
///
/// This is used as the bottom service in a method router. You shouldn't have to
/// use it manually.
#[derive(Clone, Copy, Debug)]
pub(super) struct NotFound;
impl<B> Service<Request<B>> for NotFound
where
B: Send + 'static,
{
type Response = Response;
type Error = Infallible;
type Future = std::future::Ready<Result<Response, Self::Error>>;
#[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 {
ready(Ok(StatusCode::NOT_FOUND.into_response()))
}
}
+546
View File
@@ -0,0 +1,546 @@
use crate::{
extract::{nested_path::SetNestedPath, Request},
handler::Handler,
};
use axum_core::response::IntoResponse;
use matchit::MatchError;
use std::{borrow::Cow, collections::HashMap, convert::Infallible, fmt, sync::Arc};
use tower_layer::Layer;
use tower_service::Service;
use super::{
future::RouteFuture, not_found::NotFound, strip_prefix::StripPrefix, url_params, Endpoint,
MethodRouter, Route, RouteId, FALLBACK_PARAM_PATH, NEST_TAIL_PARAM,
};
pub(super) struct PathRouter<S, const IS_FALLBACK: bool> {
routes: HashMap<RouteId, Endpoint<S>>,
node: Arc<Node>,
prev_route_id: RouteId,
v7_checks: bool,
}
impl<S> PathRouter<S, true>
where
S: Clone + Send + Sync + 'static,
{
pub(super) fn new_fallback() -> Self {
let mut this = Self::default();
this.set_fallback(Endpoint::Route(Route::new(NotFound)));
this
}
pub(super) fn set_fallback(&mut self, endpoint: Endpoint<S>) {
self.replace_endpoint("/", endpoint.clone());
self.replace_endpoint(FALLBACK_PARAM_PATH, endpoint);
}
}
fn validate_path(v7_checks: bool, path: &str) -> Result<(), &'static str> {
if path.is_empty() {
return Err("Paths must start with a `/`. Use \"/\" for root routes");
} else if !path.starts_with('/') {
return Err("Paths must start with a `/`");
}
if v7_checks {
validate_v07_paths(path)?;
}
Ok(())
}
fn validate_v07_paths(path: &str) -> Result<(), &'static str> {
path.split('/')
.find_map(|segment| {
if segment.starts_with(':') {
Some(Err(
"Path segments must not start with `:`. For capture groups, use \
`{capture}`. If you meant to literally match a segment starting with \
a colon, call `without_v07_checks` on the router.",
))
} else if segment.starts_with('*') {
Some(Err(
"Path segments must not start with `*`. For wildcard capture, use \
`{*wildcard}`. If you meant to literally match a segment starting with \
an asterisk, call `without_v07_checks` on the router.",
))
} else {
None
}
})
.unwrap_or(Ok(()))
}
impl<S, const IS_FALLBACK: bool> PathRouter<S, IS_FALLBACK>
where
S: Clone + Send + Sync + 'static,
{
pub(super) fn without_v07_checks(&mut self) {
self.v7_checks = false;
}
pub(super) fn route(
&mut self,
path: &str,
method_router: MethodRouter<S>,
) -> Result<(), Cow<'static, str>> {
validate_path(self.v7_checks, path)?;
let endpoint = 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_for_path(Some(path), method_router)?,
);
self.routes.insert(route_id, service);
return Ok(());
} else {
Endpoint::MethodRouter(method_router)
};
let id = self.next_route_id();
self.set_node(path, id)?;
self.routes.insert(id, endpoint);
Ok(())
}
pub(super) fn method_not_allowed_fallback<H, T>(&mut self, handler: H)
where
H: Handler<T, S>,
T: 'static,
{
for (_, endpoint) in self.routes.iter_mut() {
if let Endpoint::MethodRouter(rt) = endpoint {
*rt = rt.clone().default_fallback(handler.clone());
}
}
}
pub(super) fn route_service<T>(
&mut self,
path: &str,
service: T,
) -> Result<(), Cow<'static, str>>
where
T: Service<Request, Error = Infallible> + Clone + Send + Sync + 'static,
T::Response: IntoResponse,
T::Future: Send + 'static,
{
self.route_endpoint(path, Endpoint::Route(Route::new(service)))
}
pub(super) fn route_endpoint(
&mut self,
path: &str,
endpoint: Endpoint<S>,
) -> Result<(), Cow<'static, str>> {
validate_path(self.v7_checks, path)?;
let id = self.next_route_id();
self.set_node(path, id)?;
self.routes.insert(id, endpoint);
Ok(())
}
fn set_node(&mut self, path: &str, id: RouteId) -> Result<(), String> {
let node = Arc::make_mut(&mut self.node);
node.insert(path, id)
.map_err(|err| format!("Invalid route {path:?}: {err}"))
}
pub(super) fn merge(
&mut self,
other: PathRouter<S, IS_FALLBACK>,
) -> Result<(), Cow<'static, str>> {
let PathRouter {
routes,
node,
prev_route_id: _,
v7_checks,
} = other;
// If either of the two did not allow paths starting with `:` or `*`, do not allow them for the merged router either.
self.v7_checks |= v7_checks;
for (id, route) in routes {
let path = node
.route_id_to_path
.get(&id)
.expect("no path for route id. This is a bug in axum. Please file an issue");
if IS_FALLBACK && (&**path == "/" || &**path == FALLBACK_PARAM_PATH) {
// when merging two routers it doesn't matter if you do `a.merge(b)` or
// `b.merge(a)`. This must also be true for fallbacks.
//
// However all fallback routers will have routes for `/` and `/*` so when merging
// we have to ignore the top level fallbacks on one side otherwise we get
// conflicts.
//
// `Router::merge` makes sure that when merging fallbacks `other` always has the
// fallback we want to keep. It panics if both routers have a custom fallback. Thus
// it is always okay to ignore one fallback and `Router::merge` also makes sure the
// one we can ignore is that of `self`.
self.replace_endpoint(path, route);
} else {
match route {
Endpoint::MethodRouter(method_router) => self.route(path, method_router)?,
Endpoint::Route(route) => self.route_service(path, route)?,
}
}
}
Ok(())
}
pub(super) fn nest(
&mut self,
path_to_nest_at: &str,
router: PathRouter<S, IS_FALLBACK>,
) -> Result<(), Cow<'static, str>> {
let prefix = validate_nest_path(self.v7_checks, path_to_nest_at);
let PathRouter {
routes,
node,
prev_route_id: _,
// Ignore the configuration of the nested router
v7_checks: _,
} = router;
for (id, endpoint) in routes {
let inner_path = node
.route_id_to_path
.get(&id)
.expect("no path for route id. This is a bug in axum. Please file an issue");
let path = path_for_nested_route(prefix, inner_path);
let layer = (
StripPrefix::layer(prefix),
SetNestedPath::layer(path_to_nest_at),
);
match endpoint.layer(layer) {
Endpoint::MethodRouter(method_router) => {
self.route(&path, method_router)?;
}
Endpoint::Route(route) => {
self.route_endpoint(&path, Endpoint::Route(route))?;
}
}
}
Ok(())
}
pub(super) fn nest_service<T>(
&mut self,
path_to_nest_at: &str,
svc: T,
) -> Result<(), Cow<'static, str>>
where
T: Service<Request, Error = Infallible> + Clone + Send + Sync + 'static,
T::Response: IntoResponse,
T::Future: Send + 'static,
{
let path = validate_nest_path(self.v7_checks, path_to_nest_at);
let prefix = path;
let path = if path.ends_with('/') {
format!("{path}{{*{NEST_TAIL_PARAM}}}")
} else {
format!("{path}/{{*{NEST_TAIL_PARAM}}}")
};
let layer = (
StripPrefix::layer(prefix),
SetNestedPath::layer(path_to_nest_at),
);
let endpoint = Endpoint::Route(Route::new(layer.layer(svc)));
self.route_endpoint(&path, endpoint.clone())?;
// `/{*rest}` is not matched by `/` so we need to also register a router at the
// prefix itself. Otherwise if you were to nest at `/foo` then `/foo` itself
// wouldn't match, which it should
self.route_endpoint(prefix, endpoint.clone())?;
if !prefix.ends_with('/') {
// same goes for `/foo/`, that should also match
self.route_endpoint(&format!("{prefix}/"), endpoint)?;
}
Ok(())
}
pub(super) fn layer<L>(self, layer: L) -> PathRouter<S, IS_FALLBACK>
where
L: Layer<Route> + Clone + Send + Sync + 'static,
L::Service: Service<Request> + Clone + Send + Sync + 'static,
<L::Service as Service<Request>>::Response: IntoResponse + 'static,
<L::Service as Service<Request>>::Error: Into<Infallible> + 'static,
<L::Service as Service<Request>>::Future: Send + 'static,
{
let routes = self
.routes
.into_iter()
.map(|(id, endpoint)| {
let route = endpoint.layer(layer.clone());
(id, route)
})
.collect();
PathRouter {
routes,
node: self.node,
prev_route_id: self.prev_route_id,
v7_checks: self.v7_checks,
}
}
#[track_caller]
pub(super) fn route_layer<L>(self, layer: L) -> Self
where
L: Layer<Route> + Clone + Send + Sync + 'static,
L::Service: Service<Request> + Clone + Send + Sync + 'static,
<L::Service as Service<Request>>::Response: IntoResponse + 'static,
<L::Service as Service<Request>>::Error: Into<Infallible> + 'static,
<L::Service as Service<Request>>::Future: Send + 'static,
{
if self.routes.is_empty() {
panic!(
"Adding a route_layer before any routes is a no-op. \
Add the routes you want the layer to apply to first."
);
}
let routes = self
.routes
.into_iter()
.map(|(id, endpoint)| {
let route = endpoint.layer(layer.clone());
(id, route)
})
.collect();
PathRouter {
routes,
node: self.node,
prev_route_id: self.prev_route_id,
v7_checks: self.v7_checks,
}
}
pub(super) fn has_routes(&self) -> bool {
!self.routes.is_empty()
}
pub(super) fn with_state<S2>(self, state: S) -> PathRouter<S2, IS_FALLBACK> {
let routes = self
.routes
.into_iter()
.map(|(id, endpoint)| {
let endpoint: Endpoint<S2> = match endpoint {
Endpoint::MethodRouter(method_router) => {
Endpoint::MethodRouter(method_router.with_state(state.clone()))
}
Endpoint::Route(route) => Endpoint::Route(route),
};
(id, endpoint)
})
.collect();
PathRouter {
routes,
node: self.node,
prev_route_id: self.prev_route_id,
v7_checks: self.v7_checks,
}
}
#[allow(clippy::result_large_err)]
pub(super) fn call_with_state(
&self,
#[cfg_attr(not(feature = "original-uri"), allow(unused_mut))] mut req: Request,
state: S,
) -> Result<RouteFuture<Infallible>, (Request, S)> {
#[cfg(feature = "original-uri")]
{
use crate::extract::OriginalUri;
if req.extensions().get::<OriginalUri>().is_none() {
let original_uri = OriginalUri(req.uri().clone());
req.extensions_mut().insert(original_uri);
}
}
let (mut parts, body) = req.into_parts();
match self.node.at(parts.uri.path()) {
Ok(match_) => {
let id = *match_.value;
if !IS_FALLBACK {
#[cfg(feature = "matched-path")]
crate::extract::matched_path::set_matched_path_for_request(
id,
&self.node.route_id_to_path,
&mut parts.extensions,
);
}
url_params::insert_url_params(&mut parts.extensions, match_.params);
let endpoint = self
.routes
.get(&id)
.expect("no route for id. This is a bug in axum. Please file an issue");
let req = Request::from_parts(parts, body);
match endpoint {
Endpoint::MethodRouter(method_router) => {
Ok(method_router.call_with_state(req, state))
}
Endpoint::Route(route) => Ok(route.clone().call_owned(req)),
}
}
// explicitly handle all variants in case matchit adds
// new ones we need to handle differently
Err(MatchError::NotFound) => Err((Request::from_parts(parts, body), state)),
}
}
pub(super) fn replace_endpoint(&mut self, path: &str, endpoint: Endpoint<S>) {
match self.node.at(path) {
Ok(match_) => {
let id = *match_.value;
self.routes.insert(id, endpoint);
}
Err(_) => self
.route_endpoint(path, endpoint)
.expect("path wasn't matched so endpoint shouldn't exist"),
}
}
fn next_route_id(&mut self) -> RouteId {
let next_id = self
.prev_route_id
.0
.checked_add(1)
.expect("Over `u32::MAX` routes created. If you need this, please file an issue.");
self.prev_route_id = RouteId(next_id);
self.prev_route_id
}
}
impl<S, const IS_FALLBACK: bool> Default for PathRouter<S, IS_FALLBACK> {
fn default() -> Self {
Self {
routes: Default::default(),
node: Default::default(),
prev_route_id: RouteId(0),
v7_checks: true,
}
}
}
impl<S, const IS_FALLBACK: bool> fmt::Debug for PathRouter<S, IS_FALLBACK> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PathRouter")
.field("routes", &self.routes)
.field("node", &self.node)
.finish()
}
}
impl<S, const IS_FALLBACK: bool> Clone for PathRouter<S, IS_FALLBACK> {
fn clone(&self) -> Self {
Self {
routes: self.routes.clone(),
node: self.node.clone(),
prev_route_id: self.prev_route_id,
v7_checks: self.v7_checks,
}
}
}
/// Wrapper around `matchit::Router` that supports merging two `Router`s.
#[derive(Clone, Default)]
struct Node {
inner: matchit::Router<RouteId>,
route_id_to_path: HashMap<RouteId, Arc<str>>,
path_to_route_id: HashMap<Arc<str>, RouteId>,
}
impl Node {
fn insert(
&mut self,
path: impl Into<String>,
val: RouteId,
) -> Result<(), matchit::InsertError> {
let path = path.into();
self.inner.insert(&path, val)?;
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 at<'n, 'p>(
&'n self,
path: &'p str,
) -> Result<matchit::Match<'n, 'p, &'n RouteId>, MatchError> {
self.inner.at(path)
}
}
impl fmt::Debug for Node {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Node")
.field("paths", &self.route_id_to_path)
.finish()
}
}
#[track_caller]
fn validate_nest_path(v7_checks: bool, path: &str) -> &str {
assert!(path.starts_with('/'));
assert!(path.len() > 1);
if path.split('/').any(|segment| {
segment.starts_with("{*") && segment.ends_with('}') && !segment.ends_with("}}")
}) {
panic!("Invalid route: nested routes cannot contain wildcards (*)");
}
if v7_checks {
validate_v07_paths(path).unwrap();
}
path
}
pub(crate) fn path_for_nested_route<'a>(prefix: &'a str, path: &'a str) -> Cow<'a, str> {
debug_assert!(prefix.starts_with('/'));
debug_assert!(path.starts_with('/'));
if prefix.ends_with('/') {
format!("{prefix}{}", path.trim_start_matches('/')).into()
} else if path == "/" {
prefix.into()
} else {
format!("{prefix}{path}").into()
}
}
+244
View File
@@ -0,0 +1,244 @@
use crate::{
body::{Body, HttpBody},
response::Response,
util::MapIntoResponse,
};
use axum_core::{extract::Request, response::IntoResponse};
use bytes::Bytes;
use http::{
header::{self, CONTENT_LENGTH},
HeaderMap, HeaderValue, Method,
};
use pin_project_lite::pin_project;
use std::{
convert::Infallible,
fmt,
future::Future,
pin::Pin,
task::{ready, Context, Poll},
};
use tower::{
util::{BoxCloneSyncService, MapErrLayer, Oneshot},
ServiceExt,
};
use tower_layer::Layer;
use tower_service::Service;
/// How routes are stored inside a [`Router`](super::Router).
///
/// You normally shouldn't need to care about this type. It's used in
/// [`Router::layer`](super::Router::layer).
pub struct Route<E = Infallible>(BoxCloneSyncService<Request, Response, E>);
impl<E> Route<E> {
pub(crate) fn new<T>(svc: T) -> Self
where
T: Service<Request, Error = E> + Clone + Send + Sync + 'static,
T::Response: IntoResponse + 'static,
T::Future: Send + 'static,
{
Self(BoxCloneSyncService::new(MapIntoResponse::new(svc)))
}
/// Variant of [`Route::call`] that takes ownership of the route to avoid cloning.
pub(crate) fn call_owned(self, req: Request<Body>) -> RouteFuture<E> {
let req = req.map(Body::new);
self.oneshot_inner_owned(req).not_top_level()
}
pub(crate) fn oneshot_inner(&mut self, req: Request) -> RouteFuture<E> {
let method = req.method().clone();
RouteFuture::new(method, self.0.clone().oneshot(req))
}
/// Variant of [`Route::oneshot_inner`] that takes ownership of the route to avoid cloning.
pub(crate) fn oneshot_inner_owned(self, req: Request) -> RouteFuture<E> {
let method = req.method().clone();
RouteFuture::new(method, self.0.oneshot(req))
}
pub(crate) fn layer<L, NewError>(self, layer: L) -> Route<NewError>
where
L: Layer<Route<E>> + Clone + Send + 'static,
L::Service: Service<Request> + Clone + Send + Sync + 'static,
<L::Service as Service<Request>>::Response: IntoResponse + 'static,
<L::Service as Service<Request>>::Error: Into<NewError> + 'static,
<L::Service as Service<Request>>::Future: Send + 'static,
NewError: 'static,
{
let layer = (MapErrLayer::new(Into::into), layer);
Route::new(layer.layer(self))
}
}
impl<E> Clone for Route<E> {
#[track_caller]
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<E> fmt::Debug for Route<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Route").finish()
}
}
impl<B, E> Service<Request<B>> for Route<E>
where
B: HttpBody<Data = bytes::Bytes> + Send + 'static,
B::Error: Into<axum_core::BoxError>,
{
type Response = Response;
type Error = E;
type Future = RouteFuture<E>;
#[inline]
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
#[inline]
fn call(&mut self, req: Request<B>) -> Self::Future {
self.oneshot_inner(req.map(Body::new)).not_top_level()
}
}
pin_project! {
/// Response future for [`Route`].
pub struct RouteFuture<E> {
#[pin]
inner: Oneshot<BoxCloneSyncService<Request, Response, E>, Request>,
method: Method,
allow_header: Option<Bytes>,
top_level: bool,
}
}
impl<E> RouteFuture<E> {
fn new(
method: Method,
inner: Oneshot<BoxCloneSyncService<Request, Response, E>, Request>,
) -> Self {
Self {
inner,
method,
allow_header: None,
top_level: true,
}
}
pub(crate) fn allow_header(mut self, allow_header: Bytes) -> Self {
self.allow_header = Some(allow_header);
self
}
pub(crate) fn not_top_level(mut self) -> Self {
self.top_level = false;
self
}
}
impl<E> Future for RouteFuture<E> {
type Output = Result<Response, E>;
#[inline]
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
let mut res = ready!(this.inner.poll(cx))?;
if *this.method == Method::CONNECT && res.status().is_success() {
// From https://httpwg.org/specs/rfc9110.html#CONNECT:
// > A server MUST NOT send any Transfer-Encoding or
// > Content-Length header fields in a 2xx (Successful)
// > response to CONNECT.
if res.headers().contains_key(&CONTENT_LENGTH)
|| res.headers().contains_key(&header::TRANSFER_ENCODING)
|| res.size_hint().lower() != 0
{
error!("response to CONNECT with nonempty body");
res = res.map(|_| Body::empty());
}
} else if *this.top_level {
set_allow_header(res.headers_mut(), this.allow_header);
// make sure to set content-length before removing the body
set_content_length(res.size_hint(), res.headers_mut());
if *this.method == Method::HEAD {
*res.body_mut() = Body::empty();
}
}
Poll::Ready(Ok(res))
}
}
fn set_allow_header(headers: &mut HeaderMap, allow_header: &mut Option<Bytes>) {
match allow_header.take() {
Some(allow_header) if !headers.contains_key(header::ALLOW) => {
headers.insert(
header::ALLOW,
HeaderValue::from_maybe_shared(allow_header).expect("invalid `Allow` header"),
);
}
_ => {}
}
}
fn set_content_length(size_hint: http_body::SizeHint, headers: &mut HeaderMap) {
if headers.contains_key(CONTENT_LENGTH) {
return;
}
if let Some(size) = size_hint.exact() {
let header_value = if size == 0 {
#[allow(clippy::declare_interior_mutable_const)]
const ZERO: HeaderValue = HeaderValue::from_static("0");
ZERO
} else {
let mut buffer = itoa::Buffer::new();
HeaderValue::from_str(buffer.format(size)).unwrap()
};
headers.insert(CONTENT_LENGTH, header_value);
}
}
pin_project! {
/// A [`RouteFuture`] that always yields a [`Response`].
pub struct InfallibleRouteFuture {
#[pin]
future: RouteFuture<Infallible>,
}
}
impl InfallibleRouteFuture {
pub(crate) fn new(future: RouteFuture<Infallible>) -> Self {
Self { future }
}
}
impl Future for InfallibleRouteFuture {
type Output = Response;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match ready!(self.project().future.poll(cx)) {
Ok(response) => Poll::Ready(response),
Err(err) => match err {},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn traits() {
use crate::test_helpers::*;
assert_send::<Route<()>>();
}
}
+475
View File
@@ -0,0 +1,475 @@
use http::{Request, Uri};
use std::{
sync::Arc,
task::{Context, Poll},
};
use tower::Layer;
use tower_layer::layer_fn;
use tower_service::Service;
#[derive(Clone)]
pub(super) struct StripPrefix<S> {
inner: S,
prefix: Arc<str>,
}
impl<S> StripPrefix<S> {
pub(super) fn layer(prefix: &str) -> impl Layer<S, Service = Self> + Clone {
let prefix = Arc::from(prefix);
layer_fn(move |inner| Self {
inner,
prefix: Arc::clone(&prefix),
})
}
}
impl<S, B> Service<Request<B>> for StripPrefix<S>
where
S: Service<Request<B>>,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
#[inline]
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, mut req: Request<B>) -> Self::Future {
if let Some(new_uri) = strip_prefix(req.uri(), &self.prefix) {
*req.uri_mut() = new_uri;
}
self.inner.call(req)
}
}
fn strip_prefix(uri: &Uri, prefix: &str) -> Option<Uri> {
let path_and_query = uri.path_and_query()?;
// Check whether the prefix matches the path and if so how long the matching prefix is.
//
// For example:
//
// prefix = /api
// path = /api/users
// ^^^^ this much is matched and the length is 4. Thus if we chop off the first 4
// characters we get the remainder
//
// prefix = /api/{version}
// path = /api/v0/users
// ^^^^^^^ this much is matched and the length is 7.
let mut matching_prefix_length = Some(0);
for item in zip_longest(segments(path_and_query.path()), segments(prefix)) {
// count the `/`
*matching_prefix_length.as_mut().unwrap() += 1;
match item {
Item::Both(path_segment, prefix_segment) => {
if is_capture(prefix_segment) || path_segment == prefix_segment {
// the prefix segment is either a param, which matches anything, or
// it actually matches the path segment
*matching_prefix_length.as_mut().unwrap() += path_segment.len();
} else if prefix_segment.is_empty() {
// the prefix ended in a `/` so we got a match.
//
// For example:
//
// prefix = /foo/
// path = /foo/bar
//
// The prefix matches and the new path should be `/bar`
break;
} else {
// the prefix segment didn't match so there is no match
matching_prefix_length = None;
break;
}
}
// the path had more segments than the prefix but we got a match.
//
// For example:
//
// prefix = /foo
// path = /foo/bar
Item::First(_) => {
break;
}
// the prefix had more segments than the path so there is no match
Item::Second(_) => {
matching_prefix_length = None;
break;
}
}
}
// if the prefix matches it will always do so up until a `/`, it cannot match only
// part of a segment. Therefore this will always be at a char boundary and `split_at` won't
// panic
let after_prefix = uri.path().split_at(matching_prefix_length?).1;
let new_path_and_query = match (after_prefix.starts_with('/'), path_and_query.query()) {
(true, None) => after_prefix.parse().unwrap(),
(true, Some(query)) => format!("{after_prefix}?{query}").parse().unwrap(),
(false, None) => format!("/{after_prefix}").parse().unwrap(),
(false, Some(query)) => format!("/{after_prefix}?{query}").parse().unwrap(),
};
let mut parts = uri.clone().into_parts();
parts.path_and_query = Some(new_path_and_query);
Some(Uri::from_parts(parts).unwrap())
}
fn segments(s: &str) -> impl Iterator<Item = &str> {
assert!(
s.starts_with('/'),
"path didn't start with '/'. axum should have caught this higher up."
);
s.split('/')
// skip one because paths always start with `/` so `/a/b` would become ["", "a", "b"]
// otherwise
.skip(1)
}
fn zip_longest<I, I2>(a: I, b: I2) -> impl Iterator<Item = Item<I::Item>>
where
I: Iterator,
I2: Iterator<Item = I::Item>,
{
let a = a.map(Some).chain(std::iter::repeat_with(|| None));
let b = b.map(Some).chain(std::iter::repeat_with(|| None));
a.zip(b).map_while(|(a, b)| match (a, b) {
(Some(a), Some(b)) => Some(Item::Both(a, b)),
(Some(a), None) => Some(Item::First(a)),
(None, Some(b)) => Some(Item::Second(b)),
(None, None) => None,
})
}
fn is_capture(segment: &str) -> bool {
segment.starts_with('{')
&& segment.ends_with('}')
&& !segment.starts_with("{{")
&& !segment.ends_with("}}")
&& !segment.starts_with("{*")
}
#[derive(Debug)]
enum Item<T> {
Both(T, T),
First(T),
Second(T),
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
use quickcheck::Arbitrary;
use quickcheck_macros::quickcheck;
macro_rules! test {
(
$name:ident,
uri = $uri:literal,
prefix = $prefix:literal,
expected = $expected:expr,
) => {
#[test]
fn $name() {
let uri = $uri.parse().unwrap();
let new_uri = strip_prefix(&uri, $prefix).map(|uri| uri.to_string());
assert_eq!(new_uri.as_deref(), $expected);
}
};
}
test!(empty, uri = "/", prefix = "/", expected = Some("/"),);
test!(
single_segment,
uri = "/a",
prefix = "/a",
expected = Some("/"),
);
test!(
single_segment_root_uri,
uri = "/",
prefix = "/a",
expected = None,
);
// the prefix is empty, so removing it should have no effect
test!(
single_segment_root_prefix,
uri = "/a",
prefix = "/",
expected = Some("/a"),
);
test!(
single_segment_no_match,
uri = "/a",
prefix = "/b",
expected = None,
);
test!(
single_segment_trailing_slash,
uri = "/a/",
prefix = "/a/",
expected = Some("/"),
);
test!(
single_segment_trailing_slash_2,
uri = "/a",
prefix = "/a/",
expected = None,
);
test!(
single_segment_trailing_slash_3,
uri = "/a/",
prefix = "/a",
expected = Some("/"),
);
test!(
multi_segment,
uri = "/a/b",
prefix = "/a",
expected = Some("/b"),
);
test!(
multi_segment_2,
uri = "/b/a",
prefix = "/a",
expected = None,
);
test!(
multi_segment_3,
uri = "/a",
prefix = "/a/b",
expected = None,
);
test!(
multi_segment_4,
uri = "/a/b",
prefix = "/b",
expected = None,
);
test!(
multi_segment_trailing_slash,
uri = "/a/b/",
prefix = "/a/b/",
expected = Some("/"),
);
test!(
multi_segment_trailing_slash_2,
uri = "/a/b",
prefix = "/a/b/",
expected = None,
);
test!(
multi_segment_trailing_slash_3,
uri = "/a/b/",
prefix = "/a/b",
expected = Some("/"),
);
test!(
param_0,
uri = "/",
prefix = "/{param}",
expected = Some("/"),
);
test!(
param_1,
uri = "/a",
prefix = "/{param}",
expected = Some("/"),
);
test!(
param_2,
uri = "/a/b",
prefix = "/{param}",
expected = Some("/b"),
);
test!(
param_3,
uri = "/b/a",
prefix = "/{param}",
expected = Some("/a"),
);
test!(
param_4,
uri = "/a/b",
prefix = "/a/{param}",
expected = Some("/"),
);
test!(
param_5,
uri = "/b/a",
prefix = "/a/{param}",
expected = None,
);
test!(
param_6,
uri = "/a/b",
prefix = "/{param}/a",
expected = None,
);
test!(
param_7,
uri = "/b/a",
prefix = "/{param}/a",
expected = Some("/"),
);
test!(
param_8,
uri = "/a/b/c",
prefix = "/a/{param}/c",
expected = Some("/"),
);
test!(
param_9,
uri = "/c/b/a",
prefix = "/a/{param}/c",
expected = None,
);
test!(
param_10,
uri = "/a/",
prefix = "/{param}",
expected = Some("/"),
);
test!(param_11, uri = "/a", prefix = "/{param}/", expected = None,);
test!(
param_12,
uri = "/a/",
prefix = "/{param}/",
expected = Some("/"),
);
test!(
param_13,
uri = "/a/a",
prefix = "/a/",
expected = Some("/a"),
);
#[quickcheck]
fn does_not_panic(uri_and_prefix: UriAndPrefix) -> bool {
let UriAndPrefix { uri, prefix } = uri_and_prefix;
strip_prefix(&uri, &prefix);
true
}
#[derive(Clone, Debug)]
struct UriAndPrefix {
uri: Uri,
prefix: String,
}
impl Arbitrary for UriAndPrefix {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
let mut uri = String::new();
let mut prefix = String::new();
let size = u8_between(1, 20, g);
for _ in 0..size {
let segment = ascii_alphanumeric(g);
uri.push('/');
uri.push_str(&segment);
prefix.push('/');
let make_matching_segment = bool::arbitrary(g);
let make_capture = bool::arbitrary(g);
match (make_matching_segment, make_capture) {
(_, true) => {
prefix.push_str(":a");
}
(true, false) => {
prefix.push_str(&segment);
}
(false, false) => {
prefix.push_str(&ascii_alphanumeric(g));
}
}
}
if bool::arbitrary(g) {
uri.push('/');
}
if bool::arbitrary(g) {
prefix.push('/');
}
Self {
uri: uri.parse().unwrap(),
prefix,
}
}
}
fn ascii_alphanumeric(g: &mut quickcheck::Gen) -> String {
#[derive(Clone)]
struct AsciiAlphanumeric(String);
impl Arbitrary for AsciiAlphanumeric {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
let mut out = String::new();
let size = u8_between(1, 20, g) as usize;
while out.len() < size {
let c = char::arbitrary(g);
if c.is_ascii_alphanumeric() {
out.push(c);
}
}
Self(out)
}
}
let out = AsciiAlphanumeric::arbitrary(g).0;
assert!(!out.is_empty());
out
}
fn u8_between(lower: u8, upper: u8, g: &mut quickcheck::Gen) -> u8 {
loop {
let size = u8::arbitrary(g);
if size > lower && size <= upper {
break size;
}
}
}
}
+393
View File
@@ -0,0 +1,393 @@
use super::*;
use crate::middleware::{map_request, map_response};
#[crate::test]
async fn basic() {
let app = Router::new()
.route("/foo", get(|| async {}))
.fallback(|| async { "fallback" });
let client = TestClient::new(app);
assert_eq!(client.get("/foo").await.status(), StatusCode::OK);
let res = client.get("/does-not-exist").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "fallback");
}
#[crate::test]
async fn nest() {
let app = Router::new()
.nest("/foo", Router::new().route("/bar", get(|| async {})))
.fallback(|| async { "fallback" });
let client = TestClient::new(app);
assert_eq!(client.get("/foo/bar").await.status(), StatusCode::OK);
let res = client.get("/does-not-exist").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "fallback");
}
#[crate::test]
async fn two() {
let app = Router::new()
.route("/first", get(|| async {}))
.route("/second", get(|| async {}))
.fallback(get(|| async { "fallback" }));
let client = TestClient::new(app);
let res = client.get("/does-not-exist").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "fallback");
}
#[crate::test]
async fn or() {
let one = Router::new().route("/one", get(|| async {}));
let two = Router::new().route("/two", get(|| async {}));
let app = one.merge(two).fallback(|| async { "fallback" });
let client = TestClient::new(app);
assert_eq!(client.get("/one").await.status(), StatusCode::OK);
assert_eq!(client.get("/two").await.status(), StatusCode::OK);
let res = client.get("/does-not-exist").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "fallback");
}
#[crate::test]
async fn fallback_accessing_state() {
let app = Router::new()
.fallback(|State(state): State<&'static str>| async move { state })
.with_state("state");
let client = TestClient::new(app);
let res = client.get("/does-not-exist").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "state");
}
async fn inner_fallback() -> impl IntoResponse {
(StatusCode::NOT_FOUND, "inner")
}
async fn outer_fallback() -> impl IntoResponse {
(StatusCode::NOT_FOUND, "outer")
}
#[crate::test]
async fn nested_router_inherits_fallback() {
let inner = Router::new();
let app = Router::new().nest("/foo", inner).fallback(outer_fallback);
let client = TestClient::new(app);
let res = client.get("/foo/bar").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
assert_eq!(res.text().await, "outer");
}
#[crate::test]
async fn doesnt_inherit_fallback_if_overridden() {
let inner = Router::new().fallback(inner_fallback);
let app = Router::new().nest("/foo", inner).fallback(outer_fallback);
let client = TestClient::new(app);
let res = client.get("/foo/bar").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
assert_eq!(res.text().await, "inner");
let res = client.get("/").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
assert_eq!(res.text().await, "outer");
}
#[crate::test]
async fn deeply_nested_inherit_from_top() {
let app = Router::new()
.nest("/foo", Router::new().nest("/bar", Router::new()))
.fallback(outer_fallback);
let client = TestClient::new(app);
let res = client.get("/foo/bar/baz").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
assert_eq!(res.text().await, "outer");
}
#[crate::test]
async fn deeply_nested_inherit_from_middle() {
let app = Router::new().nest(
"/foo",
Router::new()
.nest("/bar", Router::new())
.fallback(outer_fallback),
);
let client = TestClient::new(app);
let res = client.get("/foo/bar/baz").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
assert_eq!(res.text().await, "outer");
}
#[crate::test]
async fn with_middleware_on_inner_fallback() {
async fn never_called<B>(_: Request<B>) -> Request<B> {
panic!("should never be called")
}
let inner = Router::new().layer(map_request(never_called));
let app = Router::new().nest("/foo", inner).fallback(outer_fallback);
let client = TestClient::new(app);
let res = client.get("/foo/bar").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
assert_eq!(res.text().await, "outer");
}
#[crate::test]
async fn also_inherits_default_layered_fallback() {
async fn set_header<B>(mut res: Response<B>) -> Response<B> {
res.headers_mut()
.insert("x-from-fallback", "1".parse().unwrap());
res
}
let inner = Router::new();
let app = Router::new()
.nest("/foo", inner)
.fallback(outer_fallback)
.layer(map_response(set_header));
let client = TestClient::new(app);
let res = client.get("/foo/bar").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
assert_eq!(res.headers()["x-from-fallback"], "1");
assert_eq!(res.text().await, "outer");
}
#[crate::test]
async fn nest_fallback_on_inner() {
let app = Router::new()
.nest(
"/foo",
Router::new()
.route("/", get(|| async {}))
.fallback(|| async { (StatusCode::NOT_FOUND, "inner fallback") }),
)
.fallback(|| async { (StatusCode::NOT_FOUND, "outer fallback") });
let client = TestClient::new(app);
let res = client.get("/foo/not-found").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
assert_eq!(res.text().await, "inner fallback");
}
// https://github.com/tokio-rs/axum/issues/1931
#[crate::test]
async fn doesnt_panic_if_used_with_nested_router() {
async fn handler() {}
let routes_static =
Router::new().nest_service("/foo", crate::routing::get_service(handler.into_service()));
let routes_all = Router::new().fallback_service(routes_static);
let client = TestClient::new(routes_all);
let res = client.get("/foo/bar").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn issue_2072() {
let nested_routes = Router::new().fallback(inner_fallback);
let app = Router::new()
.nest("/nested", nested_routes)
.merge(Router::new());
let client = TestClient::new(app);
let res = client.get("/nested/does-not-exist").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
assert_eq!(res.text().await, "inner");
let res = client.get("/does-not-exist").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
assert_eq!(res.text().await, "");
}
#[crate::test]
async fn issue_2072_outer_fallback_before_merge() {
let nested_routes = Router::new().fallback(inner_fallback);
let app = Router::new()
.nest("/nested", nested_routes)
.fallback(outer_fallback)
.merge(Router::new());
let client = TestClient::new(app);
let res = client.get("/nested/does-not-exist").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
assert_eq!(res.text().await, "inner");
let res = client.get("/does-not-exist").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
assert_eq!(res.text().await, "outer");
}
#[crate::test]
async fn issue_2072_outer_fallback_after_merge() {
let nested_routes = Router::new().fallback(inner_fallback);
let app = Router::new()
.nest("/nested", nested_routes)
.merge(Router::new())
.fallback(outer_fallback);
let client = TestClient::new(app);
let res = client.get("/nested/does-not-exist").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
assert_eq!(res.text().await, "inner");
let res = client.get("/does-not-exist").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
assert_eq!(res.text().await, "outer");
}
#[crate::test]
async fn merge_router_with_fallback_into_nested_router_with_fallback() {
let nested_routes = Router::new().fallback(inner_fallback);
let app = Router::new()
.nest("/nested", nested_routes)
.merge(Router::new().fallback(outer_fallback));
let client = TestClient::new(app);
let res = client.get("/nested/does-not-exist").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
assert_eq!(res.text().await, "inner");
let res = client.get("/does-not-exist").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
assert_eq!(res.text().await, "outer");
}
#[crate::test]
async fn merging_nested_router_with_fallback_into_router_with_fallback() {
let nested_routes = Router::new().fallback(inner_fallback);
let app = Router::new()
.fallback(outer_fallback)
.merge(Router::new().nest("/nested", nested_routes));
let client = TestClient::new(app);
let res = client.get("/nested/does-not-exist").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
assert_eq!(res.text().await, "inner");
let res = client.get("/does-not-exist").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
assert_eq!(res.text().await, "outer");
}
#[crate::test]
async fn merge_empty_into_router_with_fallback() {
let app = Router::new().fallback(outer_fallback).merge(Router::new());
let client = TestClient::new(app);
let res = client.get("/does-not-exist").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
assert_eq!(res.text().await, "outer");
}
#[crate::test]
async fn merge_router_with_fallback_into_empty() {
let app = Router::new().merge(Router::new().fallback(outer_fallback));
let client = TestClient::new(app);
let res = client.get("/does-not-exist").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
assert_eq!(res.text().await, "outer");
}
#[crate::test]
async fn mna_fallback_with_existing_fallback() {
let app = Router::new()
.route(
"/",
get(|| async { "test" }).fallback(|| async { "index fallback" }),
)
.route("/path", get(|| async { "path" }))
.method_not_allowed_fallback(|| async { "method not allowed fallback" });
let client = TestClient::new(app);
let index_fallback = client.post("/").await;
let method_not_allowed_fallback = client.post("/path").await;
assert_eq!(index_fallback.text().await, "index fallback");
assert_eq!(
method_not_allowed_fallback.text().await,
"method not allowed fallback"
);
}
#[crate::test]
async fn mna_fallback_with_state() {
let app = Router::new()
.route("/", get(|| async { "index" }))
.method_not_allowed_fallback(|State(state): State<&'static str>| async move { state })
.with_state("state");
let client = TestClient::new(app);
let res = client.post("/").await;
assert_eq!(res.text().await, "state");
}
#[crate::test]
async fn mna_fallback_with_unused_state() {
let app = Router::new()
.route("/", get(|| async { "index" }))
.with_state(())
.method_not_allowed_fallback(|| async move { "bla" });
let client = TestClient::new(app);
let res = client.post("/").await;
assert_eq!(res.text().await, "bla");
}
#[crate::test]
async fn state_isnt_cloned_too_much_with_fallback() {
let state = CountingCloneableState::new();
let app = Router::new()
.fallback(|_: State<CountingCloneableState>| async {})
.with_state(state.clone());
let client = TestClient::new(app);
// ignore clones made during setup
state.setup_done();
client.get("/does-not-exist").await;
assert_eq!(state.count(), 3);
}
@@ -0,0 +1,71 @@
use super::*;
use http::Method;
use tower::ServiceExt;
mod for_handlers {
use super::*;
#[crate::test]
async fn get_handles_head() {
let app = Router::new().route(
"/",
get(|| async {
let mut headers = HeaderMap::new();
headers.insert("x-some-header", "foobar".parse().unwrap());
(headers, "you shouldn't see this")
}),
);
// don't use reqwest because it always strips bodies from HEAD responses
let res = app
.oneshot(
Request::builder()
.uri("/")
.method(Method::HEAD)
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.headers()["x-some-header"], "foobar");
let body = BodyExt::collect(res.into_body()).await.unwrap().to_bytes();
assert_eq!(body.len(), 0);
}
}
mod for_services {
use super::*;
#[crate::test]
async fn get_handles_head() {
let app = Router::new().route(
"/",
get_service(service_fn(|_req: Request| async move {
Ok::<_, Infallible>(
([("x-some-header", "foobar")], "you shouldn't see this").into_response(),
)
})),
);
// don't use reqwest because it always strips bodies from HEAD responses
let res = app
.oneshot(
Request::builder()
.uri("/")
.method(Method::HEAD)
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.headers()["x-some-header"], "foobar");
let body = BodyExt::collect(res.into_body()).await.unwrap().to_bytes();
assert_eq!(body.len(), 0);
}
}
@@ -0,0 +1,94 @@
use super::*;
use std::future::pending;
use tower::timeout::TimeoutLayer;
async fn unit() {}
async fn forever() {
pending().await
}
fn timeout() -> TimeoutLayer {
TimeoutLayer::new(Duration::from_millis(10))
}
#[crate::test]
async fn handler() {
let app = Router::new().route(
"/",
get(forever.layer((
HandleErrorLayer::new(|_: BoxError| async { StatusCode::REQUEST_TIMEOUT }),
timeout(),
))),
);
let client = TestClient::new(app);
let res = client.get("/").await;
assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT);
}
#[crate::test]
async fn handler_multiple_methods_first() {
let app = Router::new().route(
"/",
get(forever.layer((
HandleErrorLayer::new(|_: BoxError| async { StatusCode::REQUEST_TIMEOUT }),
timeout(),
)))
.post(unit),
);
let client = TestClient::new(app);
let res = client.get("/").await;
assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT);
}
#[crate::test]
async fn handler_multiple_methods_middle() {
let app = Router::new().route(
"/",
delete(unit)
.get(forever.layer((
HandleErrorLayer::new(|_: BoxError| async { StatusCode::REQUEST_TIMEOUT }),
timeout(),
)))
.post(unit),
);
let client = TestClient::new(app);
let res = client.get("/").await;
assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT);
}
#[crate::test]
async fn handler_multiple_methods_last() {
let app = Router::new().route(
"/",
delete(unit).get(forever.layer((
HandleErrorLayer::new(|_: BoxError| async { StatusCode::REQUEST_TIMEOUT }),
timeout(),
))),
);
let client = TestClient::new(app);
let res = client.get("/").await;
assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT);
}
#[crate::test]
async fn handler_service_ext() {
let fallible_service = tower::service_fn(|_| async { Err::<(), ()>(()) });
let handle_error_service =
fallible_service.handle_error(|_| async { StatusCode::INTERNAL_SERVER_ERROR });
let app = Router::new().route("/", get_service(handle_error_service));
let client = TestClient::new(app);
let res = client.get("/").await;
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
+395
View File
@@ -0,0 +1,395 @@
use super::*;
use crate::extract::OriginalUri;
use serde_json::{json, Value};
use tower::limit::ConcurrencyLimitLayer;
#[crate::test]
async fn basic() {
let one = Router::new()
.route("/foo", get(|| async {}))
.route("/bar", get(|| async {}));
let two = Router::new().route("/baz", get(|| async {}));
let app = one.merge(two);
let client = TestClient::new(app);
let res = client.get("/foo").await;
assert_eq!(res.status(), StatusCode::OK);
let res = client.get("/bar").await;
assert_eq!(res.status(), StatusCode::OK);
let res = client.get("/baz").await;
assert_eq!(res.status(), StatusCode::OK);
let res = client.get("/qux").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[crate::test]
async fn multiple_ors_balanced_differently() {
let one = Router::new().route("/one", get(|| async { "one" }));
let two = Router::new().route("/two", get(|| async { "two" }));
let three = Router::new().route("/three", get(|| async { "three" }));
let four = Router::new().route("/four", get(|| async { "four" }));
test(
"one",
one.clone()
.merge(two.clone())
.merge(three.clone())
.merge(four.clone()),
)
.await;
test(
"two",
one.clone()
.merge(two.clone())
.merge(three.clone().merge(four.clone())),
)
.await;
test(
"three",
one.clone()
.merge(two.clone().merge(three.clone()).merge(four.clone())),
)
.await;
test("four", one.merge(two.merge(three.merge(four)))).await;
async fn test(name: &str, app: Router) {
let client = TestClient::new(app);
for n in ["one", "two", "three", "four"].iter() {
println!("running: {name} / {n}");
let res = client.get(&format!("/{n}")).await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, *n);
}
}
}
#[crate::test]
async fn nested_or() {
let bar = Router::new().route("/bar", get(|| async { "bar" }));
let baz = Router::new().route("/baz", get(|| async { "baz" }));
let bar_or_baz = bar.merge(baz);
let client = TestClient::new(bar_or_baz.clone());
assert_eq!(client.get("/bar").await.text().await, "bar");
assert_eq!(client.get("/baz").await.text().await, "baz");
let client = TestClient::new(Router::new().nest("/foo", bar_or_baz));
assert_eq!(client.get("/foo/bar").await.text().await, "bar");
assert_eq!(client.get("/foo/baz").await.text().await, "baz");
}
#[crate::test]
async fn or_with_route_following() {
let one = Router::new().route("/one", get(|| async { "one" }));
let two = Router::new().route("/two", get(|| async { "two" }));
let app = one.merge(two).route("/three", get(|| async { "three" }));
let client = TestClient::new(app);
let res = client.get("/one").await;
assert_eq!(res.status(), StatusCode::OK);
let res = client.get("/two").await;
assert_eq!(res.status(), StatusCode::OK);
let res = client.get("/three").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn layer() {
let one = Router::new().route("/foo", get(|| async {}));
let two = Router::new()
.route("/bar", get(|| async {}))
.layer(ConcurrencyLimitLayer::new(10));
let app = one.merge(two);
let client = TestClient::new(app);
let res = client.get("/foo").await;
assert_eq!(res.status(), StatusCode::OK);
let res = client.get("/bar").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn layer_and_handle_error() {
let one = Router::new().route("/foo", get(|| async {}));
let two = Router::new()
.route("/timeout", get(std::future::pending::<()>))
.layer(TimeoutLayer::with_status_code(
StatusCode::REQUEST_TIMEOUT,
Duration::from_millis(10),
));
let app = one.merge(two);
let client = TestClient::new(app);
let res = client.get("/timeout").await;
assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT);
let res = client.get("/foo").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn nesting() {
let one = Router::new().route("/foo", get(|| async {}));
let two = Router::new().nest("/bar", Router::new().route("/baz", get(|| async {})));
let app = one.merge(two);
let client = TestClient::new(app);
let res = client.get("/bar/baz").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn boxed() {
let one = Router::new().route("/foo", get(|| async {}));
let two = Router::new().route("/bar", get(|| async {}));
let app = one.merge(two);
let client = TestClient::new(app);
let res = client.get("/bar").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn many_ors() {
let app = Router::new()
.route("/r1", get(|| async {}))
.merge(Router::new().route("/r2", get(|| async {})))
.merge(Router::new().route("/r3", get(|| async {})))
.merge(Router::new().route("/r4", get(|| async {})))
.merge(Router::new().route("/r5", get(|| async {})))
.merge(Router::new().route("/r6", get(|| async {})))
.merge(Router::new().route("/r7", get(|| async {})));
let client = TestClient::new(app);
for n in 1..=7 {
let res = client.get(&format!("/r{n}")).await;
assert_eq!(res.status(), StatusCode::OK);
}
let res = client.get("/r8").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[crate::test]
async fn services() {
use crate::routing::get_service;
let app = Router::new()
.route(
"/foo",
get_service(service_fn(|_: Request| async {
Ok::<_, Infallible>(Response::new(Body::empty()))
})),
)
.merge(Router::new().route(
"/bar",
get_service(service_fn(|_: Request| async {
Ok::<_, Infallible>(Response::new(Body::empty()))
})),
));
let client = TestClient::new(app);
let res = client.get("/foo").await;
assert_eq!(res.status(), StatusCode::OK);
let res = client.get("/bar").await;
assert_eq!(res.status(), StatusCode::OK);
}
async fn all_the_uris(
uri: Uri,
OriginalUri(original_uri): OriginalUri,
req: Request,
) -> impl IntoResponse {
Json(json!({
"uri": uri.to_string(),
"request_uri": req.uri().to_string(),
"original_uri": original_uri.to_string(),
}))
}
#[crate::test]
async fn nesting_and_seeing_the_right_uri() {
let one = Router::new().nest("/foo/", Router::new().route("/bar", get(all_the_uris)));
let two = Router::new().route("/foo", get(all_the_uris));
let client = TestClient::new(one.merge(two));
let res = client.get("/foo/bar").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.json::<Value>().await,
json!({
"uri": "/bar",
"request_uri": "/bar",
"original_uri": "/foo/bar",
})
);
let res = client.get("/foo").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.json::<Value>().await,
json!({
"uri": "/foo",
"request_uri": "/foo",
"original_uri": "/foo",
})
);
}
#[crate::test]
async fn nesting_and_seeing_the_right_uri_at_more_levels_of_nesting() {
let one = Router::new().nest(
"/foo/",
Router::new().nest("/bar", Router::new().route("/baz", get(all_the_uris))),
);
let two = Router::new().route("/foo", get(all_the_uris));
let client = TestClient::new(one.merge(two));
let res = client.get("/foo/bar/baz").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.json::<Value>().await,
json!({
"uri": "/baz",
"request_uri": "/baz",
"original_uri": "/foo/bar/baz",
})
);
let res = client.get("/foo").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.json::<Value>().await,
json!({
"uri": "/foo",
"request_uri": "/foo",
"original_uri": "/foo",
})
);
}
#[crate::test]
async fn nesting_and_seeing_the_right_uri_ors_with_nesting() {
let one = Router::new().nest(
"/one",
Router::new().nest("/bar", Router::new().route("/baz", get(all_the_uris))),
);
let two = Router::new().nest("/two", Router::new().route("/qux", get(all_the_uris)));
let three = Router::new().route("/three", get(all_the_uris));
let client = TestClient::new(one.merge(two).merge(three));
let res = client.get("/one/bar/baz").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.json::<Value>().await,
json!({
"uri": "/baz",
"request_uri": "/baz",
"original_uri": "/one/bar/baz",
})
);
let res = client.get("/two/qux").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.json::<Value>().await,
json!({
"uri": "/qux",
"request_uri": "/qux",
"original_uri": "/two/qux",
})
);
let res = client.get("/three").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.json::<Value>().await,
json!({
"uri": "/three",
"request_uri": "/three",
"original_uri": "/three",
})
);
}
#[crate::test]
async fn nesting_and_seeing_the_right_uri_ors_with_multi_segment_uris() {
let one = Router::new().nest(
"/one",
Router::new().nest("/foo", Router::new().route("/bar", get(all_the_uris))),
);
let two = Router::new().route("/two/foo", get(all_the_uris));
let client = TestClient::new(one.merge(two));
let res = client.get("/one/foo/bar").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.json::<Value>().await,
json!({
"uri": "/bar",
"request_uri": "/bar",
"original_uri": "/one/foo/bar",
})
);
let res = client.get("/two/foo").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.json::<Value>().await,
json!({
"uri": "/two/foo",
"request_uri": "/two/foo",
"original_uri": "/two/foo",
})
);
}
#[allow(deprecated)]
#[crate::test]
async fn middleware_that_return_early() {
let private = Router::new()
.route("/", get(|| async {}))
.layer(ValidateRequestHeaderLayer::bearer("password"));
let public = Router::new().route("/public", get(|| async {}));
let client = TestClient::new(private.merge(public));
assert_eq!(client.get("/").await.status(), StatusCode::UNAUTHORIZED);
assert_eq!(
client
.get("/")
.header("authorization", "Bearer password")
.await
.status(),
StatusCode::OK
);
assert_eq!(
client.get("/doesnt-exist").await.status(),
StatusCode::NOT_FOUND
);
assert_eq!(client.get("/public").await.status(), StatusCode::OK);
}
File diff suppressed because it is too large Load Diff
+389
View File
@@ -0,0 +1,389 @@
use super::*;
use std::collections::HashMap;
use tower_http::services::ServeDir;
#[crate::test]
async fn nesting_apps() {
let api_routes = Router::new()
.route(
"/users",
get(|| async { "users#index" }).post(|| async { "users#create" }),
)
.route(
"/users/{id}",
get(
|params: extract::Path<HashMap<String, String>>| async move {
format!(
"{}: users#show ({})",
params.get("version").unwrap(),
params.get("id").unwrap()
)
},
),
)
.route(
"/games/{id}",
get(
|params: extract::Path<HashMap<String, String>>| async move {
format!(
"{}: games#show ({})",
params.get("version").unwrap(),
params.get("id").unwrap()
)
},
),
);
let app = Router::new()
.route("/", get(|| async { "hi" }))
.nest("/{version}/api", api_routes);
let client = TestClient::new(app);
let res = client.get("/").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "hi");
let res = client.get("/v0/api/users").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "users#index");
let res = client.get("/v0/api/users/123").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "v0: users#show (123)");
let res = client.get("/v0/api/games/123").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "v0: games#show (123)");
}
#[crate::test]
async fn wrong_method_nest() {
let nested_app = Router::new().route("/", get(|| async {}));
let app = Router::new().nest("/foo", nested_app);
let client = TestClient::new(app);
let res = client.get("/foo").await;
assert_eq!(res.status(), StatusCode::OK);
let res = client.post("/foo").await;
assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED);
assert_eq!(res.headers()[ALLOW], "GET,HEAD");
let res = client.patch("/foo/bar").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[test]
#[should_panic(expected = "Nesting at the root is no longer supported. Use merge instead.")]
fn nest_router_at_root() {
let nested = Router::new().route("/foo", get(|| async {}));
let _: Router = Router::new().nest("/", nested);
}
#[test]
#[should_panic(expected = "Nesting at the root is no longer supported. Use merge instead.")]
fn nest_router_at_empty_path() {
let nested = Router::new().route("/foo", get(|| async {}));
let _: Router = Router::new().nest("", nested);
}
#[test]
#[should_panic(
expected = "Nesting at the root is no longer supported. Use fallback_service instead."
)]
fn nest_service_at_root() {
let _: Router = Router::new().nest_service("/", get(|| async {}));
}
#[test]
#[should_panic(
expected = "Nesting at the root is no longer supported. Use fallback_service instead."
)]
fn nest_service_at_empty_path() {
let _: Router = Router::new().nest_service("", get(|| async {}));
}
#[crate::test]
async fn nested_url_extractor() {
let app = Router::new().nest(
"/foo",
Router::new().nest(
"/bar",
Router::new()
.route("/baz", get(|uri: Uri| async move { uri.to_string() }))
.route(
"/qux",
get(|req: Request| async move { req.uri().to_string() }),
),
),
);
let client = TestClient::new(app);
let res = client.get("/foo/bar/baz").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "/baz");
let res = client.get("/foo/bar/qux").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "/qux");
}
#[crate::test]
async fn nested_url_original_extractor() {
let app = Router::new().nest(
"/foo",
Router::new().nest(
"/bar",
Router::new().route(
"/baz",
get(|uri: extract::OriginalUri| async move { uri.0.to_string() }),
),
),
);
let client = TestClient::new(app);
let res = client.get("/foo/bar/baz").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "/foo/bar/baz");
}
#[crate::test]
async fn nested_service_sees_stripped_uri() {
let app = Router::new().nest(
"/foo",
Router::new().nest(
"/bar",
Router::new().route_service(
"/baz",
service_fn(|req: Request| async move {
let body = Body::from(req.uri().to_string());
Ok::<_, Infallible>(Response::new(body))
}),
),
),
);
let client = TestClient::new(app);
let res = client.get("/foo/bar/baz").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "/baz");
}
#[crate::test]
async fn nest_static_file_server() {
let app = Router::new().nest_service("/static", ServeDir::new("."));
let client = TestClient::new(app);
let res = client.get("/static/README.md").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[crate::test]
async fn nested_multiple_routes() {
let app = Router::new()
.nest(
"/api",
Router::new()
.route("/users", get(|| async { "users" }))
.route("/teams", get(|| async { "teams" })),
)
.route("/", get(|| async { "root" }));
let client = TestClient::new(app);
assert_eq!(client.get("/").await.text().await, "root");
assert_eq!(client.get("/api/users").await.text().await, "users");
assert_eq!(client.get("/api/teams").await.text().await, "teams");
}
#[crate::test]
async fn multiple_top_level_nests() {
let app = Router::new()
.nest(
"/one",
Router::new().route("/route", get(|| async { "one" })),
)
.nest(
"/two",
Router::new().route("/route", get(|| async { "two" })),
);
let client = TestClient::new(app);
assert_eq!(client.get("/one/route").await.text().await, "one");
assert_eq!(client.get("/two/route").await.text().await, "two");
}
#[crate::test]
#[should_panic(expected = "Invalid route: nested routes cannot contain wildcards (*)")]
async fn nest_cannot_contain_wildcards() {
_ = Router::<()>::new().nest("/one/{*rest}", Router::new());
}
#[crate::test]
async fn outer_middleware_still_see_whole_url() {
#[derive(Clone)]
struct SetUriExtension<S>(S);
#[derive(Clone)]
struct Uri(http::Uri);
impl<S, B> Service<Request<B>> for SetUriExtension<S>
where
S: Service<Request<B>>,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.poll_ready(cx)
}
fn call(&mut self, mut req: Request<B>) -> Self::Future {
let uri = Uri(req.uri().clone());
req.extensions_mut().insert(uri);
self.0.call(req)
}
}
async fn handler(Extension(Uri(middleware_uri)): Extension<Uri>) -> impl IntoResponse {
middleware_uri.to_string()
}
let app = Router::new()
.route("/", get(handler))
.route("/foo", get(handler))
.route("/foo/bar", get(handler))
.nest("/one", Router::new().route("/two", get(handler)))
.fallback(handler)
.layer(tower::layer::layer_fn(SetUriExtension));
let client = TestClient::new(app);
assert_eq!(client.get("/").await.text().await, "/");
assert_eq!(client.get("/foo").await.text().await, "/foo");
assert_eq!(client.get("/foo/bar").await.text().await, "/foo/bar");
assert_eq!(client.get("/not-found").await.text().await, "/not-found");
assert_eq!(client.get("/one/two").await.text().await, "/one/two");
}
#[crate::test]
async fn nest_at_capture() {
let api_routes = Router::new().route(
"/{b}",
get(|Path((a, b)): Path<(String, String)>| async move { format!("a={a} b={b}") }),
);
let app = Router::new().nest("/{a}", api_routes);
let client = TestClient::new(app);
let res = client.get("/foo/bar").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "a=foo b=bar");
}
#[crate::test]
async fn nest_with_and_without_trailing() {
let app = Router::new().nest_service("/foo", get(|| async {}));
let client = TestClient::new(app);
let res = client.get("/foo").await;
assert_eq!(res.status(), StatusCode::OK);
let res = client.get("/foo/").await;
assert_eq!(res.status(), StatusCode::OK);
let res = client.get("/foo/bar").await;
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
async fn nesting_with_root_inner_router() {
let app = Router::new()
.nest_service("/service", Router::new().route("/", get(|| async {})))
.nest("/router", Router::new().route("/", get(|| async {})))
.nest("/router-slash/", Router::new().route("/", get(|| async {})));
let client = TestClient::new(app);
// `/service/` does match the `/service` prefix and the remaining path is technically
// empty, which is the same as `/` which matches `.route("/", _)`
let res = client.get("/service").await;
assert_eq!(res.status(), StatusCode::OK);
// `/service/` does match the `/service` prefix and the remaining path is `/`
// which matches `.route("/", _)`
//
// this is perhaps a little surprising but don't think there is much we can do
let res = client.get("/service/").await;
assert_eq!(res.status(), StatusCode::OK);
// at least it does work like you'd expect when using `nest`
let res = client.get("/router").await;
assert_eq!(res.status(), StatusCode::OK);
let res = client.get("/router/").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
let res = client.get("/router-slash").await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
let res = client.get("/router-slash/").await;
assert_eq!(res.status(), StatusCode::OK);
}
macro_rules! nested_route_test {
(
$name:ident,
// the path we nest the inner router at
nest = $nested_path:literal,
// the route the inner router accepts
route = $route_path:literal,
// the route we expect to be able to call
expected = $expected_path:literal $(,)?
) => {
#[crate::test]
async fn $name() {
let inner = Router::new().route($route_path, get(|| async {}));
let app = Router::new().nest($nested_path, inner);
let client = TestClient::new(app);
let res = client.get($expected_path).await;
let status = res.status();
assert_eq!(status, StatusCode::OK, "Router");
}
};
}
// test cases taken from https://github.com/tokio-rs/axum/issues/714#issuecomment-1058144460
nested_route_test!(nest_1, nest = "/a", route = "/", expected = "/a");
nested_route_test!(nest_2, nest = "/a", route = "/a", expected = "/a/a");
nested_route_test!(nest_3, nest = "/a", route = "/a/", expected = "/a/a/");
nested_route_test!(nest_4, nest = "/a/", route = "/", expected = "/a/");
nested_route_test!(nest_5, nest = "/a/", route = "/a", expected = "/a/a");
nested_route_test!(nest_6, nest = "/a/", route = "/a/", expected = "/a/a/");
#[crate::test]
#[should_panic(
expected = "Path segments must not start with `:`. For capture groups, use `{capture}`. If you meant to literally match a segment starting with a colon, call `without_v07_checks` on the router."
)]
async fn colon_in_route() {
_ = Router::<()>::new().nest("/:foo", Router::new());
}
#[crate::test]
#[should_panic(
expected = "Path segments must not start with `*`. For wildcard capture, use `{*wildcard}`. If you meant to literally match a segment starting with an asterisk, call `without_v07_checks` on the router."
)]
async fn asterisk_in_route() {
_ = Router::<()>::new().nest("/*foo", Router::new());
}
+47
View File
@@ -0,0 +1,47 @@
use crate::util::PercentDecodedStr;
use http::Extensions;
use matchit::Params;
use std::sync::Arc;
#[derive(Clone)]
pub(crate) enum UrlParams {
Params(Vec<(Arc<str>, PercentDecodedStr)>),
InvalidUtf8InPathParam { key: Arc<str> },
}
pub(super) fn insert_url_params(extensions: &mut Extensions, params: Params<'_, '_>) {
let current_params = extensions.get_mut();
if let Some(UrlParams::InvalidUtf8InPathParam { .. }) = current_params {
// nothing to do here since an error was stored earlier
return;
}
let params = params
.iter()
.filter(|(key, _)| !key.starts_with(super::NEST_TAIL_PARAM))
.filter(|(key, _)| !key.starts_with(super::FALLBACK_PARAM))
.map(|(k, v)| {
if let Some(decoded) = PercentDecodedStr::new(v) {
Ok((Arc::from(k), decoded))
} else {
Err(Arc::from(k))
}
})
.collect::<Result<Vec<_>, _>>();
match (current_params, params) {
(Some(UrlParams::InvalidUtf8InPathParam { .. }), _) => {
unreachable!("we check for this state earlier in this method")
}
(_, Err(invalid_key)) => {
extensions.insert(UrlParams::InvalidUtf8InPathParam { key: invalid_key });
}
(Some(UrlParams::Params(current)), Ok(params)) => {
current.extend(params);
}
(None, Ok(params)) => {
extensions.insert(UrlParams::Params(params));
}
}
}
+167
View File
@@ -0,0 +1,167 @@
use std::{fmt, future::Future, time::Duration};
use tokio::{
io::{self, AsyncRead, AsyncWrite},
net::{TcpListener, TcpStream},
};
/// Types that can listen for connections.
pub trait Listener: Send + 'static {
/// The listener's IO type.
type Io: AsyncRead + AsyncWrite + Unpin + Send + 'static;
/// The listener's address type.
type Addr: Send;
/// Accept a new incoming connection to this listener.
///
/// If the underlying accept call can return an error, this function must
/// take care of logging and retrying.
fn accept(&mut self) -> impl Future<Output = (Self::Io, Self::Addr)> + Send;
/// Returns the local address that this listener is bound to.
fn local_addr(&self) -> io::Result<Self::Addr>;
}
impl Listener for TcpListener {
type Io = TcpStream;
type Addr = std::net::SocketAddr;
async fn accept(&mut self) -> (Self::Io, Self::Addr) {
loop {
match Self::accept(self).await {
Ok(tup) => return tup,
Err(e) => handle_accept_error(e).await,
}
}
}
#[inline]
fn local_addr(&self) -> io::Result<Self::Addr> {
Self::local_addr(self)
}
}
#[cfg(unix)]
impl Listener for tokio::net::UnixListener {
type Io = tokio::net::UnixStream;
type Addr = tokio::net::unix::SocketAddr;
async fn accept(&mut self) -> (Self::Io, Self::Addr) {
loop {
match Self::accept(self).await {
Ok(tup) => return tup,
Err(e) => handle_accept_error(e).await,
}
}
}
#[inline]
fn local_addr(&self) -> io::Result<Self::Addr> {
Self::local_addr(self)
}
}
/// Extensions to [`Listener`].
pub trait ListenerExt: Listener + Sized {
/// Run a mutable closure on every accepted `Io`.
///
/// # Example
///
/// ```
/// use axum::{Router, routing::get, serve::ListenerExt};
/// use tracing::trace;
///
/// # async {
/// let router = Router::new().route("/", get(|| async { "Hello, World!" }));
///
/// let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
/// .await
/// .unwrap()
/// .tap_io(|tcp_stream| {
/// if let Err(err) = tcp_stream.set_nodelay(true) {
/// trace!("failed to set TCP_NODELAY on incoming connection: {err:#}");
/// }
/// });
/// axum::serve(listener, router).await.unwrap();
/// # };
/// ```
fn tap_io<F>(self, tap_fn: F) -> TapIo<Self, F>
where
F: FnMut(&mut Self::Io) + Send + 'static,
{
TapIo {
listener: self,
tap_fn,
}
}
}
impl<L: Listener> ListenerExt for L {}
/// Return type of [`ListenerExt::tap_io`].
///
/// See that method for details.
pub struct TapIo<L, F> {
listener: L,
tap_fn: F,
}
impl<L, F> fmt::Debug for TapIo<L, F>
where
L: Listener + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TapIo")
.field("listener", &self.listener)
.finish_non_exhaustive()
}
}
impl<L, F> Listener for TapIo<L, F>
where
L: Listener,
F: FnMut(&mut L::Io) + Send + 'static,
{
type Io = L::Io;
type Addr = L::Addr;
async fn accept(&mut self) -> (Self::Io, Self::Addr) {
let (mut io, addr) = self.listener.accept().await;
(self.tap_fn)(&mut io);
(io, addr)
}
fn local_addr(&self) -> io::Result<Self::Addr> {
self.listener.local_addr()
}
}
async fn handle_accept_error(e: io::Error) {
if is_connection_error(&e) {
return;
}
// [From `hyper::Server` in 0.14](https://github.com/hyperium/hyper/blob/v0.14.27/src/server/tcp.rs#L186)
//
// > A possible scenario is that the process has hit the max open files
// > allowed, and so trying to accept a new connection will fail with
// > `EMFILE`. In some cases, it's preferable to just wait for some time, if
// > the application will likely close some files (or connections), and try
// > to accept the connection again. If this option is `true`, the error
// > will be logged at the `error` level, since it is still a big deal,
// > and then the listener will sleep for 1 second.
//
// hyper allowed customizing this but axum does not.
error!("accept error: {e}");
tokio::time::sleep(Duration::from_secs(1)).await;
}
fn is_connection_error(e: &io::Error) -> bool {
matches!(
e.kind(),
io::ErrorKind::ConnectionRefused
| io::ErrorKind::ConnectionAborted
| io::ErrorKind::ConnectionReset
)
}
+728
View File
@@ -0,0 +1,728 @@
//! Serve services.
use std::{
convert::Infallible,
fmt::Debug,
future::{Future, IntoFuture},
io,
marker::PhantomData,
pin::pin,
};
use axum_core::{body::Body, extract::Request, response::Response};
use futures_util::FutureExt;
use hyper::body::Incoming;
use hyper_util::rt::{TokioExecutor, TokioIo};
#[cfg(any(feature = "http1", feature = "http2"))]
use hyper_util::{server::conn::auto::Builder, service::TowerToHyperService};
use tokio::sync::watch;
use tower::ServiceExt as _;
use tower_service::Service;
mod listener;
pub use self::listener::{Listener, ListenerExt, TapIo};
/// Serve the service with the supplied listener.
///
/// This method of running a service is intentionally simple and doesn't support any configuration.
/// Use hyper or hyper-util if you need configuration.
///
/// It supports both HTTP/1 as well as HTTP/2.
///
/// # Examples
///
/// Serving a [`Router`]:
///
/// ```
/// use axum::{Router, routing::get};
///
/// # async {
/// let router = Router::new().route("/", get(|| async { "Hello, World!" }));
///
/// let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
/// axum::serve(listener, router).await.unwrap();
/// # };
/// ```
///
/// See also [`Router::into_make_service_with_connect_info`].
///
/// Serving a [`MethodRouter`]:
///
/// ```
/// use axum::routing::get;
///
/// # async {
/// let router = get(|| async { "Hello, World!" });
///
/// let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
/// axum::serve(listener, router).await.unwrap();
/// # };
/// ```
///
/// See also [`MethodRouter::into_make_service_with_connect_info`].
///
/// Serving a [`Handler`]:
///
/// ```
/// use axum::handler::HandlerWithoutStateExt;
///
/// # async {
/// async fn handler() -> &'static str {
/// "Hello, World!"
/// }
///
/// let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
/// axum::serve(listener, handler.into_make_service()).await.unwrap();
/// # };
/// ```
///
/// See also [`HandlerWithoutStateExt::into_make_service_with_connect_info`] and
/// [`HandlerService::into_make_service_with_connect_info`].
///
/// # Return Value
///
/// Although this future resolves to `io::Result<()>`, it will never actually complete or return an
/// error. Errors on the TCP socket will be handled by sleeping for a short while (currently, one
/// second).
///
/// [`Router`]: crate::Router
/// [`Router::into_make_service_with_connect_info`]: crate::Router::into_make_service_with_connect_info
/// [`MethodRouter`]: crate::routing::MethodRouter
/// [`MethodRouter::into_make_service_with_connect_info`]: crate::routing::MethodRouter::into_make_service_with_connect_info
/// [`Handler`]: crate::handler::Handler
/// [`HandlerWithoutStateExt::into_make_service_with_connect_info`]: crate::handler::HandlerWithoutStateExt::into_make_service_with_connect_info
/// [`HandlerService::into_make_service_with_connect_info`]: crate::handler::HandlerService::into_make_service_with_connect_info
#[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))]
pub fn serve<L, M, S>(listener: L, make_service: M) -> Serve<L, M, S>
where
L: Listener,
M: for<'a> Service<IncomingStream<'a, L>, Error = Infallible, Response = S>,
S: Service<Request, Response = Response, Error = Infallible> + Clone + Send + 'static,
S::Future: Send,
{
Serve {
listener,
make_service,
_marker: PhantomData,
}
}
/// Future returned by [`serve`].
#[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))]
#[must_use = "futures must be awaited or polled"]
pub struct Serve<L, M, S> {
listener: L,
make_service: M,
_marker: PhantomData<fn() -> S>,
}
#[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))]
impl<L, M, S> Serve<L, M, S>
where
L: Listener,
{
/// Prepares a server to handle graceful shutdown when the provided future completes.
///
/// # Example
///
/// ```
/// use axum::{Router, routing::get};
///
/// # async {
/// let router = Router::new().route("/", get(|| async { "Hello, World!" }));
///
/// let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
/// axum::serve(listener, router)
/// .with_graceful_shutdown(shutdown_signal())
/// .await
/// .unwrap();
/// # };
///
/// async fn shutdown_signal() {
/// // ...
/// }
/// ```
///
/// # Return Value
///
/// Similarly to [`serve`], although this future resolves to `io::Result<()>`, it will never
/// error. It returns `Ok(())` only after the `signal` future completes.
pub fn with_graceful_shutdown<F>(self, signal: F) -> WithGracefulShutdown<L, M, S, F>
where
F: Future<Output = ()> + Send + 'static,
{
WithGracefulShutdown {
listener: self.listener,
make_service: self.make_service,
signal,
_marker: PhantomData,
}
}
/// Returns the local address this server is bound to.
pub fn local_addr(&self) -> io::Result<L::Addr> {
self.listener.local_addr()
}
}
#[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))]
impl<L, M, S> Serve<L, M, S>
where
L: Listener,
L::Addr: Debug,
M: for<'a> Service<IncomingStream<'a, L>, Error = Infallible, Response = S> + Send + 'static,
for<'a> <M as Service<IncomingStream<'a, L>>>::Future: Send,
S: Service<Request, Response = Response, Error = Infallible> + Clone + Send + 'static,
S::Future: Send,
{
async fn run(self) -> ! {
let Self {
mut listener,
mut make_service,
_marker,
} = self;
let (signal_tx, _signal_rx) = watch::channel(());
let (_close_tx, close_rx) = watch::channel(());
loop {
let (io, remote_addr) = listener.accept().await;
handle_connection(&mut make_service, &signal_tx, &close_rx, io, remote_addr).await;
}
}
}
#[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))]
impl<L, M, S> Debug for Serve<L, M, S>
where
L: Debug + 'static,
M: Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self {
listener,
make_service,
_marker: _,
} = self;
let mut s = f.debug_struct("Serve");
s.field("listener", listener)
.field("make_service", make_service);
s.finish()
}
}
#[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))]
impl<L, M, S> IntoFuture for Serve<L, M, S>
where
L: Listener,
L::Addr: Debug,
M: for<'a> Service<IncomingStream<'a, L>, Error = Infallible, Response = S> + Send + 'static,
for<'a> <M as Service<IncomingStream<'a, L>>>::Future: Send,
S: Service<Request, Response = Response, Error = Infallible> + Clone + Send + 'static,
S::Future: Send,
{
type Output = io::Result<()>;
type IntoFuture = private::ServeFuture;
fn into_future(self) -> Self::IntoFuture {
private::ServeFuture(Box::pin(async move { self.run().await }))
}
}
/// Serve future with graceful shutdown enabled.
#[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))]
#[must_use = "futures must be awaited or polled"]
pub struct WithGracefulShutdown<L, M, S, F> {
listener: L,
make_service: M,
signal: F,
_marker: PhantomData<fn() -> S>,
}
#[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))]
impl<L, M, S, F> WithGracefulShutdown<L, M, S, F>
where
L: Listener,
{
/// Returns the local address this server is bound to.
pub fn local_addr(&self) -> io::Result<L::Addr> {
self.listener.local_addr()
}
}
#[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))]
impl<L, M, S, F> WithGracefulShutdown<L, M, S, F>
where
L: Listener,
L::Addr: Debug,
M: for<'a> Service<IncomingStream<'a, L>, Error = Infallible, Response = S> + Send + 'static,
for<'a> <M as Service<IncomingStream<'a, L>>>::Future: Send,
S: Service<Request, Response = Response, Error = Infallible> + Clone + Send + 'static,
S::Future: Send,
F: Future<Output = ()> + Send + 'static,
{
async fn run(self) {
let Self {
mut listener,
mut make_service,
signal,
_marker,
} = self;
let (signal_tx, signal_rx) = watch::channel(());
tokio::spawn(async move {
signal.await;
trace!("received graceful shutdown signal. Telling tasks to shutdown");
drop(signal_rx);
});
let (close_tx, close_rx) = watch::channel(());
loop {
let (io, remote_addr) = tokio::select! {
conn = listener.accept() => conn,
_ = signal_tx.closed() => {
trace!("signal received, not accepting new connections");
break;
}
};
handle_connection(&mut make_service, &signal_tx, &close_rx, io, remote_addr).await;
}
drop(close_rx);
drop(listener);
trace!(
"waiting for {} task(s) to finish",
close_tx.receiver_count()
);
close_tx.closed().await;
}
}
#[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))]
impl<L, M, S, F> Debug for WithGracefulShutdown<L, M, S, F>
where
L: Debug + 'static,
M: Debug,
S: Debug,
F: Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self {
listener,
make_service,
signal,
_marker: _,
} = self;
f.debug_struct("WithGracefulShutdown")
.field("listener", listener)
.field("make_service", make_service)
.field("signal", signal)
.finish()
}
}
#[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))]
impl<L, M, S, F> IntoFuture for WithGracefulShutdown<L, M, S, F>
where
L: Listener,
L::Addr: Debug,
M: for<'a> Service<IncomingStream<'a, L>, Error = Infallible, Response = S> + Send + 'static,
for<'a> <M as Service<IncomingStream<'a, L>>>::Future: Send,
S: Service<Request, Response = Response, Error = Infallible> + Clone + Send + 'static,
S::Future: Send,
F: Future<Output = ()> + Send + 'static,
{
type Output = io::Result<()>;
type IntoFuture = private::ServeFuture;
fn into_future(self) -> Self::IntoFuture {
private::ServeFuture(Box::pin(async move {
self.run().await;
Ok(())
}))
}
}
async fn handle_connection<L, M, S>(
make_service: &mut M,
signal_tx: &watch::Sender<()>,
close_rx: &watch::Receiver<()>,
io: <L as Listener>::Io,
remote_addr: <L as Listener>::Addr,
) where
L: Listener,
L::Addr: Debug,
M: for<'a> Service<IncomingStream<'a, L>, Error = Infallible, Response = S> + Send + 'static,
for<'a> <M as Service<IncomingStream<'a, L>>>::Future: Send,
S: Service<Request, Response = Response, Error = Infallible> + Clone + Send + 'static,
S::Future: Send,
{
let io = TokioIo::new(io);
trace!("connection {remote_addr:?} accepted");
make_service
.ready()
.await
.unwrap_or_else(|err| match err {});
let tower_service = make_service
.call(IncomingStream {
io: &io,
remote_addr,
})
.await
.unwrap_or_else(|err| match err {})
.map_request(|req: Request<Incoming>| req.map(Body::new));
let hyper_service = TowerToHyperService::new(tower_service);
let signal_tx = signal_tx.clone();
let close_rx = close_rx.clone();
tokio::spawn(async move {
#[allow(unused_mut)]
let mut builder = Builder::new(TokioExecutor::new());
// CONNECT protocol needed for HTTP/2 websockets
#[cfg(feature = "http2")]
builder.http2().enable_connect_protocol();
let mut conn = pin!(builder.serve_connection_with_upgrades(io, hyper_service));
let mut signal_closed = pin!(signal_tx.closed().fuse());
loop {
tokio::select! {
result = conn.as_mut() => {
if let Err(_err) = result {
trace!("failed to serve connection: {_err:#}");
}
break;
}
_ = &mut signal_closed => {
trace!("signal received in task, starting graceful shutdown");
conn.as_mut().graceful_shutdown();
}
}
}
drop(close_rx);
});
}
/// An incoming stream.
///
/// Used with [`serve`] and [`IntoMakeServiceWithConnectInfo`].
///
/// [`IntoMakeServiceWithConnectInfo`]: crate::extract::connect_info::IntoMakeServiceWithConnectInfo
#[derive(Debug)]
pub struct IncomingStream<'a, L>
where
L: Listener,
{
io: &'a TokioIo<L::Io>,
remote_addr: L::Addr,
}
impl<L> IncomingStream<'_, L>
where
L: Listener,
{
/// Get a reference to the inner IO type.
pub fn io(&self) -> &L::Io {
self.io.inner()
}
/// Returns the remote address that this stream is bound to.
pub fn remote_addr(&self) -> &L::Addr {
&self.remote_addr
}
}
mod private {
use std::{
future::Future,
io,
pin::Pin,
task::{Context, Poll},
};
pub struct ServeFuture(pub(super) futures_util::future::BoxFuture<'static, io::Result<()>>);
impl Future for ServeFuture {
type Output = io::Result<()>;
#[inline]
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.0.as_mut().poll(cx)
}
}
impl std::fmt::Debug for ServeFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ServeFuture").finish_non_exhaustive()
}
}
}
#[cfg(test)]
mod tests {
use std::{
future::{pending, IntoFuture as _},
net::{IpAddr, Ipv4Addr},
};
use axum_core::{body::Body, extract::Request};
use http::StatusCode;
use hyper_util::rt::TokioIo;
#[cfg(unix)]
use tokio::net::UnixListener;
use tokio::{
io::{self, AsyncRead, AsyncWrite},
net::TcpListener,
};
#[cfg(unix)]
use super::IncomingStream;
use super::{serve, Listener};
#[cfg(unix)]
use crate::extract::connect_info::Connected;
use crate::{
body::to_bytes,
handler::{Handler, HandlerWithoutStateExt},
routing::get,
serve::ListenerExt,
Router,
};
#[allow(dead_code, unused_must_use)]
async fn if_it_compiles_it_works() {
#[derive(Clone, Debug)]
struct UdsConnectInfo;
#[cfg(unix)]
impl Connected<IncomingStream<'_, UnixListener>> for UdsConnectInfo {
fn connect_info(_stream: IncomingStream<'_, UnixListener>) -> Self {
Self
}
}
let router: Router = Router::new();
let addr = "0.0.0.0:0";
let tcp_nodelay_listener = || async {
TcpListener::bind(addr).await.unwrap().tap_io(|tcp_stream| {
if let Err(err) = tcp_stream.set_nodelay(true) {
eprintln!("failed to set TCP_NODELAY on incoming connection: {err:#}");
}
})
};
// router
serve(TcpListener::bind(addr).await.unwrap(), router.clone());
serve(tcp_nodelay_listener().await, router.clone())
.await
.unwrap();
#[cfg(unix)]
serve(UnixListener::bind("").unwrap(), router.clone());
serve(
TcpListener::bind(addr).await.unwrap(),
router.clone().into_make_service(),
);
serve(
tcp_nodelay_listener().await,
router.clone().into_make_service(),
);
#[cfg(unix)]
serve(
UnixListener::bind("").unwrap(),
router.clone().into_make_service(),
);
serve(
TcpListener::bind(addr).await.unwrap(),
router
.clone()
.into_make_service_with_connect_info::<std::net::SocketAddr>(),
);
serve(
tcp_nodelay_listener().await,
router
.clone()
.into_make_service_with_connect_info::<std::net::SocketAddr>(),
);
#[cfg(unix)]
serve(
UnixListener::bind("").unwrap(),
router.into_make_service_with_connect_info::<UdsConnectInfo>(),
);
// method router
serve(TcpListener::bind(addr).await.unwrap(), get(handler));
serve(tcp_nodelay_listener().await, get(handler));
#[cfg(unix)]
serve(UnixListener::bind("").unwrap(), get(handler));
serve(
TcpListener::bind(addr).await.unwrap(),
get(handler).into_make_service(),
);
serve(
tcp_nodelay_listener().await,
get(handler).into_make_service(),
);
#[cfg(unix)]
serve(
UnixListener::bind("").unwrap(),
get(handler).into_make_service(),
);
serve(
TcpListener::bind(addr).await.unwrap(),
get(handler).into_make_service_with_connect_info::<std::net::SocketAddr>(),
);
serve(
tcp_nodelay_listener().await,
get(handler).into_make_service_with_connect_info::<std::net::SocketAddr>(),
);
#[cfg(unix)]
serve(
UnixListener::bind("").unwrap(),
get(handler).into_make_service_with_connect_info::<UdsConnectInfo>(),
);
// handler
serve(
TcpListener::bind(addr).await.unwrap(),
handler.into_service(),
);
serve(tcp_nodelay_listener().await, handler.into_service());
#[cfg(unix)]
serve(UnixListener::bind("").unwrap(), handler.into_service());
serve(
TcpListener::bind(addr).await.unwrap(),
handler.with_state(()),
);
serve(tcp_nodelay_listener().await, handler.with_state(()));
#[cfg(unix)]
serve(UnixListener::bind("").unwrap(), handler.with_state(()));
serve(
TcpListener::bind(addr).await.unwrap(),
handler.into_make_service(),
);
serve(tcp_nodelay_listener().await, handler.into_make_service());
#[cfg(unix)]
serve(UnixListener::bind("").unwrap(), handler.into_make_service());
serve(
TcpListener::bind(addr).await.unwrap(),
handler.into_make_service_with_connect_info::<std::net::SocketAddr>(),
);
serve(
tcp_nodelay_listener().await,
handler.into_make_service_with_connect_info::<std::net::SocketAddr>(),
);
#[cfg(unix)]
serve(
UnixListener::bind("").unwrap(),
handler.into_make_service_with_connect_info::<UdsConnectInfo>(),
);
}
async fn handler() {}
#[crate::test]
async fn test_serve_local_addr() {
let router: Router = Router::new();
let addr = "0.0.0.0:0";
let server = serve(TcpListener::bind(addr).await.unwrap(), router.clone());
let address = server.local_addr().unwrap();
assert_eq!(address.ip(), IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
assert_ne!(address.port(), 0);
}
#[crate::test]
async fn test_with_graceful_shutdown_local_addr() {
let router: Router = Router::new();
let addr = "0.0.0.0:0";
let server = serve(TcpListener::bind(addr).await.unwrap(), router.clone())
.with_graceful_shutdown(pending());
let address = server.local_addr().unwrap();
assert_eq!(address.ip(), IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
assert_ne!(address.port(), 0);
}
#[test]
fn into_future_outside_tokio() {
let router: Router = Router::new();
let addr = "0.0.0.0:0";
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let listener = rt.block_on(tokio::net::TcpListener::bind(addr)).unwrap();
// Call Serve::into_future outside of a tokio context. This used to panic.
_ = serve(listener, router).into_future();
}
#[crate::test]
async fn serving_on_custom_io_type() {
struct ReadyListener<T>(Option<T>);
impl<T> Listener for ReadyListener<T>
where
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
type Io = T;
type Addr = ();
async fn accept(&mut self) -> (Self::Io, Self::Addr) {
match self.0.take() {
Some(server) => (server, ()),
None => std::future::pending().await,
}
}
fn local_addr(&self) -> io::Result<Self::Addr> {
Ok(())
}
}
let (client, server) = io::duplex(1024);
let listener = ReadyListener(Some(server));
let app = Router::new().route("/", get(|| async { "Hello, World!" }));
tokio::spawn(serve(listener, app).into_future());
let stream = TokioIo::new(client);
let (mut sender, conn) = hyper::client::conn::http1::handshake(stream).await.unwrap();
tokio::spawn(conn);
let request = Request::builder().body(Body::empty()).unwrap();
let response = sender.send_request(request).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body = Body::new(response.into_body());
let body = to_bytes(body, usize::MAX).await.unwrap();
let body = String::from_utf8(body.to_vec()).unwrap();
assert_eq!(body, "Hello, World!");
}
}
+59
View File
@@ -0,0 +1,59 @@
use crate::error_handling::HandleError;
#[cfg(feature = "tokio")]
use crate::extract::connect_info::IntoMakeServiceWithConnectInfo;
use crate::routing::IntoMakeService;
use tower_service::Service;
/// Extension trait that adds additional methods to any [`Service`].
pub trait ServiceExt<R>: Service<R> + Sized {
/// Convert this service into a [`MakeService`], that is a [`Service`] whose
/// response is another service.
///
/// This is commonly used when applying middleware around an entire [`Router`]. See ["Rewriting
/// request URI in middleware"] for more details.
///
/// [`MakeService`]: tower::make::MakeService
/// ["Rewriting request URI in middleware"]: crate::middleware#rewriting-request-uri-in-middleware
/// [`Router`]: crate::Router
fn into_make_service(self) -> IntoMakeService<Self>;
/// Convert this service into a [`MakeService`], that will store `C`'s
/// associated `ConnectInfo` in a request extension such that [`ConnectInfo`]
/// can extract it.
///
/// This enables extracting things like the client's remote address.
/// This is commonly used when applying middleware around an entire [`Router`]. See ["Rewriting
/// request URI in middleware"] for more details.
///
/// [`MakeService`]: tower::make::MakeService
/// ["Rewriting request URI in middleware"]: crate::middleware#rewriting-request-uri-in-middleware
/// [`Router`]: crate::Router
/// [`ConnectInfo`]: crate::extract::connect_info::ConnectInfo
#[cfg(feature = "tokio")]
fn into_make_service_with_connect_info<C>(self) -> IntoMakeServiceWithConnectInfo<Self, C>;
/// Convert this service into a [`HandleError`], that will handle errors
/// by converting them into responses.
///
/// See ["error handling model"] for more details.
///
/// [`HandleError`]: crate::error_handling::HandleError
/// ["error handling model"]: crate::error_handling#axums-error-handling-model
fn handle_error<F, T>(self, f: F) -> HandleError<Self, F, T> {
HandleError::new(self, f)
}
}
impl<S, R> ServiceExt<R> for S
where
S: Service<R> + Sized,
{
fn into_make_service(self) -> IntoMakeService<Self> {
IntoMakeService::new(self)
}
#[cfg(feature = "tokio")]
fn into_make_service_with_connect_info<C>(self) -> IntoMakeServiceWithConnectInfo<Self, C> {
IntoMakeServiceWithConnectInfo::new(self)
}
}
@@ -0,0 +1,52 @@
use std::sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
Arc,
};
pub(crate) struct CountingCloneableState {
state: Arc<InnerState>,
}
struct InnerState {
setup_done: AtomicBool,
count: AtomicUsize,
}
impl CountingCloneableState {
pub(crate) fn new() -> Self {
let inner_state = InnerState {
setup_done: AtomicBool::new(false),
count: AtomicUsize::new(0),
};
CountingCloneableState {
state: Arc::new(inner_state),
}
}
pub(crate) fn setup_done(&self) {
self.state.setup_done.store(true, Ordering::SeqCst);
}
pub(crate) fn count(&self) -> usize {
self.state.count.load(Ordering::SeqCst)
}
}
impl Clone for CountingCloneableState {
fn clone(&self) -> Self {
let state = self.state.clone();
if state.setup_done.load(Ordering::SeqCst) {
let bt = std::backtrace::Backtrace::force_capture();
let bt = bt
.to_string()
.lines()
.filter(|line| line.contains("axum") || line.contains("./src"))
.collect::<Vec<_>>()
.join("\n");
println!("AppState::Clone:\n===============\n{bt}\n");
state.count.fetch_add(1, Ordering::SeqCst);
}
CountingCloneableState { state }
}
}
+20
View File
@@ -0,0 +1,20 @@
#![allow(clippy::disallowed_names)]
use crate::{extract::Request, response::Response, serve};
mod test_client;
pub use self::test_client::*;
#[cfg(test)]
pub(crate) mod tracing_helpers;
#[cfg(test)]
pub(crate) mod counting_cloneable_state;
#[cfg(test)]
pub(crate) fn assert_send<T: Send>() {}
#[cfg(test)]
pub(crate) fn assert_sync<T: Sync>() {}
#[allow(dead_code)]
pub(crate) struct NotSendSync(*const ());
@@ -0,0 +1,181 @@
use super::{serve, Request, Response};
use bytes::Bytes;
use futures_util::future::BoxFuture;
use http::header::{HeaderName, HeaderValue};
use std::ops::Deref;
use std::{convert::Infallible, future::IntoFuture, net::SocketAddr};
use tokio::net::TcpListener;
use tower::make::Shared;
use tower_service::Service;
pub(crate) fn spawn_service<S>(svc: S) -> SocketAddr
where
S: Service<Request, Response = Response, Error = Infallible> + Clone + Send + 'static,
S::Future: Send,
{
let std_listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
std_listener.set_nonblocking(true).unwrap();
let listener = TcpListener::from_std(std_listener).unwrap();
let addr = listener.local_addr().unwrap();
println!("Listening on {addr}");
tokio::spawn(async move {
serve(listener, Shared::new(svc))
.await
.expect("server error")
});
addr
}
pub struct TestClient {
client: reqwest::Client,
addr: SocketAddr,
}
impl TestClient {
pub fn new<S>(svc: S) -> Self
where
S: Service<Request, Response = Response, Error = Infallible> + Clone + Send + 'static,
S::Future: Send,
{
let addr = spawn_service(svc);
let client = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.build()
.unwrap();
TestClient { client, addr }
}
pub fn get(&self, url: &str) -> RequestBuilder {
RequestBuilder {
builder: self.client.get(format!("http://{}{url}", self.addr)),
}
}
pub fn head(&self, url: &str) -> RequestBuilder {
RequestBuilder {
builder: self.client.head(format!("http://{}{url}", self.addr)),
}
}
pub fn post(&self, url: &str) -> RequestBuilder {
RequestBuilder {
builder: self.client.post(format!("http://{}{url}", self.addr)),
}
}
#[allow(dead_code)]
pub fn put(&self, url: &str) -> RequestBuilder {
RequestBuilder {
builder: self.client.put(format!("http://{}{url}", self.addr)),
}
}
#[allow(dead_code)]
pub fn patch(&self, url: &str) -> RequestBuilder {
RequestBuilder {
builder: self.client.patch(format!("http://{}{url}", self.addr)),
}
}
#[allow(dead_code)]
#[must_use]
pub fn server_port(&self) -> u16 {
self.addr.port()
}
}
#[must_use]
pub struct RequestBuilder {
builder: reqwest::RequestBuilder,
}
impl RequestBuilder {
pub fn body(mut self, body: impl Into<reqwest::Body>) -> Self {
self.builder = self.builder.body(body);
self
}
pub fn json<T>(mut self, json: &T) -> Self
where
T: serde_core::Serialize,
{
self.builder = self.builder.json(json);
self
}
pub fn header<K, V>(mut self, key: K, value: V) -> Self
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
{
self.builder = self.builder.header(key, value);
self
}
#[allow(dead_code)]
pub fn multipart(mut self, form: reqwest::multipart::Form) -> Self {
self.builder = self.builder.multipart(form);
self
}
}
impl IntoFuture for RequestBuilder {
type Output = TestResponse;
type IntoFuture = BoxFuture<'static, Self::Output>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async {
TestResponse {
response: self.builder.send().await.unwrap(),
}
})
}
}
#[derive(Debug)]
pub struct TestResponse {
response: reqwest::Response,
}
impl Deref for TestResponse {
type Target = reqwest::Response;
fn deref(&self) -> &Self::Target {
&self.response
}
}
impl TestResponse {
#[allow(dead_code)]
pub async fn bytes(self) -> Bytes {
self.response.bytes().await.unwrap()
}
pub async fn text(self) -> String {
self.response.text().await.unwrap()
}
#[allow(dead_code)]
pub async fn json<T>(self) -> T
where
T: serde_core::de::DeserializeOwned,
{
self.response.json().await.unwrap()
}
pub async fn chunk(&mut self) -> Option<Bytes> {
self.response.chunk().await.unwrap()
}
pub async fn chunk_text(&mut self) -> Option<String> {
let chunk = self.chunk().await?;
Some(String::from_utf8(chunk.to_vec()).unwrap())
}
}
@@ -0,0 +1,141 @@
use std::{
future::{Future, IntoFuture},
io,
marker::PhantomData,
pin::Pin,
sync::{Arc, Mutex},
};
use serde::{de::DeserializeOwned, Deserialize};
use tracing::instrument::WithSubscriber;
use tracing_subscriber::prelude::*;
use tracing_subscriber::{filter::Targets, fmt::MakeWriter};
#[derive(Deserialize, Eq, PartialEq, Debug)]
#[serde(deny_unknown_fields)]
pub(crate) struct TracingEvent<T> {
pub(crate) fields: T,
pub(crate) target: String,
pub(crate) level: String,
}
/// Run an async closure and capture the tracing output it produces.
pub(crate) fn capture_tracing<T, F>(f: F) -> CaptureTracing<T, F>
where
T: DeserializeOwned,
{
CaptureTracing {
f,
filter: None,
_phantom: PhantomData,
}
}
pub(crate) struct CaptureTracing<T, F> {
f: F,
filter: Option<Targets>,
_phantom: PhantomData<fn() -> T>,
}
impl<T, F> CaptureTracing<T, F> {
pub(crate) fn with_filter(mut self, filter_string: &str) -> Self {
self.filter = Some(filter_string.parse().unwrap());
self
}
}
impl<T, F, Fut> IntoFuture for CaptureTracing<T, F>
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: Future + Send,
T: DeserializeOwned,
{
type Output = Vec<TracingEvent<T>>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
let Self { f, filter, .. } = self;
Box::pin(async move {
let (make_writer, handle) = TestMakeWriter::new();
let filter = filter.unwrap_or_else(|| "axum=trace".parse().unwrap());
let subscriber = tracing_subscriber::registry().with(
tracing_subscriber::fmt::layer()
.with_writer(make_writer)
.with_target(true)
.without_time()
.with_ansi(false)
.json()
.flatten_event(false)
.with_filter(filter),
);
let guard = tracing::subscriber::set_default(subscriber);
f().with_current_subscriber().await;
drop(guard);
handle
.take()
.lines()
.map(|line| serde_json::from_str(line).unwrap())
.collect()
})
}
}
struct TestMakeWriter {
write: Arc<Mutex<Option<Vec<u8>>>>,
}
impl TestMakeWriter {
fn new() -> (Self, Handle) {
let write = Arc::new(Mutex::new(Some(Vec::<u8>::new())));
(
Self {
write: write.clone(),
},
Handle { write },
)
}
}
impl<'a> MakeWriter<'a> for TestMakeWriter {
type Writer = Writer<'a>;
fn make_writer(&'a self) -> Self::Writer {
Writer(self)
}
}
struct Writer<'a>(&'a TestMakeWriter);
impl io::Write for Writer<'_> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match &mut *self.0.write.lock().unwrap() {
Some(vec) => {
let len = buf.len();
vec.extend(buf);
Ok(len)
}
None => Err(io::Error::other("inner writer has been taken")),
}
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
struct Handle {
write: Arc<Mutex<Option<Vec<u8>>>>,
}
impl Handle {
fn take(self) -> String {
let vec = self.write.lock().unwrap().take().unwrap();
String::from_utf8(vec).unwrap()
}
}
+116
View File
@@ -0,0 +1,116 @@
use axum_core::response::{IntoResponse, Response};
use pin_project_lite::pin_project;
use std::{
future::Future,
ops::Deref,
pin::Pin,
sync::Arc,
task::{ready, Context, Poll},
};
use tower::Service;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) struct PercentDecodedStr(Arc<str>);
impl PercentDecodedStr {
pub(crate) fn new<S>(s: S) -> Option<Self>
where
S: AsRef<str>,
{
percent_encoding::percent_decode(s.as_ref().as_bytes())
.decode_utf8()
.ok()
.map(|decoded| Self(decoded.as_ref().into()))
}
pub(crate) fn as_str(&self) -> &str {
&self.0
}
}
impl Deref for PercentDecodedStr {
type Target = str;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
pin_project! {
#[project = EitherProj]
pub(crate) enum Either<A, B> {
A { #[pin] inner: A },
B { #[pin] inner: B },
}
}
#[derive(Clone)]
pub(crate) struct MapIntoResponse<S> {
inner: S,
}
impl<S> MapIntoResponse<S> {
pub(crate) fn new(inner: S) -> Self {
Self { inner }
}
}
impl<B, S> Service<http::Request<B>> for MapIntoResponse<S>
where
S: Service<http::Request<B>>,
S::Response: IntoResponse,
{
type Response = Response;
type Error = S::Error;
type Future = MapIntoResponseFuture<S::Future>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
MapIntoResponseFuture {
inner: self.inner.call(req),
}
}
}
pin_project! {
pub(crate) struct MapIntoResponseFuture<F> {
#[pin]
inner: F,
}
}
impl<F, T, E> Future for MapIntoResponseFuture<F>
where
F: Future<Output = Result<T, E>>,
T: IntoResponse,
{
type Output = Result<Response, E>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let res = ready!(self.project().inner.poll(cx)?);
Poll::Ready(Ok(res.into_response()))
}
}
pub(crate) fn try_downcast<T, K>(k: K) -> Result<T, K>
where
T: 'static,
K: Send + 'static,
{
let mut k = Some(k);
if let Some(k) = <dyn std::any::Any>::downcast_mut::<Option<T>>(&mut k) {
Ok(k.take().unwrap())
} else {
Err(k.unwrap())
}
}
#[test]
fn test_try_downcast() {
assert_eq!(try_downcast::<i32, _>(5_u32), Err(5_u32));
assert_eq!(try_downcast::<i32, _>(5_i32), Ok(5_i32));
}
+41
View File
@@ -0,0 +1,41 @@
//! Separate test binary, because the panic hook is a global resource
use std::{
panic::{catch_unwind, set_hook, take_hook},
path::Path,
sync::OnceLock,
};
use axum::{routing::get, Router};
#[test]
fn routes_with_overlapping_method_routes() {
static PANIC_LOCATION_FILE: OnceLock<String> = OnceLock::new();
let default_hook = take_hook();
set_hook(Box::new(|panic_info| {
if let Some(location) = panic_info.location() {
_ = PANIC_LOCATION_FILE.set(location.file().to_owned());
}
}));
let result = catch_unwind(|| {
async fn handler() {}
let _: Router = Router::new()
.route("/foo/bar", get(handler))
.route("/foo/bar", get(handler));
});
set_hook(default_hook);
let panic_payload = result.unwrap_err();
let panic_msg = panic_payload.downcast_ref::<String>().unwrap();
assert_eq!(
panic_msg,
"Overlapping method route. Handler for `GET /foo/bar` already exists"
);
let file = PANIC_LOCATION_FILE.get().unwrap();
assert_eq!(Path::new(file).file_name().unwrap(), "panic_location.rs");
}