[Rust Frontend] Refactor TLS serve path with unified `MaybeTlsListener` (#47101)

Signed-off-by: Bugen Zhao <i@bugenzhao.com>

Bugen Zhao committed Jun 30, 2026 at 21:31 UTC 3675bcff67ad5688efef74a0948e879962c4bc38
6 files changed +138 -180
rust/src/server/Cargo.toml
+1
@@ -61,6 +61,7 @@ expect-test.workspace = true
61 rmp-serde.workspace = true
62 serial_test.workspace = true
63 tempfile.workspace = true
64 +tokio = { workspace = true, features = ["test-util"] }
65 tower.workspace = true
66 vllm-engine-core-client = { workspace = true, features = ["test-util"] }
67 zeromq.workspace = true
rust/src/server/src/grpc/mod.rs
+1 -78
@@ -4,21 +4,16 @@ mod convert;
4
5 use std::pin::Pin;
6 use std::sync::Arc;
7 -use std::task::{Context, Poll};
7
9 -use futures::{Stream, StreamExt as _, stream};
8 +use futures::{Stream, StreamExt as _};
9 use thiserror_ext::AsReport as _;
11 -use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
10 use tokio::sync::mpsc;
13 -use tokio_openssl::SslStream;
11 use tokio_stream::wrappers::ReceiverStream;
15 -use tonic::transport::server::{Connected, TcpConnectInfo};
12 use tonic::{Request, Response, Status};
13 use tracing::info;
14 use vllm_text::{DecodedTextEvent, TextOutputStreamExt as _};
15
16 use self::convert::ResponseOpts;
21 -use crate::listener::{Listener, ListenerIo};
17 use crate::state::AppState;
18
19 /// Generated protobuf/gRPC types for the `vllm` package.
@@ -31,78 +26,6 @@ pub use pb::generate_server::GenerateServer;
26 #[cfg(test)]
27 mod tests;
28
34 -/// Newtype over `tokio-openssl`'s `SslStream` so we can implement tonic's
35 -/// [`Connected`] on it (the orphan rule blocks doing so on the foreign type).
36 -pub(crate) struct GrpcTlsStream {
37 - inner: SslStream<ListenerIo>,
38 -}
39 -
40 -impl GrpcTlsStream {
41 - pub(crate) fn new(inner: SslStream<ListenerIo>) -> Self {
42 - Self { inner }
43 - }
44 -}
45 -
46 -impl AsyncRead for GrpcTlsStream {
47 - fn poll_read(
48 - self: Pin<&mut Self>,
49 - cx: &mut Context<'_>,
50 - buf: &mut ReadBuf<'_>,
51 - ) -> Poll<std::io::Result<()>> {
52 - Pin::new(&mut self.get_mut().inner).poll_read(cx, buf)
53 - }
54 -}
55 -
56 -impl AsyncWrite for GrpcTlsStream {
57 - fn poll_write(
58 - self: Pin<&mut Self>,
59 - cx: &mut Context<'_>,
60 - buf: &[u8],
61 - ) -> Poll<std::io::Result<usize>> {
62 - Pin::new(&mut self.get_mut().inner).poll_write(cx, buf)
63 - }
64 -
65 - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
66 - Pin::new(&mut self.get_mut().inner).poll_flush(cx)
67 - }
68 -
69 - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
70 - Pin::new(&mut self.get_mut().inner).poll_shutdown(cx)
71 - }
72 -}
73 -
74 -impl Connected for GrpcTlsStream {
75 - type ConnectInfo = TcpConnectInfo;
76 -
77 - fn connect_info(&self) -> TcpConnectInfo {
78 - self.inner.get_ref().connect_info()
79 - }
80 -}
81 -
82 -/// Adapt the shared server listener into tonic's incoming stream shape.
83 -pub(crate) fn incoming(listener: Listener) -> impl Stream<Item = std::io::Result<ListenerIo>> {
84 - stream::unfold(listener, |mut listener| async move {
85 - let (io, _) = axum::serve::Listener::accept(&mut listener).await;
86 - Some((Ok(io), listener))
87 - })
88 -}
89 -
90 -/// Wrap the gRPC listener so each accepted connection completes a TLS handshake
91 -/// before tonic serves it.
92 -pub(crate) fn tls_incoming(
93 - listener: Listener,
94 - context: openssl::ssl::SslContext,
95 - handshake_timeout: std::time::Duration,
96 -) -> impl Stream<Item = std::io::Result<GrpcTlsStream>> {
97 - tls_listener::builder(context)
98 - .handshake_timeout(handshake_timeout)
99 - .listen(listener)
100 - .map(|res| {
101 - res.map(|(inner, _addr)| GrpcTlsStream::new(inner))
102 - .map_err(std::io::Error::other)
103 - })
104 -}
105 -
29 /// gRPC Generate service implementation backed by the shared application state.
30 pub struct GenerateServiceImpl {
31 state: Arc<AppState>,
rust/src/server/src/grpc/tests.rs
+5 -5
@@ -30,8 +30,8 @@ use zeromq::prelude::{SocketRecv, SocketSend};
30 use zeromq::{DealerSocket, PushSocket, ZmqMessage};
31
32 use super::pb::generate_client::GenerateClient;
33 -use super::{GenerateServer, GenerateServiceImpl, incoming, pb, tls_incoming};
34 -use crate::listener::Listener;
33 +use super::{GenerateServer, GenerateServiceImpl, pb};
34 +use crate::listener::{Listener, MaybeTlsListener};
35 use crate::state::AppState;
36 use crate::tls;
37 use crate::tls_tests::{TestCerts, server_tls};
@@ -287,7 +287,7 @@ async fn grpc_test_server(
287 let addr = listener.local_addr().expect("local addr");
288
289 let server_task = tokio::spawn(async move {
290 - let incoming = incoming(Listener::Tcp(listener));
290 + let incoming = MaybeTlsListener::plain(Listener::Tcp(listener));
291 TonicServer::builder()
292 .add_service(svc)
293 .serve_with_incoming(incoming)
@@ -318,7 +318,7 @@ async fn grpc_tls_test_server(
318 let addr = listener.local_addr().expect("local addr").to_string();
319
320 let server_task = tokio::spawn(async move {
321 - let incoming = tls_incoming(Listener::Tcp(listener), context, tls::TLS_HANDSHAKE_TIMEOUT);
321 + let incoming = MaybeTlsListener::tls(Listener::Tcp(listener), context);
322 TonicServer::builder()
323 .add_service(svc)
324 .serve_with_incoming(incoming)
@@ -413,7 +413,7 @@ async fn grpc_server_with_keepalive(
413 }
414
415 let server_task = tokio::spawn(async move {
416 - let incoming = incoming(Listener::Tcp(listener));
416 + let incoming = MaybeTlsListener::plain(Listener::Tcp(listener));
417 builder
418 .add_service(svc)
419 .serve_with_incoming(incoming)
rust/src/server/src/lib.rs
+17 -62
@@ -27,7 +27,6 @@ pub use config::{
27 ApiServerOptions, Config, CoordinatorMode, CorsConfig, DEFAULT_KEEP_ALIVE_TIMEOUT,
28 HttpListenerMode, TlsConfig,
29 };
30 -use futures::FutureExt as _;
30 use hyper::body::Incoming;
31 use hyper::server::conn::http1;
32 use hyper_util::rt::{TokioIo, TokioTimer};
@@ -45,7 +44,7 @@ use vllm_engine_core_client::{EngineCoreClient, EngineCoreClientConfig};
44 use vllm_llm::Llm;
45 use vllm_text::TextLlm;
46
48 -use crate::listener::Listener;
47 +use crate::listener::{Listener, MaybeTlsListener};
48 use crate::routes::build_router;
49 use crate::server_info::ServerInfoSnapshot;
50 use crate::state::AppState;
@@ -179,7 +178,7 @@ where
178 let listener = Listener::bind(&config.listener_mode)
179 .await
180 .context("failed to bind listener for OpenAI server")?;
182 - let bind_address = listener.local_addr()?;
181 + let bind_address = listener.local_addr_display()?;
182 let model = state.primary_model_name().to_owned();
183 let app = extend_router(build_router(state.clone()));
184
@@ -252,7 +251,6 @@ where
251 // silent client cannot hold the connection open.
252 let keep_alive_timeout = config.keep_alive_timeout;
253 let timeouts = ConnectionTimeouts {
255 - handshake: tls::TLS_HANDSHAKE_TIMEOUT,
254 header_read: if keep_alive_timeout.is_zero() {
255 DEFAULT_KEEP_ALIVE_TIMEOUT
256 } else {
@@ -266,9 +264,15 @@ where
264 let server_shutdown = server_shutdown.clone();
265 let force_shutdown = force_shutdown.clone();
266 async move {
267 + let listener = match tls_config {
268 + Some(context) => MaybeTlsListener::tls(listener, context),
269 + None => MaybeTlsListener::plain(listener),
270 + };
271 + let server = serve_connections(listener, app, shutdown.cancelled_owned(), timeouts);
272 +
273 let result = tokio::select! {
270 - result = serve_listener(listener, tls_config, app, shutdown.cancelled_owned(), timeouts) => {
271 - result
274 + result = server => {
275 + result.context("HTTP server failed")
276 }
277 _ = force_shutdown.cancelled() => {
278 warn!("HTTP graceful shutdown deadline elapsed; aborting server");
@@ -292,18 +296,11 @@ where
296 shutdown.cancelled().await;
297 return Ok(());
298 };
295 - // Box to unify the TLS and plaintext arms' different stream types.
296 - let server = match grpc_tls {
297 - Some(context) => {
298 - let incoming =
299 - grpc::tls_incoming(grpc_listener, context, tls::TLS_HANDSHAKE_TIMEOUT);
300 - svc.serve_with_incoming_shutdown(incoming, shutdown.cancelled_owned()).boxed()
301 - }
302 - None => {
303 - let incoming = grpc::incoming(grpc_listener);
304 - svc.serve_with_incoming_shutdown(incoming, shutdown.cancelled_owned()).boxed()
305 - }
299 + let incoming = match grpc_tls {
300 + Some(context) => MaybeTlsListener::tls(grpc_listener, context),
301 + None => MaybeTlsListener::plain(grpc_listener),
302 };
303 + let server = svc.serve_with_incoming_shutdown(incoming, shutdown.cancelled_owned());
304
305 let result = tokio::select! {
306 result = server => {
@@ -333,61 +330,19 @@ where
330 /// Per-connection timeouts applied while serving HTTP/HTTPS.
331 #[derive(Clone, Copy)]
332 pub(crate) struct ConnectionTimeouts {
336 - /// Max time for a client to complete the TLS handshake (TLS path only).
337 - pub(crate) handshake: Duration,
333 /// HTTP/1 header-read timeout (bounds idle keep-alive and the head read).
334 pub(crate) header_read: Duration,
335 /// Whether HTTP/1 keep-alive is enabled; `false` closes after each response.
336 pub(crate) keep_alive_enabled: bool,
337 }
338
344 -/// Apply optional TLS termination and per-connection HTTP timeouts, then serve
345 -/// `app`. Shared by [`serve_with_router_extension`] and the TLS tests.
346 -async fn serve_listener(
347 - listener: Listener,
348 - tls: Option<openssl::ssl::SslContext>,
349 - app: Router,
350 - shutdown: impl Future<Output = ()> + Send + 'static,
351 - timeouts: ConnectionTimeouts,
352 -) -> Result<()> {
353 - match tls {
354 - Some(context) => {
355 - // tls-listener terminates TLS (handshake + timeout); serve_connections
356 - // owns the HTTP keep-alive/idle bound that axum::serve cannot express.
357 - // Failed handshakes (incl. timeouts) log at ERROR via tls-listener.
358 - let listener = tls_listener::builder(context)
359 - .handshake_timeout(timeouts.handshake)
360 - .listen(listener);
361 - serve_connections(
362 - listener,
363 - app,
364 - shutdown,
365 - timeouts.header_read,
366 - timeouts.keep_alive_enabled,
367 - )
368 - .await
369 - .context("HTTPS server failed")
370 - }
371 - None => serve_connections(
372 - listener,
373 - app,
374 - shutdown,
375 - timeouts.header_read,
376 - timeouts.keep_alive_enabled,
377 - )
378 - .await
379 - .context("HTTP server failed"),
380 - }
381 -}
382 -
339 /// Serve `app` per connection (HTTP/1) with a keep-alive idle timeout and
340 /// graceful drain. Hand-rolled on hyper because [`axum::serve()`] takes no config.
341 async fn serve_connections<L>(
342 mut listener: L,
343 app: Router,
344 shutdown: impl Future<Output = ()> + Send,
389 - header_read: Duration,
390 - keep_alive_enabled: bool,
345 + timeouts: ConnectionTimeouts,
346 ) -> Result<()>
347 where
348 L: axum::serve::Listener,
@@ -404,8 +359,8 @@ where
359 app.clone().map_request(|req: Request<Incoming>| req.map(Body::new)),
360 );
361 let mut builder = http1::Builder::new();
407 - builder.timer(TokioTimer::new()).header_read_timeout(header_read);
408 - if !keep_alive_enabled {
362 + builder.timer(TokioTimer::new()).header_read_timeout(timeouts.header_read);
363 + if !timeouts.keep_alive_enabled {
364 builder.keep_alive(false);
365 }
366 let connection = builder.serve_connection(TokioIo::new(io), service);
rust/src/server/src/listener.rs
+96 -6
@@ -12,13 +12,14 @@ use std::pin::Pin;
12 use std::task::{Context, Poll, ready};
13
14 use auto_enums::enum_derive;
15 +use openssl::ssl::SslContext;
16 use socket2::Socket;
17 use tls_listener::{AsyncAccept, AsyncListener};
18 use tokio::net::{TcpListener, TcpStream, UnixListener, UnixStream};
19 use tonic::transport::server::{Connected, TcpConnectInfo};
20 use tracing::trace;
21
21 -use crate::HttpListenerMode;
22 +use crate::{HttpListenerMode, tls};
23
24 /// Runtime listener type used by the OpenAI-compatible HTTP or gRPC server,
25 /// which is either a TCP listener or a Unix-domain listener.
@@ -61,7 +62,7 @@ impl Listener {
62
63 /// Return a log-friendly local address string for either TCP or Unix
64 /// sockets.
64 - pub fn local_addr(&self) -> Result<String> {
65 + pub fn local_addr_display(&self) -> Result<String> {
66 match self {
67 Self::Tcp(listener) => Ok(listener.local_addr()?.to_string()),
68 Self::Unix(listener) => Ok(match listener.local_addr()?.as_pathname() {
@@ -92,7 +93,7 @@ impl Listener {
93 }
94 }
95
95 - fn listener_addr(&self) -> Result<ListenerAddr> {
96 + fn local_addr(&self) -> Result<ListenerAddr> {
97 match self {
98 Self::Tcp(listener) => listener.local_addr().map(ListenerAddr::Tcp),
99 Self::Unix(listener) => listener.local_addr().map(ListenerAddr::Unix),
@@ -100,6 +101,7 @@ impl Listener {
101 }
102 }
103
104 +/// Allow the unified listener to plug directly into tonic's gRPC server.
105 impl Connected for ListenerIo {
106 type ConnectInfo = TcpConnectInfo;
107
@@ -144,7 +146,7 @@ impl axum::serve::Listener for Listener {
146 }
147
148 fn local_addr(&self) -> Result<Self::Addr> {
147 - self.listener_addr()
149 + self.local_addr()
150 }
151 }
152
@@ -173,10 +175,98 @@ impl AsyncAccept for Listener {
175 }
176 }
177 }
176 -
178 impl AsyncListener for Listener {
179 fn local_addr(&self) -> Result<Self::Address> {
179 - self.listener_addr()
180 + self.local_addr()
181 + }
182 +}
183 +
184 +/// A listener that may be either a plain TCP/UDS listener or a TLS listener over it.
185 +pub enum MaybeTlsListener {
186 + Plain(Listener),
187 + Tls(tls_listener::TlsListener<Listener, SslContext>),
188 +}
189 +
190 +impl MaybeTlsListener {
191 + /// Create a plain listener without TLS.
192 + pub fn plain(listener: Listener) -> Self {
193 + Self::Plain(listener)
194 + }
195 +
196 + /// Create a TLS listener over the given plain listener.
197 + pub fn tls(listener: Listener, context: SslContext) -> Self {
198 + Self::Tls(
199 + tls_listener::builder(context)
200 + .handshake_timeout(tls::TLS_HANDSHAKE_TIMEOUT)
201 + .listen(listener),
202 + )
203 + }
204 +}
205 +
206 +/// Listener I/O type that may be either a plain TCP/UDS stream or a TLS stream over it.
207 +#[derive(Debug)]
208 +#[enum_derive(tokio1::AsyncRead, tokio1::AsyncWrite)]
209 +pub enum MaybeTlsStream {
210 + Plain(ListenerIo),
211 + Tls(tokio_openssl::SslStream<ListenerIo>),
212 +}
213 +
214 +/// Allow the maybe-TLS listener to plug directly into `axum::serve(...)`.
215 +impl axum::serve::Listener for MaybeTlsListener {
216 + type Addr = ListenerAddr;
217 + type Io = MaybeTlsStream;
218 +
219 + async fn accept(&mut self) -> (Self::Io, Self::Addr) {
220 + match self {
221 + Self::Plain(listener) => {
222 + let (io, addr) = axum::serve::Listener::accept(listener).await;
223 + (MaybeTlsStream::Plain(io), addr)
224 + }
225 + Self::Tls(tls_listener) => {
226 + let (io, addr) = axum::serve::Listener::accept(tls_listener).await;
227 + (MaybeTlsStream::Tls(io), addr)
228 + }
229 + }
230 + }
231 +
232 + fn local_addr(&self) -> tokio::io::Result<Self::Addr> {
233 + match self {
234 + Self::Plain(listener) => listener.local_addr(),
235 + Self::Tls(tls_listener) => tls_listener.local_addr(),
236 + }
237 + }
238 +}
239 +
240 +/// Allow the maybe-TLS listener to plug directly into tonic's gRPC server.
241 +impl Connected for MaybeTlsStream {
242 + type ConnectInfo = TcpConnectInfo;
243 +
244 + fn connect_info(&self) -> TcpConnectInfo {
245 + match self {
246 + Self::Plain(stream) => stream.connect_info(),
247 + Self::Tls(stream) => stream.get_ref().connect_info(),
248 + }
249 + }
250 +}
251 +
252 +/// Allow the maybe-TLS listener to be adaptable to tonic's incoming stream shape.
253 +impl futures::Stream for MaybeTlsListener {
254 + type Item = std::io::Result<MaybeTlsStream>;
255 +
256 + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
257 + match self.get_mut() {
258 + Self::Plain(listener) => {
259 + let listener = Pin::new(listener);
260 + let (io, _) = ready!(listener.poll_accept(cx))?;
261 + Poll::Ready(Some(Ok(MaybeTlsStream::Plain(io))))
262 + }
263 + Self::Tls(tls_listener) => {
264 + let tls_listener = Pin::new(tls_listener);
265 + let (io, _) =
266 + ready!(tls_listener.poll_accept(cx)).map_err(std::io::Error::other)?;
267 + Poll::Ready(Some(Ok(MaybeTlsStream::Tls(io))))
268 + }
269 + }
270 }
271 }
272
rust/src/server/src/tls_tests.rs
+18 -29
@@ -1,5 +1,5 @@
1 //! TLS tests: `build_server_config` unit checks plus end-to-end OpenSSL handshakes
2 -//! through the production `serve_listener` path, with a trivial router since TLS
2 +//! through the production listener/connection path, with a trivial router since TLS
3 //! terminates below the app.
4
5 use std::pin::Pin;
@@ -23,8 +23,8 @@ use tokio_openssl::SslStream;
23 use tokio_util::sync::CancellationToken;
24
25 use crate::config::{HttpListenerMode, TlsConfig};
26 -use crate::listener::Listener;
27 -use crate::{ConnectionTimeouts, serve_listener, tls};
26 +use crate::listener::{Listener, MaybeTlsListener};
27 +use crate::{ConnectionTimeouts, serve_connections, tls};
28
29 // ============================================================================
30 // Test infrastructure
@@ -251,7 +251,6 @@ fn build_tls(certs: &TestCerts, cert: &str, key: Option<&str>) -> TlsConfig {
251
252 /// Generous per-connection timeouts that never fire during the fast tests.
253 const TEST_TIMEOUTS: ConnectionTimeouts = ConnectionTimeouts {
254 - handshake: Duration::from_secs(60),
254 header_read: Duration::from_secs(5),
255 keep_alive_enabled: true,
256 };
@@ -261,9 +260,8 @@ async fn spawn_server(tls_config: Option<TlsConfig>) -> (String, CancellationTok
260 }
261
262 /// Bind an ephemeral listener and serve a trivial router via the production
264 -/// `serve_listener`, optionally with TLS. The listener is bound (and thus
265 -/// accepting into the backlog) before returning, so a client may connect
266 -/// immediately without a sleep.
263 +/// listener/connection path. The listener is bound (and thus accepting into the
264 +/// backlog) before returning, so a client may connect immediately without a sleep.
265 async fn spawn_server_with_timeouts(
266 tls_config: Option<TlsConfig>,
267 timeouts: ConnectionTimeouts,
@@ -274,7 +272,7 @@ async fn spawn_server_with_timeouts(
272 })
273 .await
274 .expect("bind listener");
277 - let addr = listener.local_addr().expect("local addr");
275 + let addr = listener.local_addr_display().expect("local addr");
276
277 let server_config =
278 tls_config.map(|cfg| tls::build_server_config(&cfg).expect("build server config"));
@@ -282,14 +280,11 @@ async fn spawn_server_with_timeouts(
280 let shutdown = CancellationToken::new();
281 let server_shutdown = shutdown.clone();
282 tokio::spawn(async move {
285 - let _ = serve_listener(
286 - listener,
287 - server_config,
288 - app,
289 - server_shutdown.cancelled_owned(),
290 - timeouts,
291 - )
292 - .await;
283 + let listener = match server_config {
284 + Some(context) => MaybeTlsListener::tls(listener, context),
285 + None => MaybeTlsListener::plain(listener),
286 + };
287 + let _ = serve_connections(listener, app, server_shutdown.cancelled_owned(), timeouts).await;
288 });
289 (addr, shutdown)
290 }
@@ -521,20 +516,19 @@ async fn plain_http_serves_when_tls_is_disabled() {
516 shutdown.cancel();
517 }
518
524 -#[tokio::test]
519 +#[tokio::test(start_paused = true)]
520 async fn tls_handshake_timeout_drops_silent_client() {
521 // Silent client (no ClientHello) must be dropped at the handshake deadline.
522 let certs = TestCerts::generate();
528 - let timeouts = ConnectionTimeouts {
529 - handshake: Duration::from_millis(150),
530 - header_read: Duration::from_secs(5),
531 - keep_alive_enabled: true,
532 - };
533 - let (addr, shutdown) = spawn_server_with_timeouts(Some(server_tls(&certs, 0)), timeouts).await;
523 + let (addr, shutdown) = spawn_server(Some(server_tls(&certs, 0))).await;
524
525 let mut tcp = TcpStream::connect(&addr).await.expect("connect");
526 + tokio::task::yield_now().await;
527 + tokio::time::advance(tls::TLS_HANDSHAKE_TIMEOUT + Duration::from_millis(1)).await;
528 + tokio::task::yield_now().await;
529 +
530 let mut buf = [0u8; 1];
537 - let read = tokio::time::timeout(Duration::from_secs(5), tcp.read(&mut buf)).await;
531 + let read = tokio::time::timeout(Duration::from_secs(1), tcp.read(&mut buf)).await;
532 assert!(
533 matches!(read, Ok(Ok(0)) | Ok(Err(_))),
534 "server must drop a stalled TLS handshake (expected close, got {read:?})"
@@ -546,7 +540,6 @@ async fn tls_handshake_timeout_drops_silent_client() {
540 async fn keep_alive_timeout_closes_idle_connection() {
541 // Idle keep-alive connection must be closed at the deadline.
542 let timeouts = ConnectionTimeouts {
549 - handshake: Duration::from_secs(60),
543 header_read: Duration::from_millis(150),
544 keep_alive_enabled: true,
545 };
@@ -582,7 +575,6 @@ async fn keep_alive_timeout_closes_idle_tls_connection() {
575 // still fires through tls-listener's post-handshake SslStream, not just plaintext.
576 let certs = TestCerts::generate();
577 let timeouts = ConnectionTimeouts {
585 - handshake: Duration::from_secs(60),
578 header_read: Duration::from_millis(150),
579 keep_alive_enabled: true,
580 };
@@ -618,7 +610,6 @@ async fn keep_alive_timeout_closes_idle_tls_connection() {
610 async fn idle_timeout_closes_silent_client() {
611 // Silent client closed by the header-read timeout (http1-only arms it from byte 0).
612 let timeouts = ConnectionTimeouts {
621 - handshake: Duration::from_secs(60),
613 header_read: Duration::from_millis(150),
614 keep_alive_enabled: true,
615 };
@@ -638,7 +629,6 @@ async fn idle_timeout_closes_silent_client() {
629 async fn keep_alive_zero_disables_keep_alive() {
630 // 0 disables keep-alive (serve, then close), like uvicorn's timeout_keep_alive=0.
631 let timeouts = ConnectionTimeouts {
641 - handshake: Duration::from_secs(60),
632 header_read: Duration::from_secs(5),
633 keep_alive_enabled: false,
634 };
@@ -671,7 +661,6 @@ async fn disabled_keep_alive_still_closes_silent_client() {
661 // Even with keep-alive off, the head read stays bounded, so a silent client
662 // is dropped rather than held open.
663 let timeouts = ConnectionTimeouts {
674 - handshake: Duration::from_secs(60),
664 header_read: Duration::from_millis(150),
665 keep_alive_enabled: false,
666 };