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