mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-21 00:00:14 +02:00
Add support for extracting typed headers (#18)
Uses the `headers` crate.
This commit is contained in:
@@ -38,6 +38,7 @@ tower-http = { version = "0.1", features = ["add-extension", "map-response-body"
|
||||
tokio-tungstenite = { optional = true, version = "0.14" }
|
||||
sha-1 = { optional = true, version = "0.9.6" }
|
||||
base64 = { optional = true, version = "0.13" }
|
||||
headers = { optional = true, version = "0.3" }
|
||||
|
||||
[dev-dependencies]
|
||||
askama = "0.10.5"
|
||||
|
||||
@@ -766,3 +766,44 @@ macro_rules! impl_from_request_tuple {
|
||||
}
|
||||
|
||||
impl_from_request_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16);
|
||||
|
||||
/// Extractor that extracts a typed header value from [`headers`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use awebframework::{extract::TypedHeader, prelude::*};
|
||||
/// use headers::UserAgent;
|
||||
///
|
||||
/// async fn users_teams_show(
|
||||
/// TypedHeader(user_agent): TypedHeader<UserAgent>,
|
||||
/// ) {
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// let app = route("/users/:user_id/team/:team_id", get(users_teams_show));
|
||||
/// ```
|
||||
#[cfg(feature = "headers")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "headers")))]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct TypedHeader<T>(pub T);
|
||||
|
||||
#[cfg(feature = "headers")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "headers")))]
|
||||
#[async_trait]
|
||||
impl<T> FromRequest for TypedHeader<T>
|
||||
where
|
||||
T: headers::Header,
|
||||
{
|
||||
type Rejection = rejection::TypedHeaderRejection;
|
||||
|
||||
async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection> {
|
||||
let header_values = req.headers().get_all(T::name());
|
||||
T::decode(&mut header_values.iter())
|
||||
.map(Self)
|
||||
.map_err(|err| rejection::TypedHeaderRejection {
|
||||
err,
|
||||
name: T::name(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Rejection response types.
|
||||
|
||||
use http::StatusCode;
|
||||
use tower::BoxError;
|
||||
|
||||
use super::IntoResponse;
|
||||
@@ -341,3 +342,22 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Rejection used for [`TypedHeader`](super::TypedHeader).
|
||||
#[cfg(feature = "headers")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "headers")))]
|
||||
#[derive(Debug)]
|
||||
pub struct TypedHeaderRejection {
|
||||
pub(super) name: &'static http::header::HeaderName,
|
||||
pub(super) err: headers::Error,
|
||||
}
|
||||
|
||||
#[cfg(feature = "headers")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "headers")))]
|
||||
impl IntoResponse for TypedHeaderRejection {
|
||||
fn into_response(self) -> http::Response<Body> {
|
||||
let mut res = format!("{} ({})", self.err, self.name).into_response();
|
||||
*res.status_mut() = StatusCode::BAD_REQUEST;
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,6 +551,7 @@
|
||||
//! - `ws`: Enables WebSockets support.
|
||||
//! - `hyper-h1`: Enables hyper's `http1` feature. On by default.
|
||||
//! - `hyper-h2`: Enables hyper's `http2` feature.
|
||||
//! - `headers`: Enables extracing typed headers via [`extract::TypedHeader`].
|
||||
//!
|
||||
//! [tower]: https://crates.io/crates/tower
|
||||
//! [tower-http]: https://crates.io/crates/tower-http
|
||||
|
||||
+28
-1
@@ -1,4 +1,4 @@
|
||||
use crate::{handler::on, prelude::*, routing::MethodFilter, service};
|
||||
use crate::{handler::on, prelude::*, response::IntoResponse, routing::MethodFilter, service};
|
||||
use bytes::Bytes;
|
||||
use http::{Request, Response, StatusCode};
|
||||
use hyper::{Body, Server};
|
||||
@@ -580,6 +580,33 @@ async fn disjunction() {
|
||||
assert_eq!(res.text().await.unwrap(), "v0: games#show (123)");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn typed_header() {
|
||||
use extract::TypedHeader;
|
||||
async fn handle(TypedHeader(user_agent): TypedHeader<headers::UserAgent>) -> impl IntoResponse {
|
||||
user_agent.to_string()
|
||||
}
|
||||
|
||||
let app = route("/", get(handle));
|
||||
|
||||
let addr = run_in_background(app).await;
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let res = client
|
||||
.get(format!("http://{}", addr))
|
||||
.header("user-agent", "foobar")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
let body = res.text().await.unwrap();
|
||||
assert_eq!(body, "foobar");
|
||||
|
||||
let res = client.get(format!("http://{}", addr)).send().await.unwrap();
|
||||
let body = res.text().await.unwrap();
|
||||
assert_eq!(body, "invalid HTTP header (user-agent)");
|
||||
}
|
||||
|
||||
/// Run a `tower::Service` in the background and get a URI for it.
|
||||
async fn run_in_background<S, ResBody>(svc: S) -> SocketAddr
|
||||
where
|
||||
|
||||
Reference in New Issue
Block a user