1use pingap_config::PingapConfig;
16use pingora::protocols::l4::ext::TcpKeepalive;
17use std::fmt;
18use std::time::Duration;
19
20static ERROR_TEMPLATE: &str = include_str!("error.html");
21
22#[derive(Debug, Default)]
24pub struct ServerConf {
25 pub admin: bool,
27
28 pub name: String,
30
31 pub addr: String,
34
35 pub access_log: Option<String>,
38
39 pub locations: Vec<String>,
41
42 pub tls_cipher_list: Option<String>,
45
46 pub tls_ciphersuites: Option<String>,
49
50 pub tls_min_version: Option<String>,
53
54 pub tls_max_version: Option<String>,
57
58 pub threads: Option<usize>,
61
62 pub error_template: String,
65
66 pub tcp_keepalive: Option<TcpKeepalive>,
69
70 pub tcp_fastopen: Option<usize>,
73
74 pub reuse_port: Option<bool>,
78
79 pub global_certificates: bool,
82
83 pub enabled_h2: bool,
86
87 pub prometheus_metrics: Option<String>,
90
91 pub otlp_exporter: Option<String>,
94
95 pub modules: Option<Vec<String>>,
98
99 pub enable_server_timing: bool,
101
102 pub downstream_read_timeout: Option<Duration>,
104
105 pub downstream_write_timeout: Option<Duration>,
107}
108
109impl fmt::Display for ServerConf {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 let format_opt_usize = |opt: &Option<usize>| {
113 opt.map_or("default".to_string(), |v| v.to_string())
114 };
115 let format_opt_duration = |opt: &Option<Duration>| {
116 opt.map_or("default".to_string(), |v| format!("{v:?}"))
117 };
118
119 writeln!(f, "Server Configuration: '{}'", self.name)?;
121 writeln!(f, " Listen Addresses: {}", self.addr)?;
122
123 writeln!(f, " - General Settings:")?;
125 writeln!(f, " Admin Role: {}", self.admin)?;
126 writeln!(
127 f,
128 " Access Log: {}",
129 self.access_log.as_deref().unwrap_or("disabled")
130 )?;
131 if !self.locations.is_empty() {
132 writeln!(f, " Locations: {}", self.locations.join(", "))?;
133 }
134 writeln!(f, " Worker Threads: {}", format_opt_usize(&self.threads))?;
135
136 writeln!(f, " - Protocols & Timeouts:")?;
138 writeln!(f, " HTTP/2 Enabled: {}", self.enabled_h2)?;
139 writeln!(
140 f,
141 " Downstream Read Timeout: {}",
142 format_opt_duration(&self.downstream_read_timeout)
143 )?;
144 writeln!(
145 f,
146 " Downstream Write Timeout: {}",
147 format_opt_duration(&self.downstream_write_timeout)
148 )?;
149
150 writeln!(f, " - TLS Settings:")?;
152 writeln!(f, " Global Certificates: {}", self.global_certificates)?;
153 writeln!(
154 f,
155 " Min Version: {}",
156 self.tls_min_version.clone().unwrap_or_default()
157 )?;
158 writeln!(
159 f,
160 " Max Version: {}",
161 self.tls_max_version.clone().unwrap_or_default()
162 )?;
163 writeln!(
164 f,
165 " Cipher List (TLS <1.3): {}",
166 self.tls_cipher_list.clone().unwrap_or_default()
167 )?;
168 writeln!(
169 f,
170 " Ciphersuites (TLS 1.3): {}",
171 self.tls_ciphersuites.clone().unwrap_or_default()
172 )?;
173
174 writeln!(f, " - TCP Settings:")?;
176 if let Some(keepalive) = &self.tcp_keepalive {
177 writeln!(
179 f,
180 " Keepalive: idle={:?}, interval={:?}, count={:?}",
181 keepalive.idle, keepalive.interval, keepalive.count
182 )?;
183 } else {
184 writeln!(f, " Keepalive: disabled")?;
185 }
186 writeln!(f, " Fast Open: {}", format_opt_usize(&self.tcp_fastopen))?;
187 writeln!(f, " Reuse Port: {}", self.reuse_port.unwrap_or(false))?;
188
189 writeln!(f, " - Observability:")?;
191 writeln!(
192 f,
193 " Prometheus Endpoint: {}",
194 self.prometheus_metrics.as_deref().unwrap_or("disabled")
195 )?;
196 writeln!(
197 f,
198 " OTLP Exporter: {}",
199 self.otlp_exporter.as_deref().unwrap_or("disabled")
200 )?;
201 writeln!(f, " Server-Timing Header: {}", self.enable_server_timing)?;
202
203 if let Some(modules) = &self.modules {
205 if !modules.is_empty() {
206 writeln!(f, " - Enabled Modules:")?;
207 writeln!(f, " {}", modules.join(", "))?;
208 }
209 }
210
211 Ok(())
212 }
213}
214pub fn parse_from_conf(conf: PingapConfig) -> Vec<ServerConf> {
216 let mut upstreams = vec![];
217 for (name, item) in conf.upstreams {
218 upstreams.push((name, item));
219 }
220 let mut locations = vec![];
221 for (name, item) in conf.locations {
222 locations.push((name, item));
223 }
224 locations.sort_by_key(|b| std::cmp::Reverse(b.1.get_weight()));
226 let mut servers = vec![];
227 for (name, item) in conf.servers {
228 let mut error_template =
230 conf.basic.error_template.clone().unwrap_or_default();
231 if error_template.is_empty() {
232 error_template = ERROR_TEMPLATE.to_string();
233 }
234
235 let tcp_keepalive = if (item.tcp_idle.is_some()
237 && item.tcp_probe_count.is_some()
238 && item.tcp_interval.is_some())
239 || item.tcp_user_timeout.is_some()
240 {
241 Some(TcpKeepalive {
242 idle: item.tcp_idle.unwrap_or_default(),
243 count: item.tcp_probe_count.unwrap_or_default(),
244 interval: item.tcp_interval.unwrap_or_default(),
245 #[cfg(target_os = "linux")]
246 user_timeout: item.tcp_user_timeout.unwrap_or_default(),
247 })
248 } else {
249 None
250 };
251
252 servers.push(ServerConf {
254 name,
255 admin: false,
256 tls_cipher_list: item.tls_cipher_list.clone(),
257 tls_ciphersuites: item.tls_ciphersuites.clone(),
258 tls_min_version: item.tls_min_version.clone(),
259 tls_max_version: item.tls_max_version.clone(),
260 addr: item.addr,
261 access_log: item.access_log,
262 locations: item.locations.unwrap_or_default(),
263 threads: item.threads,
264 global_certificates: item.global_certificates.unwrap_or_default(),
265 enabled_h2: item.enabled_h2.unwrap_or_default(),
266 tcp_keepalive,
267 tcp_fastopen: item.tcp_fastopen,
268 reuse_port: item.reuse_port,
269 prometheus_metrics: item.prometheus_metrics,
270 otlp_exporter: item.otlp_exporter.clone(),
271 modules: item.modules.clone(),
272 enable_server_timing: item.enable_server_timing.unwrap_or_default(),
273 error_template,
274 downstream_read_timeout: item.downstream_read_timeout,
275 downstream_write_timeout: item.downstream_write_timeout,
276 });
277 }
278
279 servers
280}
281
282#[cfg(test)]
283mod tests {
284 use super::ServerConf;
285 use pingora::protocols::l4::ext::TcpKeepalive;
286 use pretty_assertions::assert_eq;
287 use std::time::Duration;
288
289 #[test]
290 fn test_server_conf() {
291 let conf = ServerConf {
292 name: "pingap".to_string(),
293 addr: "127.0.0.1:3000,127.0.0.1:3001".to_string(),
294 access_log: Some("combined".to_string()),
295
296 locations: vec!["charts-location".to_string()],
297 threads: Some(4),
298 error_template: "<html></html>".to_string(),
299 tcp_keepalive: Some(TcpKeepalive {
300 idle: Duration::from_secs(10),
301 interval: Duration::from_secs(5),
302 count: 10,
303 #[cfg(target_os = "linux")]
304 user_timeout: Duration::from_secs(0),
305 }),
306 tcp_fastopen: Some(10),
307 enabled_h2: true,
308 ..Default::default()
309 };
310
311 #[cfg(target_os = "linux")]
312 assert_eq!(
313 r#"Server Configuration: 'pingap'
314 Listen Addresses: 127.0.0.1:3000,127.0.0.1:3001
315 - General Settings:
316 Admin Role: false
317 Access Log: combined
318 Locations: charts-location
319 Worker Threads: 4
320 - Protocols & Timeouts:
321 HTTP/2 Enabled: true
322 Downstream Read Timeout: default
323 Downstream Write Timeout: default
324 - TLS Settings:
325 Global Certificates: false
326 Min Version:
327 Max Version:
328 Cipher List (TLS <1.3):
329 Ciphersuites (TLS 1.3):
330 - TCP Settings:
331 Keepalive: idle=10s, interval=5s, count=10
332 Fast Open: 10
333 Reuse Port: false
334 - Observability:
335 Prometheus Endpoint: disabled
336 OTLP Exporter: disabled
337 Server-Timing Header: false
338"#,
339 conf.to_string()
340 );
341 #[cfg(not(target_os = "linux"))]
342 assert_eq!(
343 r#"Server Configuration: 'pingap'
344 Listen Addresses: 127.0.0.1:3000,127.0.0.1:3001
345 - General Settings:
346 Admin Role: false
347 Access Log: combined
348 Locations: charts-location
349 Worker Threads: 4
350 - Protocols & Timeouts:
351 HTTP/2 Enabled: true
352 Downstream Read Timeout: default
353 Downstream Write Timeout: default
354 - TLS Settings:
355 Global Certificates: false
356 Min Version:
357 Max Version:
358 Cipher List (TLS <1.3):
359 Ciphersuites (TLS 1.3):
360 - TCP Settings:
361 Keepalive: idle=10s, interval=5s, count=10
362 Fast Open: 10
363 Reuse Port: false
364 - Observability:
365 Prometheus Endpoint: disabled
366 OTLP Exporter: disabled
367 Server-Timing Header: false
368"#,
369 conf.to_string()
370 );
371 }
372}