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