iota_http/config.rs
1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2025 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::time::Duration;
6
7const DEFAULT_HTTP2_KEEPALIVE_TIMEOUT_SECS: u64 = 20;
8/// Covers a round trip plus a few TCP retransmissions on a lossy link; an
9/// unloaded TLS 1.3 handshake completes in one round trip.
10const DEFAULT_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(5);
11/// Concurrent handshakes only build up when peers are slow or silent, so this
12/// leaves ample room for a legitimate reconnect burst.
13const DEFAULT_MAX_PENDING_CONNECTIONS: usize = 512;
14
15#[derive(Debug, Clone)]
16pub struct Config {
17 init_stream_window_size: Option<u32>,
18 init_connection_window_size: Option<u32>,
19 max_concurrent_streams: Option<u32>,
20 pub(crate) tcp_keepalive: Option<Duration>,
21 pub(crate) tcp_nodelay: bool,
22 http2_keepalive_interval: Option<Duration>,
23 http2_keepalive_timeout: Option<Duration>,
24 http2_adaptive_window: Option<bool>,
25 http2_max_pending_accept_reset_streams: Option<usize>,
26 http2_max_header_list_size: Option<u32>,
27 max_frame_size: Option<u32>,
28 pub(crate) accept_http1: bool,
29 enable_connect_protocol: bool,
30 pub(crate) max_connection_age: Option<Duration>,
31 pub(crate) allow_insecure: bool,
32 pub(crate) handshake_timeout: Option<Duration>,
33 pub(crate) max_pending_connections: Option<usize>,
34}
35
36impl Default for Config {
37 fn default() -> Self {
38 Self {
39 init_stream_window_size: None,
40 init_connection_window_size: None,
41 max_concurrent_streams: None,
42 tcp_keepalive: None,
43 tcp_nodelay: true,
44 http2_keepalive_interval: None,
45 http2_keepalive_timeout: None,
46 http2_adaptive_window: None,
47 http2_max_pending_accept_reset_streams: None,
48 http2_max_header_list_size: None,
49 max_frame_size: None,
50 accept_http1: true,
51 enable_connect_protocol: true,
52 max_connection_age: None,
53 allow_insecure: false,
54 handshake_timeout: Some(DEFAULT_HANDSHAKE_TIMEOUT),
55 max_pending_connections: Some(DEFAULT_MAX_PENDING_CONNECTIONS),
56 }
57 }
58}
59
60impl Config {
61 /// Sets the [`SETTINGS_INITIAL_WINDOW_SIZE`][spec] option for HTTP2
62 /// stream-level flow control.
63 ///
64 /// Default is 65,535
65 ///
66 /// [spec]: https://httpwg.org/specs/rfc9113.html#InitialWindowSize
67 pub fn initial_stream_window_size(self, sz: impl Into<Option<u32>>) -> Self {
68 Self {
69 init_stream_window_size: sz.into(),
70 ..self
71 }
72 }
73
74 /// Sets the max connection-level flow control for HTTP2
75 ///
76 /// Default is 65,535
77 pub fn initial_connection_window_size(self, sz: impl Into<Option<u32>>) -> Self {
78 Self {
79 init_connection_window_size: sz.into(),
80 ..self
81 }
82 }
83
84 /// Sets the [`SETTINGS_MAX_CONCURRENT_STREAMS`][spec] option for HTTP2
85 /// connections.
86 ///
87 /// Default is no limit (`None`).
88 ///
89 /// [spec]: https://httpwg.org/specs/rfc9113.html#n-stream-concurrency
90 pub fn max_concurrent_streams(self, max: impl Into<Option<u32>>) -> Self {
91 Self {
92 max_concurrent_streams: max.into(),
93 ..self
94 }
95 }
96
97 /// Sets the maximum time option in milliseconds that a connection may exist
98 ///
99 /// Default is no limit (`None`).
100 pub fn max_connection_age(self, max_connection_age: Duration) -> Self {
101 Self {
102 max_connection_age: Some(max_connection_age),
103 ..self
104 }
105 }
106
107 /// Set whether HTTP2 Ping frames are enabled on accepted connections.
108 ///
109 /// If `None` is specified, HTTP2 keepalive is disabled, otherwise the
110 /// duration specified will be the time interval between HTTP2 Ping
111 /// frames. The timeout for receiving an acknowledgement of the
112 /// keepalive ping can be set with [`Config::http2_keepalive_timeout`].
113 ///
114 /// Default is no HTTP2 keepalive (`None`)
115 pub fn http2_keepalive_interval(self, http2_keepalive_interval: Option<Duration>) -> Self {
116 Self {
117 http2_keepalive_interval,
118 ..self
119 }
120 }
121
122 /// Sets a timeout for receiving an acknowledgement of the keepalive ping.
123 ///
124 /// If the ping is not acknowledged within the timeout, the connection will
125 /// be closed. Does nothing if http2_keep_alive_interval is disabled.
126 ///
127 /// Default is 20 seconds.
128 pub fn http2_keepalive_timeout(self, http2_keepalive_timeout: Option<Duration>) -> Self {
129 Self {
130 http2_keepalive_timeout,
131 ..self
132 }
133 }
134
135 /// Sets whether to use an adaptive flow control. Defaults to false.
136 /// Enabling this will override the limits set in
137 /// http2_initial_stream_window_size and
138 /// http2_initial_connection_window_size.
139 pub fn http2_adaptive_window(self, enabled: Option<bool>) -> Self {
140 Self {
141 http2_adaptive_window: enabled,
142 ..self
143 }
144 }
145
146 /// Configures the maximum number of pending reset streams allowed before a
147 /// GOAWAY will be sent.
148 ///
149 /// This will default to whatever the default in h2 is. As of v0.3.17, it is
150 /// 20.
151 ///
152 /// See <https://github.com/hyperium/hyper/issues/2877> for more information.
153 pub fn http2_max_pending_accept_reset_streams(self, max: Option<usize>) -> Self {
154 Self {
155 http2_max_pending_accept_reset_streams: max,
156 ..self
157 }
158 }
159
160 /// Set whether TCP keepalive messages are enabled on accepted connections.
161 ///
162 /// If `None` is specified, keepalive is disabled, otherwise the duration
163 /// specified will be the time to remain idle before sending TCP keepalive
164 /// probes.
165 ///
166 /// Default is no keepalive (`None`)
167 pub fn tcp_keepalive(self, tcp_keepalive: Option<Duration>) -> Self {
168 Self {
169 tcp_keepalive,
170 ..self
171 }
172 }
173
174 /// Set the value of `TCP_NODELAY` option for accepted connections. Enabled
175 /// by default.
176 pub fn tcp_nodelay(self, enabled: bool) -> Self {
177 Self {
178 tcp_nodelay: enabled,
179 ..self
180 }
181 }
182
183 /// Sets the max size of received header frames.
184 ///
185 /// This will default to whatever the default in hyper is. As of v1.4.1, it
186 /// is 16 KiB.
187 pub fn http2_max_header_list_size(self, max: impl Into<Option<u32>>) -> Self {
188 Self {
189 http2_max_header_list_size: max.into(),
190 ..self
191 }
192 }
193
194 /// Sets the maximum frame size to use for HTTP2.
195 ///
196 /// Passing `None` will do nothing.
197 ///
198 /// If not set, will default from underlying transport.
199 pub fn max_frame_size(self, frame_size: impl Into<Option<u32>>) -> Self {
200 Self {
201 max_frame_size: frame_size.into(),
202 ..self
203 }
204 }
205
206 /// Allow this accepting http1 requests.
207 ///
208 /// Default is `true`.
209 pub fn accept_http1(self, accept_http1: bool) -> Self {
210 Config {
211 accept_http1,
212 ..self
213 }
214 }
215
216 /// Allow accepting insecure connections when a tls_config is provided.
217 ///
218 /// This will allow clients to connect both using TLS as well as without TLS
219 /// on the same network interface.
220 ///
221 /// Default is `false`.
222 ///
223 /// NOTE: This presently will only work for `tokio::net::TcpStream` IO
224 /// connections
225 pub fn allow_insecure(self, allow_insecure: bool) -> Self {
226 Config {
227 allow_insecure,
228 ..self
229 }
230 }
231
232 /// Sets how long an accepted connection may take to complete its TLS
233 /// handshake before it is closed. The peer is unauthenticated for the whole
234 /// handshake, so without this a silent peer holds a task and a file
235 /// descriptor indefinitely.
236 ///
237 /// Default is 5 seconds. `None` disables the deadline.
238 pub fn handshake_timeout(self, handshake_timeout: Option<Duration>) -> Self {
239 Self {
240 handshake_timeout,
241 ..self
242 }
243 }
244
245 /// Sets how many accepted connections may be handshaking at the same time.
246 /// While the limit is reached the server stops accepting, leaving new
247 /// connections in the kernel backlog instead of holding file descriptors
248 /// for them.
249 ///
250 /// Default is 512. `None` removes the limit.
251 pub fn max_pending_connections(self, max_pending_connections: Option<usize>) -> Self {
252 Self {
253 max_pending_connections,
254 ..self
255 }
256 }
257
258 /// Rejects settings the accept loop cannot recover from.
259 pub(crate) fn validate(&self) -> Result<(), crate::BoxError> {
260 match self.max_pending_connections {
261 Some(0) => Err("'max_pending_connections' must be greater than zero, \
262 a server that accepts no connection is never useful"
263 .into()),
264 // Reaching the limit stops accepting until a handshake finishes, so
265 // without a deadline enough silent peers stall the server for good.
266 Some(_) if self.handshake_timeout.is_none() => Err(
267 "'max_pending_connections' requires a 'handshake_timeout' to release its \
268 slots"
269 .into(),
270 ),
271 _ => Ok(()),
272 }
273 }
274
275 pub(crate) fn connection_builder(
276 &self,
277 ) -> hyper_util::server::conn::auto::Builder<hyper_util::rt::TokioExecutor> {
278 let mut builder =
279 hyper_util::server::conn::auto::Builder::new(hyper_util::rt::TokioExecutor::new());
280
281 if !self.accept_http1 {
282 builder = builder.http2_only();
283 }
284
285 if self.enable_connect_protocol {
286 builder.http2().enable_connect_protocol();
287 }
288
289 let http2_keepalive_timeout = self
290 .http2_keepalive_timeout
291 .unwrap_or_else(|| Duration::new(DEFAULT_HTTP2_KEEPALIVE_TIMEOUT_SECS, 0));
292
293 builder
294 .http2()
295 .timer(hyper_util::rt::TokioTimer::new())
296 .initial_connection_window_size(self.init_connection_window_size)
297 .initial_stream_window_size(self.init_stream_window_size)
298 .max_concurrent_streams(self.max_concurrent_streams)
299 .keep_alive_interval(self.http2_keepalive_interval)
300 .keep_alive_timeout(http2_keepalive_timeout)
301 .adaptive_window(self.http2_adaptive_window.unwrap_or_default())
302 .max_pending_accept_reset_streams(self.http2_max_pending_accept_reset_streams)
303 .max_frame_size(self.max_frame_size);
304
305 if let Some(max_header_list_size) = self.http2_max_header_list_size {
306 builder.http2().max_header_list_size(max_header_list_size);
307 }
308
309 builder
310 }
311}