monolake_services/http/handlers/
connection_persistence.rs

1//! HTTP connection persistence and keep-alive management module.
2//!
3//! This module provides functionality to manage HTTP connection persistence (keep-alive)
4//! across different HTTP versions. It handles the intricacies of connection reuse for
5//! HTTP/1.0, HTTP/1.1, and HTTP/2, ensuring proper header management and version compatibility.
6//! # Key Components
7//!
8//! - [`ConnectionReuseHandler`]: The main service component responsible for managing connection
9//!   persistence and keep-alive behavior.
10//!
11//! # Features
12//!
13//! - Automatic detection and handling of keep-alive support for incoming requests
14//! - Version-specific handling for HTTP/1.0, HTTP/1.1, and HTTP/2
15//! - Modification of request and response headers to ensure proper keep-alive behavior
16//! - Seamless integration with `service_async` for easy composition in service stacks
17//! - Support for upgrading HTTP/1.0 connections to HTTP/1.1-like behavior
18//!
19//! # Usage
20//!
21//! This handler is typically used as part of a larger HTTP service stack. Here's a basic example:
22//!
23//! ```rust
24//! use monolake_services::{
25//!     common::ContextService,
26//!     http::{
27//!         core::HttpCoreService,
28//!         detect::H2Detect,
29//!         handlers::{
30//!             route::RouteConfig, ConnectionReuseHandler, ContentHandler, RewriteAndRouteHandler,
31//!             UpstreamHandler,
32//!         },
33//!         HttpServerTimeout,
34//!     },
35//! };
36//! use service_async::{layer::FactoryLayer, stack::FactoryStack, Param};
37//!
38//! // Dummy struct to satisfy Param trait requirements
39//! struct DummyConfig;
40//!
41//! // Implement Param for DummyConfig to return Vec<RouteConfig>
42//! impl Param<Vec<RouteConfig>> for DummyConfig {
43//!     fn param(&self) -> Vec<RouteConfig> {
44//!         vec![]
45//!     }
46//! }
47//! impl Param<HttpServerTimeout> for DummyConfig {
48//!     fn param(&self) -> HttpServerTimeout {
49//!         HttpServerTimeout::default()
50//!     }
51//! }
52//!
53//! let config = DummyConfig;
54//! let stacks = FactoryStack::new(config)
55//!     .replace(UpstreamHandler::factory(
56//!         Default::default(),
57//!         Default::default(),
58//!     ))
59//!     .push(ContentHandler::layer())
60//!     .push(RewriteAndRouteHandler::layer())
61//!     .push(ConnectionReuseHandler::layer())
62//!     .push(HttpCoreService::layer())
63//!     .push(H2Detect::layer());
64//!
65//! // Use the service to handle HTTP requests
66//! ```
67//!
68//! # Performance Considerations
69//!
70//! - Efficient header manipulation to minimize overhead
71//! - Optimized handling for HTTP/2, which has built-in connection persistence
72use http::{Request, Version};
73use monolake_core::http::{HttpHandler, ResponseWithContinue};
74use service_async::{
75    layer::{layer_fn, FactoryLayer},
76    AsyncMakeService, MakeService, Service,
77};
78use tracing::debug;
79
80use crate::http::{CLOSE, CLOSE_VALUE, KEEPALIVE, KEEPALIVE_VALUE};
81
82/// Handler for managing HTTP connection persistence and keep-alive behavior.
83///
84/// `ConnectionReuseHandler` is responsible for:
85/// 1. Detecting whether an incoming request supports keep-alive.
86/// 2. Modifying request and response headers to ensure proper keep-alive behavior.
87/// 3. Handling version-specific connection persistence logic for HTTP/1.0, HTTP/1.1, and HTTP/2.
88///
89/// For implementation details and example usage, see the
90/// [module level documentation](crate::http::handlers::connection_persistence).
91#[derive(Clone)]
92pub struct ConnectionReuseHandler<H> {
93    inner: H,
94}
95
96impl<H, CX, B> Service<(Request<B>, CX)> for ConnectionReuseHandler<H>
97where
98    H: HttpHandler<CX, B>,
99{
100    type Response = ResponseWithContinue<H::Body>;
101    type Error = H::Error;
102
103    async fn call(
104        &self,
105        (mut request, ctx): (Request<B>, CX),
106    ) -> Result<Self::Response, Self::Error> {
107        let version = request.version();
108        let keepalive = is_conn_keepalive(request.headers(), version);
109        debug!("frontend keepalive {:?}", keepalive);
110
111        match version {
112            // for http 1.0, hack it to 1.1 like setting nginx `proxy_http_version` to 1.1
113            Version::HTTP_10 => {
114                // modify to 1.1 and remove connection header
115                *request.version_mut() = Version::HTTP_11;
116                let _ = request.headers_mut().remove(http::header::CONNECTION);
117
118                // send
119                let (mut response, mut cont) = self.inner.handle(request, ctx).await?;
120                cont &= keepalive;
121
122                // modify back and make sure reply keepalive if client want it and server
123                // support it.
124                let _ = response.headers_mut().remove(http::header::CONNECTION);
125                if cont {
126                    // insert keepalive header
127                    response
128                        .headers_mut()
129                        .insert(http::header::CONNECTION, KEEPALIVE_VALUE);
130                }
131                *response.version_mut() = version;
132
133                Ok((response, cont))
134            }
135            Version::HTTP_11 => {
136                // remove connection header
137                let _ = request.headers_mut().remove(http::header::CONNECTION);
138
139                // send
140                let (mut response, mut cont) = self.inner.handle(request, ctx).await?;
141                cont &= keepalive;
142
143                // modify back and make sure reply keepalive if client want it and server
144                // support it.
145                let _ = response.headers_mut().remove(http::header::CONNECTION);
146                if !cont {
147                    // insert close header
148                    response
149                        .headers_mut()
150                        .insert(http::header::CONNECTION, CLOSE_VALUE);
151                }
152                Ok((response, cont))
153            }
154            Version::HTTP_2 => {
155                let (response, _) = self.inner.handle(request, ctx).await?;
156                Ok((response, true))
157            }
158            // for http 0.9 and other versions, just relay it
159            _ => {
160                let (response, _) = self.inner.handle(request, ctx).await?;
161                Ok((response, false))
162            }
163        }
164    }
165}
166
167// ConnReuseHandler is a Service and a MakeService.
168impl<F: MakeService> MakeService for ConnectionReuseHandler<F> {
169    type Service = ConnectionReuseHandler<F::Service>;
170    type Error = F::Error;
171
172    fn make_via_ref(&self, old: Option<&Self::Service>) -> Result<Self::Service, Self::Error> {
173        Ok(ConnectionReuseHandler {
174            inner: self.inner.make_via_ref(old.map(|o| &o.inner))?,
175        })
176    }
177}
178
179impl<F: AsyncMakeService> AsyncMakeService for ConnectionReuseHandler<F> {
180    type Service = ConnectionReuseHandler<F::Service>;
181    type Error = F::Error;
182
183    async fn make_via_ref(
184        &self,
185        old: Option<&Self::Service>,
186    ) -> Result<Self::Service, Self::Error> {
187        Ok(ConnectionReuseHandler {
188            inner: self.inner.make_via_ref(old.map(|o| &o.inner)).await?,
189        })
190    }
191}
192
193impl<F> ConnectionReuseHandler<F> {
194    pub fn layer<C>() -> impl FactoryLayer<C, F, Factory = Self> {
195        layer_fn(|_: &C, inner| Self { inner })
196    }
197}
198
199fn is_conn_keepalive(headers: &http::HeaderMap<http::HeaderValue>, version: Version) -> bool {
200    match (version, headers.get(http::header::CONNECTION)) {
201        (Version::HTTP_10, Some(header))
202            if header.as_bytes().eq_ignore_ascii_case(KEEPALIVE.as_bytes()) =>
203        {
204            true
205        }
206        (Version::HTTP_11, None) => true,
207        (Version::HTTP_11, Some(header))
208            if !header.as_bytes().eq_ignore_ascii_case(CLOSE.as_bytes()) =>
209        {
210            true
211        }
212        _ => false,
213    }
214}