Automatically handle http_body::LengthLimitError (#1048)

* Automatically handle `http_body::LengthLimitError`

* add tower-http dev dep to axum-core

* just make it a link

* Make `FailedToBufferBody` an enum

* Fix tests now that tower-http handles `Content-Length`

* Bring back explanation for `LengthLimitError`

* remove todo we likely can't fix

* improve wording in docs
This commit is contained in:
David Pedersen
2022-06-08 13:47:57 +00:00
committed by GitHub
parent 73b1bafbf8
commit ef750799fa
8 changed files with 105 additions and 8 deletions
+1 -1
View File
@@ -81,7 +81,7 @@ features = [
]
[dev-dependencies.tower-http]
version = "0.3.0"
version = "0.3.4"
features = ["full"]
[package.metadata.docs.rs]
+3
View File
@@ -29,6 +29,9 @@ use std::ops::Deref;
/// ```
///
/// This requires the request to have a `Content-Length` header.
///
/// If you want to limit the size of request bodies without requiring a `Content-Length` header,
/// consider using [`tower_http::limit::RequestBodyLimitLayer`].
#[derive(Debug, Clone)]
pub struct ContentLengthLimit<T, const N: u64>(pub T);
+56 -2
View File
@@ -8,7 +8,7 @@ use crate::{
test_helpers::*,
BoxError, Json, Router,
};
use http::{Method, Request, Response, StatusCode, Uri};
use http::{header::CONTENT_LENGTH, HeaderMap, Method, Request, Response, StatusCode, Uri};
use hyper::Body;
use serde::Deserialize;
use serde_json::{json, Value};
@@ -20,7 +20,7 @@ use std::{
time::Duration,
};
use tower::{service_fn, timeout::TimeoutLayer, ServiceBuilder, ServiceExt};
use tower_http::auth::RequireAuthorizationLayer;
use tower_http::{auth::RequireAuthorizationLayer, limit::RequestBodyLimitLayer};
use tower_service::Service;
mod fallback;
@@ -699,3 +699,57 @@ async fn routes_must_start_with_slash() {
let app = Router::new().route(":foo", get(|| async {}));
TestClient::new(app);
}
#[tokio::test]
async fn limited_body_with_content_length() {
const LIMIT: usize = 3;
let app = Router::new()
.route(
"/",
post(|headers: HeaderMap, _body: Bytes| async move {
assert!(headers.get(CONTENT_LENGTH).is_some());
}),
)
.layer(RequestBodyLimitLayer::new(LIMIT));
let client = TestClient::new(app);
let res = client.post("/").body("a".repeat(LIMIT)).send().await;
assert_eq!(res.status(), StatusCode::OK);
let res = client.post("/").body("a".repeat(LIMIT * 2)).send().await;
assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE);
}
#[tokio::test]
async fn limited_body_with_streaming_body() {
const LIMIT: usize = 3;
let app = Router::new()
.route(
"/",
post(|headers: HeaderMap, _body: Bytes| async move {
assert!(headers.get(CONTENT_LENGTH).is_none());
}),
)
.layer(RequestBodyLimitLayer::new(LIMIT));
let client = TestClient::new(app);
let stream = futures_util::stream::iter(vec![Ok::<_, hyper::Error>("a".repeat(LIMIT))]);
let res = client
.post("/")
.body(Body::wrap_stream(stream))
.send()
.await;
assert_eq!(res.status(), StatusCode::OK);
let stream = futures_util::stream::iter(vec![Ok::<_, hyper::Error>("a".repeat(LIMIT * 2))]);
let res = client
.post("/")
.body(Body::wrap_stream(stream))
.send()
.await;
assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE);
}
+1
View File
@@ -118,6 +118,7 @@ impl RequestBuilder {
}
}
#[derive(Debug)]
pub(crate) struct TestResponse {
response: reqwest::Response,
}