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