Skip to content

Commit 10b09ff

Browse files
authored
fix(server): start http1 header read timeout when conn is idle (#3828)
Currently, the header read timeout is started before any part of the first request is received. This allows closing the connection if no requests are received. However, after the first request, the connection can remain open indefinitely. This change ensures that the header read timeout is started immediately after the connection is idle, following the transmission of the response, before the first part of the subsequent request is received. This change allows a potential future addition of an idle_timeout, which if set, would be used instead of the header_read_timeout. This behavior is matched in other servers, such as Golang. Fixes #3780 Closes #3781
1 parent 8ce1fcf commit 10b09ff

File tree

3 files changed

+60
-4
lines changed

3 files changed

+60
-4
lines changed

src/error.rs

+4
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,10 @@ impl Error {
240240

241241
/// Returns true if the error was caused by a timeout.
242242
pub fn is_timeout(&self) -> bool {
243+
#[cfg(all(feature = "http1", feature = "server"))]
244+
if matches!(self.inner.kind, Kind::HeaderTimeout) {
245+
return true;
246+
}
243247
self.find_source::<TimedOut>().is_some()
244248
}
245249

src/proto/h1/conn.rs

+8
Original file line numberDiff line numberDiff line change
@@ -1122,6 +1122,14 @@ impl State {
11221122
if !T::should_read_first() {
11231123
self.notify_read = true;
11241124
}
1125+
1126+
#[cfg(feature = "server")]
1127+
if self.h1_header_read_timeout.is_some() {
1128+
// Next read will start and poll the header read timeout,
1129+
// so we can close the connection if another header isn't
1130+
// received in a timely manner.
1131+
self.notify_read = true;
1132+
}
11251133
}
11261134

11271135
fn is_idle(&self) -> bool {

tests/server.rs

+48-4
Original file line numberDiff line numberDiff line change
@@ -1504,14 +1504,14 @@ async fn header_read_timeout_slow_writes() {
15041504
tcp.write_all(
15051505
b"\
15061506
Something: 1\r\n\
1507-
\r\n\
15081507
",
15091508
)
15101509
.expect("write 2");
15111510
thread::sleep(Duration::from_secs(6));
15121511
tcp.write_all(
15131512
b"\
15141513
Works: 0\r\n\
1514+
\r\n\
15151515
",
15161516
)
15171517
.expect_err("write 3");
@@ -1553,7 +1553,7 @@ async fn header_read_timeout_starts_immediately() {
15531553
.timer(TokioTimer)
15541554
.header_read_timeout(Duration::from_secs(2))
15551555
.serve_connection(socket, unreachable_service());
1556-
conn.await.expect_err("header timeout");
1556+
assert!(conn.await.unwrap_err().is_timeout());
15571557
}
15581558

15591559
#[tokio::test]
@@ -1601,14 +1601,14 @@ async fn header_read_timeout_slow_writes_multiple_requests() {
16011601
b"\
16021602
GET / HTTP/1.1\r\n\
16031603
Something: 1\r\n\
1604-
\r\n\
16051604
",
16061605
)
16071606
.expect("write 5");
16081607
thread::sleep(Duration::from_secs(6));
16091608
tcp.write_all(
16101609
b"\
16111610
Works: 0\r\n\
1611+
\r\n
16121612
",
16131613
)
16141614
.expect_err("write 6");
@@ -1629,7 +1629,51 @@ async fn header_read_timeout_slow_writes_multiple_requests() {
16291629
future::ready(Ok::<_, hyper::Error>(res))
16301630
}),
16311631
);
1632-
conn.without_shutdown().await.expect_err("header timeout");
1632+
assert!(conn.without_shutdown().await.unwrap_err().is_timeout());
1633+
}
1634+
1635+
#[tokio::test]
1636+
async fn header_read_timeout_as_idle_timeout() {
1637+
let (listener, addr) = setup_tcp_listener();
1638+
1639+
thread::spawn(move || {
1640+
let mut tcp = connect(&addr);
1641+
1642+
tcp.write_all(
1643+
b"\
1644+
GET / HTTP/1.1\r\n\
1645+
\r\n\
1646+
",
1647+
)
1648+
.expect("request 1");
1649+
1650+
thread::sleep(Duration::from_secs(6));
1651+
1652+
tcp.write_all(
1653+
b"\
1654+
GET / HTTP/1.1\r\n\
1655+
\r\n\
1656+
",
1657+
)
1658+
.expect_err("request 2");
1659+
});
1660+
1661+
let (socket, _) = listener.accept().await.unwrap();
1662+
let socket = TokioIo::new(socket);
1663+
let conn = http1::Builder::new()
1664+
.timer(TokioTimer)
1665+
.header_read_timeout(Duration::from_secs(3))
1666+
.serve_connection(
1667+
socket,
1668+
service_fn(|_| {
1669+
let res = Response::builder()
1670+
.status(200)
1671+
.body(Empty::<Bytes>::new())
1672+
.unwrap();
1673+
future::ready(Ok::<_, hyper::Error>(res))
1674+
}),
1675+
);
1676+
assert!(conn.without_shutdown().await.unwrap_err().is_timeout());
16331677
}
16341678

16351679
#[tokio::test]

0 commit comments

Comments
 (0)