mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-23 00:00:15 +02:00
* Reorganize tests This breaks up the large `crate::tests` module by moving some of the tests into a place that makes more sense. For example tests of JSON serialization are moved to the `crate::json` module. The remaining routing tests have been moved to `crate::routing::tests`. I generally prefer having tests close to the code they're testing. Makes it easier to see how/if something is tested. * Try pinning to older version of async-graphql * Revert "Try pinning to older version of async-graphql" This reverts commit 2e2cae7d12f5e433a16d6607497d587863f04384. * don't test examples on 1.54 on CI * move ci steps around a bit
134 lines
3.5 KiB
Rust
134 lines
3.5 KiB
Rust
use crate::response::IntoResponse;
|
|
|
|
use super::{rejection::*, FromRequest, RequestParts};
|
|
use async_trait::async_trait;
|
|
use std::ops::Deref;
|
|
|
|
/// Extractor that will reject requests with a body larger than some size.
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```rust,no_run
|
|
/// use axum::{
|
|
/// extract::ContentLengthLimit,
|
|
/// routing::post,
|
|
/// Router,
|
|
/// };
|
|
///
|
|
/// async fn handler(body: ContentLengthLimit<String, 1024>) {
|
|
/// // ...
|
|
/// }
|
|
///
|
|
/// let app = Router::new().route("/", post(handler));
|
|
/// # async {
|
|
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
|
/// # };
|
|
/// ```
|
|
///
|
|
/// This requires the request to have a `Content-Length` header.
|
|
#[derive(Debug, Clone)]
|
|
pub struct ContentLengthLimit<T, const N: u64>(pub T);
|
|
|
|
#[async_trait]
|
|
impl<T, B, const N: u64> FromRequest<B> for ContentLengthLimit<T, N>
|
|
where
|
|
T: FromRequest<B>,
|
|
T::Rejection: IntoResponse,
|
|
B: Send,
|
|
{
|
|
type Rejection = ContentLengthLimitRejection<T::Rejection>;
|
|
|
|
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
|
let content_length = req
|
|
.headers()
|
|
.ok_or(ContentLengthLimitRejection::HeadersAlreadyExtracted(
|
|
HeadersAlreadyExtracted,
|
|
))?
|
|
.get(http::header::CONTENT_LENGTH);
|
|
|
|
let content_length =
|
|
content_length.and_then(|value| value.to_str().ok()?.parse::<u64>().ok());
|
|
|
|
if let Some(length) = content_length {
|
|
if length > N {
|
|
return Err(ContentLengthLimitRejection::PayloadTooLarge(
|
|
PayloadTooLarge,
|
|
));
|
|
}
|
|
} else {
|
|
return Err(ContentLengthLimitRejection::LengthRequired(LengthRequired));
|
|
};
|
|
|
|
let value = T::from_request(req)
|
|
.await
|
|
.map_err(ContentLengthLimitRejection::Inner)?;
|
|
|
|
Ok(Self(value))
|
|
}
|
|
}
|
|
|
|
impl<T, const N: u64> Deref for ContentLengthLimit<T, N> {
|
|
type Target = T;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::{routing::post, test_helpers::*, Router};
|
|
use bytes::Bytes;
|
|
use http::StatusCode;
|
|
use serde::Deserialize;
|
|
|
|
#[tokio::test]
|
|
async fn body_with_length_limit() {
|
|
use std::iter::repeat;
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct Input {
|
|
foo: String,
|
|
}
|
|
|
|
const LIMIT: u64 = 8;
|
|
|
|
let app = Router::new().route(
|
|
"/",
|
|
post(|_body: ContentLengthLimit<Bytes, LIMIT>| async {}),
|
|
);
|
|
|
|
let client = TestClient::new(app);
|
|
let res = client
|
|
.post("/")
|
|
.body(repeat(0_u8).take((LIMIT - 1) as usize).collect::<Vec<_>>())
|
|
.send()
|
|
.await;
|
|
assert_eq!(res.status(), StatusCode::OK);
|
|
|
|
let res = client
|
|
.post("/")
|
|
.body(repeat(0_u8).take(LIMIT as usize).collect::<Vec<_>>())
|
|
.send()
|
|
.await;
|
|
assert_eq!(res.status(), StatusCode::OK);
|
|
|
|
let res = client
|
|
.post("/")
|
|
.body(repeat(0_u8).take((LIMIT + 1) as usize).collect::<Vec<_>>())
|
|
.send()
|
|
.await;
|
|
assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE);
|
|
|
|
let res = client
|
|
.post("/")
|
|
.body(reqwest::Body::wrap_stream(futures_util::stream::iter(
|
|
vec![Ok::<_, std::io::Error>(bytes::Bytes::new())],
|
|
)))
|
|
.send()
|
|
.await;
|
|
assert_eq!(res.status(), StatusCode::LENGTH_REQUIRED);
|
|
}
|
|
}
|