Skip to content

body: Introduce Bodies of Unknown Size#3742

Open
lorenzleutgeb wants to merge 1 commit intotokio-rs:mainfrom
lorenzleutgeb:body-unknown
Open

body: Introduce Bodies of Unknown Size#3742
lorenzleutgeb wants to merge 1 commit intotokio-rs:mainfrom
lorenzleutgeb:body-unknown

Conversation

@lorenzleutgeb
Copy link
Copy Markdown

As reported in #3741, in some responses to HEAD requests, the header content-type: 0 would be included, even though the size of the body was not known and also not zero.

The reason for this was that axum::body::Body::empty, which represents a body that is known to be empty was used, even though nothing was known about the body.

To remedy, introduce a new utility type axum::body::Unknown, which represents an unknown body. This type could be considered for upstreaming to the http-body-util crate in the future.

@lorenzleutgeb
Copy link
Copy Markdown
Author

While this works to resolve the issue I faced when reporting #3741, I think this is conceptually not perfectly clean.

Consider the definition of http_body::Body. It states:

The size_hint function provides insight into the total number of bytes that will be streamed.

While the value returned from http_body::SizeHint::new is not wrong here, it is slightly misleading. If ever the size hint of the newly introduced axum::body::Unknown is used to allocate buffers, they will be unnecessarily large.

I lack knowledge about axum to judge how much of a problem this might be.

@davidpdrsn
Copy link
Copy Markdown
Member

While the value returned from http_body::SizeHint::new is not wrong here, it is slightly misleading. If ever the size hint of the newly introduced axum::body::Unknown is used to allocate buffers, they will be unnecessarily large.

I lack knowledge about axum to judge how much of a problem this might be.

I think that's fine. It's like Iterator's size hint, which is allowed to be wrong and shouldn't be relied on for soundness.

@lorenzleutgeb
Copy link
Copy Markdown
Author

I am adding two tests.

@lorenzleutgeb lorenzleutgeb force-pushed the body-unknown branch 2 times, most recently from 95dc10b to fa82c3b Compare May 2, 2026 12:33
impl IntoResponse for () {
fn into_response(self) -> Response {
Body::empty().into_response()
Body::unknown().into_response()
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davidpdrsn please note this change in particular. Tests are passing and it seems to achieve what I want, but for an outsider like me it is hard to judge whether this actually is a sane change.

What is difficult for me to assess is the intention behind the semantics of converting () into a response. Up to now, it meant an empty body, i.e. one of known size zero.

This seems to have been introduced in 18f613f#diff-6f9f8f882acd334c5bf0b598b309afef5f90f63d20b4cc6c26751d66bcb1dba6R19 with a slight change from ::default to ::empty in a005427#diff-6f9f8f882acd334c5bf0b598b309afef5f90f63d20b4cc6c26751d66bcb1dba6L18-R15

If it is intentional that () should convert to the empty body, then my suggestion here is probably bad and should be rejected.

If however it is accidental that () converts to the empty body, and it actually fits better to convert to a body of unknown size, then maybe I am on the right track.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a user, I think this would be very confusing for the typical user to see, and thus probably deserves a comment?

Copy link
Copy Markdown
Member

@davidpdrsn davidpdrsn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've come around! Since it doesn't impact GET responses with () I think this solution is good.

One small comment about docs but otherwise LGTM!

Would you mind adding a line in axum and axum-core's changelogs as well?

Comment thread axum/src/routing/route.rs
Comment on lines +294 to +307
#[crate::test]
async fn unit_content_length_zero() {
let content_length: HeaderValue = HeaderValue::from_static("0");

async fn handler() -> Response {
().into_response()
}

let client = TestClient::new(Router::new().route("/", get(handler)));

let get = client.get("/").await;
assert_eq!(get.status(), http::StatusCode::OK);
assert_eq!(get.headers().get(CONTENT_LENGTH), Some(&content_length));
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay I think I see what's going on now and why this test passes:

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Thanks for the explanation!

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think putting some of this explanation as a comment would help future travellers?

Comment thread axum-core/src/body.rs Outdated
@lorenzleutgeb
Copy link
Copy Markdown
Author

lorenzleutgeb commented May 2, 2026

Would you mind adding a line in axum and axum-core's changelogs as well?

I did that in f65a1407 but am not sure I managed to match your style of changelogs well enough. Happy to work on it with some more feedback.

As reported in <tokio-rs#3741>,
in some responses to HEAD requests, the header `content-type: 0`
would be included, even though the size of the body was not known
and also not zero.

The reason for this was that `axum::body::Body::empty`, which represents
a body that is *known to be empty* was used, even though *nothing was
known about the body*.

To remedy, introduce a new utility type `axum::body::Unknown`, which
represents an unknown body. This type could be considered for
upstreaming to the `http-body-util` crate in the future.

Fixes: tokio-rs#3741
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants