Skip to main content

folk_plugin_http/
config.rs

1use std::net::SocketAddr;
2use std::path::PathBuf;
3use std::time::Duration;
4
5use ipnet::IpNet;
6use serde::{Deserialize, Serialize};
7
8/// HTTP plugin configuration.
9///
10/// ```toml
11/// [http]
12/// listen = "0.0.0.0:8080"
13/// read_timeout = "10s"
14/// write_timeout = "30s"
15/// max_request_size = "10mb"
16/// access_log = false
17/// trusted_proxies = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
18/// h2c = false
19///
20/// [http.tls]
21/// cert = "/path/to/cert.pem"
22/// key = "/path/to/key.pem"
23///
24/// [http.compression]
25/// enabled = true
26/// algorithms = ["gzip", "br", "zstd"]
27/// min_size = 256
28/// ```
29#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(default)]
31pub struct HttpConfig {
32    /// Listening address. Default: 0.0.0.0:8080
33    pub listen: SocketAddr,
34    /// Max time to read request body. Default: 10s
35    #[serde(with = "humantime_serde")]
36    pub read_timeout: Duration,
37    /// Max time to write response. Default: 30s
38    #[serde(with = "humantime_serde")]
39    pub write_timeout: Duration,
40    /// Maximum request body size. Accepts: "10mb", "512kb", "1gb", or raw bytes as integer.
41    /// Default: "10mb".
42    #[serde(with = "human_bytes")]
43    pub max_request_size: usize,
44    /// Enable HTTP access logging (method, uri, status, duration). Default: false
45    pub access_log: bool,
46    /// Trusted proxy subnets for X-Forwarded-For parsing.
47    /// When a request comes from a trusted proxy, the real client IP
48    /// is extracted from X-Forwarded-For. Default: empty (trust no proxies).
49    #[serde(default)]
50    pub trusted_proxies: Vec<IpNet>,
51    /// TLS configuration. If set, the server listens on HTTPS.
52    /// HTTP/2 via ALPN is negotiated automatically when TLS is enabled.
53    #[serde(default)]
54    pub tls: Option<TlsConfig>,
55    /// Enable HTTP/2 cleartext (h2c) — HTTP/2 without TLS. Default: false.
56    #[serde(default)]
57    pub h2c: bool,
58    /// Response compression. Default: disabled.
59    #[serde(default)]
60    pub compression: CompressionConfig,
61}
62
63/// TLS certificate + key paths.
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct TlsConfig {
66    /// Path to PEM-encoded certificate chain.
67    pub cert: PathBuf,
68    /// Path to PEM-encoded private key.
69    pub key: PathBuf,
70}
71
72/// Response compression configuration.
73#[derive(Debug, Clone, Serialize, Deserialize)]
74#[serde(default)]
75pub struct CompressionConfig {
76    /// Enable response compression. Default: false.
77    pub enabled: bool,
78    /// Compression algorithms in priority order. Default: ["gzip", "br", "zstd"].
79    /// Supported: "gzip", "br" (brotli), "zstd", "deflate".
80    pub algorithms: Vec<CompressionAlgorithm>,
81    /// Minimum response body size (bytes) to compress. Default: 256.
82    #[serde(with = "human_bytes")]
83    pub min_size: usize,
84}
85
86impl Default for CompressionConfig {
87    fn default() -> Self {
88        Self {
89            enabled: false,
90            algorithms: vec![
91                CompressionAlgorithm::Gzip,
92                CompressionAlgorithm::Br,
93                CompressionAlgorithm::Zstd,
94            ],
95            min_size: 256,
96        }
97    }
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
101#[serde(rename_all = "lowercase")]
102pub enum CompressionAlgorithm {
103    Gzip,
104    Br,
105    Zstd,
106    Deflate,
107}
108
109impl Default for HttpConfig {
110    fn default() -> Self {
111        Self {
112            listen: "0.0.0.0:8080".parse().unwrap(),
113            read_timeout: Duration::from_secs(10),
114            write_timeout: Duration::from_secs(30),
115            max_request_size: 10 * 1024 * 1024, // 10 MiB
116            access_log: false,
117            trusted_proxies: Vec::new(),
118            tls: None,
119            h2c: false,
120            compression: CompressionConfig::default(),
121        }
122    }
123}
124
125/// Parse a human-readable byte size: "10mb", "512kb", "1gb", "256b", or a plain integer.
126/// Case-insensitive. Accepts both "mb" and "mib" (binary interpretation throughout).
127pub fn parse_byte_size(s: &str) -> Result<usize, String> {
128    let s = s.trim().to_lowercase();
129
130    // Try plain integer first
131    if let Ok(n) = s.parse::<usize>() {
132        return Ok(n);
133    }
134
135    let (num_part, multiplier) = if let Some(n) = s.strip_suffix("gib") {
136        (n, 1024 * 1024 * 1024)
137    } else if let Some(n) = s.strip_suffix("gb") {
138        (n, 1024 * 1024 * 1024)
139    } else if let Some(n) = s.strip_suffix("mib") {
140        (n, 1024 * 1024)
141    } else if let Some(n) = s.strip_suffix("mb") {
142        (n, 1024 * 1024)
143    } else if let Some(n) = s.strip_suffix("kib") {
144        (n, 1024)
145    } else if let Some(n) = s.strip_suffix("kb") {
146        (n, 1024)
147    } else if let Some(n) = s.strip_suffix("b") {
148        (n, 1)
149    } else {
150        return Err(format!("invalid byte size: {s:?}"));
151    };
152
153    let num: usize = num_part
154        .trim()
155        .parse()
156        .map_err(|_| format!("invalid byte size number: {num_part:?}"))?;
157
158    Ok(num * multiplier)
159}
160
161mod human_bytes {
162    use serde::{Deserialize, Deserializer, Serializer, de};
163
164    pub fn serialize<S: Serializer>(value: &usize, ser: S) -> Result<S::Ok, S::Error> {
165        ser.serialize_u64(*value as u64)
166    }
167
168    pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result<usize, D::Error> {
169        #[derive(Deserialize)]
170        #[serde(untagged)]
171        enum ByteSize {
172            Str(String),
173            Num(usize),
174        }
175
176        match ByteSize::deserialize(de)? {
177            ByteSize::Num(n) => Ok(n),
178            ByteSize::Str(s) => super::parse_byte_size(&s).map_err(de::Error::custom),
179        }
180    }
181}