Skip to main content

actix_http/
config.rs

1use std::{
2    net::SocketAddr,
3    rc::Rc,
4    time::{Duration, Instant},
5};
6
7use bytes::BytesMut;
8
9use crate::{date::DateService, KeepAlive};
10
11/// Default HTTP/2 initial connection-level flow control window size.
12///
13/// Matches awc's defaults to avoid poor throughput on high-BDP links.
14pub(crate) const DEFAULT_H2_CONN_WINDOW_SIZE: u32 = 1024 * 1024 * 2; // 2MiB
15
16/// Default HTTP/2 initial stream-level flow control window size.
17///
18/// Matches awc's defaults to avoid poor throughput on high-BDP links.
19pub(crate) const DEFAULT_H2_STREAM_WINDOW_SIZE: u32 = 1024 * 1024; // 1MiB
20
21/// A builder for creating a [`ServiceConfig`]
22#[derive(Default, Debug)]
23pub struct ServiceConfigBuilder {
24    inner: Inner,
25}
26
27impl ServiceConfigBuilder {
28    /// Creates a new, default, [`ServiceConfigBuilder`]
29    ///
30    /// It uses the following default values:
31    ///
32    /// - [`KeepAlive::default`] for the connection keep-alive setting
33    /// - 5 seconds for the client request timeout
34    /// - 0 seconds for the client shutdown timeout
35    /// - secure value of `false`
36    /// - [`None`] for the local address setting
37    /// - Allow for half closed HTTP/1 connections
38    pub fn new() -> Self {
39        Self::default()
40    }
41
42    /// Sets the `secure` attribute for this configuration
43    pub fn secure(mut self, secure: bool) -> Self {
44        self.inner.secure = secure;
45        self
46    }
47
48    /// Sets the local address for this configuration
49    pub fn local_addr(mut self, local_addr: Option<SocketAddr>) -> Self {
50        self.inner.local_addr = local_addr;
51        self
52    }
53
54    /// Sets connection keep-alive setting
55    pub fn keep_alive(mut self, keep_alive: KeepAlive) -> Self {
56        self.inner.keep_alive = keep_alive;
57        self
58    }
59
60    /// Sets the timeout for the client to finish sending the head of its first request
61    pub fn client_request_timeout(mut self, timeout: Duration) -> Self {
62        self.inner.client_request_timeout = timeout;
63        self
64    }
65
66    /// Sets the timeout for cleanly disconnecting from the client after connection shutdown has
67    /// started
68    pub fn client_disconnect_timeout(mut self, timeout: Duration) -> Self {
69        self.inner.client_disconnect_timeout = timeout;
70        self
71    }
72
73    /// Sets `TCP_NODELAY` preference for accepted TCP connections.
74    pub fn tcp_nodelay(mut self, nodelay: Option<bool>) -> Self {
75        self.inner.tcp_nodelay = nodelay;
76        self
77    }
78
79    /// Sets whether HTTP/1 connections should support half-closures.
80    ///
81    /// Clients can choose to shutdown their writer-side of the connection after completing their
82    /// request and while waiting for the server response. Setting this to `false` will cause the
83    /// server to abort the connection handling as soon as it detects an EOF from the client
84    pub fn h1_allow_half_closed(mut self, allow: bool) -> Self {
85        self.inner.h1_allow_half_closed = allow;
86        self
87    }
88
89    /// Sets initial stream-level flow control window size for HTTP/2 connections.
90    ///
91    /// Higher values can improve upload performance on high-latency links at the cost of higher
92    /// worst-case memory usage per connection.
93    ///
94    /// The default value is 1MiB.
95    pub fn h2_initial_window_size(mut self, size: u32) -> Self {
96        self.inner.h2_stream_window_size = size;
97        self
98    }
99
100    /// Sets initial connection-level flow control window size for HTTP/2 connections.
101    ///
102    /// Higher values can improve upload performance on high-latency links at the cost of higher
103    /// worst-case memory usage per connection.
104    ///
105    /// The default value is 2MiB.
106    pub fn h2_initial_connection_window_size(mut self, size: u32) -> Self {
107        self.inner.h2_conn_window_size = size;
108        self
109    }
110
111    /// Builds a [`ServiceConfig`] from this [`ServiceConfigBuilder`] instance
112    pub fn build(self) -> ServiceConfig {
113        ServiceConfig(Rc::new(self.inner))
114    }
115}
116
117/// HTTP service configuration.
118#[derive(Debug, Clone, Default)]
119pub struct ServiceConfig(Rc<Inner>);
120
121#[derive(Debug)]
122struct Inner {
123    keep_alive: KeepAlive,
124    client_request_timeout: Duration,
125    client_disconnect_timeout: Duration,
126    secure: bool,
127    local_addr: Option<SocketAddr>,
128    tcp_nodelay: Option<bool>,
129    date_service: DateService,
130    h1_allow_half_closed: bool,
131    h2_conn_window_size: u32,
132    h2_stream_window_size: u32,
133}
134
135impl Default for Inner {
136    fn default() -> Self {
137        Self {
138            keep_alive: KeepAlive::default(),
139            client_request_timeout: Duration::from_secs(5),
140            client_disconnect_timeout: Duration::ZERO,
141            secure: false,
142            local_addr: None,
143            tcp_nodelay: None,
144            date_service: DateService::new(),
145            h1_allow_half_closed: true,
146            h2_conn_window_size: DEFAULT_H2_CONN_WINDOW_SIZE,
147            h2_stream_window_size: DEFAULT_H2_STREAM_WINDOW_SIZE,
148        }
149    }
150}
151
152impl ServiceConfig {
153    /// Create instance of `ServiceConfig`.
154    pub fn new(
155        keep_alive: KeepAlive,
156        client_request_timeout: Duration,
157        client_disconnect_timeout: Duration,
158        secure: bool,
159        local_addr: Option<SocketAddr>,
160    ) -> ServiceConfig {
161        ServiceConfig(Rc::new(Inner {
162            keep_alive: keep_alive.normalize(),
163            client_request_timeout,
164            client_disconnect_timeout,
165            secure,
166            local_addr,
167            tcp_nodelay: None,
168            date_service: DateService::new(),
169            h1_allow_half_closed: true,
170            h2_conn_window_size: DEFAULT_H2_CONN_WINDOW_SIZE,
171            h2_stream_window_size: DEFAULT_H2_STREAM_WINDOW_SIZE,
172        }))
173    }
174
175    /// Returns `true` if connection is secure (i.e., using TLS / HTTPS).
176    #[inline]
177    pub fn secure(&self) -> bool {
178        self.0.secure
179    }
180
181    /// Returns the local address that this server is bound to.
182    ///
183    /// Returns `None` for connections via UDS (Unix Domain Socket).
184    #[inline]
185    pub fn local_addr(&self) -> Option<SocketAddr> {
186        self.0.local_addr
187    }
188
189    /// Connection keep-alive setting.
190    #[inline]
191    pub fn keep_alive(&self) -> KeepAlive {
192        self.0.keep_alive
193    }
194
195    /// Creates a time object representing the deadline for this connection's keep-alive period, if
196    /// enabled.
197    ///
198    /// When [`KeepAlive::Os`] or [`KeepAlive::Disabled`] is set, this will return `None`.
199    pub fn keep_alive_deadline(&self) -> Option<Instant> {
200        match self.keep_alive() {
201            KeepAlive::Timeout(dur) => Some(self.now() + dur),
202            KeepAlive::Os => None,
203            KeepAlive::Disabled => None,
204        }
205    }
206
207    /// Creates a time object representing the deadline for the client to finish sending the head of
208    /// its first request.
209    ///
210    /// Returns `None` if this `ServiceConfig was` constructed with `client_request_timeout: 0`.
211    pub fn client_request_deadline(&self) -> Option<Instant> {
212        let timeout = self.0.client_request_timeout;
213        (timeout != Duration::ZERO).then(|| self.now() + timeout)
214    }
215
216    /// Creates a time object representing the deadline for the client to disconnect.
217    pub fn client_disconnect_deadline(&self) -> Option<Instant> {
218        let timeout = self.0.client_disconnect_timeout;
219        (timeout != Duration::ZERO).then(|| self.now() + timeout)
220    }
221
222    /// Whether HTTP/1 connections should support half-closures.
223    ///
224    /// Clients can choose to shutdown their writer-side of the connection after completing their
225    /// request and while waiting for the server response. If this configuration is `false`, the
226    /// server will abort the connection handling as soon as it detects an EOF from the client
227    pub fn h1_allow_half_closed(&self) -> bool {
228        self.0.h1_allow_half_closed
229    }
230
231    /// Returns configured `TCP_NODELAY` setting for accepted TCP connections.
232    pub fn tcp_nodelay(&self) -> Option<bool> {
233        self.0.tcp_nodelay
234    }
235
236    /// HTTP/2 initial stream-level flow control window size (in bytes).
237    pub fn h2_initial_window_size(&self) -> u32 {
238        self.0.h2_stream_window_size
239    }
240
241    /// HTTP/2 initial connection-level flow control window size (in bytes).
242    pub fn h2_initial_connection_window_size(&self) -> u32 {
243        self.0.h2_conn_window_size
244    }
245
246    pub(crate) fn now(&self) -> Instant {
247        self.0.date_service.now()
248    }
249
250    /// Writes date header to `dst` buffer.
251    ///
252    /// Low-level method that utilizes the built-in efficient date service, requiring fewer syscalls
253    /// than normal. Note that a CRLF (`\r\n`) is included in what is written.
254    #[doc(hidden)]
255    pub fn write_date_header(&self, dst: &mut BytesMut, camel_case: bool) {
256        let mut buf: [u8; 37] = [0; 37];
257
258        buf[..6].copy_from_slice(if camel_case { b"Date: " } else { b"date: " });
259
260        self.0
261            .date_service
262            .with_date(|date| buf[6..35].copy_from_slice(&date.bytes));
263
264        buf[35..].copy_from_slice(b"\r\n");
265        dst.extend_from_slice(&buf);
266    }
267
268    #[allow(unused)] // used with `http2` feature flag
269    pub(crate) fn write_date_header_value(&self, dst: &mut BytesMut) {
270        self.0
271            .date_service
272            .with_date(|date| dst.extend_from_slice(&date.bytes));
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use actix_rt::{
279        task::yield_now,
280        time::{sleep, sleep_until},
281    };
282    use memchr::memmem;
283
284    use super::*;
285    use crate::{date::DATE_VALUE_LENGTH, notify_on_drop};
286
287    #[actix_rt::test]
288    async fn test_date_service_update() {
289        let settings =
290            ServiceConfig::new(KeepAlive::Os, Duration::ZERO, Duration::ZERO, false, None);
291
292        yield_now().await;
293
294        let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
295        settings.write_date_header(&mut buf1, false);
296        let now1 = settings.now();
297
298        sleep_until((Instant::now() + Duration::from_secs(2)).into()).await;
299        yield_now().await;
300
301        let now2 = settings.now();
302        let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
303        settings.write_date_header(&mut buf2, false);
304
305        assert_ne!(now1, now2);
306
307        assert_ne!(buf1, buf2);
308
309        drop(settings);
310
311        // Ensure the task will drop eventually
312        let mut times = 0;
313        while !notify_on_drop::is_dropped() {
314            sleep(Duration::from_millis(100)).await;
315            times += 1;
316            assert!(times < 10, "Timeout waiting for task drop");
317        }
318    }
319
320    #[actix_rt::test]
321    async fn test_date_service_drop() {
322        let service = Rc::new(DateService::new());
323
324        // yield so date service have a chance to register the spawned timer update task.
325        yield_now().await;
326
327        let clone1 = service.clone();
328        let clone2 = service.clone();
329        let clone3 = service.clone();
330
331        drop(clone1);
332        assert!(!notify_on_drop::is_dropped());
333        drop(clone2);
334        assert!(!notify_on_drop::is_dropped());
335        drop(clone3);
336        assert!(!notify_on_drop::is_dropped());
337
338        drop(service);
339
340        // Ensure the task will drop eventually
341        let mut times = 0;
342        while !notify_on_drop::is_dropped() {
343            sleep(Duration::from_millis(100)).await;
344            times += 1;
345            assert!(times < 10, "Timeout waiting for task drop");
346        }
347    }
348
349    #[test]
350    fn test_date_len() {
351        assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len());
352    }
353
354    #[actix_rt::test]
355    async fn test_date() {
356        let settings = ServiceConfig::default();
357
358        let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
359        settings.write_date_header(&mut buf1, false);
360
361        let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
362        settings.write_date_header(&mut buf2, false);
363
364        assert_eq!(buf1, buf2);
365    }
366
367    #[actix_rt::test]
368    async fn test_date_camel_case() {
369        let settings = ServiceConfig::default();
370
371        let mut buf = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
372        settings.write_date_header(&mut buf, false);
373        assert!(memmem::find(&buf, b"date:").is_some());
374
375        let mut buf = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
376        settings.write_date_header(&mut buf, true);
377        assert!(memmem::find(&buf, b"Date:").is_some());
378    }
379}