From 90f55adffff7a7950ba8d12353182ddab64f26fb Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Tue, 26 Oct 2021 10:45:24 +0200 Subject: [PATCH 01/14] feat(server): Implement http1 server header read timeout --- src/error.rs | 10 ++++++++++ src/proto/h1/conn.rs | 22 ++++++++++++++++++++++ src/proto/h1/io.rs | 18 +++++++++++++++++- src/proto/h1/mod.rs | 6 ++++++ src/proto/h1/role.rs | 36 ++++++++++++++++++++++++++++++++++++ src/server/conn.rs | 21 ++++++++++++++++++++- src/server/server.rs | 13 ++++++++++++- tests/server.rs | 39 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 162 insertions(+), 3 deletions(-) diff --git a/src/error.rs b/src/error.rs index 470a23b601..46c4a7d3a8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -104,6 +104,9 @@ pub(super) enum User { #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "server")] UnexpectedHeader, + /// User took too long to send headers + #[cfg(feature = "http1")] + HeaderTimeout, /// User tried to create a Request with bad version. #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "client")] @@ -310,6 +313,11 @@ impl Error { Error::new_user(User::UnexpectedHeader) } + #[cfg(feature = "http1")] + pub(super) fn new_header_timeout() -> Error { + Error::new_user(User::HeaderTimeout) + } + #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "client")] pub(super) fn new_user_unsupported_version() -> Error { @@ -441,6 +449,8 @@ impl Error { #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "server")] Kind::User(User::UnexpectedHeader) => "user sent unexpected header", + #[cfg(feature = "http1")] + Kind::User(User::HeaderTimeout) => "read header from client timeout", #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "client")] Kind::User(User::UnsupportedVersion) => "request has unsupported HTTP version", diff --git a/src/proto/h1/conn.rs b/src/proto/h1/conn.rs index 887dee48e5..9fdc1a4109 100644 --- a/src/proto/h1/conn.rs +++ b/src/proto/h1/conn.rs @@ -1,12 +1,14 @@ use std::fmt; use std::io; use std::marker::PhantomData; +use std::time::Duration; use bytes::{Buf, Bytes}; use http::header::{HeaderValue, CONNECTION}; use http::{HeaderMap, Method, Version}; use httparse::ParserConfig; use tokio::io::{AsyncRead, AsyncWrite}; +use tokio::time::Sleep; use tracing::{debug, error, trace}; use super::io::Buffered; @@ -47,6 +49,10 @@ where keep_alive: KA::Busy, method: None, h1_parser_config: ParserConfig::default(), + #[cfg(feature = "server")] + h1_header_read_timeout: None, + #[cfg(feature = "server")] + h1_header_read_timeout_fut: None, preserve_header_case: false, title_case_headers: false, h09_responses: false, @@ -103,6 +109,14 @@ where self.state.h09_responses = true; } + #[cfg(feature = "server")] + pub(crate) fn set_http1_header_parse_timeout(&mut self, val: Duration) { + debug!("setting initial h1 header read timeout timer"); + + self.state.h1_header_read_timeout = Some(val); + self.state.h1_header_read_timeout_fut = Some(Box::pin(tokio::time::sleep(val))); + } + #[cfg(feature = "server")] pub(crate) fn set_allow_half_close(&mut self) { self.state.allow_half_close = true; @@ -175,6 +189,10 @@ where cached_headers: &mut self.state.cached_headers, req_method: &mut self.state.method, h1_parser_config: self.state.h1_parser_config.clone(), + #[cfg(feature = "server")] + h1_header_read_timeout: self.state.h1_header_read_timeout, + #[cfg(feature = "server")] + h1_header_read_timeout_fut: &mut self.state.h1_header_read_timeout_fut, preserve_header_case: self.state.preserve_header_case, h09_responses: self.state.h09_responses, #[cfg(feature = "ffi")] @@ -795,6 +813,10 @@ struct State { /// a body or not. method: Option, h1_parser_config: ParserConfig, + #[cfg(feature = "server")] + h1_header_read_timeout: Option, + #[cfg(feature = "server")] + h1_header_read_timeout_fut: Option>>, preserve_header_case: bool, title_case_headers: bool, h09_responses: bool, diff --git a/src/proto/h1/io.rs b/src/proto/h1/io.rs index 9fa6899001..ca20615e20 100644 --- a/src/proto/h1/io.rs +++ b/src/proto/h1/io.rs @@ -3,10 +3,11 @@ use std::fmt; use std::io::{self, IoSlice}; use std::marker::Unpin; use std::mem::MaybeUninit; +use std::future::Future; use bytes::{Buf, BufMut, Bytes, BytesMut}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; -use tracing::{debug, trace}; +use tracing::{debug, warn, trace}; use super::{Http1Transaction, ParseContext, ParsedMessage}; use crate::common::buf::BufList; @@ -180,6 +181,8 @@ where cached_headers: parse_ctx.cached_headers, req_method: parse_ctx.req_method, h1_parser_config: parse_ctx.h1_parser_config.clone(), + h1_header_read_timeout: parse_ctx.h1_header_read_timeout, + h1_header_read_timeout_fut: parse_ctx.h1_header_read_timeout_fut, preserve_header_case: parse_ctx.preserve_header_case, h09_responses: parse_ctx.h09_responses, #[cfg(feature = "ffi")] @@ -190,6 +193,8 @@ where )? { Some(msg) => { debug!("parsed {} headers", msg.head.headers.len()); + + *parse_ctx.h1_header_read_timeout_fut = None; return Poll::Ready(Ok(msg)); } None => { @@ -198,6 +203,15 @@ where debug!("max_buf_size ({}) reached, closing", max); return Poll::Ready(Err(crate::Error::new_too_large())); } + + if let Some(h1_header_read_timeout_fut) = parse_ctx.h1_header_read_timeout_fut { + if Pin::new( h1_header_read_timeout_fut).poll(cx).is_ready() { + *parse_ctx.h1_header_read_timeout_fut = None; + + warn!("read header from client timeout"); + return Poll::Ready(Err(crate::Error::new_header_timeout())) + } + } } } if ready!(self.poll_read_from_io(cx)).map_err(crate::Error::new_io)? == 0 { @@ -693,6 +707,8 @@ mod tests { cached_headers: &mut None, req_method: &mut None, h1_parser_config: Default::default(), + h1_header_read_timeout: None, + h1_header_read_timeout_fut: &mut None, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] diff --git a/src/proto/h1/mod.rs b/src/proto/h1/mod.rs index 758ac7b073..da8bfdfb3d 100644 --- a/src/proto/h1/mod.rs +++ b/src/proto/h1/mod.rs @@ -1,6 +1,10 @@ +use std::pin::Pin; +use std::time::Duration; + use bytes::BytesMut; use http::{HeaderMap, Method}; use httparse::ParserConfig; +use tokio::time::Sleep; use crate::body::DecodedLength; use crate::proto::{BodyLength, MessageHead}; @@ -72,6 +76,8 @@ pub(crate) struct ParseContext<'a> { cached_headers: &'a mut Option, req_method: &'a mut Option, h1_parser_config: ParserConfig, + h1_header_read_timeout: Option, + h1_header_read_timeout_fut: &'a mut Option>>, preserve_header_case: bool, h09_responses: bool, #[cfg(feature = "ffi")] diff --git a/src/proto/h1/role.rs b/src/proto/h1/role.rs index 5c701ed429..ab28e552df 100644 --- a/src/proto/h1/role.rs +++ b/src/proto/h1/role.rs @@ -69,6 +69,14 @@ where let span = trace_span!("parse_headers"); let _s = span.enter(); + + if let Some(h1_header_read_timeout) = ctx.h1_header_read_timeout { + if ctx.h1_header_read_timeout_fut.is_none() { + debug!("setting h1 header read timeout timer"); + *ctx.h1_header_read_timeout_fut = Some(Box::pin(tokio::time::sleep(h1_header_read_timeout))); + } + } + T::parse(bytes, ctx) } @@ -1428,6 +1436,8 @@ mod tests { cached_headers: &mut None, req_method: &mut method, h1_parser_config: Default::default(), + h1_header_read_timeout: None, + h1_header_read_timeout_fut: &mut None, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -1455,6 +1465,8 @@ mod tests { cached_headers: &mut None, req_method: &mut Some(crate::Method::GET), h1_parser_config: Default::default(), + h1_header_read_timeout: None, + h1_header_read_timeout_fut: &mut None, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -1477,6 +1489,8 @@ mod tests { cached_headers: &mut None, req_method: &mut None, h1_parser_config: Default::default(), + h1_header_read_timeout: None, + h1_header_read_timeout_fut: &mut None, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -1497,6 +1511,8 @@ mod tests { cached_headers: &mut None, req_method: &mut Some(crate::Method::GET), h1_parser_config: Default::default(), + h1_header_read_timeout: None, + h1_header_read_timeout_fut: &mut None, preserve_header_case: false, h09_responses: true, #[cfg(feature = "ffi")] @@ -1519,6 +1535,8 @@ mod tests { cached_headers: &mut None, req_method: &mut Some(crate::Method::GET), h1_parser_config: Default::default(), + h1_header_read_timeout: None, + h1_header_read_timeout_fut: &mut None, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -1545,6 +1563,8 @@ mod tests { cached_headers: &mut None, req_method: &mut Some(crate::Method::GET), h1_parser_config, + h1_header_read_timeout: None, + h1_header_read_timeout_fut: &mut None, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -1568,6 +1588,8 @@ mod tests { cached_headers: &mut None, req_method: &mut Some(crate::Method::GET), h1_parser_config: Default::default(), + h1_header_read_timeout: None, + h1_header_read_timeout_fut: &mut None, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -1586,6 +1608,8 @@ mod tests { cached_headers: &mut None, req_method: &mut None, h1_parser_config: Default::default(), + h1_header_read_timeout: None, + h1_header_read_timeout_fut: &mut None, preserve_header_case: true, h09_responses: false, #[cfg(feature = "ffi")] @@ -1625,6 +1649,8 @@ mod tests { cached_headers: &mut None, req_method: &mut None, h1_parser_config: Default::default(), + h1_header_read_timeout: None, + h1_header_read_timeout_fut: &mut None, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -1645,6 +1671,8 @@ mod tests { cached_headers: &mut None, req_method: &mut None, h1_parser_config: Default::default(), + h1_header_read_timeout: None, + h1_header_read_timeout_fut: &mut None, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -1874,6 +1902,8 @@ mod tests { cached_headers: &mut None, req_method: &mut Some(Method::GET), h1_parser_config: Default::default(), + h1_header_read_timeout: None, + h1_header_read_timeout_fut: &mut None, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -1894,6 +1924,8 @@ mod tests { cached_headers: &mut None, req_method: &mut Some(m), h1_parser_config: Default::default(), + h1_header_read_timeout: None, + h1_header_read_timeout_fut: &mut None, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -1914,6 +1946,8 @@ mod tests { cached_headers: &mut None, req_method: &mut Some(Method::GET), h1_parser_config: Default::default(), + h1_header_read_timeout: None, + h1_header_read_timeout_fut: &mut None, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -2411,6 +2445,8 @@ mod tests { cached_headers: &mut None, req_method: &mut Some(Method::GET), h1_parser_config: Default::default(), + h1_header_read_timeout: None, + h1_header_read_timeout_fut: &mut None, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] diff --git a/src/server/conn.rs b/src/server/conn.rs index b488f8ba7e..77a7dde8f9 100644 --- a/src/server/conn.rs +++ b/src/server/conn.rs @@ -50,7 +50,6 @@ use std::marker::PhantomData; #[cfg(feature = "tcp")] use std::net::SocketAddr; -#[cfg(all(feature = "runtime", feature = "http2"))] use std::time::Duration; #[cfg(feature = "http2")] @@ -103,6 +102,8 @@ pub struct Http { h1_keep_alive: bool, h1_title_case_headers: bool, h1_preserve_header_case: bool, + #[cfg(feature = "http1")] + h1_header_read_timeout: Option, #[cfg(feature = "http2")] h2_builder: proto::h2::server::Config, mode: ConnectionMode, @@ -284,6 +285,8 @@ impl Http { h1_keep_alive: true, h1_title_case_headers: false, h1_preserve_header_case: false, + #[cfg(feature = "http1")] + h1_header_read_timeout: None, #[cfg(feature = "http2")] h2_builder: Default::default(), mode: ConnectionMode::default(), @@ -363,6 +366,17 @@ impl Http { self } + /// Set a timeout for reading client request headers. If a client does not + /// transmit the entire header withing this time, the connection is closed. + /// + /// Default is None. + #[cfg(feature = "http1")] + #[cfg_attr(docsrs, doc(cfg(feature = "http1")))] + pub fn http1_header_read_timeout(&mut self, read_timeout: Duration) -> &mut Self { + self.h1_header_read_timeout = Some(read_timeout); + self + } + /// Sets whether HTTP2 is required. /// /// Default is false @@ -538,6 +552,8 @@ impl Http { h1_keep_alive: self.h1_keep_alive, h1_title_case_headers: self.h1_title_case_headers, h1_preserve_header_case: self.h1_preserve_header_case, + #[cfg(feature = "http1")] + h1_header_read_timeout: self.h1_header_read_timeout, #[cfg(feature = "http2")] h2_builder: self.h2_builder, mode: self.mode, @@ -599,6 +615,9 @@ impl Http { if self.h1_preserve_header_case { conn.set_preserve_header_case(); } + if let Some(header_parse_timeout) = self.h1_header_read_timeout { + conn.set_http1_header_parse_timeout(header_parse_timeout); + } conn.set_flush_pipeline(self.pipeline_flush); if let Some(max) = self.max_buf_size { conn.set_max_buf_size(max); diff --git a/src/server/server.rs b/src/server/server.rs index 0d559c579d..801d2812d7 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,7 +1,7 @@ use std::fmt; #[cfg(feature = "tcp")] use std::net::{SocketAddr, TcpListener as StdTcpListener}; -#[cfg(feature = "tcp")] +#[cfg(any(feature = "tcp", feature = "http1"))] use std::time::Duration; #[cfg(all(feature = "tcp", any(feature = "http1", feature = "http2")))] @@ -284,6 +284,17 @@ impl Builder { self } + /// Set a timeout for reading client request headers. If a client does not + /// transmit the entire header withing this time, the connection is closed. + /// + /// Default is None. + #[cfg(feature = "http1")] + #[cfg_attr(docsrs, doc(cfg(feature = "http1")))] + pub fn http1_header_read_timeout(mut self, read_timeout: Duration) -> Self { + self.protocol.http1_header_read_timeout(read_timeout); + self + } + /// Sets whether HTTP/1 is required. /// /// Default is `false`. diff --git a/tests/server.rs b/tests/server.rs index 624c1eb8e7..7c4ade1e57 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -1261,6 +1261,45 @@ 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() { + 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\ + Something: 1\r\n\ + ", + ) + .expect("write 1"); + thread::sleep(Duration::from_secs(6)); + tcp.write_all( + b"\ + Works: 0\r\n\ + ", + ) + .expect_err("write 2"); + }); + + 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}; From c45b169a8cdf5cc1677071b70f5354a97880f215 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Tue, 26 Oct 2021 11:18:00 +0200 Subject: [PATCH 02/14] Fix features --- src/proto/h1/conn.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/proto/h1/conn.rs b/src/proto/h1/conn.rs index 9fdc1a4109..384165f227 100644 --- a/src/proto/h1/conn.rs +++ b/src/proto/h1/conn.rs @@ -49,9 +49,7 @@ where keep_alive: KA::Busy, method: None, h1_parser_config: ParserConfig::default(), - #[cfg(feature = "server")] h1_header_read_timeout: None, - #[cfg(feature = "server")] h1_header_read_timeout_fut: None, preserve_header_case: false, title_case_headers: false, @@ -189,9 +187,7 @@ where cached_headers: &mut self.state.cached_headers, req_method: &mut self.state.method, h1_parser_config: self.state.h1_parser_config.clone(), - #[cfg(feature = "server")] h1_header_read_timeout: self.state.h1_header_read_timeout, - #[cfg(feature = "server")] h1_header_read_timeout_fut: &mut self.state.h1_header_read_timeout_fut, preserve_header_case: self.state.preserve_header_case, h09_responses: self.state.h09_responses, @@ -813,9 +809,7 @@ struct State { /// a body or not. method: Option, h1_parser_config: ParserConfig, - #[cfg(feature = "server")] h1_header_read_timeout: Option, - #[cfg(feature = "server")] h1_header_read_timeout_fut: Option>>, preserve_header_case: bool, title_case_headers: bool, From 6ea00dbce1c3c98e9dec11fd8c6043ffcd83eb41 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Tue, 26 Oct 2021 11:24:15 +0200 Subject: [PATCH 03/14] More fixes --- Cargo.toml | 2 +- src/proto/h1/role.rs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c119a106c3..eeb287b315 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,7 +86,7 @@ full = [ ] # HTTP versions -http1 = [] +http1 = ["tokio/time"] http2 = ["h2"] # Client/Server diff --git a/src/proto/h1/role.rs b/src/proto/h1/role.rs index ab28e552df..923d091c6a 100644 --- a/src/proto/h1/role.rs +++ b/src/proto/h1/role.rs @@ -2531,6 +2531,8 @@ mod tests { cached_headers: &mut headers, req_method: &mut None, h1_parser_config: Default::default(), + h1_header_read_timeout: None, + h1_header_read_timeout_fut: &mut None, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -2571,6 +2573,8 @@ mod tests { cached_headers: &mut headers, req_method: &mut None, h1_parser_config: Default::default(), + h1_header_read_timeout: None, + h1_header_read_timeout_fut: &mut None, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] From 0e50492b7f516ec4be249f663d871b0742df36d0 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Wed, 27 Oct 2021 05:38:29 +0200 Subject: [PATCH 04/14] Add more tests --- tests/server.rs | 85 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/tests/server.rs b/tests/server.rs index 7c4ade1e57..16019407fd 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -1262,7 +1262,41 @@ fn header_name_too_long() { } #[tokio::test] -async fn header_read_timeout() { +async fn header_read_timeout_no_write() { + 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); + thread::sleep(Duration::from_secs(6)); + tcp.write_all( + b"\ + GET / HTTP/1.1\r\n\ + Something: 1\r\n\ + \r\n\ + ", + ) + .expect_err("write 1"); + }); + + 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("header timeout"); +} + +#[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(); @@ -1300,6 +1334,55 @@ async fn header_read_timeout() { conn.without_shutdown().await.expect_err("header timeout"); } +#[tokio::test] +async fn header_read_timeout_slow_writes_2nd_request() { + 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\ + Something: 1\r\n\ + \r\n\ + ", + ) + .expect("write 1"); + thread::sleep(Duration::from_secs(6)); + tcp.write_all( + b"\ + GET / HTTP/1.1\r\n\ + 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 upgrades() { use tokio::io::{AsyncReadExt, AsyncWriteExt}; From 16b410be54c7ed224f9142dd41c518dd7485a068 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Thu, 28 Oct 2021 09:11:06 +0200 Subject: [PATCH 05/14] Expand test --- tests/server.rs | 45 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/tests/server.rs b/tests/server.rs index 16019407fd..c4c3406a01 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -1305,17 +1305,24 @@ async fn header_read_timeout_slow_writes() { tcp.write_all( b"\ GET / HTTP/1.1\r\n\ - Something: 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 2"); + .expect_err("write 3"); }); let (socket, _) = listener.accept().await.unwrap(); @@ -1335,21 +1342,47 @@ async fn header_read_timeout_slow_writes() { } #[tokio::test] -async fn header_read_timeout_slow_writes_2nd_request() { +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 1"); + .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\ @@ -1357,14 +1390,14 @@ async fn header_read_timeout_slow_writes_2nd_request() { \r\n\ ", ) - .expect("write 2"); + .expect("write 5"); thread::sleep(Duration::from_secs(6)); tcp.write_all( b"\ Works: 0\r\n\ ", ) - .expect_err("write 3"); + .expect_err("write 6"); }); let (socket, _) = listener.accept().await.unwrap(); From 7b907273137d014029905b570257b824e1e0de87 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Fri, 5 Nov 2021 07:08:43 +0100 Subject: [PATCH 06/14] Remove old terminology --- src/proto/h1/conn.rs | 2 +- src/server/conn.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/proto/h1/conn.rs b/src/proto/h1/conn.rs index 20131dea8a..d73a30c7b5 100644 --- a/src/proto/h1/conn.rs +++ b/src/proto/h1/conn.rs @@ -111,7 +111,7 @@ where } #[cfg(feature = "server")] - pub(crate) fn set_http1_header_parse_timeout(&mut self, val: Duration) { + pub(crate) fn set_http1_header_read_timeout(&mut self, val: Duration) { debug!("setting initial h1 header read timeout timer"); self.state.h1_header_read_timeout = Some(val); diff --git a/src/server/conn.rs b/src/server/conn.rs index 3a0f5d1782..0e43f992e3 100644 --- a/src/server/conn.rs +++ b/src/server/conn.rs @@ -638,8 +638,8 @@ impl Http { if self.h1_preserve_header_case { conn.set_preserve_header_case(); } - if let Some(header_parse_timeout) = self.h1_header_read_timeout { - conn.set_http1_header_parse_timeout(header_parse_timeout); + if let Some(header_read_timeout) = self.h1_header_read_timeout { + conn.set_http1_header_read_timeout(header_read_timeout); } if let Some(writev) = self.h1_writev { if writev { From 72bd2c13874ec7fe380b1bb2dd4d2f79f975d48a Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Thu, 11 Nov 2021 09:21:02 +0100 Subject: [PATCH 07/14] Don't start timeout when the connection is established --- src/proto/h1/conn.rs | 3 --- tests/server.rs | 34 ---------------------------------- 2 files changed, 37 deletions(-) diff --git a/src/proto/h1/conn.rs b/src/proto/h1/conn.rs index d73a30c7b5..519bb6120d 100644 --- a/src/proto/h1/conn.rs +++ b/src/proto/h1/conn.rs @@ -112,10 +112,7 @@ where #[cfg(feature = "server")] pub(crate) fn set_http1_header_read_timeout(&mut self, val: Duration) { - debug!("setting initial h1 header read timeout timer"); - self.state.h1_header_read_timeout = Some(val); - self.state.h1_header_read_timeout_fut = Some(Box::pin(tokio::time::sleep(val))); } #[cfg(feature = "server")] diff --git a/tests/server.rs b/tests/server.rs index c4c3406a01..16e905884c 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -1261,40 +1261,6 @@ 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_no_write() { - 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); - thread::sleep(Duration::from_secs(6)); - tcp.write_all( - b"\ - GET / HTTP/1.1\r\n\ - Something: 1\r\n\ - \r\n\ - ", - ) - .expect_err("write 1"); - }); - - 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("header timeout"); -} - #[tokio::test] async fn header_read_timeout_slow_writes() { let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); From b9b7e7340e1e0ff884e7e4ff1354af935c1766aa Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Thu, 18 Nov 2021 10:52:20 +0100 Subject: [PATCH 08/14] Require the runtime feature --- Cargo.toml | 3 ++- src/error.rs | 6 +++--- src/proto/h1/conn.rs | 9 ++++++++- src/proto/h1/io.rs | 8 +++++++- src/proto/h1/mod.rs | 3 +++ src/proto/h1/role.rs | 1 + src/server/conn.rs | 1 + src/server/server.rs | 4 ++-- 8 files changed, 27 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1ff676d0d1..06a6863624 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,7 +86,7 @@ full = [ ] # HTTP versions -http1 = ["tokio/time"] +http1 = [] http2 = ["h2"] # Client/Server @@ -100,6 +100,7 @@ stream = [] runtime = [ "tcp", "tokio/rt", + "tokio/time", ] tcp = [ "socket2", diff --git a/src/error.rs b/src/error.rs index 46c4a7d3a8..c26b1d41f7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -105,7 +105,7 @@ pub(super) enum User { #[cfg(feature = "server")] UnexpectedHeader, /// User took too long to send headers - #[cfg(feature = "http1")] + #[cfg(all(feature = "http1", feature = "runtime"))] HeaderTimeout, /// User tried to create a Request with bad version. #[cfg(any(feature = "http1", feature = "http2"))] @@ -313,7 +313,7 @@ impl Error { Error::new_user(User::UnexpectedHeader) } - #[cfg(feature = "http1")] + #[cfg(all(feature = "http1", feature = "runtime"))] pub(super) fn new_header_timeout() -> Error { Error::new_user(User::HeaderTimeout) } @@ -449,7 +449,7 @@ impl Error { #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "server")] Kind::User(User::UnexpectedHeader) => "user sent unexpected header", - #[cfg(feature = "http1")] + #[cfg(all(feature = "http1", feature = "runtime"))] Kind::User(User::HeaderTimeout) => "read header from client timeout", #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "client")] diff --git a/src/proto/h1/conn.rs b/src/proto/h1/conn.rs index 519bb6120d..391f576d64 100644 --- a/src/proto/h1/conn.rs +++ b/src/proto/h1/conn.rs @@ -8,6 +8,7 @@ use http::header::{HeaderValue, CONNECTION}; use http::{HeaderMap, Method, Version}; use httparse::ParserConfig; use tokio::io::{AsyncRead, AsyncWrite}; +#[cfg(all(feature = "server", feature = "runtime"))] use tokio::time::Sleep; use tracing::{debug, error, trace}; @@ -49,7 +50,9 @@ where keep_alive: KA::Busy, method: None, h1_parser_config: ParserConfig::default(), + #[cfg(all(feature = "server", feature = "runtime"))] h1_header_read_timeout: None, + #[cfg(all(feature = "server", feature = "runtime"))] h1_header_read_timeout_fut: None, preserve_header_case: false, title_case_headers: false, @@ -110,7 +113,7 @@ where self.state.h09_responses = true; } - #[cfg(feature = "server")] + #[cfg(all(feature = "server", feature = "runtime"))] pub(crate) fn set_http1_header_read_timeout(&mut self, val: Duration) { self.state.h1_header_read_timeout = Some(val); } @@ -187,7 +190,9 @@ where cached_headers: &mut self.state.cached_headers, req_method: &mut self.state.method, h1_parser_config: self.state.h1_parser_config.clone(), + #[cfg(all(feature = "server", feature = "runtime"))] h1_header_read_timeout: self.state.h1_header_read_timeout, + #[cfg(all(feature = "server", feature = "runtime"))] h1_header_read_timeout_fut: &mut self.state.h1_header_read_timeout_fut, preserve_header_case: self.state.preserve_header_case, h09_responses: self.state.h09_responses, @@ -809,7 +814,9 @@ struct State { /// a body or not. method: Option, h1_parser_config: ParserConfig, + #[cfg(all(feature = "server", feature = "runtime"))] h1_header_read_timeout: Option, + #[cfg(all(feature = "server", feature = "runtime"))] h1_header_read_timeout_fut: Option>>, preserve_header_case: bool, title_case_headers: bool, diff --git a/src/proto/h1/io.rs b/src/proto/h1/io.rs index b24758a74d..5f499bb7d0 100644 --- a/src/proto/h1/io.rs +++ b/src/proto/h1/io.rs @@ -182,7 +182,9 @@ where cached_headers: parse_ctx.cached_headers, req_method: parse_ctx.req_method, h1_parser_config: parse_ctx.h1_parser_config.clone(), + #[cfg(all(feature = "server", feature = "runtime"))] h1_header_read_timeout: parse_ctx.h1_header_read_timeout, + #[cfg(all(feature = "server", feature = "runtime"))] h1_header_read_timeout_fut: parse_ctx.h1_header_read_timeout_fut, preserve_header_case: parse_ctx.preserve_header_case, h09_responses: parse_ctx.h09_responses, @@ -195,7 +197,10 @@ where Some(msg) => { debug!("parsed {} headers", msg.head.headers.len()); - *parse_ctx.h1_header_read_timeout_fut = None; + #[cfg(all(feature = "server", feature = "runtime"))] + { + *parse_ctx.h1_header_read_timeout_fut = None; + } return Poll::Ready(Ok(msg)); } None => { @@ -205,6 +210,7 @@ where return Poll::Ready(Err(crate::Error::new_too_large())); } + #[cfg(all(feature = "server", feature = "runtime"))] if let Some(h1_header_read_timeout_fut) = parse_ctx.h1_header_read_timeout_fut { if Pin::new( h1_header_read_timeout_fut).poll(cx).is_ready() { *parse_ctx.h1_header_read_timeout_fut = None; diff --git a/src/proto/h1/mod.rs b/src/proto/h1/mod.rs index da8bfdfb3d..64f27ff7ec 100644 --- a/src/proto/h1/mod.rs +++ b/src/proto/h1/mod.rs @@ -4,6 +4,7 @@ use std::time::Duration; use bytes::BytesMut; use http::{HeaderMap, Method}; use httparse::ParserConfig; +#[cfg(all(feature = "server", feature = "runtime"))] use tokio::time::Sleep; use crate::body::DecodedLength; @@ -76,7 +77,9 @@ pub(crate) struct ParseContext<'a> { cached_headers: &'a mut Option, req_method: &'a mut Option, h1_parser_config: ParserConfig, + #[cfg(all(feature = "server", feature = "runtime"))] h1_header_read_timeout: Option, + #[cfg(all(feature = "server", feature = "runtime"))] h1_header_read_timeout_fut: &'a mut Option>>, preserve_header_case: bool, h09_responses: bool, diff --git a/src/proto/h1/role.rs b/src/proto/h1/role.rs index 923d091c6a..62ee126a3c 100644 --- a/src/proto/h1/role.rs +++ b/src/proto/h1/role.rs @@ -70,6 +70,7 @@ where let span = trace_span!("parse_headers"); let _s = span.enter(); + #[cfg(all(feature = "server", feature = "runtime"))] if let Some(h1_header_read_timeout) = ctx.h1_header_read_timeout { if ctx.h1_header_read_timeout_fut.is_none() { debug!("setting h1 header read timeout timer"); diff --git a/src/server/conn.rs b/src/server/conn.rs index d27723a156..beb63e398b 100644 --- a/src/server/conn.rs +++ b/src/server/conn.rs @@ -645,6 +645,7 @@ impl Http { if self.h1_preserve_header_case { conn.set_preserve_header_case(); } + #[cfg(all(feature = "http1", feature = "runtime"))] if let Some(header_read_timeout) = self.h1_header_read_timeout { conn.set_http1_header_read_timeout(header_read_timeout); } diff --git a/src/server/server.rs b/src/server/server.rs index e141f39f4b..e4dbd51d75 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -313,8 +313,8 @@ impl Builder { /// transmit the entire header withing this time, the connection is closed. /// /// Default is None. - #[cfg(feature = "http1")] - #[cfg_attr(docsrs, doc(cfg(feature = "http1")))] + #[cfg(all(feature = "http1", feature = "runtime"))] + #[cfg_attr(docsrs, doc(cfg(all(feature = "http1", feature = "runtime"))))] pub fn http1_header_read_timeout(mut self, read_timeout: Duration) -> Self { self.protocol.http1_header_read_timeout(read_timeout); self From 1f6b7b7a55659109790e2446d47f2d1b81da078c Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Thu, 18 Nov 2021 10:58:47 +0100 Subject: [PATCH 09/14] Use main error Kind --- src/error.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/error.rs b/src/error.rs index c26b1d41f7..0949e1cb85 100644 --- a/src/error.rs +++ b/src/error.rs @@ -44,6 +44,9 @@ pub(super) enum Kind { #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "server")] Accept, + /// User took too long to send headers + #[cfg(all(feature = "http1", feature = "server", feature = "runtime"))] + HeaderTimeout, /// Error while reading a body from connection. #[cfg(any(feature = "http1", feature = "http2", feature = "stream"))] Body, @@ -104,9 +107,6 @@ pub(super) enum User { #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "server")] UnexpectedHeader, - /// User took too long to send headers - #[cfg(all(feature = "http1", feature = "runtime"))] - HeaderTimeout, /// User tried to create a Request with bad version. #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "client")] @@ -313,9 +313,9 @@ impl Error { Error::new_user(User::UnexpectedHeader) } - #[cfg(all(feature = "http1", feature = "runtime"))] + #[cfg(all(feature = "http1", feature = "server", feature = "runtime"))] pub(super) fn new_header_timeout() -> Error { - Error::new_user(User::HeaderTimeout) + Error::new(Kind::HeaderTimeout) } #[cfg(any(feature = "http1", feature = "http2"))] @@ -427,6 +427,8 @@ impl Error { #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "server")] Kind::Accept => "error accepting connection", + #[cfg(all(feature = "http1", feature = "server", feature = "runtime"))] + Kind::HeaderTimeout => "read header from client timeout", #[cfg(any(feature = "http1", feature = "http2", feature = "stream"))] Kind::Body => "error reading a body from connection", #[cfg(any(feature = "http1", feature = "http2"))] @@ -449,8 +451,6 @@ impl Error { #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "server")] Kind::User(User::UnexpectedHeader) => "user sent unexpected header", - #[cfg(all(feature = "http1", feature = "runtime"))] - Kind::User(User::HeaderTimeout) => "read header from client timeout", #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "client")] Kind::User(User::UnsupportedVersion) => "request has unsupported HTTP version", From a2f82d0dd45981377cc1b57593bd24e9c5c90399 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Thu, 18 Nov 2021 11:32:40 +0100 Subject: [PATCH 10/14] Recycle the same Box --- src/proto/h1/conn.rs | 6 ++++++ src/proto/h1/io.rs | 28 +++++++++++++++++++++------- src/proto/h1/mod.rs | 2 ++ src/proto/h1/role.rs | 32 +++++++++++++++++++++++++++++--- 4 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/proto/h1/conn.rs b/src/proto/h1/conn.rs index 391f576d64..ed694ec02c 100644 --- a/src/proto/h1/conn.rs +++ b/src/proto/h1/conn.rs @@ -54,6 +54,8 @@ where h1_header_read_timeout: None, #[cfg(all(feature = "server", feature = "runtime"))] h1_header_read_timeout_fut: None, + #[cfg(all(feature = "server", feature = "runtime"))] + h1_header_read_timeout_running: false, preserve_header_case: false, title_case_headers: false, h09_responses: false, @@ -194,6 +196,8 @@ where h1_header_read_timeout: self.state.h1_header_read_timeout, #[cfg(all(feature = "server", feature = "runtime"))] h1_header_read_timeout_fut: &mut self.state.h1_header_read_timeout_fut, + #[cfg(all(feature = "server", feature = "runtime"))] + h1_header_read_timeout_running: &mut self.state.h1_header_read_timeout_running, preserve_header_case: self.state.preserve_header_case, h09_responses: self.state.h09_responses, #[cfg(feature = "ffi")] @@ -818,6 +822,8 @@ struct State { h1_header_read_timeout: Option, #[cfg(all(feature = "server", feature = "runtime"))] h1_header_read_timeout_fut: Option>>, + #[cfg(all(feature = "server", feature = "runtime"))] + h1_header_read_timeout_running: bool, preserve_header_case: bool, title_case_headers: bool, h09_responses: bool, diff --git a/src/proto/h1/io.rs b/src/proto/h1/io.rs index 5f499bb7d0..69c4997073 100644 --- a/src/proto/h1/io.rs +++ b/src/proto/h1/io.rs @@ -4,7 +4,11 @@ use std::io::{self, IoSlice}; use std::marker::Unpin; use std::mem::MaybeUninit; use std::future::Future; +#[cfg(all(feature = "server", feature = "runtime"))] +use std::time::Duration; +#[cfg(all(feature = "server", feature = "runtime"))] +use tokio::time::Instant; use bytes::{Buf, BufMut, Bytes, BytesMut}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use tracing::{debug, warn, trace}; @@ -186,6 +190,8 @@ where h1_header_read_timeout: parse_ctx.h1_header_read_timeout, #[cfg(all(feature = "server", feature = "runtime"))] h1_header_read_timeout_fut: parse_ctx.h1_header_read_timeout_fut, + #[cfg(all(feature = "server", feature = "runtime"))] + h1_header_read_timeout_running: parse_ctx.h1_header_read_timeout_running, preserve_header_case: parse_ctx.preserve_header_case, h09_responses: parse_ctx.h09_responses, #[cfg(feature = "ffi")] @@ -199,7 +205,12 @@ where #[cfg(all(feature = "server", feature = "runtime"))] { - *parse_ctx.h1_header_read_timeout_fut = None; + *parse_ctx.h1_header_read_timeout_running = false; + + if let Some(h1_header_read_timeout_fut) = parse_ctx.h1_header_read_timeout_fut { + // Reset the timer in order to avoid woken up when the timeout finishes + h1_header_read_timeout_fut.as_mut().reset(Instant::now() + Duration::from_secs(30 * 24 * 60 * 60)); + } } return Poll::Ready(Ok(msg)); } @@ -211,12 +222,14 @@ where } #[cfg(all(feature = "server", feature = "runtime"))] - if let Some(h1_header_read_timeout_fut) = parse_ctx.h1_header_read_timeout_fut { - if Pin::new( h1_header_read_timeout_fut).poll(cx).is_ready() { - *parse_ctx.h1_header_read_timeout_fut = None; - - warn!("read header from client timeout"); - return Poll::Ready(Err(crate::Error::new_header_timeout())) + if *parse_ctx.h1_header_read_timeout_running { + if let Some(h1_header_read_timeout_fut) = parse_ctx.h1_header_read_timeout_fut { + if Pin::new( h1_header_read_timeout_fut).poll(cx).is_ready() { + *parse_ctx.h1_header_read_timeout_running = false; + + warn!("read header from client timeout"); + return Poll::Ready(Err(crate::Error::new_header_timeout())) + } } } } @@ -715,6 +728,7 @@ mod tests { h1_parser_config: Default::default(), h1_header_read_timeout: None, h1_header_read_timeout_fut: &mut None, + h1_header_read_timeout_running: &mut false, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] diff --git a/src/proto/h1/mod.rs b/src/proto/h1/mod.rs index 64f27ff7ec..a39fabf13b 100644 --- a/src/proto/h1/mod.rs +++ b/src/proto/h1/mod.rs @@ -81,6 +81,8 @@ pub(crate) struct ParseContext<'a> { h1_header_read_timeout: Option, #[cfg(all(feature = "server", feature = "runtime"))] h1_header_read_timeout_fut: &'a mut Option>>, + #[cfg(all(feature = "server", feature = "runtime"))] + h1_header_read_timeout_running: &'a mut bool, preserve_header_case: bool, h09_responses: bool, #[cfg(feature = "ffi")] diff --git a/src/proto/h1/role.rs b/src/proto/h1/role.rs index 62ee126a3c..46affe03b6 100644 --- a/src/proto/h1/role.rs +++ b/src/proto/h1/role.rs @@ -1,6 +1,8 @@ use std::fmt::{self, Write}; use std::mem::MaybeUninit; +#[cfg(all(feature = "server", feature = "runtime"))] +use tokio::time::Instant; #[cfg(any(test, feature = "server", feature = "ffi"))] use bytes::Bytes; use bytes::BytesMut; @@ -71,12 +73,22 @@ where let _s = span.enter(); #[cfg(all(feature = "server", feature = "runtime"))] + if !*ctx.h1_header_read_timeout_running { if let Some(h1_header_read_timeout) = ctx.h1_header_read_timeout { - if ctx.h1_header_read_timeout_fut.is_none() { - debug!("setting h1 header read timeout timer"); - *ctx.h1_header_read_timeout_fut = Some(Box::pin(tokio::time::sleep(h1_header_read_timeout))); + let deadline = Instant::now() + h1_header_read_timeout; + + match ctx.h1_header_read_timeout_fut { + Some(h1_header_read_timeout_fut) => { + debug!("resetting h1 header read timeout timer"); + h1_header_read_timeout_fut.as_mut().reset(deadline); + }, + None => { + debug!("setting h1 header read timeout timer"); + *ctx.h1_header_read_timeout_fut = Some(Box::pin(tokio::time::sleep_until(deadline))); + } } } +} T::parse(bytes, ctx) } @@ -1439,6 +1451,7 @@ mod tests { h1_parser_config: Default::default(), h1_header_read_timeout: None, h1_header_read_timeout_fut: &mut None, + h1_header_read_timeout_running: &mut false, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -1468,6 +1481,7 @@ mod tests { h1_parser_config: Default::default(), h1_header_read_timeout: None, h1_header_read_timeout_fut: &mut None, + h1_header_read_timeout_running: &mut false, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -1492,6 +1506,7 @@ mod tests { h1_parser_config: Default::default(), h1_header_read_timeout: None, h1_header_read_timeout_fut: &mut None, + h1_header_read_timeout_running: &mut false, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -1514,6 +1529,7 @@ mod tests { h1_parser_config: Default::default(), h1_header_read_timeout: None, h1_header_read_timeout_fut: &mut None, + h1_header_read_timeout_running: &mut false, preserve_header_case: false, h09_responses: true, #[cfg(feature = "ffi")] @@ -1538,6 +1554,7 @@ mod tests { h1_parser_config: Default::default(), h1_header_read_timeout: None, h1_header_read_timeout_fut: &mut None, + h1_header_read_timeout_running: &mut false, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -1566,6 +1583,7 @@ mod tests { h1_parser_config, h1_header_read_timeout: None, h1_header_read_timeout_fut: &mut None, + h1_header_read_timeout_running: &mut false, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -1591,6 +1609,7 @@ mod tests { h1_parser_config: Default::default(), h1_header_read_timeout: None, h1_header_read_timeout_fut: &mut None, + h1_header_read_timeout_running: &mut false, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -1611,6 +1630,7 @@ mod tests { h1_parser_config: Default::default(), h1_header_read_timeout: None, h1_header_read_timeout_fut: &mut None, + h1_header_read_timeout_running: &mut false, preserve_header_case: true, h09_responses: false, #[cfg(feature = "ffi")] @@ -1652,6 +1672,7 @@ mod tests { h1_parser_config: Default::default(), h1_header_read_timeout: None, h1_header_read_timeout_fut: &mut None, + h1_header_read_timeout_running: &mut false, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -1674,6 +1695,7 @@ mod tests { h1_parser_config: Default::default(), h1_header_read_timeout: None, h1_header_read_timeout_fut: &mut None, + h1_header_read_timeout_running: &mut false, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -1905,6 +1927,7 @@ mod tests { h1_parser_config: Default::default(), h1_header_read_timeout: None, h1_header_read_timeout_fut: &mut None, + h1_header_read_timeout_running: &mut false, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -1927,6 +1950,7 @@ mod tests { h1_parser_config: Default::default(), h1_header_read_timeout: None, h1_header_read_timeout_fut: &mut None, + h1_header_read_timeout_running: &mut false, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -1949,6 +1973,7 @@ mod tests { h1_parser_config: Default::default(), h1_header_read_timeout: None, h1_header_read_timeout_fut: &mut None, + h1_header_read_timeout_running: &mut false, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -2448,6 +2473,7 @@ mod tests { h1_parser_config: Default::default(), h1_header_read_timeout: None, h1_header_read_timeout_fut: &mut None, + h1_header_read_timeout_running: &mut false, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] From d312ecd51c584b36b0960e2f221e3dd419691ac8 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Thu, 18 Nov 2021 11:41:02 +0100 Subject: [PATCH 11/14] Fix compiling --- src/proto/h1/role.rs | 2 ++ src/server/conn.rs | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/proto/h1/role.rs b/src/proto/h1/role.rs index 46affe03b6..794ee7945c 100644 --- a/src/proto/h1/role.rs +++ b/src/proto/h1/role.rs @@ -2560,6 +2560,7 @@ mod tests { h1_parser_config: Default::default(), h1_header_read_timeout: None, h1_header_read_timeout_fut: &mut None, + h1_header_read_timeout_running: &mut false, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] @@ -2602,6 +2603,7 @@ mod tests { h1_parser_config: Default::default(), h1_header_read_timeout: None, h1_header_read_timeout_fut: &mut None, + h1_header_read_timeout_running: &mut false, preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] diff --git a/src/server/conn.rs b/src/server/conn.rs index beb63e398b..a6e26f636b 100644 --- a/src/server/conn.rs +++ b/src/server/conn.rs @@ -286,7 +286,7 @@ impl Http { h1_keep_alive: true, h1_title_case_headers: false, h1_preserve_header_case: false, - #[cfg(feature = "http1")] + #[cfg(all(feature = "http1", feature = "runtime"))] h1_header_read_timeout: None, h1_writev: None, #[cfg(feature = "http2")] @@ -379,8 +379,8 @@ impl Http { /// transmit the entire header withing this time, the connection is closed. /// /// Default is None. - #[cfg(feature = "http1")] - #[cfg_attr(docsrs, doc(cfg(feature = "http1")))] + #[cfg(all(feature = "http1", feature = "runtime"))] + #[cfg_attr(docsrs, doc(cfg(all(feature = "http1", feature = "runtime"))))] pub fn http1_header_read_timeout(&mut self, read_timeout: Duration) -> &mut Self { self.h1_header_read_timeout = Some(read_timeout); self From ef01e7cecb5fd9a94015058c31eaa87252a89ea1 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Thu, 18 Nov 2021 11:46:25 +0100 Subject: [PATCH 12/14] Another fix --- src/server/conn.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/conn.rs b/src/server/conn.rs index a6e26f636b..90f2e59477 100644 --- a/src/server/conn.rs +++ b/src/server/conn.rs @@ -102,7 +102,7 @@ pub struct Http { h1_keep_alive: bool, h1_title_case_headers: bool, h1_preserve_header_case: bool, - #[cfg(feature = "http1")] + #[cfg(all(feature = "http1", feature = "runtime"))] h1_header_read_timeout: Option, h1_writev: Option, #[cfg(feature = "http2")] @@ -581,7 +581,7 @@ impl Http { h1_keep_alive: self.h1_keep_alive, h1_title_case_headers: self.h1_title_case_headers, h1_preserve_header_case: self.h1_preserve_header_case, - #[cfg(feature = "http1")] + #[cfg(all(feature = "http1", feature = "runtime"))] h1_header_read_timeout: self.h1_header_read_timeout, h1_writev: self.h1_writev, #[cfg(feature = "http2")] From a5cf3515e1c9488008f86f71864831bef4eba384 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Thu, 18 Nov 2021 20:35:37 +0100 Subject: [PATCH 13/14] Update src/server/conn.rs Co-authored-by: Sean McArthur --- src/server/conn.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/conn.rs b/src/server/conn.rs index 90f2e59477..c49c8ae571 100644 --- a/src/server/conn.rs +++ b/src/server/conn.rs @@ -376,7 +376,7 @@ impl Http { } /// Set a timeout for reading client request headers. If a client does not - /// transmit the entire header withing this time, the connection is closed. + /// transmit the entire header within this time, the connection is closed. /// /// Default is None. #[cfg(all(feature = "http1", feature = "runtime"))] From 0f8a85d9fbeab968d70bccc23380f06df69a472d Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Thu, 18 Nov 2021 20:35:43 +0100 Subject: [PATCH 14/14] Update src/server/server.rs Co-authored-by: Sean McArthur --- src/server/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/server.rs b/src/server/server.rs index e4dbd51d75..87027bbefc 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -310,7 +310,7 @@ impl Builder { } /// Set a timeout for reading client request headers. If a client does not - /// transmit the entire header withing this time, the connection is closed. + /// transmit the entire header within this time, the connection is closed. /// /// Default is None. #[cfg(all(feature = "http1", feature = "runtime"))]