feat(server): add HTTP/1 header read timeout option (#2675)

Adds `Server::http1_header_read_timeout(Duration)`. Setting a duration will determine how long a client has to finish sending all the request headers before trigger a timeout test. This can help reduce resource usage when bad actors open connections without sending full requests.

Closes #2457
This commit is contained in:
Paolo Barbolini
2021-11-18 21:02:06 +01:00
committed by GitHub
parent d0b1d9ed3a
commit 842c6553a5
9 changed files with 308 additions and 3 deletions

View File

@@ -1261,6 +1261,127 @@ fn header_name_too_long() {
assert!(s(&buf[..n]).starts_with("HTTP/1.1 431 Request Header Fields Too Large\r\n"));
}
#[tokio::test]
async fn header_read_timeout_slow_writes() {
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
let addr = listener.local_addr().unwrap();
thread::spawn(move || {
let mut tcp = connect(&addr);
tcp.write_all(
b"\
GET / HTTP/1.1\r\n\
",
)
.expect("write 1");
thread::sleep(Duration::from_secs(3));
tcp.write_all(
b"\
Something: 1\r\n\
\r\n\
",
)
.expect("write 2");
thread::sleep(Duration::from_secs(6));
tcp.write_all(
b"\
Works: 0\r\n\
",
)
.expect_err("write 3");
});
let (socket, _) = listener.accept().await.unwrap();
let conn = Http::new()
.http1_header_read_timeout(Duration::from_secs(5))
.serve_connection(
socket,
service_fn(|_| {
let res = Response::builder()
.status(200)
.body(hyper::Body::empty())
.unwrap();
future::ready(Ok::<_, hyper::Error>(res))
}),
);
conn.without_shutdown().await.expect_err("header timeout");
}
#[tokio::test]
async fn header_read_timeout_slow_writes_multiple_requests() {
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
let addr = listener.local_addr().unwrap();
thread::spawn(move || {
let mut tcp = connect(&addr);
tcp.write_all(
b"\
GET / HTTP/1.1\r\n\
",
)
.expect("write 1");
thread::sleep(Duration::from_secs(3));
tcp.write_all(
b"\
Something: 1\r\n\
\r\n\
",
)
.expect("write 2");
thread::sleep(Duration::from_secs(3));
tcp.write_all(
b"\
GET / HTTP/1.1\r\n\
",
)
.expect("write 3");
thread::sleep(Duration::from_secs(3));
tcp.write_all(
b"\
Something: 1\r\n\
\r\n\
",
)
.expect("write 4");
thread::sleep(Duration::from_secs(6));
tcp.write_all(
b"\
GET / HTTP/1.1\r\n\
Something: 1\r\n\
\r\n\
",
)
.expect("write 5");
thread::sleep(Duration::from_secs(6));
tcp.write_all(
b"\
Works: 0\r\n\
",
)
.expect_err("write 6");
});
let (socket, _) = listener.accept().await.unwrap();
let conn = Http::new()
.http1_header_read_timeout(Duration::from_secs(5))
.serve_connection(
socket,
service_fn(|_| {
let res = Response::builder()
.status(200)
.body(hyper::Body::empty())
.unwrap();
future::ready(Ok::<_, hyper::Error>(res))
}),
);
conn.without_shutdown().await.expect_err("header timeout");
}
#[tokio::test]
async fn upgrades() {
use tokio::io::{AsyncReadExt, AsyncWriteExt};