Skip to content

Commit

Permalink
fix(http2): received Body::size_hint() now return 0 if implicitly e…
Browse files Browse the repository at this point in the history
…mpty (#2715)

An HTTP/2 stream may include a set of headers, and a flag signalling
END-STREAM, even if a `content-length` isn't included. hyper wouldn't
notice, and so the `Body` would report a size-hint of `0..MAX`. hyper
now notices that the stream is ended, and couldn't possibly include any
bytes for the body, and thus will give a size-hint of `0` exactly.
  • Loading branch information
seanmonstar committed Dec 6, 2021
1 parent ce82425 commit 84b78b6
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 5 deletions.
7 changes: 6 additions & 1 deletion src/body/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,14 @@ impl Body {
#[cfg(all(feature = "http2", any(feature = "client", feature = "server")))]
pub(crate) fn h2(
recv: h2::RecvStream,
content_length: DecodedLength,
mut content_length: DecodedLength,
ping: ping::Recorder,
) -> Self {
// If the stream is already EOS, then the "unknown length" is clearly
// actually ZERO.
if !content_length.is_exact() && recv.is_end_stream() {
content_length = DecodedLength::ZERO;
}
let body = Body::new(Kind::H2 {
ping,
content_length,
Expand Down
10 changes: 10 additions & 0 deletions src/body/length.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ impl DecodedLength {
}
}
}

/// Returns whether this represents an exact length.
///
/// This includes 0, which of course is an exact known length.
///
/// It would return false if "chunked" or otherwise size-unknown.
#[cfg(feature = "http2")]
pub(crate) fn is_exact(&self) -> bool {
self.0 <= MAX_LEN
}
}

impl fmt::Debug for DecodedLength {
Expand Down
9 changes: 5 additions & 4 deletions src/proto/h2/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,12 +484,13 @@ where
}
}

// automatically set Content-Length from body...
if let Some(len) = body.size_hint().exact() {
headers::set_content_length_if_missing(res.headers_mut(), len);
}

if !body.is_end_stream() {
// automatically set Content-Length from body...
if let Some(len) = body.size_hint().exact() {
headers::set_content_length_if_missing(res.headers_mut(), len);
}

let body_tx = reply!(me, res, false);
H2StreamState::Body {
pipe: PipeToSendStream::new(body, body_tx),
Expand Down
20 changes: 20 additions & 0 deletions tests/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,26 @@ mod response_body_lengths {
assert_eq!(res.headers().get("content-length").unwrap(), "10");
assert_eq!(res.body().size_hint().exact(), Some(10));
}

#[tokio::test]
async fn http2_implicit_empty_size_hint() {
use http_body::Body;

let server = serve();
let addr_str = format!("http://{}", server.addr());
server.reply();

let client = Client::builder()
.http2_only(true)
.build_http::<hyper::Body>();
let uri = addr_str
.parse::<hyper::Uri>()
.expect("server addr should parse");

let res = client.get(uri).await.unwrap();
assert_eq!(res.headers().get("content-length"), None);
assert_eq!(res.body().size_hint().exact(), Some(0));
}
}

#[test]
Expand Down

0 comments on commit 84b78b6

Please sign in to comment.