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}