mirror of
https://github.com/tokio-rs/axum.git
synced 2024-12-13 18:30:42 +01:00
Merge branch 'main' into main
This commit is contained in:
commit
2077c89b2b
64 changed files with 309 additions and 245 deletions
|
@ -28,7 +28,6 @@ If your project isn't listed here and you would like it to be, please feel free
|
||||||
- [aide](https://docs.rs/aide): Code-first Open API documentation generator with [axum integration](https://docs.rs/aide/latest/aide/axum/index.html).
|
- [aide](https://docs.rs/aide): Code-first Open API documentation generator with [axum integration](https://docs.rs/aide/latest/aide/axum/index.html).
|
||||||
- [axum-typed-routing](https://docs.rs/axum-typed-routing/latest/axum_typed_routing/): Statically typed routing macros with OpenAPI generation using aide.
|
- [axum-typed-routing](https://docs.rs/axum-typed-routing/latest/axum_typed_routing/): Statically typed routing macros with OpenAPI generation using aide.
|
||||||
- [axum-jsonschema](https://docs.rs/axum-jsonschema/): A `Json<T>` extractor that does JSON schema validation of requests.
|
- [axum-jsonschema](https://docs.rs/axum-jsonschema/): A `Json<T>` extractor that does JSON schema validation of requests.
|
||||||
- [tower-sessions](https://docs.rs/tower-sessions): Cookie-based sessions.
|
|
||||||
- [axum-login](https://docs.rs/axum-login): Session-based user authentication for axum.
|
- [axum-login](https://docs.rs/axum-login): Session-based user authentication for axum.
|
||||||
- [axum-csrf-sync-pattern](https://crates.io/crates/axum-csrf-sync-pattern): A middleware implementing CSRF STP for AJAX backends and API endpoints.
|
- [axum-csrf-sync-pattern](https://crates.io/crates/axum-csrf-sync-pattern): A middleware implementing CSRF STP for AJAX backends and API endpoints.
|
||||||
- [axum-otel-metrics](https://github.com/ttys3/axum-otel-metrics/): A axum OpenTelemetry Metrics middleware with prometheus exporter supported.
|
- [axum-otel-metrics](https://github.com/ttys3/axum-otel-metrics/): A axum OpenTelemetry Metrics middleware with prometheus exporter supported.
|
||||||
|
@ -105,6 +104,7 @@ If your project isn't listed here and you would like it to be, please feel free
|
||||||
- [Introduction to axum]: YouTube playlist
|
- [Introduction to axum]: YouTube playlist
|
||||||
- [Rust Axum Full Course]: YouTube video
|
- [Rust Axum Full Course]: YouTube video
|
||||||
- [Deploying Axum projects with Shuttle]
|
- [Deploying Axum projects with Shuttle]
|
||||||
|
- [API Development with Rust](https://rust-api.dev/docs/front-matter/preface/): REST APIs based on Axum
|
||||||
|
|
||||||
[axum-tutorial]: https://github.com/programatik29/axum-tutorial
|
[axum-tutorial]: https://github.com/programatik29/axum-tutorial
|
||||||
[axum-tutorial-website]: https://programatik29.github.io/axum-tutorial/
|
[axum-tutorial-website]: https://programatik29.github.io/axum-tutorial/
|
||||||
|
|
|
@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
# 0.4.5
|
||||||
|
|
||||||
|
- **fixed:** Compile errors from the internal `__log_rejection` macro under
|
||||||
|
certain Cargo feature combinations between axum crates ([#2933])
|
||||||
|
|
||||||
|
[#2933]: https://github.com/tokio-rs/axum/pull/2933
|
||||||
|
|
||||||
# 0.4.4
|
# 0.4.4
|
||||||
|
|
||||||
- **added:** Derive `Clone` and `Copy` for `AppendHeaders` ([#2776])
|
- **added:** Derive `Clone` and `Copy` for `AppendHeaders` ([#2776])
|
||||||
|
|
|
@ -9,7 +9,7 @@ license = "MIT"
|
||||||
name = "axum-core"
|
name = "axum-core"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/tokio-rs/axum"
|
repository = "https://github.com/tokio-rs/axum"
|
||||||
version = "0.4.4" # remember to also bump the version that axum and axum-extra depend on
|
version = "0.4.5" # remember to also bump the version that axum and axum-extra depend on
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
tracing = ["dep:tracing"]
|
tracing = ["dep:tracing"]
|
||||||
|
@ -54,6 +54,9 @@ allowed = [
|
||||||
"http_body",
|
"http_body",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[package.metadata.cargo-machete]
|
||||||
|
ignored = ["tower-http"] # See __private_docs feature
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
|
@ -50,6 +50,11 @@
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub(crate) mod macros;
|
pub(crate) mod macros;
|
||||||
|
#[doc(hidden)] // macro helpers
|
||||||
|
pub mod __private {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
pub use tracing;
|
||||||
|
}
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
mod ext_traits;
|
mod ext_traits;
|
||||||
|
|
|
@ -9,12 +9,12 @@ macro_rules! __log_rejection {
|
||||||
status = $status:expr,
|
status = $status:expr,
|
||||||
) => {
|
) => {
|
||||||
{
|
{
|
||||||
tracing::event!(
|
$crate::__private::tracing::event!(
|
||||||
target: "axum::rejection",
|
target: "axum::rejection",
|
||||||
tracing::Level::TRACE,
|
$crate::__private::tracing::Level::TRACE,
|
||||||
status = $status.as_u16(),
|
status = $status.as_u16(),
|
||||||
body = $body_text,
|
body = $body_text,
|
||||||
rejection_type = std::any::type_name::<$ty>(),
|
rejection_type = ::std::any::type_name::<$ty>(),
|
||||||
"rejecting request",
|
"rejecting request",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,13 +34,13 @@ json-lines = [
|
||||||
multipart = ["dep:multer"]
|
multipart = ["dep:multer"]
|
||||||
protobuf = ["dep:prost"]
|
protobuf = ["dep:prost"]
|
||||||
query = ["dep:serde_html_form"]
|
query = ["dep:serde_html_form"]
|
||||||
tracing = ["dep:tracing", "axum-core/tracing", "axum/tracing"]
|
tracing = ["axum-core/tracing", "axum/tracing"]
|
||||||
typed-header = ["dep:headers"]
|
typed-header = ["dep:headers"]
|
||||||
typed-routing = ["dep:axum-macros", "dep:percent-encoding", "dep:serde_html_form", "dep:form_urlencoded"]
|
typed-routing = ["dep:axum-macros", "dep:percent-encoding", "dep:serde_html_form", "dep:form_urlencoded"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../axum", version = "0.7.6", default-features = false }
|
axum = { path = "../axum", version = "0.7.7", default-features = false }
|
||||||
axum-core = { path = "../axum-core", version = "0.4.4" }
|
axum-core = { path = "../axum-core", version = "0.4.5" }
|
||||||
bytes = "1.1.0"
|
bytes = "1.1.0"
|
||||||
futures-util = { version = "0.3", default-features = false, features = ["alloc"] }
|
futures-util = { version = "0.3", default-features = false, features = ["alloc"] }
|
||||||
http = "1.0.0"
|
http = "1.0.0"
|
||||||
|
|
|
@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
# Unreleased
|
||||||
|
|
||||||
|
- **breaking:** The tuple and tuple_struct `Path` extractor deserializers now check that the number of parameters matches the tuple length exactly ([#2931])
|
||||||
|
|
||||||
|
[#2931]: https://github.com/tokio-rs/axum/pull/2931
|
||||||
|
|
||||||
|
# 0.7.7
|
||||||
|
|
||||||
|
- **change**: Remove manual tables of content from the documentation, since
|
||||||
|
rustdoc now generates tables of content in the sidebar ([#2921])
|
||||||
|
|
||||||
|
[#2921]: https://github.com/tokio-rs/axum/pull/2921
|
||||||
|
|
||||||
# 0.7.6
|
# 0.7.6
|
||||||
|
|
||||||
- **change:** Avoid cloning `Arc` during deserialization of `Path`
|
- **change:** Avoid cloning `Arc` during deserialization of `Path`
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "axum"
|
name = "axum"
|
||||||
version = "0.7.6"
|
version = "0.7.7"
|
||||||
categories = ["asynchronous", "network-programming", "web-programming::http-server"]
|
categories = ["asynchronous", "network-programming", "web-programming::http-server"]
|
||||||
description = "Web framework that focuses on ergonomics and modularity"
|
description = "Web framework that focuses on ergonomics and modularity"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
@ -41,7 +41,7 @@ ws = ["dep:hyper", "tokio", "dep:tokio-tungstenite", "dep:sha1", "dep:base64"]
|
||||||
__private_docs = ["tower/full", "dep:tower-http"]
|
__private_docs = ["tower/full", "dep:tower-http"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum-core = { path = "../axum-core", version = "0.4.4" }
|
axum-core = { path = "../axum-core", version = "0.4.5" }
|
||||||
bytes = "1.0"
|
bytes = "1.0"
|
||||||
futures-util = { version = "0.3", default-features = false, features = ["alloc"] }
|
futures-util = { version = "0.3", default-features = false, features = ["alloc"] }
|
||||||
http = "1.0.0"
|
http = "1.0.0"
|
||||||
|
@ -62,7 +62,7 @@ tower-service = "0.3"
|
||||||
|
|
||||||
# optional dependencies
|
# optional dependencies
|
||||||
axum-macros = { path = "../axum-macros", version = "0.4.2", optional = true }
|
axum-macros = { path = "../axum-macros", version = "0.4.2", optional = true }
|
||||||
base64 = { version = "0.21.0", optional = true }
|
base64 = { version = "0.22.1", optional = true }
|
||||||
hyper = { 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 }
|
hyper-util = { version = "0.1.3", features = ["tokio", "server", "service"], optional = true }
|
||||||
multer = { version = "3.0.0", optional = true }
|
multer = { version = "3.0.0", optional = true }
|
||||||
|
@ -71,7 +71,7 @@ serde_path_to_error = { version = "0.1.8", optional = true }
|
||||||
serde_urlencoded = { version = "0.7", optional = true }
|
serde_urlencoded = { version = "0.7", optional = true }
|
||||||
sha1 = { version = "0.10", optional = true }
|
sha1 = { version = "0.10", optional = true }
|
||||||
tokio = { package = "tokio", version = "1.25.0", features = ["time"], optional = true }
|
tokio = { package = "tokio", version = "1.25.0", features = ["time"], optional = true }
|
||||||
tokio-tungstenite = { version = "0.23", optional = true }
|
tokio-tungstenite = { version = "0.24.0", optional = true }
|
||||||
tracing = { version = "0.1", default-features = false, optional = true }
|
tracing = { version = "0.1", default-features = false, optional = true }
|
||||||
|
|
||||||
[dependencies.tower-http]
|
[dependencies.tower-http]
|
||||||
|
@ -120,7 +120,7 @@ serde_json = "1.0"
|
||||||
time = { version = "0.3", features = ["serde-human-readable"] }
|
time = { version = "0.3", features = ["serde-human-readable"] }
|
||||||
tokio = { package = "tokio", version = "1.25.0", features = ["macros", "rt", "rt-multi-thread", "net", "test-util"] }
|
tokio = { package = "tokio", version = "1.25.0", features = ["macros", "rt", "rt-multi-thread", "net", "test-util"] }
|
||||||
tokio-stream = "0.1"
|
tokio-stream = "0.1"
|
||||||
tokio-tungstenite = "0.23"
|
tokio-tungstenite = "0.24.0"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["json"] }
|
tracing-subscriber = { version = "0.3", features = ["json"] }
|
||||||
uuid = { version = "1.0", features = ["serde", "v4"] }
|
uuid = { version = "1.0", features = ["serde", "v4"] }
|
||||||
|
|
|
@ -1,12 +1,5 @@
|
||||||
Error handling model and utilities
|
Error handling model and utilities
|
||||||
|
|
||||||
# Table of contents
|
|
||||||
|
|
||||||
- [axum's error handling model](#axums-error-handling-model)
|
|
||||||
- [Routing to fallible services](#routing-to-fallible-services)
|
|
||||||
- [Applying fallible middleware](#applying-fallible-middleware)
|
|
||||||
- [Running extractors for error handling](#running-extractors-for-error-handling)
|
|
||||||
|
|
||||||
# axum's error handling model
|
# axum's error handling model
|
||||||
|
|
||||||
axum is based on [`tower::Service`] which bundles errors through its associated
|
axum is based on [`tower::Service`] which bundles errors through its associated
|
||||||
|
|
|
@ -1,20 +1,5 @@
|
||||||
Types and traits for extracting data from requests.
|
Types and traits for extracting data from requests.
|
||||||
|
|
||||||
# Table of contents
|
|
||||||
|
|
||||||
- [Intro](#intro)
|
|
||||||
- [Common extractors](#common-extractors)
|
|
||||||
- [Applying multiple extractors](#applying-multiple-extractors)
|
|
||||||
- [The order of extractors](#the-order-of-extractors)
|
|
||||||
- [Optional extractors](#optional-extractors)
|
|
||||||
- [Customizing extractor responses](#customizing-extractor-responses)
|
|
||||||
- [Accessing inner errors](#accessing-inner-errors)
|
|
||||||
- [Defining custom extractors](#defining-custom-extractors)
|
|
||||||
- [Accessing other extractors in `FromRequest` or `FromRequestParts` implementations](#accessing-other-extractors-in-fromrequest-or-fromrequestparts-implementations)
|
|
||||||
- [Request body limits](#request-body-limits)
|
|
||||||
- [Wrapping extractors](#wrapping-extractors)
|
|
||||||
- [Logging rejections](#logging-rejections)
|
|
||||||
|
|
||||||
# Intro
|
# Intro
|
||||||
|
|
||||||
A handler function is an async function that takes any number of
|
A handler function is an async function that takes any number of
|
||||||
|
|
|
@ -1,15 +1,3 @@
|
||||||
# Table of contents
|
|
||||||
|
|
||||||
- [Intro](#intro)
|
|
||||||
- [Applying middleware](#applying-middleware)
|
|
||||||
- [Commonly used middleware](#commonly-used-middleware)
|
|
||||||
- [Ordering](#ordering)
|
|
||||||
- [Writing middleware](#writing-middleware)
|
|
||||||
- [Routing to services/middleware and backpressure](#routing-to-servicesmiddleware-and-backpressure)
|
|
||||||
- [Accessing state in middleware](#accessing-state-in-middleware)
|
|
||||||
- [Passing state from middleware to handlers](#passing-state-from-middleware-to-handlers)
|
|
||||||
- [Rewriting request URI in middleware](#rewriting-request-uri-in-middleware)
|
|
||||||
|
|
||||||
# Intro
|
# Intro
|
||||||
|
|
||||||
axum is unique in that it doesn't have its own bespoke middleware system and
|
axum is unique in that it doesn't have its own bespoke middleware system and
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
Types and traits for generating responses.
|
Types and traits for generating responses.
|
||||||
|
|
||||||
# Table of contents
|
|
||||||
|
|
||||||
- [Building responses](#building-responses)
|
|
||||||
- [Returning different response types](#returning-different-response-types)
|
|
||||||
- [Regarding `impl IntoResponse`](#regarding-impl-intoresponse)
|
|
||||||
|
|
||||||
# Building responses
|
# Building responses
|
||||||
|
|
||||||
Anything that implements [`IntoResponse`] can be returned from a handler. axum
|
Anything that implements [`IntoResponse`] can be returned from a handler. axum
|
||||||
|
|
|
@ -23,7 +23,11 @@ async fn fallback(uri: Uri) -> (StatusCode, String) {
|
||||||
|
|
||||||
Fallbacks only apply to routes that aren't matched by anything in the
|
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
|
router. If a handler is matched by a request but returns 404 the
|
||||||
fallback is not called.
|
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
|
# Handling all requests without other routes
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,11 @@ let app = Router::new()
|
||||||
# let _: Router = app;
|
# 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
|
# Fallbacks
|
||||||
|
|
||||||
If a nested router doesn't have its own fallback then it will inherit the
|
If a nested router doesn't have its own fallback then it will inherit the
|
||||||
|
|
|
@ -140,7 +140,7 @@ impl<'de> Deserializer<'de> for PathDeserializer<'de> {
|
||||||
where
|
where
|
||||||
V: Visitor<'de>,
|
V: Visitor<'de>,
|
||||||
{
|
{
|
||||||
if self.url_params.len() < len {
|
if self.url_params.len() != len {
|
||||||
return Err(PathDeserializationError::wrong_number_of_parameters()
|
return Err(PathDeserializationError::wrong_number_of_parameters()
|
||||||
.got(self.url_params.len())
|
.got(self.url_params.len())
|
||||||
.expected(len));
|
.expected(len));
|
||||||
|
@ -160,7 +160,7 @@ impl<'de> Deserializer<'de> for PathDeserializer<'de> {
|
||||||
where
|
where
|
||||||
V: Visitor<'de>,
|
V: Visitor<'de>,
|
||||||
{
|
{
|
||||||
if self.url_params.len() < len {
|
if self.url_params.len() != len {
|
||||||
return Err(PathDeserializationError::wrong_number_of_parameters()
|
return Err(PathDeserializationError::wrong_number_of_parameters()
|
||||||
.got(self.url_params.len())
|
.got(self.url_params.len())
|
||||||
.expected(len));
|
.expected(len));
|
||||||
|
@ -773,20 +773,6 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_tuple_ignoring_additional_fields() {
|
|
||||||
let url_params = create_url_params(vec![
|
|
||||||
("a", "abc"),
|
|
||||||
("b", "true"),
|
|
||||||
("c", "1"),
|
|
||||||
("d", "false"),
|
|
||||||
]);
|
|
||||||
assert_eq!(
|
|
||||||
<(&str, bool, u32)>::deserialize(PathDeserializer::new(&url_params)).unwrap(),
|
|
||||||
("abc", true, 1)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_map() {
|
fn test_parse_map() {
|
||||||
let url_params = create_url_params(vec![("a", "1"), ("b", "true"), ("c", "abc")]);
|
let url_params = create_url_params(vec![("a", "1"), ("b", "true"), ("c", "abc")]);
|
||||||
|
@ -813,6 +799,18 @@ mod tests {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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]
|
#[test]
|
||||||
fn test_wrong_number_of_parameters_error() {
|
fn test_wrong_number_of_parameters_error() {
|
||||||
test_parse_error!(
|
test_parse_error!(
|
||||||
|
|
|
@ -744,6 +744,33 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[crate::test]
|
||||||
|
async fn tuple_param_matches_exactly() {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Tuple(String, String);
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/foo/:a/:b/:c", get(|_: Path<(String, String)>| async {}))
|
||||||
|
.route("/bar/:a/:b/:c", get(|_: Path<Tuple>| async {}));
|
||||||
|
|
||||||
|
let client = TestClient::new(app);
|
||||||
|
|
||||||
|
let res = client.get("/foo/a/b/c").await;
|
||||||
|
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
assert_eq!(
|
||||||
|
res.text().await,
|
||||||
|
"Wrong number of path arguments for `Path`. Expected 2 but got 3",
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = client.get("/bar/a/b/c").await;
|
||||||
|
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
assert_eq!(
|
||||||
|
res.text().await,
|
||||||
|
"Wrong number of path arguments for `Path`. Expected 2 but got 3",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[crate::test]
|
#[crate::test]
|
||||||
async fn deserialize_into_vec_of_tuples() {
|
async fn deserialize_into_vec_of_tuples() {
|
||||||
let app = Router::new().route(
|
let app = Router::new().route(
|
||||||
|
|
|
@ -1,22 +1,5 @@
|
||||||
//! axum is a web application framework that focuses on ergonomics and modularity.
|
//! axum is a web application framework that focuses on ergonomics and modularity.
|
||||||
//!
|
//!
|
||||||
//! # Table of contents
|
|
||||||
//!
|
|
||||||
//! - [High-level features](#high-level-features)
|
|
||||||
//! - [Compatibility](#compatibility)
|
|
||||||
//! - [Example](#example)
|
|
||||||
//! - [Routing](#routing)
|
|
||||||
//! - [Handlers](#handlers)
|
|
||||||
//! - [Extractors](#extractors)
|
|
||||||
//! - [Responses](#responses)
|
|
||||||
//! - [Error handling](#error-handling)
|
|
||||||
//! - [Middleware](#middleware)
|
|
||||||
//! - [Sharing state with handlers](#sharing-state-with-handlers)
|
|
||||||
//! - [Building integrations for axum](#building-integrations-for-axum)
|
|
||||||
//! - [Required dependencies](#required-dependencies)
|
|
||||||
//! - [Examples](#examples)
|
|
||||||
//! - [Feature flags](#feature-flags)
|
|
||||||
//!
|
|
||||||
//! # High-level features
|
//! # High-level features
|
||||||
//!
|
//!
|
||||||
//! - Route requests to handlers with a macro-free API.
|
//! - Route requests to handlers with a macro-free API.
|
||||||
|
|
|
@ -966,7 +966,7 @@ async fn logging_rejections() {
|
||||||
rejection_type: String,
|
rejection_type: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
let events = capture_tracing::<RejectionEvent, _, _>(|| async {
|
let events = capture_tracing::<RejectionEvent, _>(|| async {
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/extension", get(|_: Extension<Infallible>| async {}))
|
.route("/extension", get(|_: Extension<Infallible>| async {}))
|
||||||
.route("/string", post(|_: String| async {}));
|
.route("/string", post(|_: String| async {}));
|
||||||
|
@ -987,6 +987,7 @@ async fn logging_rejections() {
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
.with_filter("axum::rejection=trace")
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -213,61 +213,8 @@ where
|
||||||
type IntoFuture = private::ServeFuture;
|
type IntoFuture = private::ServeFuture;
|
||||||
|
|
||||||
fn into_future(self) -> Self::IntoFuture {
|
fn into_future(self) -> Self::IntoFuture {
|
||||||
private::ServeFuture(Box::pin(async move {
|
self.with_graceful_shutdown(std::future::pending())
|
||||||
let Self {
|
.into_future()
|
||||||
tcp_listener,
|
|
||||||
mut make_service,
|
|
||||||
tcp_nodelay,
|
|
||||||
_marker: _,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let (tcp_stream, remote_addr) = match tcp_accept(&tcp_listener).await {
|
|
||||||
Some(conn) => conn,
|
|
||||||
None => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(nodelay) = tcp_nodelay {
|
|
||||||
if let Err(err) = tcp_stream.set_nodelay(nodelay) {
|
|
||||||
trace!("failed to set TCP_NODELAY on incoming connection: {err:#}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let tcp_stream = TokioIo::new(tcp_stream);
|
|
||||||
|
|
||||||
poll_fn(|cx| make_service.poll_ready(cx))
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(|err| match err {});
|
|
||||||
|
|
||||||
let tower_service = make_service
|
|
||||||
.call(IncomingStream {
|
|
||||||
tcp_stream: &tcp_stream,
|
|
||||||
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);
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
|
||||||
match Builder::new(TokioExecutor::new())
|
|
||||||
// upgrades needed for websockets
|
|
||||||
.serve_connection_with_upgrades(tcp_stream, hyper_service)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(()) => {}
|
|
||||||
Err(_err) => {
|
|
||||||
// This error only appears when the client doesn't send a request and
|
|
||||||
// terminate the connection.
|
|
||||||
//
|
|
||||||
// If client sends one request then terminate connection whenever, it doesn't
|
|
||||||
// appear.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
use crate::util::AxumMutex;
|
use crate::util::AxumMutex;
|
||||||
use std::{future::Future, io, sync::Arc};
|
use std::{
|
||||||
|
future::{Future, IntoFuture},
|
||||||
|
io,
|
||||||
|
marker::PhantomData,
|
||||||
|
pin::Pin,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
use serde::{de::DeserializeOwned, Deserialize};
|
use serde::{de::DeserializeOwned, Deserialize};
|
||||||
|
use tracing::instrument::WithSubscriber;
|
||||||
use tracing_subscriber::prelude::*;
|
use tracing_subscriber::prelude::*;
|
||||||
use tracing_subscriber::{filter::Targets, fmt::MakeWriter};
|
use tracing_subscriber::{filter::Targets, fmt::MakeWriter};
|
||||||
|
|
||||||
|
@ -14,36 +21,69 @@ pub(crate) struct TracingEvent<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run an async closure and capture the tracing output it produces.
|
/// Run an async closure and capture the tracing output it produces.
|
||||||
pub(crate) async fn capture_tracing<T, F, Fut>(f: F) -> Vec<TracingEvent<T>>
|
pub(crate) fn capture_tracing<T, F>(f: F) -> CaptureTracing<T, F>
|
||||||
where
|
where
|
||||||
F: Fn() -> Fut,
|
|
||||||
Fut: Future,
|
|
||||||
T: DeserializeOwned,
|
T: DeserializeOwned,
|
||||||
{
|
{
|
||||||
let (make_writer, handle) = TestMakeWriter::new();
|
CaptureTracing {
|
||||||
|
f,
|
||||||
|
filter: None,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let subscriber = tracing_subscriber::registry().with(
|
pub(crate) struct CaptureTracing<T, F> {
|
||||||
tracing_subscriber::fmt::layer()
|
f: F,
|
||||||
.with_writer(make_writer)
|
filter: Option<Targets>,
|
||||||
.with_target(true)
|
_phantom: PhantomData<fn() -> T>,
|
||||||
.without_time()
|
}
|
||||||
.with_ansi(false)
|
|
||||||
.json()
|
|
||||||
.flatten_event(false)
|
|
||||||
.with_filter("axum=trace".parse::<Targets>().unwrap()),
|
|
||||||
);
|
|
||||||
|
|
||||||
let guard = tracing::subscriber::set_default(subscriber);
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
f().await;
|
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>>;
|
||||||
|
|
||||||
drop(guard);
|
fn into_future(self) -> Self::IntoFuture {
|
||||||
|
let Self { f, filter, .. } = self;
|
||||||
|
Box::pin(async move {
|
||||||
|
let (make_writer, handle) = TestMakeWriter::new();
|
||||||
|
|
||||||
handle
|
let filter = filter.unwrap_or_else(|| "axum=trace".parse().unwrap());
|
||||||
.take()
|
let subscriber = tracing_subscriber::registry().with(
|
||||||
.lines()
|
tracing_subscriber::fmt::layer()
|
||||||
.map(|line| serde_json::from_str(line).unwrap())
|
.with_writer(make_writer)
|
||||||
.collect()
|
.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 {
|
struct TestMakeWriter {
|
||||||
|
|
|
@ -8,6 +8,5 @@ publish = false
|
||||||
axum = { path = "../../axum", features = ["ws"] }
|
axum = { path = "../../axum", features = ["ws"] }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tower = { version = "0.4", features = ["util"] }
|
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -6,17 +6,16 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
axum-extra = { path = "../../axum-extra", features = ["typed-header"] }
|
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
||||||
tower = "0.4"
|
tower = "0.5.1"
|
||||||
tower-http = { version = "0.5", features = ["compression-full", "decompression-full"] }
|
tower-http = { version = "0.6.1", features = ["compression-full", "decompression-full"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert-json-diff = "2.0"
|
assert-json-diff = "2.0"
|
||||||
brotli = "3.4"
|
brotli = "6.0"
|
||||||
flate2 = "1"
|
flate2 = "1"
|
||||||
http = "1"
|
http = "1"
|
||||||
zstd = "0.13"
|
zstd = "0.13"
|
||||||
|
|
|
@ -7,9 +7,6 @@ publish = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
http-body-util = "0.1.0"
|
http-body-util = "0.1.0"
|
||||||
hyper = "1.0.0"
|
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tower = "0.4"
|
|
||||||
tower-http = { version = "0.5.0", features = ["map-request-body", "util"] }
|
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -7,4 +7,4 @@ publish = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tower-http = { version = "0.5.0", features = ["cors"] }
|
tower-http = { version = "0.6.1", features = ["cors"] }
|
||||||
|
|
|
@ -7,7 +7,6 @@ publish = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -8,9 +8,8 @@ publish = false
|
||||||
axum = { path = "../../axum", features = ["macros"] }
|
axum = { path = "../../axum", features = ["macros"] }
|
||||||
bb8 = "0.8"
|
bb8 = "0.8"
|
||||||
diesel = "2"
|
diesel = "2"
|
||||||
diesel-async = { version = "0.3", features = ["postgres", "bb8"] }
|
diesel-async = { version = "0.5", features = ["postgres", "bb8"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1"
|
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -6,11 +6,10 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum", features = ["macros"] }
|
axum = { path = "../../axum", features = ["macros"] }
|
||||||
deadpool-diesel = { version = "0.4.1", features = ["postgres"] }
|
deadpool-diesel = { version = "0.6.1", features = ["postgres"] }
|
||||||
diesel = { version = "2", features = ["postgres"] }
|
diesel = { version = "2", features = ["postgres"] }
|
||||||
diesel_migrations = "2"
|
diesel_migrations = "2"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1"
|
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -8,6 +8,6 @@ publish = false
|
||||||
axum = { path = "../../axum", features = ["macros"] }
|
axum = { path = "../../axum", features = ["macros"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tower-http = { version = "0.5", features = ["trace"] }
|
tower-http = { version = "0.6.1", features = ["trace"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -7,6 +7,5 @@ publish = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tower = { version = "0.4", features = ["util"] }
|
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -6,10 +6,6 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum", features = ["tracing"] }
|
axum = { path = "../../axum", features = ["tracing"] }
|
||||||
hyper = { version = "1.0", features = [] }
|
|
||||||
hyper-util = { version = "0.1", features = ["tokio", "server-auto", "http1"] }
|
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tower = { version = "0.4", features = ["util"] }
|
tower-http = { version = "0.6.1", features = ["timeout", "trace"] }
|
||||||
tower-http = { version = "0.5", features = ["timeout", "trace"] }
|
|
||||||
tracing = "0.1"
|
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -11,4 +11,4 @@ tokio = { version = "1.0", features = ["full"] }
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
http-body-util = "0.1.0"
|
http-body-util = "0.1.0"
|
||||||
hyper = { version = "1.0.0", features = ["full"] }
|
hyper = { version = "1.0.0", features = ["full"] }
|
||||||
tower = { version = "0.4", features = ["util"] }
|
tower = { version = "0.5.1", features = ["util"] }
|
||||||
|
|
|
@ -9,6 +9,6 @@ axum = { path = "../../axum" }
|
||||||
hyper = { version = "1", features = ["full"] }
|
hyper = { version = "1", features = ["full"] }
|
||||||
hyper-util = "0.1.1"
|
hyper-util = "0.1.1"
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tower = { version = "0.4", features = ["make"] }
|
tower = { version = "0.5.1", features = ["make", "util"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -7,7 +7,7 @@ publish = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
axum-extra = { path = "../../axum-extra", features = ["typed-header"] }
|
axum-extra = { path = "../../axum-extra", features = ["typed-header"] }
|
||||||
jsonwebtoken = "8.0"
|
jsonwebtoken = "9.3"
|
||||||
once_cell = "1.8"
|
once_cell = "1.8"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|
|
@ -7,14 +7,13 @@ publish = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tower = { version = "0.4", features = ["util", "timeout", "load-shed", "limit"] }
|
tower = { version = "0.5.1", features = ["util", "timeout", "load-shed", "limit"] }
|
||||||
tower-http = { version = "0.5.0", features = [
|
tower-http = { version = "0.6.1", features = [
|
||||||
"add-extension",
|
"add-extension",
|
||||||
"auth",
|
"auth",
|
||||||
"compression-full",
|
"compression-full",
|
||||||
"limit",
|
"limit",
|
||||||
"trace",
|
"trace",
|
||||||
] }
|
] }
|
||||||
tower-layer = "0.3.2"
|
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "listen-multiple-addrs"
|
name = "example-listen-multiple-addrs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
@ -9,4 +9,4 @@ axum = { path = "../../axum" }
|
||||||
hyper = { version = "1.0.0", features = ["full"] }
|
hyper = { version = "1.0.0", features = ["full"] }
|
||||||
hyper-util = { version = "0.1", features = ["tokio", "server-auto", "http1"] }
|
hyper-util = { version = "0.1", features = ["tokio", "server-auto", "http1"] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tower = { version = "0.4", features = ["util"] }
|
tower = { version = "0.5.1", features = ["util"] }
|
||||||
|
|
|
@ -12,6 +12,6 @@ hyper-util = { version = "0.1" }
|
||||||
openssl = "0.10"
|
openssl = "0.10"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tokio-openssl = "0.6"
|
tokio-openssl = "0.6"
|
||||||
tower = { version = "0.4", features = ["make"] }
|
tower = { version = "0.5.1", features = ["make"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -12,7 +12,6 @@ hyper-util = { version = "0.1" }
|
||||||
rustls-pemfile = "1.0.4"
|
rustls-pemfile = "1.0.4"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tokio-rustls = "0.24.1"
|
tokio-rustls = "0.24.1"
|
||||||
tower = { version = "0.4", features = ["make"] }
|
|
||||||
tower-service = "0.3.2"
|
tower-service = "0.3.2"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -7,6 +7,6 @@ publish = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum", features = ["multipart"] }
|
axum = { path = "../../axum", features = ["multipart"] }
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tower-http = { version = "0.5.0", features = ["limit", "trace"] }
|
tower-http = { version = "0.6.1", features = ["limit", "trace"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -7,8 +7,6 @@ publish = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
http-body-util = "0.1.0"
|
http-body-util = "0.1.0"
|
||||||
hyper = { version = "1.0.0", features = ["full"] }
|
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tower = { version = "0.4", features = ["util", "filter"] }
|
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -6,8 +6,8 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
metrics = { version = "0.22", default-features = false }
|
metrics = { version = "0.23", default-features = false }
|
||||||
metrics-exporter-prometheus = { version = "0.13", default-features = false }
|
metrics-exporter-prometheus = { version = "0.15", default-features = false }
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -7,7 +7,6 @@ publish = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
http-body-util = "0.1.0"
|
http-body-util = "0.1.0"
|
||||||
hyper = "1.0.0"
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tower = { version = "0.4", features = ["util"] }
|
tower = { version = "0.5.1", features = ["util"] }
|
||||||
|
|
|
@ -7,7 +7,6 @@ publish = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0.68"
|
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
13
examples/request-id/Cargo.toml
Normal file
13
examples/request-id/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "example-request-id"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
axum = { path = "../../axum" }
|
||||||
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
|
tower = "0.5"
|
||||||
|
tower-http = { version = "0.5", features = ["request-id", "trace"] }
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
81
examples/request-id/src/main.rs
Normal file
81
examples/request-id/src/main.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
//! Run with
|
||||||
|
//!
|
||||||
|
//! ```not_rust
|
||||||
|
//! cargo run -p example-request-id
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
http::{HeaderName, Request},
|
||||||
|
response::Html,
|
||||||
|
routing::get,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
use tower::ServiceBuilder;
|
||||||
|
use tower_http::{
|
||||||
|
request_id::{MakeRequestUuid, PropagateRequestIdLayer, SetRequestIdLayer},
|
||||||
|
trace::TraceLayer,
|
||||||
|
};
|
||||||
|
use tracing::{error, info, info_span};
|
||||||
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
|
|
||||||
|
const REQUEST_ID_HEADER: &str = "x-request-id";
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(
|
||||||
|
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||||
|
// axum logs rejections from built-in extractors with the `axum::rejection`
|
||||||
|
// target, at `TRACE` level. `axum::rejection=trace` enables showing those events
|
||||||
|
format!(
|
||||||
|
"{}=debug,tower_http=debug,axum::rejection=trace",
|
||||||
|
env!("CARGO_CRATE_NAME")
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.with(tracing_subscriber::fmt::layer())
|
||||||
|
.init();
|
||||||
|
|
||||||
|
let x_request_id = HeaderName::from_static(REQUEST_ID_HEADER);
|
||||||
|
|
||||||
|
let middleware = ServiceBuilder::new()
|
||||||
|
.layer(SetRequestIdLayer::new(
|
||||||
|
x_request_id.clone(),
|
||||||
|
MakeRequestUuid,
|
||||||
|
))
|
||||||
|
.layer(
|
||||||
|
TraceLayer::new_for_http().make_span_with(|request: &Request<_>| {
|
||||||
|
// Log the request id as generated.
|
||||||
|
let request_id = request.headers().get(REQUEST_ID_HEADER);
|
||||||
|
|
||||||
|
match request_id {
|
||||||
|
Some(request_id) => info_span!(
|
||||||
|
"http_request",
|
||||||
|
request_id = ?request_id,
|
||||||
|
),
|
||||||
|
None => {
|
||||||
|
error!("could not extract request_id");
|
||||||
|
info_span!("http_request")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
// send headers from request to response headers
|
||||||
|
.layer(PropagateRequestIdLayer::new(x_request_id));
|
||||||
|
|
||||||
|
// build our application with a route
|
||||||
|
let app = Router::new().route("/", get(handler)).layer(middleware);
|
||||||
|
|
||||||
|
// run it
|
||||||
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
println!("listening on {}", listener.local_addr().unwrap());
|
||||||
|
axum::serve(listener, app).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handler() -> Html<&'static str> {
|
||||||
|
info!("Hello world!");
|
||||||
|
Html("<h1>Hello, World!</h1>")
|
||||||
|
}
|
|
@ -9,6 +9,6 @@ axum = { path = "../../axum" }
|
||||||
reqwest = { version = "0.12", features = ["stream"] }
|
reqwest = { version = "0.12", features = ["stream"] }
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tokio-stream = "0.1"
|
tokio-stream = "0.1"
|
||||||
tower-http = { version = "0.5.0", features = ["trace"] }
|
tower-http = { version = "0.6.1", features = ["trace"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -8,13 +8,13 @@ publish = false
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
hyper = { version = "1.0.0", features = ["full"] }
|
hyper = { version = "1.0.0", features = ["full"] }
|
||||||
prost = "0.11"
|
#prost = "0.11"
|
||||||
tokio = { version = "1", features = ["full"] }
|
#tokio = { version = "1", features = ["full"] }
|
||||||
tonic = { version = "0.9" }
|
#tonic = { version = "0.9" }
|
||||||
tonic-reflection = "0.9"
|
#tonic-reflection = "0.9"
|
||||||
tower = { version = "0.4", features = ["full"] }
|
tower = { version = "0.5.1", features = ["full"] }
|
||||||
tracing = "0.1"
|
#tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
#tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tonic-build = { version = "0.9", features = ["prost"] }
|
tonic-build = { version = "0.9", features = ["prost"] }
|
||||||
|
|
|
@ -9,4 +9,4 @@ axum = { path = "../../axum" }
|
||||||
hyper = { version = "1.0", features = [] }
|
hyper = { version = "1.0", features = [] }
|
||||||
hyper-util = { version = "0.1", features = ["tokio", "server-auto", "http1"] }
|
hyper-util = { version = "0.1", features = ["tokio", "server-auto", "http1"] }
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tower = { version = "0.4", features = ["util"] }
|
tower = { version = "0.5.1", features = ["util"] }
|
||||||
|
|
|
@ -14,3 +14,6 @@ axum-extra = { path = "../../axum-extra", default-features = false }
|
||||||
futures-executor = "0.3.21"
|
futures-executor = "0.3.21"
|
||||||
http = "1.0.0"
|
http = "1.0.0"
|
||||||
tower-service = "0.3.1"
|
tower-service = "0.3.1"
|
||||||
|
|
||||||
|
[package.metadata.cargo-machete]
|
||||||
|
ignored = ["axum-extra"]
|
||||||
|
|
|
@ -10,4 +10,4 @@ tokio = { version = "1.0", features = ["full"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
||||||
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "any", "postgres"] }
|
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "any", "postgres"] }
|
||||||
|
|
|
@ -11,11 +11,11 @@ futures = "0.3"
|
||||||
headers = "0.4"
|
headers = "0.4"
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tokio-stream = "0.1"
|
tokio-stream = "0.1"
|
||||||
tower-http = { version = "0.5.0", features = ["fs", "trace"] }
|
tower-http = { version = "0.6.1", features = ["fs", "trace"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
eventsource-stream = "0.2"
|
eventsource-stream = "0.2"
|
||||||
reqwest = { version = "0.12", features = ["stream"] }
|
reqwest = { version = "0.12", features = ["stream"] }
|
||||||
reqwest-eventsource = "0.5"
|
reqwest-eventsource = "0.6"
|
||||||
|
|
|
@ -6,9 +6,8 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
axum-extra = { path = "../../axum-extra" }
|
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tower = { version = "0.4", features = ["util"] }
|
tower = { version = "0.5.1", features = ["util"] }
|
||||||
tower-http = { version = "0.5.0", features = ["fs", "trace"] }
|
tower-http = { version = "0.6.1", features = ["fs", "trace"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -6,5 +6,5 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
minijinja = "1.0.11"
|
minijinja = "2.3.1"
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
|
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
askama = "0.11"
|
askama = "0.12"
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
|
|
@ -7,6 +7,5 @@ publish = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum", features = ["ws"] }
|
axum = { path = "../../axum", features = ["ws"] }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
hyper = { version = "1.0.0", features = ["full"] }
|
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tokio-tungstenite = "0.23"
|
tokio-tungstenite = "0.24"
|
||||||
|
|
|
@ -7,14 +7,13 @@ publish = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
http-body-util = "0.1.0"
|
http-body-util = "0.1.0"
|
||||||
hyper = { version = "1.0.0", features = ["full"] }
|
|
||||||
hyper-util = { version = "0.1", features = ["client", "http1", "client-legacy"] }
|
hyper-util = { version = "0.1", features = ["client", "http1", "client-legacy"] }
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tower-http = { version = "0.5.0", features = ["trace"] }
|
tower-http = { version = "0.6.1", features = ["trace"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tower = { version = "0.4", features = ["util"] }
|
tower = { version = "0.5.1", features = ["util"] }
|
||||||
|
|
|
@ -6,8 +6,7 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
axum-server = { version = "0.6", features = ["tls-rustls"] }
|
axum-server = { version = "0.7", features = ["tls-rustls"] }
|
||||||
hyper = { version = "0.14", features = ["full"] }
|
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -6,7 +6,7 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
axum-server = { version = "0.6", features = ["tls-rustls"] }
|
axum-server = { version = "0.7", features = ["tls-rustls"] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -8,8 +8,8 @@ publish = false
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tower = { version = "0.4", features = ["util", "timeout"] }
|
tower = { version = "0.5.1", features = ["util", "timeout"] }
|
||||||
tower-http = { version = "0.5.0", features = ["add-extension", "trace"] }
|
tower-http = { version = "0.6.1", features = ["add-extension", "trace"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
uuid = { version = "1.0", features = ["serde", "v4"] }
|
uuid = { version = "1.0", features = ["serde", "v4"] }
|
||||||
|
|
|
@ -6,8 +6,8 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
bb8 = "0.7.1"
|
bb8 = "0.8.5"
|
||||||
bb8-postgres = "0.7.0"
|
bb8-postgres = "0.8.1"
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tokio-postgres = "0.7.2"
|
tokio-postgres = "0.7.2"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
|
|
@ -6,9 +6,9 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
bb8 = "0.7.1"
|
bb8 = "0.8.5"
|
||||||
bb8-redis = "0.14.0"
|
bb8-redis = "0.17.0"
|
||||||
redis = "0.24.0"
|
redis = "0.27.2"
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -7,6 +7,6 @@ publish = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum", features = ["tracing"] }
|
axum = { path = "../../axum", features = ["tracing"] }
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tower-http = { version = "0.5.0", features = ["trace"] }
|
tower-http = { version = "0.6.1", features = ["trace"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -10,6 +10,5 @@ http-body-util = "0.1"
|
||||||
hyper = { version = "1.0.0", features = ["full"] }
|
hyper = { version = "1.0.0", features = ["full"] }
|
||||||
hyper-util = { version = "0.1", features = ["tokio", "server-auto", "http1"] }
|
hyper-util = { version = "0.1", features = ["tokio", "server-auto", "http1"] }
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tower = { version = "0.4", features = ["util"] }
|
tower = { version = "0.5.1", features = ["util"] }
|
||||||
tracing = "0.1"
|
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
|
@ -6,10 +6,9 @@ version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum" }
|
axum = { path = "../../axum" }
|
||||||
http-body = "1.0.0"
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
thiserror = "1.0.29"
|
thiserror = "1.0.29"
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
validator = { version = "0.14.0", features = ["derive"] }
|
validator = { version = "0.18.1", features = ["derive"] }
|
||||||
|
|
|
@ -11,9 +11,8 @@ futures = "0.3"
|
||||||
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
|
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
|
||||||
headers = "0.4"
|
headers = "0.4"
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tokio-tungstenite = "0.23"
|
tokio-tungstenite = "0.24.0"
|
||||||
tower = { version = "0.4", features = ["util"] }
|
tower-http = { version = "0.6.1", features = ["fs", "trace"] }
|
||||||
tower-http = { version = "0.5.0", features = ["fs", "trace"] }
|
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue