Only allow last extractor to mutate the request (#1272)

* Only allow last extractor to mutate the request

* Change `FromRequest` and add `FromRequestParts` trait (#1275)

* Add `Once`/`Mut` type parameter for `FromRequest` and `RequestParts`

* 🪄

* split traits

* `FromRequest` for tuples

* Remove `BodyAlreadyExtracted`

* don't need fully qualified path

* don't export `Once` and `Mut`

* remove temp tests

* depend on axum again

Co-authored-by: Jonas Platte <[email protected]>

* Port `Handler` and most extractors (#1277)

* Port `Handler` and most extractors

* Put `M` inside `Handler` impls, not trait itself

* comment out tuples for now

* fix lints

* Reorder arguments to `Handler` (#1281)

I think `Request<B>, Arc<S>` is better since its consistent with
`FromRequest` and `FromRequestParts`.

* Port most things in axum-extra (#1282)

* Port `#[derive(TypedPath)]` and `#[debug_handler]` (#1283)

* port #[derive(TypedPath)]

* wip: #[debug_handler]

* fix #[debug_handler]

* don't need itertools

* also require `Send`

* update expected error

* support fully qualified `self`

* Implement FromRequest[Parts] for tuples (#1286)

* Port docs for axum and axum-core (#1285)

* Port axum-extra (#1287)

* Port axum-extra

* Update axum-core/Cargo.toml

Co-authored-by: Jonas Platte <[email protected]>

* remove `impl FromRequest for Either*`

Co-authored-by: Jonas Platte <[email protected]>

* New FromRequest[Parts] trait cleanup (#1288)

* Make private module truly private again

* Simplify tuple FromRequest implementation

* Port `#[derive(FromRequest)]` (#1289)

* fix tests

* fix docs

* revert examples

* fix docs link

* fix intra docs links

* Port examples (#1291)

* Document wrapping other extractors (#1292)

* axum-extra doesn't need to depend on axum-core (#1294)

Missed this in https://github.com/tokio-rs/axum/pull/1287

* Add `FromRequest` changes to changelogs (#1293)

* Update changelog

* Remove default type for `S` in `Handler`

* Clarify which types have default types for `S`

* Apply suggestions from code review

Co-authored-by: Jonas Platte <[email protected]>

Co-authored-by: Jonas Platte <[email protected]>

* remove unused import

* Rename `Mut` and `Once` (#1296)

* fix trybuild expected output

Co-authored-by: Jonas Platte <[email protected]>
This commit is contained in:
David Pedersen
2022-08-22 12:23:20 +02:00
committed by GitHub
co-authored by Jonas Platte
parent f1769e5134
commit be624306f4
104 changed files with 1513 additions and 1936 deletions
+201 -85
View File
@@ -5,14 +5,15 @@ Types and traits for extracting data from requests.
- [Intro](#intro)
- [Common extractors](#common-extractors)
- [Applying multiple extractors](#applying-multiple-extractors)
- [Be careful when extracting `Request`](#be-careful-when-extracting-request)
- [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` implementations](#accessing-other-extractors-in-fromrequest-implementations)
- [Accessing other extractors in `FromRequest` or `FromRequestParts` implementations](#accessing-other-extractors-in-fromrequest-or-fromrequestparts-implementations)
- [Request body extractors](#request-body-extractors)
- [Running extractors from middleware](#running-extractors-from-middleware)
- [Wrapping extractors](#wrapping-extractors)
# Intro
@@ -152,83 +153,74 @@ async fn get_user_things(
# };
```
# The order of extractors
Extractors always run in the order of the function parameters that is from
left to right.
# Be careful when extracting `Request`
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 by that requiring such extractors to be the _last_ argument your
handler takes.
[`Request`] is itself an extractor:
For example
```rust,no_run
use axum::{http::Request, body::Body};
```rust
use axum::http::{Method, HeaderMap};
async fn handler(request: Request<Body>) {
async fn handler(
// `Method` and `HeaderMap` don't consume the request body so they can
// put anywhere in the argument list
method: Method,
headers: HeaderMap,
// `String` consumes the request body and thus must be the last extractor
body: String,
) {
// ...
}
#
# let _: axum::routing::MethodRouter = axum::routing::get(handler);
```
However be careful when combining it with other extractors since it will consume
all extensions and the request body. Therefore it is recommended to always apply
the request extractor last:
We get a compile error if `String` isn't the last extractor:
```rust,no_run
use axum::{http::Request, Extension, body::Body};
```rust,compile_fail
use axum::http::Method;
// this will fail at runtime since `Request<Body>` will have consumed all the
// extensions so `Extension<State>` will be missing
async fn broken(
request: Request<Body>,
Extension(state): Extension<State>,
async fn handler(
// this doesn't work since `String` must be the last argument
body: String,
method: Method,
) {
// ...
}
// this will work since we extract `Extension<State>` before `Request<Body>`
async fn works(
Extension(state): Extension<State>,
request: Request<Body>,
) {
// ...
}
#[derive(Clone)]
struct State {};
#
# let _: axum::routing::MethodRouter = axum::routing::get(handler);
```
# Extracting request bodies
This also means you cannot consume the request body twice:
Since request bodies are asynchronous streams they can only be extracted once:
```rust,compile_fail
use axum::Json;
use serde::Deserialize;
```rust,no_run
use axum::{Json, http::Request, body::{Bytes, Body}};
use serde_json::Value;
#[derive(Deserialize)]
struct Payload {}
// this will fail at runtime since `Json<Value>` and `Bytes` both attempt to extract
// the body
//
// the solution is to only extract the body once so remove either
// `body_json: Json<Value>` or `body_bytes: Bytes`
async fn broken(
body_json: Json<Value>,
body_bytes: Bytes,
) {
// ...
}
// this doesn't work either for the same reason: `Bytes` and `Request<Body>`
// both extract the body
async fn also_broken(
body_json: Json<Value>,
request: Request<Body>,
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);
```
Also keep this in mind if you extract or otherwise consume the body in
middleware. You either need to not extract the body in handlers or make sure
your middleware reinserts the body using [`RequestParts::body_mut`] so it's
available to handlers.
axum enforces this by requiring the last extractor implements [`FromRequest`]
and all others implement [`FromRequestParts`].
# Optional extractors
@@ -407,29 +399,38 @@ happen without major breaking versions.
# Defining custom extractors
You can also define your own extractors by implementing [`FromRequest`]:
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::{
async_trait,
extract::{FromRequest, RequestParts},
extract::FromRequestParts,
routing::get,
Router,
http::{
StatusCode,
header::{HeaderValue, USER_AGENT},
request::Parts,
},
};
use http::{StatusCode, header::{HeaderValue, USER_AGENT}};
struct ExtractUserAgent(HeaderValue);
#[async_trait]
impl<S, B> FromRequest<S, B> for ExtractUserAgent
impl<S> FromRequestParts<S> for ExtractUserAgent
where
B: Send,
S: Send + Sync,
{
type Rejection = (StatusCode, &'static str);
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
if let Some(user_agent) = req.headers().get(USER_AGENT) {
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"))
@@ -447,7 +448,58 @@ let app = Router::new().route("/foo", get(handler));
# };
```
# Accessing other extractors in [`FromRequest`] implementations
## Implementing `FromRequest`
If your extractor needs to consume the request body you must implement [`FromRequest`]
```rust,no_run
use axum::{
async_trait,
extract::FromRequest,
response::{Response, IntoResponse},
body::Bytes,
routing::get,
Router,
http::{
StatusCode,
header::{HeaderValue, USER_AGENT},
Request,
},
};
struct ValidatedBody(Bytes);
#[async_trait]
impl<S, B> FromRequest<S, B> for ValidatedBody
where
Bytes: FromRequest<S, B>,
B: Send + 'static,
S: Send + Sync,
{
type Rejection = Response;
async fn from_request(req: Request<B>, 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));
# async {
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
# };
```
# Accessing other extractors in `FromRequest` or `FromRequestParts` implementations
When defining custom extractors you often need to access another extractors
in your implementation.
@@ -455,9 +507,9 @@ in your implementation.
```rust
use axum::{
async_trait,
extract::{Extension, FromRequest, RequestParts, TypedHeader},
extract::{Extension, FromRequestParts, TypedHeader},
headers::{authorization::Bearer, Authorization},
http::StatusCode,
http::{StatusCode, request::Parts},
response::{IntoResponse, Response},
routing::get,
Router,
@@ -473,20 +525,19 @@ struct AuthenticatedUser {
}
#[async_trait]
impl<S, B> FromRequest<S, B> for AuthenticatedUser
impl<S> FromRequestParts<S> for AuthenticatedUser
where
B: Send,
S: Send + Sync,
{
type Rejection = Response;
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let TypedHeader(Authorization(token)) =
TypedHeader::<Authorization<Bearer>>::from_request(req)
TypedHeader::<Authorization<Bearer>>::from_request_parts(parts, state)
.await
.map_err(|err| err.into_response())?;
let Extension(state): Extension<State> = Extension::from_request(req)
let Extension(state): Extension<State> = Extension::from_request_parts(parts, state)
.await
.map_err(|err| err.into_response())?;
@@ -584,14 +635,13 @@ let app = Router::new()
# Running extractors from middleware
Extractors can also be run from middleware by making a [`RequestParts`] and
running your extractor:
Extractors can also be run from middleware:
```rust
use axum::{
Router,
middleware::{self, Next},
extract::{RequestParts, TypedHeader},
extract::{TypedHeader, FromRequestParts},
http::{Request, StatusCode},
response::Response,
headers::authorization::{Authorization, Bearer},
@@ -604,12 +654,11 @@ async fn auth_middleware<B>(
where
B: Send,
{
// running extractors requires a `RequestParts`
let mut request_parts = RequestParts::new(request);
// running extractors requires a `axum::http::request::Parts`
let (mut parts, body) = request.into_parts();
// `TypedHeader<Authorization<Bearer>>` extracts the auth token but
// `RequestParts::extract` works with anything that implements `FromRequest`
let auth = request_parts.extract::<TypedHeader<Authorization<Bearer>>>()
// `TypedHeader<Authorization<Bearer>>` extracts the auth token
let auth = TypedHeader::<Authorization<Bearer>>::from_request_parts(&mut parts, &())
.await
.map_err(|_| StatusCode::UNAUTHORIZED)?;
@@ -617,14 +666,8 @@ where
return Err(StatusCode::UNAUTHORIZED);
}
// get the request back so we can run `next`
//
// `try_into_request` will fail if you have extracted the request body. We
// know that `TypedHeader` never does that.
//
// see the `consume-body-in-extractor-or-middleware` example if you need to
// extract the body
let request = request_parts.try_into_request().expect("body extracted");
// reconstruct the request
let request = Request::from_parts(parts, body);
Ok(next.run(request).await)
}
@@ -638,8 +681,81 @@ let app = Router::new().layer(middleware::from_fn(auth_middleware));
# let _: Router<()> = app;
```
# Wrapping extractors
If you want 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,
routing::get,
extract::{FromRequest, FromRequestParts},
http::{Request, HeaderMap, request::Parts},
async_trait,
};
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`
#[async_trait]
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`
#[async_trait]
impl<S, B, T> FromRequest<S, B> for Timing<T>
where
B: Send + 'static,
S: Send + Sync,
T: FromRequest<S, B>,
{
type Rejection = T::Rejection;
async fn from_request(req: Request<B>, 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);
```
[`body::Body`]: crate::body::Body
[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
[`RequestParts::body_mut`]: crate::extract::RequestParts::body_mut
[`JsonRejection::JsonDataError`]: rejection::JsonRejection::JsonDataError
+6 -11
View File
@@ -1,8 +1,8 @@
#![doc = include_str!("../docs/error_handling.md")]
use crate::{
extract::{FromRequest, RequestParts},
http::{Request, StatusCode},
extract::FromRequestParts,
http::Request,
response::{IntoResponse, Response},
};
use std::{
@@ -161,7 +161,7 @@ macro_rules! impl_service {
F: FnOnce($($ty),*, S::Error) -> Fut + Clone + Send + 'static,
Fut: Future<Output = Res> + Send,
Res: IntoResponse,
$( $ty: FromRequest<(), B> + Send,)*
$( $ty: FromRequestParts<()> + Send,)*
B: Send + 'static,
{
type Response = Response;
@@ -181,21 +181,16 @@ macro_rules! impl_service {
let inner = std::mem::replace(&mut self.inner, clone);
let future = Box::pin(async move {
let mut req = RequestParts::new(req);
let (mut parts, body) = req.into_parts();
$(
let $ty = match $ty::from_request(&mut req).await {
let $ty = match $ty::from_request_parts(&mut parts, &()).await {
Ok(value) => value,
Err(rejection) => return Ok(rejection.into_response()),
};
)*
let req = match req.try_into_request() {
Ok(req) => req,
Err(err) => {
return Ok((StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response());
}
};
let req = Request::from_parts(parts, body);
match inner.oneshot(req).await {
Ok(res) => Ok(res.into_response()),
+9 -10
View File
@@ -1,10 +1,10 @@
use crate::{
extract::{rejection::*, FromRequest, RequestParts},
response::IntoResponseParts,
};
use crate::{extract::rejection::*, response::IntoResponseParts};
use async_trait::async_trait;
use axum_core::response::{IntoResponse, Response, ResponseParts};
use http::Request;
use axum_core::{
extract::FromRequestParts,
response::{IntoResponse, Response, ResponseParts},
};
use http::{request::Parts, Request};
use std::{
convert::Infallible,
ops::Deref,
@@ -73,17 +73,16 @@ use tower_service::Service;
pub struct Extension<T>(pub T);
#[async_trait]
impl<T, S, B> FromRequest<S, B> for Extension<T>
impl<T, S> FromRequestParts<S> for Extension<T>
where
T: Clone + Send + Sync + 'static,
B: Send,
S: Send + Sync,
{
type Rejection = ExtensionRejection;
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
async fn from_request_parts(req: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
let value = req
.extensions()
.extensions
.get::<T>()
.ok_or_else(|| {
MissingExtension::from_err(format!(
+6 -6
View File
@@ -4,9 +4,10 @@
//!
//! [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info
use super::{Extension, FromRequest, RequestParts};
use super::{Extension, FromRequestParts};
use crate::middleware::AddExtension;
use async_trait::async_trait;
use http::request::Parts;
use hyper::server::conn::AddrStream;
use std::{
convert::Infallible,
@@ -128,16 +129,15 @@ opaque_future! {
pub struct ConnectInfo<T>(pub T);
#[async_trait]
impl<S, B, T> FromRequest<S, B> for ConnectInfo<T>
impl<S, T> FromRequestParts<S> for ConnectInfo<T>
where
B: Send,
S: Send + Sync,
T: Clone + Send + Sync + 'static,
{
type Rejection = <Extension<Self> as FromRequest<S, B>>::Rejection;
type Rejection = <Extension<Self> as FromRequestParts<S>>::Rejection;
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
let Extension(connect_info) = Extension::<Self>::from_request(req).await?;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let Extension(connect_info) = Extension::<Self>::from_request_parts(parts, state).await?;
Ok(connect_info)
}
}
+63 -35
View File
@@ -1,7 +1,7 @@
use super::{rejection::*, FromRequest, RequestParts};
use super::{rejection::*, FromRequest};
use async_trait::async_trait;
use axum_core::response::IntoResponse;
use http::Method;
use axum_core::{extract::FromRequestParts, response::IntoResponse};
use http::{request::Parts, Method, Request};
use std::ops::Deref;
/// Extractor that will reject requests with a body larger than some size.
@@ -40,43 +40,17 @@ impl<T, S, B, const N: u64> FromRequest<S, B> for ContentLengthLimit<T, N>
where
T: FromRequest<S, B>,
T::Rejection: IntoResponse,
B: Send,
B: Send + 'static,
S: Send + Sync,
{
type Rejection = ContentLengthLimitRejection<T::Rejection>;
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
let content_length = req
.headers()
.get(http::header::CONTENT_LENGTH)
.and_then(|value| value.to_str().ok()?.parse::<u64>().ok());
async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
let (parts, body) = req.into_parts();
validate::<_, N>(&parts)?;
match (content_length, req.method()) {
(content_length, &(Method::GET | Method::HEAD | Method::OPTIONS)) => {
if content_length.is_some() {
return Err(ContentLengthLimitRejection::ContentLengthNotAllowed(
ContentLengthNotAllowed,
));
} else if req
.headers()
.get(http::header::TRANSFER_ENCODING)
.map_or(false, |value| value.as_bytes() == b"chunked")
{
return Err(ContentLengthLimitRejection::LengthRequired(LengthRequired));
}
}
(Some(content_length), _) if content_length > N => {
return Err(ContentLengthLimitRejection::PayloadTooLarge(
PayloadTooLarge,
));
}
(None, _) => {
return Err(ContentLengthLimitRejection::LengthRequired(LengthRequired));
}
_ => {}
}
let value = T::from_request(req)
let req = Request::from_parts(parts, body);
let value = T::from_request(req, state)
.await
.map_err(ContentLengthLimitRejection::Inner)?;
@@ -84,6 +58,60 @@ where
}
}
#[async_trait]
impl<T, S, const N: u64> FromRequestParts<S> for ContentLengthLimit<T, N>
where
T: FromRequestParts<S>,
T::Rejection: IntoResponse,
S: Send + Sync,
{
type Rejection = ContentLengthLimitRejection<T::Rejection>;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
validate::<_, N>(parts)?;
let value = T::from_request_parts(parts, state)
.await
.map_err(ContentLengthLimitRejection::Inner)?;
Ok(Self(value))
}
}
fn validate<E, const N: u64>(parts: &Parts) -> Result<(), ContentLengthLimitRejection<E>> {
let content_length = parts
.headers
.get(http::header::CONTENT_LENGTH)
.and_then(|value| value.to_str().ok()?.parse::<u64>().ok());
match (content_length, &parts.method) {
(content_length, &(Method::GET | Method::HEAD | Method::OPTIONS)) => {
if content_length.is_some() {
return Err(ContentLengthLimitRejection::ContentLengthNotAllowed(
ContentLengthNotAllowed,
));
} else if parts
.headers
.get(http::header::TRANSFER_ENCODING)
.map_or(false, |value| value.as_bytes() == b"chunked")
{
return Err(ContentLengthLimitRejection::LengthRequired(LengthRequired));
}
}
(Some(content_length), _) if content_length > N => {
return Err(ContentLengthLimitRejection::PayloadTooLarge(
PayloadTooLarge,
));
}
(None, _) => {
return Err(ContentLengthLimitRejection::LengthRequired(LengthRequired));
}
_ => {}
}
Ok(())
}
impl<T, const N: u64> Deref for ContentLengthLimit<T, N> {
type Target = T;
+13 -11
View File
@@ -1,9 +1,12 @@
use super::{
rejection::{FailedToResolveHost, HostRejection},
FromRequest, RequestParts,
FromRequestParts,
};
use async_trait::async_trait;
use http::header::{HeaderMap, FORWARDED};
use http::{
header::{HeaderMap, FORWARDED},
request::Parts,
};
const X_FORWARDED_HOST_HEADER_KEY: &str = "X-Forwarded-Host";
@@ -21,35 +24,34 @@ const X_FORWARDED_HOST_HEADER_KEY: &str = "X-Forwarded-Host";
pub struct Host(pub String);
#[async_trait]
impl<S, B> FromRequest<S, B> for Host
impl<S> FromRequestParts<S> for Host
where
B: Send,
S: Send + Sync,
{
type Rejection = HostRejection;
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
if let Some(host) = parse_forwarded(req.headers()) {
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
if let Some(host) = parse_forwarded(&parts.headers) {
return Ok(Host(host.to_owned()));
}
if let Some(host) = req
.headers()
if let Some(host) = parts
.headers
.get(X_FORWARDED_HOST_HEADER_KEY)
.and_then(|host| host.to_str().ok())
{
return Ok(Host(host.to_owned()));
}
if let Some(host) = req
.headers()
if let Some(host) = parts
.headers
.get(http::header::HOST)
.and_then(|host| host.to_str().ok())
{
return Ok(Host(host.to_owned()));
}
if let Some(host) = req.uri().host() {
if let Some(host) = parts.uri.host() {
return Ok(Host(host.to_owned()));
}
+6 -6
View File
@@ -1,5 +1,6 @@
use super::{rejection::*, FromRequest, RequestParts};
use super::{rejection::*, FromRequestParts};
use async_trait::async_trait;
use http::request::Parts;
use std::sync::Arc;
/// Access the path in the router that matches the request.
@@ -64,16 +65,15 @@ impl MatchedPath {
}
#[async_trait]
impl<S, B> FromRequest<S, B> for MatchedPath
impl<S> FromRequestParts<S> for MatchedPath
where
B: Send,
S: Send + Sync,
{
type Rejection = MatchedPathRejection;
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
let matched_path = req
.extensions()
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();
+4 -12
View File
@@ -1,7 +1,6 @@
#![doc = include_str!("../docs/extract.md")]
use http::header;
use rejection::*;
use http::header::{self, HeaderMap};
pub mod connect_info;
pub mod path;
@@ -17,7 +16,7 @@ mod request_parts;
mod state;
#[doc(inline)]
pub use axum_core::extract::{FromRef, FromRequest, RequestParts};
pub use axum_core::extract::{FromRef, FromRequest, FromRequestParts};
#[doc(inline)]
#[allow(deprecated)]
@@ -75,16 +74,9 @@ pub use self::ws::WebSocketUpgrade;
#[doc(no_inline)]
pub use crate::TypedHeader;
pub(crate) fn take_body<S, B>(req: &mut RequestParts<S, B>) -> Result<B, BodyAlreadyExtracted> {
req.take_body().ok_or_else(BodyAlreadyExtracted::default)
}
// this is duplicated in `axum-extra/src/extract/form.rs`
pub(super) fn has_content_type<S, B>(
req: &RequestParts<S, B>,
expected_content_type: &mime::Mime,
) -> bool {
let content_type = if let Some(content_type) = req.headers().get(header::CONTENT_TYPE) {
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;
+8 -6
View File
@@ -2,12 +2,13 @@
//!
//! See [`Multipart`] for more details.
use super::{rejection::*, BodyStream, FromRequest, RequestParts};
use super::{BodyStream, FromRequest};
use crate::body::{Bytes, HttpBody};
use crate::BoxError;
use async_trait::async_trait;
use futures_util::stream::Stream;
use http::header::{HeaderMap, CONTENT_TYPE};
use http::Request;
use std::{
fmt,
pin::Pin,
@@ -58,10 +59,12 @@ where
{
type Rejection = MultipartRejection;
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
let stream = BodyStream::from_request(req).await?;
let headers = req.headers();
let boundary = parse_boundary(headers).ok_or(InvalidBoundary)?;
async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
let boundary = parse_boundary(req.headers()).ok_or(InvalidBoundary)?;
let stream = match BodyStream::from_request(req, state).await {
Ok(stream) => stream,
Err(err) => match err {},
};
let multipart = multer::Multipart::new(stream, boundary);
Ok(Self { inner: multipart })
}
@@ -224,7 +227,6 @@ composite_rejection! {
///
/// Contains one variant for each way the [`Multipart`] extractor can fail.
pub enum MultipartRejection {
BodyAlreadyExtracted,
InvalidBoundary,
}
}
+6 -22
View File
@@ -4,12 +4,12 @@
mod de;
use crate::{
extract::{rejection::*, FromRequest, RequestParts},
extract::{rejection::*, FromRequestParts},
routing::url_params::UrlParams,
};
use async_trait::async_trait;
use axum_core::response::{IntoResponse, Response};
use http::StatusCode;
use http::{request::Parts, StatusCode};
use serde::de::DeserializeOwned;
use std::{
fmt,
@@ -163,16 +163,15 @@ impl<T> DerefMut for Path<T> {
}
#[async_trait]
impl<T, S, B> FromRequest<S, B> for Path<T>
impl<T, S> FromRequestParts<S> for Path<T>
where
T: DeserializeOwned + Send,
B: Send,
S: Send + Sync,
{
type Rejection = PathRejection;
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
let params = match req.extensions_mut().get::<UrlParams>() {
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
let params = match parts.extensions.get::<UrlParams>() {
Some(UrlParams::Params(params)) => params,
Some(UrlParams::InvalidUtf8InPathParam { key }) => {
let err = PathDeserializationError {
@@ -413,8 +412,7 @@ impl std::error::Error for FailedToDeserializePathParams {}
mod tests {
use super::*;
use crate::{routing::get, test_helpers::*, Router};
use http::{Request, StatusCode};
use hyper::Body;
use http::StatusCode;
use std::collections::HashMap;
#[tokio::test]
@@ -519,20 +517,6 @@ mod tests {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
async fn when_extensions_are_missing() {
let app = Router::new().route("/:key", get(|_: Request<Body>, _: Path<String>| async {}));
let client = TestClient::new(app);
let res = client.get("/foo").send().await;
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
assert_eq!(
res.text().await,
"No paths parameters found for matched route. Are you also extracting `Request<_>`?"
);
}
#[tokio::test]
async fn str_reference_deserialize() {
struct Param(String);
+11 -9
View File
@@ -1,5 +1,6 @@
use super::{rejection::*, FromRequest, RequestParts};
use super::{rejection::*, FromRequestParts};
use async_trait::async_trait;
use http::request::Parts;
use serde::de::DeserializeOwned;
use std::ops::Deref;
@@ -49,16 +50,15 @@ use std::ops::Deref;
pub struct Query<T>(pub T);
#[async_trait]
impl<T, S, B> FromRequest<S, B> for Query<T>
impl<T, S> FromRequestParts<S> for Query<T>
where
T: DeserializeOwned,
B: Send,
S: Send + Sync,
{
type Rejection = QueryRejection;
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
let query = req.uri().query().unwrap_or_default();
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
let query = parts.uri.query().unwrap_or_default();
let value = serde_urlencoded::from_str(query)
.map_err(FailedToDeserializeQueryString::__private_new)?;
Ok(Query(value))
@@ -76,15 +76,17 @@ impl<T> Deref for Query<T> {
#[cfg(test)]
mod tests {
use super::*;
use crate::extract::RequestParts;
use axum_core::extract::FromRequest;
use http::Request;
use serde::Deserialize;
use std::fmt::Debug;
async fn check<T: DeserializeOwned + PartialEq + Debug>(uri: impl AsRef<str>, value: T) {
async fn check<T>(uri: impl AsRef<str>, value: T)
where
T: DeserializeOwned + PartialEq + Debug,
{
let req = Request::builder().uri(uri.as_ref()).body(()).unwrap();
let mut req = RequestParts::new(req);
assert_eq!(Query::<T>::from_request(&mut req).await.unwrap().0, value);
assert_eq!(Query::<T>::from_request(req, &()).await.unwrap().0, value);
}
#[tokio::test]
+5 -5
View File
@@ -1,5 +1,6 @@
use super::{FromRequest, RequestParts};
use super::FromRequestParts;
use async_trait::async_trait;
use http::request::Parts;
use std::convert::Infallible;
/// Extractor that extracts the raw query string, without parsing it.
@@ -27,15 +28,14 @@ use std::convert::Infallible;
pub struct RawQuery(pub Option<String>);
#[async_trait]
impl<S, B> FromRequest<S, B> for RawQuery
impl<S> FromRequestParts<S> for RawQuery
where
B: Send,
S: Send + Sync,
{
type Rejection = Infallible;
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
let query = req.uri().query().map(|query| query.to_owned());
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))
}
}
+1 -1
View File
@@ -73,7 +73,7 @@ define_rejection! {
define_rejection! {
#[status = INTERNAL_SERVER_ERROR]
#[body = "No paths parameters found for matched route. Are you also extracting `Request<_>`?"]
#[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.
+15 -53
View File
@@ -1,11 +1,11 @@
use super::{rejection::*, take_body, Extension, FromRequest, RequestParts};
use super::{Extension, FromRequest, FromRequestParts};
use crate::{
body::{Body, Bytes, HttpBody},
BoxError, Error,
};
use async_trait::async_trait;
use futures_util::stream::Stream;
use http::Uri;
use http::{request::Parts, Request, Uri};
use std::{
convert::Infallible,
fmt,
@@ -86,17 +86,16 @@ pub struct OriginalUri(pub Uri);
#[cfg(feature = "original-uri")]
#[async_trait]
impl<S, B> FromRequest<S, B> for OriginalUri
impl<S> FromRequestParts<S> for OriginalUri
where
B: Send,
S: Send + Sync,
{
type Rejection = Infallible;
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
let uri = Extension::<Self>::from_request(req)
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(req.uri().clone())))
.unwrap_or_else(|_| Extension(OriginalUri(parts.uri.clone())))
.0;
Ok(uri)
}
@@ -148,10 +147,11 @@ where
B::Error: Into<BoxError>,
S: Send + Sync,
{
type Rejection = BodyAlreadyExtracted;
type Rejection = Infallible;
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
let body = take_body(req)?
async fn from_request(req: Request<B>, _state: &S) -> Result<Self, Self::Rejection> {
let body = req
.into_body()
.map_data(Into::into)
.map_err(|err| Error::new(err.into()));
let stream = BodyStream(SyncWrapper::new(Box::pin(body)));
@@ -203,40 +203,17 @@ where
B: Send,
S: Send + Sync,
{
type Rejection = BodyAlreadyExtracted;
type Rejection = Infallible;
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
let body = take_body(req)?;
Ok(Self(body))
async fn from_request(req: Request<B>, _state: &S) -> Result<Self, Self::Rejection> {
Ok(Self(req.into_body()))
}
}
#[cfg(test)]
mod tests {
use crate::{
body::Body,
extract::Extension,
routing::{get, post},
test_helpers::*,
Router,
};
use http::{Method, Request, StatusCode};
#[tokio::test]
async fn multiple_request_extractors() {
async fn handler(_: Request<Body>, _: Request<Body>) {}
let app = Router::new().route("/", post(handler));
let client = TestClient::new(app);
let res = client.post("/").body("hi there").send().await;
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
assert_eq!(
res.text().await,
"Cannot have two request body extractors for a single handler"
);
}
use crate::{extract::Extension, routing::get, test_helpers::*, Router};
use http::{Method, StatusCode};
#[tokio::test]
async fn extract_request_parts() {
@@ -256,19 +233,4 @@ mod tests {
let res = client.get("/").header("x-foo", "123").send().await;
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
async fn extract_request_parts_doesnt_consume_the_body() {
#[derive(Clone)]
struct Ext;
async fn handler(_parts: http::request::Parts, body: String) {
assert_eq!(body, "foo");
}
let client = TestClient::new(Router::new().route("/", get(handler)));
let res = client.get("/").body("foo").send().await;
assert_eq!(res.status(), StatusCode::OK);
}
}
+13 -13
View File
@@ -1,5 +1,6 @@
use async_trait::async_trait;
use axum_core::extract::{FromRef, FromRequest, RequestParts};
use axum_core::extract::{FromRef, FromRequestParts};
use http::request::Parts;
use std::{
convert::Infallible,
ops::{Deref, DerefMut},
@@ -139,7 +140,8 @@ use std::{
/// to do it:
///
/// ```rust
/// use axum_core::extract::{FromRequest, RequestParts, FromRef};
/// use axum_core::extract::{FromRequestParts, FromRef};
/// use http::request::Parts;
/// use async_trait::async_trait;
/// use std::convert::Infallible;
///
@@ -147,9 +149,8 @@ use std::{
/// struct MyLibraryExtractor;
///
/// #[async_trait]
/// impl<S, B> FromRequest<S, B> for MyLibraryExtractor
/// impl<S> FromRequestParts<S> for MyLibraryExtractor
/// where
/// B: Send,
/// // 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>,
@@ -157,9 +158,9 @@ use std::{
/// {
/// type Rejection = Infallible;
///
/// async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
/// 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(req.state());
/// let state = MyLibraryState::from_ref(state);
///
/// // ...
/// # todo!()
@@ -171,23 +172,22 @@ use std::{
/// // ...
/// }
/// ```
///
/// Note that you don't need to use the `State` extractor since you can access the state directly
/// from [`RequestParts`].
#[derive(Debug, Default, Clone, Copy)]
pub struct State<S>(pub S);
#[async_trait]
impl<B, OuterState, InnerState> FromRequest<OuterState, B> for State<InnerState>
impl<OuterState, InnerState> FromRequestParts<OuterState> for State<InnerState>
where
B: Send,
InnerState: FromRef<OuterState>,
OuterState: Send + Sync,
{
type Rejection = Infallible;
async fn from_request(req: &mut RequestParts<OuterState, B>) -> Result<Self, Self::Rejection> {
let inner_state = InnerState::from_ref(req.state());
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))
}
}
+18 -18
View File
@@ -95,7 +95,7 @@
//! [`StreamExt::split`]: https://docs.rs/futures/0.3.17/futures/stream/trait.StreamExt.html#method.split
use self::rejection::*;
use super::{FromRequest, RequestParts};
use super::FromRequestParts;
use crate::{
body::{self, Bytes},
response::Response,
@@ -107,7 +107,8 @@ use futures_util::{
stream::{Stream, StreamExt},
};
use http::{
header::{self, HeaderName, HeaderValue},
header::{self, HeaderMap, HeaderName, HeaderValue},
request::Parts,
Method, StatusCode,
};
use hyper::upgrade::{OnUpgrade, Upgraded};
@@ -275,41 +276,40 @@ impl WebSocketUpgrade {
}
#[async_trait]
impl<S, B> FromRequest<S, B> for WebSocketUpgrade
impl<S> FromRequestParts<S> for WebSocketUpgrade
where
B: Send,
S: Send + Sync,
{
type Rejection = WebSocketUpgradeRejection;
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
if req.method() != Method::GET {
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
if parts.method != Method::GET {
return Err(MethodNotGet.into());
}
if !header_contains(req, header::CONNECTION, "upgrade") {
if !header_contains(&parts.headers, header::CONNECTION, "upgrade") {
return Err(InvalidConnectionHeader.into());
}
if !header_eq(req, header::UPGRADE, "websocket") {
if !header_eq(&parts.headers, header::UPGRADE, "websocket") {
return Err(InvalidUpgradeHeader.into());
}
if !header_eq(req, header::SEC_WEBSOCKET_VERSION, "13") {
if !header_eq(&parts.headers, header::SEC_WEBSOCKET_VERSION, "13") {
return Err(InvalidWebSocketVersionHeader.into());
}
let sec_websocket_key = req
.headers_mut()
let sec_websocket_key = parts
.headers
.remove(header::SEC_WEBSOCKET_KEY)
.ok_or(WebSocketKeyHeaderMissing)?;
let on_upgrade = req
.extensions_mut()
let on_upgrade = parts
.extensions
.remove::<OnUpgrade>()
.ok_or(ConnectionNotUpgradable)?;
let sec_websocket_protocol = req.headers().get(header::SEC_WEBSOCKET_PROTOCOL).cloned();
let sec_websocket_protocol = parts.headers.get(header::SEC_WEBSOCKET_PROTOCOL).cloned();
Ok(Self {
config: Default::default(),
@@ -321,16 +321,16 @@ where
}
}
fn header_eq<S, B>(req: &RequestParts<S, B>, key: HeaderName, value: &'static str) -> bool {
if let Some(header) = req.headers().get(&key) {
fn header_eq(headers: &HeaderMap, key: HeaderName, value: &'static str) -> bool {
if let Some(header) = headers.get(&key) {
header.as_bytes().eq_ignore_ascii_case(value.as_bytes())
} else {
false
}
}
fn header_contains<S, B>(req: &RequestParts<S, B>, key: HeaderName, value: &'static str) -> bool {
let header = if let Some(header) = req.headers().get(&key) {
fn header_contains(headers: &HeaderMap, key: HeaderName, value: &'static str) -> bool {
let header = if let Some(header) = headers.get(&key) {
header
} else {
return false;
+9 -13
View File
@@ -1,10 +1,10 @@
use crate::body::{Bytes, HttpBody};
use crate::extract::{has_content_type, rejection::*, FromRequest, RequestParts};
use crate::extract::{has_content_type, rejection::*, FromRequest};
use crate::BoxError;
use async_trait::async_trait;
use axum_core::response::{IntoResponse, Response};
use http::header::CONTENT_TYPE;
use http::{Method, StatusCode};
use http::{Method, Request, StatusCode};
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::ops::Deref;
@@ -59,25 +59,25 @@ pub struct Form<T>(pub T);
impl<T, S, B> FromRequest<S, B> for Form<T>
where
T: DeserializeOwned,
B: HttpBody + Send,
B: HttpBody + Send + 'static,
B::Data: Send,
B::Error: Into<BoxError>,
S: Send + Sync,
{
type Rejection = FormRejection;
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
if req.method() == Method::GET {
let query = req.uri().query().unwrap_or_default();
let value = serde_urlencoded::from_str(query)
.map_err(FailedToDeserializeQueryString::__private_new)?;
Ok(Form(value))
} else {
if !has_content_type(req, &mime::APPLICATION_WWW_FORM_URLENCODED) {
if !has_content_type(req.headers(), &mime::APPLICATION_WWW_FORM_URLENCODED) {
return Err(InvalidFormContentType.into());
}
let bytes = Bytes::from_request(req).await?;
let bytes = Bytes::from_request(req, state).await?;
let value = serde_urlencoded::from_bytes(&bytes)
.map_err(FailedToDeserializeQueryString::__private_new)?;
@@ -114,7 +114,6 @@ impl<T> Deref for Form<T> {
mod tests {
use super::*;
use crate::body::{Empty, Full};
use crate::extract::RequestParts;
use http::Request;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
@@ -130,8 +129,7 @@ mod tests {
.uri(uri.as_ref())
.body(Empty::<Bytes>::new())
.unwrap();
let mut req = RequestParts::new(req);
assert_eq!(Form::<T>::from_request(&mut req).await.unwrap().0, value);
assert_eq!(Form::<T>::from_request(req, &()).await.unwrap().0, value);
}
async fn check_body<T: Serialize + DeserializeOwned + PartialEq + Debug>(value: T) {
@@ -146,8 +144,7 @@ mod tests {
serde_urlencoded::to_string(&value).unwrap().into(),
))
.unwrap();
let mut req = RequestParts::new(req);
assert_eq!(Form::<T>::from_request(&mut req).await.unwrap().0, value);
assert_eq!(Form::<T>::from_request(req, &()).await.unwrap().0, value);
}
#[tokio::test]
@@ -216,9 +213,8 @@ mod tests {
.into(),
))
.unwrap();
let mut req = RequestParts::new(req);
assert!(matches!(
Form::<Pagination>::from_request(&mut req)
Form::<Pagination>::from_request(req, &())
.await
.unwrap_err(),
FormRejection::InvalidFormContentType(InvalidFormContentType)
+1 -1
View File
@@ -88,7 +88,7 @@ where
use futures_util::future::FutureExt;
let handler = self.handler.clone();
let future = Handler::call(handler, Arc::clone(&self.state), req);
let future = Handler::call(handler, req, Arc::clone(&self.state));
let future = future.map(Ok as _);
super::future::IntoServiceFuture::new(future)
@@ -78,7 +78,7 @@ where
.expect("state extension missing. This is a bug in axum, please file an issue");
let handler = self.handler.clone();
let future = Handler::call(handler, state, req);
let future = Handler::call(handler, req, state);
let future = future.map(Ok as _);
super::future::IntoServiceFuture::new(future)
+51 -16
View File
@@ -37,7 +37,7 @@
use crate::{
body::Body,
extract::{connect_info::IntoMakeServiceWithConnectInfo, FromRequest, RequestParts},
extract::{connect_info::IntoMakeServiceWithConnectInfo, FromRequest, FromRequestParts},
response::{IntoResponse, Response},
routing::IntoMakeService,
};
@@ -95,12 +95,12 @@ pub use self::{into_service::IntoService, with_state::WithState};
/// {}
/// ```
#[doc = include_str!("../docs/debugging_handler_type_errors.md")]
pub trait Handler<T, S = (), B = Body>: Clone + Send + Sized + 'static {
pub trait Handler<T, S, B = Body>: Clone + Send + 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, state: Arc<S>, req: Request<B>) -> Self::Future;
fn call(self, req: Request<B>, state: Arc<S>) -> Self::Future;
/// Apply a [`tower::Layer`] to the handler.
///
@@ -162,7 +162,7 @@ pub trait Handler<T, S = (), B = Body>: Clone + Send + Sized + 'static {
}
}
impl<F, Fut, Res, S, B> Handler<(), S, B> for F
impl<F, Fut, Res, S, B> Handler<((),), S, B> for F
where
F: FnOnce() -> Fut + Clone + Send + 'static,
Fut: Future<Output = Res> + Send,
@@ -171,37 +171,48 @@ where
{
type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
fn call(self, _state: Arc<S>, _req: Request<B>) -> Self::Future {
fn call(self, _req: Request<B>, _state: Arc<S>) -> Self::Future {
Box::pin(async move { self().await.into_response() })
}
}
macro_rules! impl_handler {
( $($ty:ident),* $(,)? ) => {
#[allow(non_snake_case)]
impl<F, Fut, S, B, Res, $($ty,)*> Handler<($($ty,)*), S, B> for F
(
[$($ty:ident),*], $last:ident
) => {
#[allow(non_snake_case, unused_mut)]
impl<F, Fut, S, B, Res, M, $($ty,)* $last> Handler<(M, $($ty,)* $last,), S, B> for F
where
F: FnOnce($($ty,)*) -> Fut + Clone + Send + 'static,
F: FnOnce($($ty,)* $last,) -> Fut + Clone + Send + 'static,
Fut: Future<Output = Res> + Send,
B: Send + 'static,
S: Send + Sync + 'static,
Res: IntoResponse,
$( $ty: FromRequest<S, B> + Send,)*
$( $ty: FromRequestParts<S> + Send, )*
$last: FromRequest<S, B, M> + Send,
{
type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
fn call(self, state: Arc<S>, req: Request<B>) -> Self::Future {
fn call(self, req: Request<B>, state: Arc<S>) -> Self::Future {
Box::pin(async move {
let mut req = RequestParts::with_state_arc(state, req);
let (mut parts, body) = req.into_parts();
let state = &state;
$(
let $ty = match $ty::from_request(&mut req).await {
let $ty = match $ty::from_request_parts(&mut parts, state).await {
Ok(value) => value,
Err(rejection) => return rejection.into_response(),
};
)*
let res = self($($ty,)*).await;
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 res = self($($ty,)* $last,).await;
res.into_response()
})
@@ -210,7 +221,31 @@ macro_rules! impl_handler {
};
}
all_the_tuples!(impl_handler);
impl_handler!([], T1);
impl_handler!([T1], T2);
impl_handler!([T1, T2], T3);
impl_handler!([T1, T2, T3], T4);
impl_handler!([T1, T2, T3, T4], T5);
impl_handler!([T1, T2, T3, T4, T5], T6);
impl_handler!([T1, T2, T3, T4, T5, T6], T7);
impl_handler!([T1, T2, T3, T4, T5, T6, T7], T8);
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8], T9);
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9], T10);
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T11);
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], T12);
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], T13);
impl_handler!(
[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13],
T14
);
impl_handler!(
[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14],
T15
);
impl_handler!(
[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15],
T16
);
/// A [`Service`] created from a [`Handler`] by applying a Tower middleware.
///
@@ -259,7 +294,7 @@ where
{
type Future = future::LayeredFuture<B, L::Service>;
fn call(self, state: Arc<S>, req: Request<B>) -> Self::Future {
fn call(self, req: Request<B>, state: Arc<S>) -> Self::Future {
use futures_util::future::{FutureExt, Map};
let svc = self.handler.with_state_arc(state);
+9 -9
View File
@@ -1,14 +1,14 @@
use crate::{
body::{Bytes, HttpBody},
extract::{rejection::*, FromRequest, RequestParts},
extract::{rejection::*, FromRequest},
BoxError,
};
use async_trait::async_trait;
use axum_core::response::{IntoResponse, Response};
use bytes::{BufMut, BytesMut};
use http::{
header::{self, HeaderValue},
StatusCode,
header::{self, HeaderMap, HeaderValue},
Request, StatusCode,
};
use serde::{de::DeserializeOwned, Serialize};
use std::ops::{Deref, DerefMut};
@@ -97,16 +97,16 @@ pub struct Json<T>(pub T);
impl<T, S, B> FromRequest<S, B> for Json<T>
where
T: DeserializeOwned,
B: HttpBody + Send,
B: HttpBody + Send + 'static,
B::Data: Send,
B::Error: Into<BoxError>,
S: Send + Sync,
{
type Rejection = JsonRejection;
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
if json_content_type(req) {
let bytes = Bytes::from_request(req).await?;
async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
if json_content_type(req.headers()) {
let bytes = Bytes::from_request(req, state).await?;
let value = match serde_json::from_slice(&bytes) {
Ok(value) => value,
@@ -137,8 +137,8 @@ where
}
}
fn json_content_type<S, B>(req: &RequestParts<S, B>) -> bool {
let content_type = if let Some(content_type) = req.headers().get(header::CONTENT_TYPE) {
fn json_content_type(headers: &HeaderMap) -> bool {
let content_type = if let Some(content_type) = headers.get(header::CONTENT_TYPE) {
content_type
} else {
return false;
+7 -5
View File
@@ -93,8 +93,8 @@
//!
//! # Extractors
//!
//! An extractor is a type that implements [`FromRequest`]. Extractors is how
//! you pick apart the incoming request to get the parts your handler needs.
//! An extractor is a type that implements [`FromRequest`] or [`FromRequestParts`]. Extractors is
//! how you pick apart the incoming request to get the parts your handler needs.
//!
//! ```rust
//! use axum::extract::{Path, Query, Json};
@@ -302,9 +302,10 @@
//!
//! # Building integrations for axum
//!
//! Libraries authors that want to provide [`FromRequest`] 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.
//! 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
//!
@@ -376,6 +377,7 @@
//! [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
+23 -22
View File
@@ -1,5 +1,5 @@
use crate::{
extract::{FromRequest, RequestParts},
extract::FromRequestParts,
response::{IntoResponse, Response},
};
use futures_util::{future::BoxFuture, ready};
@@ -33,28 +33,27 @@ use tower_service::Service;
///
/// ```rust
/// use axum::{
/// extract::{FromRequest, RequestParts},
/// extract::FromRequestParts,
/// middleware::from_extractor,
/// routing::{get, post},
/// Router,
/// http::{header, StatusCode, request::Parts},
/// };
/// use http::{header, StatusCode};
/// use async_trait::async_trait;
///
/// // An extractor that performs authorization.
/// struct RequireAuth;
///
/// #[async_trait]
/// impl<S, B> FromRequest<S, B> for RequireAuth
/// impl<S> FromRequestParts<S> for RequireAuth
/// where
/// B: Send,
/// S: Send + Sync,
/// {
/// type Rejection = StatusCode;
///
/// async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
/// let auth_header = req
/// .headers()
/// 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());
///
@@ -169,7 +168,7 @@ where
impl<S, E, B> Service<Request<B>> for FromExtractor<S, E>
where
E: FromRequest<(), B> + 'static,
E: FromRequestParts<()> + 'static,
B: Default + Send + 'static,
S: Service<Request<B>> + Clone,
S::Response: IntoResponse,
@@ -185,8 +184,9 @@ where
fn call(&mut self, req: Request<B>) -> Self::Future {
let extract_future = Box::pin(async move {
let mut req = RequestParts::new(req);
let extracted = E::from_request(&mut req).await;
let (mut parts, body) = req.into_parts();
let extracted = E::from_request_parts(&mut parts, &()).await;
let req = Request::from_parts(parts, body);
(req, extracted)
});
@@ -204,7 +204,7 @@ pin_project! {
#[allow(missing_debug_implementations)]
pub struct ResponseFuture<B, S, E>
where
E: FromRequest<(), B>,
E: FromRequestParts<()>,
S: Service<Request<B>>,
{
#[pin]
@@ -217,11 +217,11 @@ pin_project! {
#[project = StateProj]
enum State<B, S, E>
where
E: FromRequest<(), B>,
E: FromRequestParts<()>,
S: Service<Request<B>>,
{
Extracting {
future: BoxFuture<'static, (RequestParts<(), B>, Result<E, E::Rejection>)>,
future: BoxFuture<'static, (Request<B>, Result<E, E::Rejection>)>,
},
Call { #[pin] future: S::Future },
}
@@ -229,7 +229,7 @@ pin_project! {
impl<B, S, E> Future for ResponseFuture<B, S, E>
where
E: FromRequest<(), B>,
E: FromRequestParts<()>,
S: Service<Request<B>>,
S::Response: IntoResponse,
B: Default,
@@ -247,7 +247,6 @@ where
match extracted {
Ok(_) => {
let mut svc = this.svc.take().expect("future polled after completion");
let req = req.try_into_request().unwrap_or_default();
let future = svc.call(req);
State::Call { future }
}
@@ -273,23 +272,25 @@ where
mod tests {
use super::*;
use crate::{handler::Handler, routing::get, test_helpers::*, Router};
use http::{header, StatusCode};
use http::{header, request::Parts, StatusCode};
#[tokio::test]
async fn test_from_extractor() {
struct RequireAuth;
#[async_trait::async_trait]
impl<S, B> FromRequest<S, B> for RequireAuth
impl<S> FromRequestParts<S> for RequireAuth
where
B: Send,
S: Send + Sync,
{
type Rejection = StatusCode;
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
if let Some(auth) = req
.headers()
async fn from_request_parts(
parts: &mut Parts,
_state: &S,
) -> Result<Self, Self::Rejection> {
if let Some(auth) = parts
.headers
.get(header::AUTHORIZATION)
.and_then(|v| v.to_str().ok())
{
+45 -10
View File
@@ -1,5 +1,5 @@
use crate::response::{IntoResponse, Response};
use axum_core::extract::{FromRequest, RequestParts};
use axum_core::extract::{FromRequest, FromRequestParts};
use futures_util::future::BoxFuture;
use http::Request;
use std::{
@@ -249,12 +249,15 @@ where
}
macro_rules! impl_service {
( $($ty:ident),* $(,)? ) => {
#[allow(non_snake_case)]
impl<F, Fut, Out, S, B, $($ty,)*> Service<Request<B>> for FromFn<F, S, ($($ty,)*)>
(
[$($ty:ident),*], $last:ident
) => {
#[allow(non_snake_case, unused_mut)]
impl<F, Fut, Out, S, B, $($ty,)* $last> Service<Request<B>> for FromFn<F, S, ($($ty,)* $last,)>
where
F: FnMut($($ty),*, Next<B>) -> Fut + Clone + Send + 'static,
$( $ty: FromRequest<(), B> + Send, )*
F: FnMut($($ty,)* $last, Next<B>) -> Fut + Clone + Send + 'static,
$( $ty: FromRequestParts<()> + Send, )*
$last: FromRequest<(), B> + Send,
Fut: Future<Output = Out> + Send + 'static,
Out: IntoResponse + 'static,
S: Service<Request<B>, Error = Infallible>
@@ -280,21 +283,29 @@ macro_rules! impl_service {
let mut f = self.f.clone();
let future = Box::pin(async move {
let mut parts = RequestParts::new(req);
let (mut parts, body) = req.into_parts();
$(
let $ty = match $ty::from_request(&mut parts).await {
let $ty = match $ty::from_request_parts(&mut parts, &()).await {
Ok(value) => value,
Err(rejection) => return rejection.into_response(),
};
)*
let req = Request::from_parts(parts, body);
let $last = match $last::from_request(req, &()).await {
Ok(value) => value,
Err(rejection) => return rejection.into_response(),
};
let inner = ServiceBuilder::new()
.boxed_clone()
.map_response(IntoResponse::into_response)
.service(ready_inner);
let next = Next { inner };
f($($ty),*, next).await.into_response()
f($($ty,)* $last, next).await.into_response()
});
ResponseFuture {
@@ -305,7 +316,31 @@ macro_rules! impl_service {
};
}
all_the_tuples!(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, T> fmt::Debug for FromFn<F, S, T>
where
+5 -5
View File
@@ -1,7 +1,8 @@
use crate::extract::{FromRequest, RequestParts};
use crate::extract::FromRequestParts;
use async_trait::async_trait;
use axum_core::response::{IntoResponse, IntoResponseParts, Response, ResponseParts};
use headers::HeaderMapExt;
use http::request::Parts;
use std::{convert::Infallible, ops::Deref};
/// Extractor and response that works with typed header values from [`headers`].
@@ -52,16 +53,15 @@ use std::{convert::Infallible, ops::Deref};
pub struct TypedHeader<T>(pub T);
#[async_trait]
impl<T, S, B> FromRequest<S, B> for TypedHeader<T>
impl<T, S> FromRequestParts<S> for TypedHeader<T>
where
T: headers::Header,
B: Send,
S: Send + Sync,
{
type Rejection = TypedHeaderRejection;
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
match req.headers().typed_try_get::<T>() {
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
match parts.headers.typed_try_get::<T>() {
Ok(Some(value)) => Ok(Self(value)),
Ok(None) => Err(TypedHeaderRejection {
name: T::name(),