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