From 8b3065757dd46a812807dd58fb2a0608521f7913 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Wed, 12 Feb 2025 20:57:20 +0100 Subject: [PATCH] feat(h1): allow to set keep alive configuration from response header --- client/src/pool/exclusive.rs | 13 ++++++++++++ client/src/service/http.rs | 40 +++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/client/src/pool/exclusive.rs b/client/src/pool/exclusive.rs index 812151bb..d9f466ba 100644 --- a/client/src/pool/exclusive.rs +++ b/client/src/pool/exclusive.rs @@ -206,6 +206,19 @@ where self.destroy_on_drop = true; } + #[cfg(feature = "http1")] + pub(crate) fn keep_alive_hint(&mut self, timeout: Option, max_requests: Option) { + if let Some(conn) = self.conn.as_mut() { + if let Some(timeout) = timeout { + conn.state.keep_alive_idle = timeout; + } + + if let Some(max_requests) = max_requests { + conn.state.max_requests = max_requests; + } + } + } + #[cfg(feature = "http1")] pub(crate) fn is_destroy_on_drop(&self) -> bool { self.destroy_on_drop diff --git a/client/src/service/http.rs b/client/src/service/http.rs index 07653b97..258a6993 100644 --- a/client/src/service/http.rs +++ b/client/src/service/http.rs @@ -1,7 +1,9 @@ +use std::time::Duration; + use crate::{ connect::Connect, error::Error, - http::Version, + http::{HeaderName, Version}, pool::{exclusive, shared}, response::Response, service::ServiceDyn, @@ -166,6 +168,9 @@ pub(crate) fn base_service() -> HttpService { Ok(Ok((res, buf, decoder, is_close))) => { if is_close { _conn.destroy_on_drop(); + } else { + let (timeout, max) = parse_keep_alive(&res); + _conn.keep_alive_hint(timeout, max); } let body = crate::h1::body::ResponseBody::new(_conn, buf, decoder); let res = res.map(|_| crate::body::ResponseBody::H1(body)); @@ -201,3 +206,36 @@ pub(crate) fn base_service() -> HttpService { Box::new(HttpService) } + +const KEEP_ALIVE: HeaderName = HeaderName::from_static("keep-alive"); + +fn parse_keep_alive(res: &crate::http::Response) -> (Option, Option) { + let header = match res.headers().get(KEEP_ALIVE).map(|h| h.to_str()) { + Some(Ok(header)) => header, + _ => return (None, None), + }; + + let mut timeout = None; + let mut max = None; + + for (key, value) in header.split(',').map(|item| { + let mut kv = item.splitn(2, '='); + + ( + kv.next().map(|s| s.trim()).unwrap_or_default(), + kv.next().map(|s| s.trim()).unwrap_or_default(), + ) + }) { + match key.to_lowercase().as_str() { + "timeout" => { + timeout = value.parse::().ok().map(Duration::from_secs); + } + "max" => { + max = value.parse().ok(); + } + _ => {} + } + } + + (timeout, max) +}