Skip to content

Commit aac6760

Browse files
authored
fix(client): early respond from server shouldn't propagate reset error (#3274)
Closes #2872 for `0.14.x` version.
1 parent d77c259 commit aac6760

File tree

3 files changed

+62
-2
lines changed

3 files changed

+62
-2
lines changed

src/body/body.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,12 @@ impl Body {
323323
ping.record_data(bytes.len());
324324
Poll::Ready(Some(Ok(bytes)))
325325
}
326-
Some(Err(e)) => Poll::Ready(Some(Err(crate::Error::new_body(e)))),
326+
Some(Err(e)) => match e.reason() {
327+
// These reasons should cause stop of body reading, but nor fail it.
328+
// The same logic as for `AsyncRead for H2Upgraded` is applied here.
329+
Some(h2::Reason::NO_ERROR) | Some(h2::Reason::CANCEL) => Poll::Ready(None),
330+
_ => Poll::Ready(Some(Err(crate::Error::new_body(e)))),
331+
},
327332
None => Poll::Ready(None),
328333
},
329334

src/proto/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ pub(crate) enum BodyLength {
5050
Unknown,
5151
}
5252

53-
/// Status of when a Disaptcher future completes.
53+
/// Status of when a Dispatcher future completes.
5454
pub(crate) enum Dispatched {
5555
/// Dispatcher completely shutdown connection.
5656
Shutdown,

tests/client.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3154,6 +3154,61 @@ mod conn {
31543154
.expect("client should be open");
31553155
}
31563156

3157+
#[tokio::test]
3158+
async fn http2_responds_before_consuming_request_body() {
3159+
// Test that a early-response from server works correctly (request body wasn't fully consumed).
3160+
// https://github.com/hyperium/hyper/issues/2872
3161+
use hyper::service::service_fn;
3162+
3163+
let _ = pretty_env_logger::try_init();
3164+
3165+
let listener = TkTcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0)))
3166+
.await
3167+
.unwrap();
3168+
let addr = listener.local_addr().unwrap();
3169+
3170+
// Spawn an HTTP2 server that responds before reading the whole request body.
3171+
// It's normal case to decline the request due to headers or size of the body.
3172+
tokio::spawn(async move {
3173+
let sock = listener.accept().await.unwrap().0;
3174+
hyper::server::conn::Http::new()
3175+
.http2_only(true)
3176+
.serve_connection(
3177+
sock,
3178+
service_fn(|_req| async move {
3179+
Ok::<_, hyper::Error>(http::Response::new(hyper::Body::from(
3180+
"No bread for you!",
3181+
)))
3182+
}),
3183+
)
3184+
.await
3185+
.expect("serve_connection");
3186+
});
3187+
3188+
let io = tcp_connect(&addr).await.expect("tcp connect");
3189+
let (mut client, conn) = conn::Builder::new()
3190+
.http2_only(true)
3191+
.handshake::<_, Body>(io)
3192+
.await
3193+
.expect("http handshake");
3194+
3195+
tokio::spawn(async move {
3196+
conn.await.expect("client conn shouldn't error");
3197+
});
3198+
3199+
// Use a channel to keep request stream open
3200+
let (_tx, body) = hyper::Body::channel();
3201+
let req = Request::post("/a").body(body).unwrap();
3202+
let resp = client.send_request(req).await.expect("send_request");
3203+
assert!(resp.status().is_success());
3204+
3205+
let body = hyper::body::to_bytes(resp.into_body())
3206+
.await
3207+
.expect("get response body with no error");
3208+
3209+
assert_eq!(body.as_ref(), b"No bread for you!");
3210+
}
3211+
31573212
#[tokio::test]
31583213
async fn h2_connect() {
31593214
let _ = pretty_env_logger::try_init();

0 commit comments

Comments
 (0)