Skip to main content

actix_http/
config.rs

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