Skip to main content

moirai_http/server/
config.rs

1//! Resource and deadline policy for the one-shot HTTP server.
2
3use std::io;
4use std::time::Duration;
5
6use crate::request::MAX_HEADER_SLOTS;
7
8/// Default maximum number of accepted connections tracked by the listener.
9pub const DEFAULT_MAX_CONNECTIONS: usize = 256;
10/// Default maximum size of one request header block.
11pub const DEFAULT_MAX_HEADER_BYTES: usize = 16 * 1024;
12/// Default maximum number of request or response headers.
13pub const DEFAULT_MAX_HEADER_COUNT: usize = 64;
14/// Default maximum size of one request body.
15pub const DEFAULT_MAX_BODY_BYTES: usize = 1024 * 1024;
16/// Default maximum logical size of one response, including its body.
17pub const DEFAULT_MAX_RESPONSE_BYTES: usize = 4 * 1024 * 1024;
18/// Default deadline for one request read or response write.
19pub const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
20
21/// Resource and deadline limits for the one-shot HTTP server.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct ServerConfig {
24    /// Maximum number of concurrent accepted connections.
25    pub max_connections: usize,
26    /// Maximum bytes read while parsing one request head.
27    pub max_header_bytes: usize,
28    /// Maximum request and response header count.
29    pub max_header_count: usize,
30    /// Maximum request body size.
31    pub max_body_bytes: usize,
32    /// Maximum logical response size, including the response body.
33    pub max_response_bytes: usize,
34    /// Deadline for one complete request read or response write.
35    pub request_timeout: Duration,
36}
37
38impl Default for ServerConfig {
39    fn default() -> Self {
40        Self {
41            max_connections: DEFAULT_MAX_CONNECTIONS,
42            max_header_bytes: DEFAULT_MAX_HEADER_BYTES,
43            max_header_count: DEFAULT_MAX_HEADER_COUNT,
44            max_body_bytes: DEFAULT_MAX_BODY_BYTES,
45            max_response_bytes: DEFAULT_MAX_RESPONSE_BYTES,
46            request_timeout: DEFAULT_REQUEST_TIMEOUT,
47        }
48    }
49}
50
51impl ServerConfig {
52    /// Construct and validate a server configuration.
53    ///
54    /// # Errors
55    /// Returns [`io::ErrorKind::InvalidInput`] when a resource limit is zero,
56    /// the header count cannot carry the transport framing fields or exceeds
57    /// the parser's fixed storage, or the deadline is zero.
58    pub fn new(
59        max_connections: usize,
60        max_header_bytes: usize,
61        max_header_count: usize,
62        max_body_bytes: usize,
63        max_response_bytes: usize,
64        request_timeout: Duration,
65    ) -> io::Result<Self> {
66        let config = Self {
67            max_connections,
68            max_header_bytes,
69            max_header_count,
70            max_body_bytes,
71            max_response_bytes,
72            request_timeout,
73        };
74        config.validate()?;
75        Ok(config)
76    }
77
78    /// Validate this configuration before allocating or binding sockets.
79    ///
80    /// # Errors
81    /// Returns [`io::ErrorKind::InvalidInput`] for a zero bound, unsupported
82    /// header count, or zero deadline.
83    pub fn validate(&self) -> io::Result<()> {
84        if self.max_connections == 0
85            || self.max_header_bytes == 0
86            || self.max_header_count < 2
87            || self.max_header_count > MAX_HEADER_SLOTS
88            || self.max_body_bytes == 0
89            || self.max_response_bytes == 0
90            || self.request_timeout.is_zero()
91        {
92            return Err(invalid_input(
93                "HTTP server limits and deadline must be non-zero and bounded",
94            ));
95        }
96        Ok(())
97    }
98}
99
100fn invalid_input(message: &str) -> io::Error {
101    io::Error::new(io::ErrorKind::InvalidInput, message)
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn configuration_rejects_unbounded_values() {
110        assert!(ServerConfig::new(0, 1024, 4, 1024, 1024, Duration::from_secs(1)).is_err());
111        assert!(ServerConfig::new(1, 1024, 1, 1024, 1024, Duration::from_secs(1)).is_err());
112        assert!(
113            ServerConfig::new(
114                1,
115                1024,
116                MAX_HEADER_SLOTS + 1,
117                1024,
118                1024,
119                Duration::from_secs(1)
120            )
121            .is_err()
122        );
123        assert!(ServerConfig::new(1, 1024, 4, 1024, 1024, Duration::ZERO).is_err());
124    }
125}