From c412cb45857aeab491389d467fb0fa5c83e0e43b Mon Sep 17 00:00:00 2001 From: Marek Kuskowski <50183564+nylonicious@users.noreply.github.com> Date: Wed, 21 Sep 2022 22:20:58 +0200 Subject: [PATCH 01/15] feat: remove ContentLengthLimit --- axum/src/extract/content_length_limit.rs | 274 ----------------------- axum/src/extract/mod.rs | 2 - axum/src/extract/rejection.rs | 59 ----- examples/key-value-store/src/main.rs | 9 +- examples/multipart-form/src/main.rs | 12 +- 5 files changed, 6 insertions(+), 350 deletions(-) delete mode 100644 axum/src/extract/content_length_limit.rs diff --git a/axum/src/extract/content_length_limit.rs b/axum/src/extract/content_length_limit.rs deleted file mode 100644 index f5775842b8..0000000000 --- a/axum/src/extract/content_length_limit.rs +++ /dev/null @@ -1,274 +0,0 @@ -use super::{rejection::*, FromRequest}; -use async_trait::async_trait; -use axum_core::{extract::FromRequestParts, response::IntoResponse}; -use http::{request::Parts, Method, Request}; -use http_body::Limited; -use std::ops::Deref; - -/// Extractor that will reject requests with a body larger than some size. -/// -/// `GET`, `HEAD`, and `OPTIONS` requests are rejected if they have a `Content-Length` header, -/// otherwise they're accepted without the body being checked. -/// -/// Note: `ContentLengthLimit` can wrap types that extract the body (for example, [`Form`] or [`Json`]) -/// if that is the case, the inner type will consume the request's body, which means the -/// `ContentLengthLimit` must come *last* if the handler uses several extractors. See -/// ["the order of extractors"][order-of-extractors] -/// -/// [order-of-extractors]: crate::extract#the-order-of-extractors -/// [`Form`]: crate::form::Form -/// [`Json`]: crate::json::Json -/// -/// # Example -/// -/// ```rust,no_run -/// use axum::{ -/// extract::ContentLengthLimit, -/// routing::post, -/// Router, -/// }; -/// -/// async fn handler(body: ContentLengthLimit) { -/// // ... -/// } -/// -/// let app = Router::new().route("/", post(handler)); -/// # async { -/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); -/// # }; -/// ``` -#[derive(Debug, Clone)] -pub struct ContentLengthLimit(pub T); - -#[async_trait] -impl FromRequest for ContentLengthLimit -where - T: FromRequest + FromRequest, Rejection = R>, - R: IntoResponse + Send, - B: Send + 'static, - S: Send + Sync, -{ - type Rejection = ContentLengthLimitRejection; - - async fn from_request(req: Request, state: &S) -> Result { - let (parts, body) = req.into_parts(); - - let value = if let Some(err) = validate::(&parts).err() { - match err { - RequestValidationError::LengthRequiredStream => { - // `Limited` supports limiting streams, so use that instead since this is a - // streaming request - let body = Limited::new(body, N as usize); - let req = Request::from_parts(parts, body); - T::from_request(req, state) - .await - .map_err(ContentLengthLimitRejection::Inner)? - } - other => return Err(other.into()), - } - } else { - let req = Request::from_parts(parts, body); - T::from_request(req, state) - .await - .map_err(ContentLengthLimitRejection::Inner)? - }; - - Ok(Self(value)) - } -} - -#[async_trait] -impl FromRequestParts for ContentLengthLimit -where - T: FromRequestParts, - T::Rejection: IntoResponse, - S: Send + Sync, -{ - type Rejection = ContentLengthLimitRejection; - - async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { - validate::(parts)?; - - let value = T::from_request_parts(parts, state) - .await - .map_err(ContentLengthLimitRejection::Inner)?; - - Ok(Self(value)) - } -} - -fn validate(parts: &Parts) -> Result<(), RequestValidationError> { - let content_length = parts - .headers - .get(http::header::CONTENT_LENGTH) - .and_then(|value| value.to_str().ok()?.parse::().ok()); - - match (content_length, &parts.method) { - (content_length, &(Method::GET | Method::HEAD | Method::OPTIONS)) => { - if content_length.is_some() { - return Err(RequestValidationError::ContentLengthNotAllowed); - } else if parts - .headers - .get(http::header::TRANSFER_ENCODING) - .map_or(false, |value| value.as_bytes() == b"chunked") - { - return Err(RequestValidationError::LengthRequiredChunkedHeadOrGet); - } - } - (Some(content_length), _) if content_length > N => { - return Err(RequestValidationError::PayloadTooLarge); - } - (None, _) => { - return Err(RequestValidationError::LengthRequiredStream); - } - _ => {} - } - - Ok(()) -} - -impl Deref for ContentLengthLimit { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -/// Similar to `ContentLengthLimitRejection` but more fine grained in that we can tell the -/// difference between `LengthRequiredStream` and `LengthRequiredChunkedHeadOrGet` -enum RequestValidationError { - PayloadTooLarge, - LengthRequiredStream, - LengthRequiredChunkedHeadOrGet, - ContentLengthNotAllowed, -} - -impl From for ContentLengthLimitRejection { - fn from(inner: RequestValidationError) -> Self { - match inner { - RequestValidationError::PayloadTooLarge => Self::PayloadTooLarge(PayloadTooLarge), - RequestValidationError::LengthRequiredStream - | RequestValidationError::LengthRequiredChunkedHeadOrGet => { - Self::LengthRequired(LengthRequired) - } - RequestValidationError::ContentLengthNotAllowed => { - Self::ContentLengthNotAllowed(ContentLengthNotAllowed) - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - body::Bytes, - routing::{get, post}, - test_helpers::*, - Router, - }; - use http::StatusCode; - use serde::Deserialize; - - #[tokio::test] - async fn body_with_length_limit() { - use std::iter::repeat; - - #[derive(Debug, Deserialize)] - #[allow(dead_code)] - struct Input { - foo: String, - } - - const LIMIT: u64 = 8; - - let app = Router::new().route( - "/", - post(|_body: ContentLengthLimit| async {}), - ); - - let client = TestClient::new(app); - let res = client - .post("/") - .body(repeat(0_u8).take((LIMIT - 1) as usize).collect::>()) - .send() - .await; - assert_eq!(res.status(), StatusCode::OK); - - let res = client - .post("/") - .body(repeat(0_u8).take(LIMIT as usize).collect::>()) - .send() - .await; - assert_eq!(res.status(), StatusCode::OK); - - let res = client - .post("/") - .body(repeat(0_u8).take((LIMIT + 1) as usize).collect::>()) - .send() - .await; - assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE); - - let chunk = repeat(0_u8).take(LIMIT as usize).collect::(); - let res = client - .post("/") - .body(reqwest::Body::wrap_stream(futures_util::stream::iter( - vec![Ok::<_, std::io::Error>(chunk)], - ))) - .send() - .await; - assert_eq!(res.status(), StatusCode::OK); - - let chunk = repeat(0_u8).take((LIMIT + 1) as usize).collect::(); - let res = client - .post("/") - .body(reqwest::Body::wrap_stream(futures_util::stream::iter( - vec![Ok::<_, std::io::Error>(chunk)], - ))) - .send() - .await; - assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE); - } - - #[tokio::test] - async fn get_request_without_content_length_is_accepted() { - let app = Router::new().route("/", get(|_body: ContentLengthLimit| async {})); - - let client = TestClient::new(app); - - let res = client.get("/").send().await; - assert_eq!(res.status(), StatusCode::OK); - } - - #[tokio::test] - async fn get_request_with_content_length_is_rejected() { - let app = Router::new().route("/", get(|_body: ContentLengthLimit| async {})); - - let client = TestClient::new(app); - - let res = client - .get("/") - .header("content-length", 3) - .body("foo") - .send() - .await; - assert_eq!(res.status(), StatusCode::BAD_REQUEST); - } - - #[tokio::test] - async fn get_request_with_chunked_encoding_is_rejected() { - let app = Router::new().route("/", get(|_body: ContentLengthLimit| async {})); - - let client = TestClient::new(app); - - let res = client - .get("/") - .header("transfer-encoding", "chunked") - .body("3\r\nfoo\r\n0\r\n\r\n") - .send() - .await; - - assert_eq!(res.status(), StatusCode::LENGTH_REQUIRED); - } -} diff --git a/axum/src/extract/mod.rs b/axum/src/extract/mod.rs index c6e8bf53fc..698f8b175b 100644 --- a/axum/src/extract/mod.rs +++ b/axum/src/extract/mod.rs @@ -9,7 +9,6 @@ pub mod rejection; #[cfg(feature = "ws")] pub mod ws; -mod content_length_limit; mod host; mod raw_query; mod request_parts; @@ -25,7 +24,6 @@ pub use axum_macros::{FromRequest, FromRequestParts}; #[allow(deprecated)] pub use self::{ connect_info::ConnectInfo, - content_length_limit::ContentLengthLimit, host::Host, path::Path, raw_query::RawQuery, diff --git a/axum/src/extract/rejection.rs b/axum/src/extract/rejection.rs index d6f11c31fa..7a9cbdf1d4 100644 --- a/axum/src/extract/rejection.rs +++ b/axum/src/extract/rejection.rs @@ -216,64 +216,5 @@ composite_rejection! { } } -/// Rejection used for [`ContentLengthLimit`](super::ContentLengthLimit). -/// -/// Contains one variant for each way the -/// [`ContentLengthLimit`](super::ContentLengthLimit) extractor can fail. -#[derive(Debug)] -#[non_exhaustive] -pub enum ContentLengthLimitRejection { - #[allow(missing_docs)] - PayloadTooLarge(PayloadTooLarge), - #[allow(missing_docs)] - LengthRequired(LengthRequired), - #[allow(missing_docs)] - ContentLengthNotAllowed(ContentLengthNotAllowed), - #[allow(missing_docs)] - Inner(T), -} - -impl IntoResponse for ContentLengthLimitRejection -where - T: IntoResponse, -{ - fn into_response(self) -> Response { - match self { - Self::PayloadTooLarge(inner) => inner.into_response(), - Self::LengthRequired(inner) => inner.into_response(), - Self::ContentLengthNotAllowed(inner) => inner.into_response(), - Self::Inner(inner) => inner.into_response(), - } - } -} - -impl std::fmt::Display for ContentLengthLimitRejection -where - T: std::fmt::Display, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::PayloadTooLarge(inner) => inner.fmt(f), - Self::LengthRequired(inner) => inner.fmt(f), - Self::ContentLengthNotAllowed(inner) => inner.fmt(f), - Self::Inner(inner) => inner.fmt(f), - } - } -} - -impl std::error::Error for ContentLengthLimitRejection -where - T: std::error::Error + 'static, -{ - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::PayloadTooLarge(inner) => Some(inner), - Self::LengthRequired(inner) => Some(inner), - Self::ContentLengthNotAllowed(inner) => Some(inner), - Self::Inner(inner) => Some(inner), - } - } -} - #[cfg(feature = "headers")] pub use crate::typed_header::{TypedHeaderRejection, TypedHeaderRejectionReason}; diff --git a/examples/key-value-store/src/main.rs b/examples/key-value-store/src/main.rs index 4f46af7430..1ab9e3f7e5 100644 --- a/examples/key-value-store/src/main.rs +++ b/examples/key-value-store/src/main.rs @@ -9,7 +9,7 @@ use axum::{ body::Bytes, error_handling::HandleErrorLayer, - extract::{ContentLengthLimit, Path, State}, + extract::{DefaultBodyLimit, Path, State}, handler::Handler, http::StatusCode, response::IntoResponse, @@ -61,6 +61,7 @@ async fn main() { .load_shed() .concurrency_limit(1024) .timeout(Duration::from_secs(10)) + .layer(DefaultBodyLimit::max(1024 * 5_000 /* ~5mb */)) .layer(TraceLayer::new_for_http()) .into_inner(), ); @@ -94,11 +95,7 @@ async fn kv_get( } } -async fn kv_set( - Path(key): Path, - State(state): State, - ContentLengthLimit(bytes): ContentLengthLimit, // ~5mb -) { +async fn kv_set(Path(key): Path, State(state): State, bytes: Bytes) { state.write().unwrap().db.insert(key, bytes); } diff --git a/examples/multipart-form/src/main.rs b/examples/multipart-form/src/main.rs index 7c6c141088..4528bad10d 100644 --- a/examples/multipart-form/src/main.rs +++ b/examples/multipart-form/src/main.rs @@ -5,7 +5,7 @@ //! ``` use axum::{ - extract::{ContentLengthLimit, Multipart}, + extract::{DefaultBodyLimit, Multipart}, response::Html, routing::get, Router, @@ -26,6 +26,7 @@ async fn main() { // build our application with some routes let app = Router::new() .route("/", get(show_form).post(accept_form)) + .layer(DefaultBodyLimit::max(250 * 1024 * 1024 /* 250mb */)) .layer(tower_http::trace::TraceLayer::new_for_http()); // run it with hyper @@ -58,14 +59,7 @@ async fn show_form() -> Html<&'static str> { ) } -async fn accept_form( - ContentLengthLimit(mut multipart): ContentLengthLimit< - Multipart, - { - 250 * 1024 * 1024 /* 250mb */ - }, - >, -) { +async fn accept_form(mut multipart: Multipart) { while let Some(field) = multipart.next_field().await.unwrap() { let name = field.name().unwrap().to_string(); let file_name = field.file_name().unwrap().to_string(); From dc3434dafbe0c1939dfaa84da92856fc33a3875e Mon Sep 17 00:00:00 2001 From: Marek Kuskowski <50183564+nylonicious@users.noreply.github.com> Date: Thu, 22 Sep 2022 02:22:46 +0200 Subject: [PATCH 02/15] feat: remove ContentLengthLimit rejections --- axum/src/extract/rejection.rs | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/axum/src/extract/rejection.rs b/axum/src/extract/rejection.rs index 7a9cbdf1d4..01f8d84918 100644 --- a/axum/src/extract/rejection.rs +++ b/axum/src/extract/rejection.rs @@ -47,30 +47,6 @@ define_rejection! { pub struct MissingExtension(Error); } -define_rejection! { - #[status = PAYLOAD_TOO_LARGE] - #[body = "Request payload is too large"] - /// Rejection type for [`ContentLengthLimit`](super::ContentLengthLimit) if - /// the request body is too large. - pub struct PayloadTooLarge; -} - -define_rejection! { - #[status = LENGTH_REQUIRED] - #[body = "Content length header is required"] - /// Rejection type for [`ContentLengthLimit`](super::ContentLengthLimit) if - /// the request is missing the `Content-Length` header or it is invalid. - pub struct LengthRequired; -} - -define_rejection! { - #[status = BAD_REQUEST] - #[body = "`GET`, `HEAD`, `OPTIONS` requests are not allowed to have a `Content-Length` header"] - /// Rejection type for [`ContentLengthLimit`](super::ContentLengthLimit) if - /// the request is `GET`, `HEAD`, or `OPTIONS` and has a `Content-Length` header. - pub struct ContentLengthNotAllowed; -} - define_rejection! { #[status = INTERNAL_SERVER_ERROR] #[body = "No paths parameters found for matched route"] From a57af7c8185ab8400774940221f6c00e12572e35 Mon Sep 17 00:00:00 2001 From: Marek Kuskowski <50183564+nylonicious@users.noreply.github.com> Date: Thu, 22 Sep 2022 03:00:28 +0200 Subject: [PATCH 03/15] fix: update multipart docs --- axum/src/extract/multipart.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axum/src/extract/multipart.rs b/axum/src/extract/multipart.rs index 9d2a47c92d..83f01de85c 100644 --- a/axum/src/extract/multipart.rs +++ b/axum/src/extract/multipart.rs @@ -49,7 +49,7 @@ use std::{ /// ``` /// /// For security reasons it's recommended to combine this with -/// [`ContentLengthLimit`](super::ContentLengthLimit) to limit the size of the request payload. +/// [`DefaultLengthLimit`](super::DefaultLengthLimit) to limit the size of the request payload. #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))] #[derive(Debug)] pub struct Multipart { From 22eee13a7ab493390dec6f812891192598b40e16 Mon Sep 17 00:00:00 2001 From: Marek Kuskowski <50183564+nylonicious@users.noreply.github.com> Date: Thu, 22 Sep 2022 03:07:57 +0200 Subject: [PATCH 04/15] fix: typo --- axum/src/extract/multipart.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axum/src/extract/multipart.rs b/axum/src/extract/multipart.rs index 83f01de85c..96192cd406 100644 --- a/axum/src/extract/multipart.rs +++ b/axum/src/extract/multipart.rs @@ -49,7 +49,7 @@ use std::{ /// ``` /// /// For security reasons it's recommended to combine this with -/// [`DefaultLengthLimit`](super::DefaultLengthLimit) to limit the size of the request payload. +/// [`DefaultBodyLimit`](super::DefaultBodyLimit) to limit the size of the request payload. #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))] #[derive(Debug)] pub struct Multipart { From cd382a5624535e976aca43599a3459f318535c6b Mon Sep 17 00:00:00 2001 From: Marek Kuskowski <50183564+nylonicious@users.noreply.github.com> Date: Fri, 23 Sep 2022 03:12:43 +0200 Subject: [PATCH 05/15] feat: add wip extractor code --- axum-core/src/extract/request_parts.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/axum-core/src/extract/request_parts.rs b/axum-core/src/extract/request_parts.rs index fc151a2da0..d283208c77 100644 --- a/axum-core/src/extract/request_parts.rs +++ b/axum-core/src/extract/request_parts.rs @@ -94,6 +94,16 @@ where .await .map_err(FailedToBufferBody::from_err)?, Some(DefaultBodyLimitKind::Limit(limit)) => { + let content_length: usize = req + .headers() + .get(http::header::CONTENT_LENGTH) + .and_then(|val| val.to_str().ok()?.parse().ok()) + .unwrap(); + + if content_length > limit { + todo!() + } + let body = http_body::Limited::new(req.into_body(), limit); crate::body::to_bytes(body) .await From 2f65d8d0676b30e3f5876fd192ee58551bd284ba Mon Sep 17 00:00:00 2001 From: Marek Kuskowski <50183564+nylonicious@users.noreply.github.com> Date: Fri, 23 Sep 2022 10:53:47 +0200 Subject: [PATCH 06/15] feat: revert "feat: add wip extractor code" --- axum-core/src/extract/request_parts.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/axum-core/src/extract/request_parts.rs b/axum-core/src/extract/request_parts.rs index d283208c77..fc151a2da0 100644 --- a/axum-core/src/extract/request_parts.rs +++ b/axum-core/src/extract/request_parts.rs @@ -94,16 +94,6 @@ where .await .map_err(FailedToBufferBody::from_err)?, Some(DefaultBodyLimitKind::Limit(limit)) => { - let content_length: usize = req - .headers() - .get(http::header::CONTENT_LENGTH) - .and_then(|val| val.to_str().ok()?.parse().ok()) - .unwrap(); - - if content_length > limit { - todo!() - } - let body = http_body::Limited::new(req.into_body(), limit); crate::body::to_bytes(body) .await From 0e1d0ef0034c0b0a9f9bc769c288e0b69c856012 Mon Sep 17 00:00:00 2001 From: Marek Kuskowski <50183564+nylonicious@users.noreply.github.com> Date: Fri, 23 Sep 2022 11:02:34 +0200 Subject: [PATCH 07/15] fix: update Multipart docs --- axum/src/extract/multipart.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/axum/src/extract/multipart.rs b/axum/src/extract/multipart.rs index 96192cd406..65813abdbd 100644 --- a/axum/src/extract/multipart.rs +++ b/axum/src/extract/multipart.rs @@ -49,7 +49,8 @@ use std::{ /// ``` /// /// For security reasons it's recommended to combine this with -/// [`DefaultBodyLimit`](super::DefaultBodyLimit) to limit the size of the request payload. +/// [`RequestBodyLimitLayer`](tower_http::limit::RequestBodyLimitLayer) +/// to limit the size of the request payload. #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))] #[derive(Debug)] pub struct Multipart { From 43d9908339f70ccd1dea1a18fc9fff6931e462c4 Mon Sep 17 00:00:00 2001 From: Marek Kuskowski <50183564+nylonicious@users.noreply.github.com> Date: Fri, 23 Sep 2022 11:03:20 +0200 Subject: [PATCH 08/15] fix: update examples --- examples/key-value-store/src/main.rs | 7 ++++--- examples/multipart-form/src/main.rs | 11 ++++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/examples/key-value-store/src/main.rs b/examples/key-value-store/src/main.rs index 1ab9e3f7e5..37fae68ca7 100644 --- a/examples/key-value-store/src/main.rs +++ b/examples/key-value-store/src/main.rs @@ -9,7 +9,7 @@ use axum::{ body::Bytes, error_handling::HandleErrorLayer, - extract::{DefaultBodyLimit, Path, State}, + extract::{Path, State}, handler::Handler, http::StatusCode, response::IntoResponse, @@ -25,7 +25,8 @@ use std::{ }; use tower::{BoxError, ServiceBuilder}; use tower_http::{ - auth::RequireAuthorizationLayer, compression::CompressionLayer, trace::TraceLayer, + auth::RequireAuthorizationLayer, compression::CompressionLayer, limit::RequestBodyLimitLayer, + trace::TraceLayer, }; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; @@ -61,7 +62,7 @@ async fn main() { .load_shed() .concurrency_limit(1024) .timeout(Duration::from_secs(10)) - .layer(DefaultBodyLimit::max(1024 * 5_000 /* ~5mb */)) + .layer(RequestBodyLimitLayer::new(1024 * 5_000 /* ~5mb */)) .layer(TraceLayer::new_for_http()) .into_inner(), ); diff --git a/examples/multipart-form/src/main.rs b/examples/multipart-form/src/main.rs index 4528bad10d..f549c49b9a 100644 --- a/examples/multipart-form/src/main.rs +++ b/examples/multipart-form/src/main.rs @@ -4,12 +4,7 @@ //! cd examples && cargo run -p example-multipart-form //! ``` -use axum::{ - extract::{DefaultBodyLimit, Multipart}, - response::Html, - routing::get, - Router, -}; +use axum::{extract::Multipart, response::Html, routing::get, Router}; use std::net::SocketAddr; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; @@ -26,7 +21,9 @@ async fn main() { // build our application with some routes let app = Router::new() .route("/", get(show_form).post(accept_form)) - .layer(DefaultBodyLimit::max(250 * 1024 * 1024 /* 250mb */)) + .layer(RequestBodyLimitLayer::new( + 250 * 1024 * 1024, /* 250mb */ + )) .layer(tower_http::trace::TraceLayer::new_for_http()); // run it with hyper From b9bcc3dd53742369955ad0e6b4e20b413fc9fc8a Mon Sep 17 00:00:00 2001 From: Marek Kuskowski <50183564+nylonicious@users.noreply.github.com> Date: Fri, 23 Sep 2022 11:09:08 +0200 Subject: [PATCH 09/15] fix: missing import in an example --- examples/multipart-form/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/multipart-form/src/main.rs b/examples/multipart-form/src/main.rs index f549c49b9a..21f55ef905 100644 --- a/examples/multipart-form/src/main.rs +++ b/examples/multipart-form/src/main.rs @@ -6,6 +6,7 @@ use axum::{extract::Multipart, response::Html, routing::get, Router}; use std::net::SocketAddr; +use tower::limit::RequestBodyLimitLayer; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; #[tokio::main] From 976890c46d1939941905f9d71af935d512cb928b Mon Sep 17 00:00:00 2001 From: Marek Kuskowski <50183564+nylonicious@users.noreply.github.com> Date: Fri, 23 Sep 2022 11:13:48 +0200 Subject: [PATCH 10/15] fix: broken import yet again --- examples/multipart-form/Cargo.toml | 2 +- examples/multipart-form/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/multipart-form/Cargo.toml b/examples/multipart-form/Cargo.toml index fc8f971a86..392c52c46f 100644 --- a/examples/multipart-form/Cargo.toml +++ b/examples/multipart-form/Cargo.toml @@ -7,6 +7,6 @@ publish = false [dependencies] axum = { path = "../../axum", features = ["multipart"] } tokio = { version = "1.0", features = ["full"] } -tower-http = { version = "0.3.0", features = ["trace"] } +tower-http = { version = "0.3.0", features = ["limit", "trace"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/examples/multipart-form/src/main.rs b/examples/multipart-form/src/main.rs index 21f55ef905..8fb3dad3ef 100644 --- a/examples/multipart-form/src/main.rs +++ b/examples/multipart-form/src/main.rs @@ -6,7 +6,7 @@ use axum::{extract::Multipart, response::Html, routing::get, Router}; use std::net::SocketAddr; -use tower::limit::RequestBodyLimitLayer; +use tower_http::limit::RequestBodyLimitLayer; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; #[tokio::main] From fdc2c0387a272a457199f03b133d5e3e5d760bd6 Mon Sep 17 00:00:00 2001 From: Marek Kuskowski <50183564+nylonicious@users.noreply.github.com> Date: Fri, 23 Sep 2022 11:57:28 +0200 Subject: [PATCH 11/15] fix: disable default body limit for example --- examples/multipart-form/src/main.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/multipart-form/src/main.rs b/examples/multipart-form/src/main.rs index 8fb3dad3ef..bcddc60a6d 100644 --- a/examples/multipart-form/src/main.rs +++ b/examples/multipart-form/src/main.rs @@ -4,7 +4,12 @@ //! cd examples && cargo run -p example-multipart-form //! ``` -use axum::{extract::Multipart, response::Html, routing::get, Router}; +use axum::{ + extract::{DefaultBodyLimit, Multipart}, + response::Html, + routing::get, + Router, +}; use std::net::SocketAddr; use tower_http::limit::RequestBodyLimitLayer; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; @@ -22,6 +27,7 @@ async fn main() { // build our application with some routes let app = Router::new() .route("/", get(show_form).post(accept_form)) + .layer(DefaultBodyLimit::disable()) .layer(RequestBodyLimitLayer::new( 250 * 1024 * 1024, /* 250mb */ )) From 3b5d2dc1a84052bf559264cbab541b886e33e7d7 Mon Sep 17 00:00:00 2001 From: Marek Kuskowski <50183564+nylonicious@users.noreply.github.com> Date: Fri, 23 Sep 2022 12:04:20 +0200 Subject: [PATCH 12/15] fix: key value store example --- examples/key-value-store/Cargo.toml | 8 +++++++- examples/key-value-store/src/main.rs | 10 +++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/examples/key-value-store/Cargo.toml b/examples/key-value-store/Cargo.toml index e4c9063cbf..73fb191b5d 100644 --- a/examples/key-value-store/Cargo.toml +++ b/examples/key-value-store/Cargo.toml @@ -8,6 +8,12 @@ publish = false axum = { path = "../../axum" } tokio = { version = "1.0", features = ["full"] } tower = { version = "0.4", features = ["util", "timeout", "load-shed", "limit"] } -tower-http = { version = "0.3.0", features = ["add-extension", "auth", "compression-full", "trace"] } +tower-http = { version = "0.3.0", features = [ + "add-extension", + "auth", + "compression-full", + "limit", + "trace", +] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/examples/key-value-store/src/main.rs b/examples/key-value-store/src/main.rs index 37fae68ca7..1d33b36447 100644 --- a/examples/key-value-store/src/main.rs +++ b/examples/key-value-store/src/main.rs @@ -9,7 +9,7 @@ use axum::{ body::Bytes, error_handling::HandleErrorLayer, - extract::{Path, State}, + extract::{DefaultBodyLimit, Path, State}, handler::Handler, http::StatusCode, response::IntoResponse, @@ -49,7 +49,12 @@ async fn main() { // Add compression to `kv_get` get(kv_get.layer(CompressionLayer::new())) // But don't compress `kv_set` - .post(kv_set), + .post_service( + ServiceBuilder::new() + .layer(DefaultBodyLimit::disable()) + .layer(RequestBodyLimitLayer::new(1024 * 5_000 /* ~5mb */)) + .service(kv_set.with_state(Arc::clone(&shared_state))), + ), ) .route("/keys", get(list_keys)) // Nest our admin routes under `/admin` @@ -62,7 +67,6 @@ async fn main() { .load_shed() .concurrency_limit(1024) .timeout(Duration::from_secs(10)) - .layer(RequestBodyLimitLayer::new(1024 * 5_000 /* ~5mb */)) .layer(TraceLayer::new_for_http()) .into_inner(), ); From 845c11d738d4fa7f8280b20fc5d8f01edb8b7a2b Mon Sep 17 00:00:00 2001 From: Marek Kuskowski <50183564+nylonicious@users.noreply.github.com> Date: Fri, 23 Sep 2022 12:17:20 +0200 Subject: [PATCH 13/15] fix: update expected debug_handler output --- .../tests/debug_handler/fail/argument_not_extractor.stderr | 2 +- .../fail/doesnt_implement_from_request_parts.stderr | 2 +- axum-macros/tests/debug_handler/fail/wrong_return_type.stderr | 2 +- .../tests/from_request/fail/parts_extracting_body.stderr | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/axum-macros/tests/debug_handler/fail/argument_not_extractor.stderr b/axum-macros/tests/debug_handler/fail/argument_not_extractor.stderr index c669438aaf..9f653ad2b4 100644 --- a/axum-macros/tests/debug_handler/fail/argument_not_extractor.stderr +++ b/axum-macros/tests/debug_handler/fail/argument_not_extractor.stderr @@ -13,7 +13,7 @@ error[E0277]: the trait bound `bool: FromRequestParts<()>` is not satisfied <(T1, T2, T3, T4, T5, T6) as FromRequestParts> <(T1, T2, T3, T4, T5, T6, T7) as FromRequestParts> <(T1, T2, T3, T4, T5, T6, T7, T8) as FromRequestParts> - and 26 others + and 25 others = note: required because of the requirements on the impl of `FromRequest<(), Body, axum_core::extract::private::ViaParts>` for `bool` note: required by a bound in `__axum_macros_check_handler_0_from_request_check` --> tests/debug_handler/fail/argument_not_extractor.rs:3:1 diff --git a/axum-macros/tests/debug_handler/fail/doesnt_implement_from_request_parts.stderr b/axum-macros/tests/debug_handler/fail/doesnt_implement_from_request_parts.stderr index 87f3ab36d1..03803249f3 100644 --- a/axum-macros/tests/debug_handler/fail/doesnt_implement_from_request_parts.stderr +++ b/axum-macros/tests/debug_handler/fail/doesnt_implement_from_request_parts.stderr @@ -13,6 +13,6 @@ error[E0277]: the trait bound `String: FromRequestParts<()>` is not satisfied <(T1, T2, T3, T4, T5, T6) as FromRequestParts> <(T1, T2, T3, T4, T5, T6, T7) as FromRequestParts> <(T1, T2, T3, T4, T5, T6, T7, T8) as FromRequestParts> - and 26 others + and 25 others = help: see issue #48214 = note: this error originates in the attribute macro `debug_handler` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/axum-macros/tests/debug_handler/fail/wrong_return_type.stderr b/axum-macros/tests/debug_handler/fail/wrong_return_type.stderr index 89a5ed55ad..ce8fd05a67 100644 --- a/axum-macros/tests/debug_handler/fail/wrong_return_type.stderr +++ b/axum-macros/tests/debug_handler/fail/wrong_return_type.stderr @@ -13,7 +13,7 @@ error[E0277]: the trait bound `bool: IntoResponse` is not satisfied (Response<()>, T1, T2, R) (Response<()>, T1, T2, T3, R) (Response<()>, T1, T2, T3, T4, R) - and 122 others + and 118 others note: required by a bound in `__axum_macros_check_handler_into_response::{closure#0}::check` --> tests/debug_handler/fail/wrong_return_type.rs:4:23 | diff --git a/axum-macros/tests/from_request/fail/parts_extracting_body.stderr b/axum-macros/tests/from_request/fail/parts_extracting_body.stderr index 2a2c804016..9d92f40a49 100644 --- a/axum-macros/tests/from_request/fail/parts_extracting_body.stderr +++ b/axum-macros/tests/from_request/fail/parts_extracting_body.stderr @@ -13,4 +13,4 @@ error[E0277]: the trait bound `String: FromRequestParts` is not satisfied <(T1, T2, T3, T4, T5, T6) as FromRequestParts> <(T1, T2, T3, T4, T5, T6, T7) as FromRequestParts> <(T1, T2, T3, T4, T5, T6, T7, T8) as FromRequestParts> - and 27 others + and 26 others From 987e72dcad2c11086bc49b2992a275c61dccddc3 Mon Sep 17 00:00:00 2001 From: Marek Kuskowski <50183564+nylonicious@users.noreply.github.com> Date: Sat, 24 Sep 2022 12:49:04 +0200 Subject: [PATCH 14/15] chore: update CHANGELOG --- axum/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/axum/CHANGELOG.md b/axum/CHANGELOG.md index a1323b47f3..e1ac9b2db9 100644 --- a/axum/CHANGELOG.md +++ b/axum/CHANGELOG.md @@ -18,12 +18,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **added:** Add `middleware::from_extractor_with_state` and `middleware::from_extractor_with_state_arc` ([#1396]) - **added:** Add `DefaultBodyLimit::max` for changing the default body limit ([#1397]) +- **breaking:** `ContentLengthLimit` has been removed ([#1400]) [#1371]: https://github.com/tokio-rs/axum/pull/1371 [#1387]: https://github.com/tokio-rs/axum/pull/1387 [#1389]: https://github.com/tokio-rs/axum/pull/1389 [#1396]: https://github.com/tokio-rs/axum/pull/1396 [#1397]: https://github.com/tokio-rs/axum/pull/1397 +[#1400]: https://github.com/tokio-rs/axum/pull/1400 # 0.6.0-rc.2 (10. September, 2022) From 35868e9bbb9160598f14608347bb16ccc3efd31d Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sat, 24 Sep 2022 13:25:35 +0200 Subject: [PATCH 15/15] Update axum/CHANGELOG.md --- axum/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axum/CHANGELOG.md b/axum/CHANGELOG.md index e1ac9b2db9..bc9ca0e0fb 100644 --- a/axum/CHANGELOG.md +++ b/axum/CHANGELOG.md @@ -18,7 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **added:** Add `middleware::from_extractor_with_state` and `middleware::from_extractor_with_state_arc` ([#1396]) - **added:** Add `DefaultBodyLimit::max` for changing the default body limit ([#1397]) -- **breaking:** `ContentLengthLimit` has been removed ([#1400]) +- **breaking:** `ContentLengthLimit` has been removed. `Use DefaultBodyLimit` instead ([#1400]) [#1371]: https://github.com/tokio-rs/axum/pull/1371 [#1387]: https://github.com/tokio-rs/axum/pull/1387