Skip to main content

rust_web_server/entry_point/
mod.rs

1#[cfg(test)]
2mod tests;
3pub mod command_line_args;
4pub mod config_file;
5pub mod environment_variables;
6
7
8use std::{env};
9use crate::virtual_host::VirtualHostConfig;
10
11use crate::entry_point::command_line_args::{override_environment_variables_from_command_line_args};
12use crate::entry_point::config_file::override_environment_variables_from_config;
13use crate::entry_point::environment_variables::read_system_environment_variables;
14
15/// `value` as startup output may show it: secrets (a name with `SECRET`,
16/// `PASSWORD` or `TOKEN`) are replaced.
17pub fn shown<'a>(name: &str, value: &'a str) -> std::borrow::Cow<'a, str> {
18    let upper = name.to_ascii_uppercase();
19    if ["SECRET", "PASSWORD", "TOKEN"].iter().any(|word| upper.contains(word)) && !value.is_empty() {
20        return std::borrow::Cow::Borrowed("(hidden)");
21    }
22    // A PIN inside a PKCS#11 URI (`tls_key_file = "pkcs11:…?pin-value=1234"`).
23    if let Some(at) = value.find("pin-value=") {
24        let start = at + "pin-value=".len();
25        let end = value[start..].find(['&', ';']).map_or(value.len(), |n| start + n);
26        return std::borrow::Cow::Owned(format!("{}(hidden){}", &value[..start], &value[end..]));
27    }
28    std::borrow::Cow::Borrowed(value)
29}
30
31#[derive(PartialEq, Eq, Clone, Debug)]
32pub struct Config {}
33
34impl Config {
35    pub const RWS_CONFIG_IP: &'static str = "RWS_CONFIG_IP";
36    /// Default is `0.0.0.0` so the server is reachable inside containers and K8s pods.
37    /// For local development you can override to `127.0.0.1` via env var or config file.
38    pub const RWS_CONFIG_IP_DEFAULT_VALUE: &'static str = "0.0.0.0";
39
40    /// Log format: `"combined"` (default, Combined Log Format) or `"json"` (structured JSON).
41    pub const RWS_CONFIG_LOG_FORMAT: &'static str = "RWS_CONFIG_LOG_FORMAT";
42    pub const RWS_CONFIG_LOG_FORMAT_DEFAULT_VALUE: &'static str = "json";
43
44    pub const RWS_CONFIG_PORT: &'static str = "RWS_CONFIG_PORT";
45    pub const RWS_CONFIG_PORT_DEFAULT_VALUE: &'static str = "7878";
46
47    pub const RWS_CONFIG_THREAD_COUNT: &'static str = "RWS_CONFIG_THREAD_COUNT";
48    pub const RWS_CONFIG_THREAD_COUNT_DEFAULT_VALUE: &'static str = "200";
49
50    pub const RWS_CONFIG_CORS_ALLOW_ALL: &'static str = "RWS_CONFIG_CORS_ALLOW_ALL";
51    pub const RWS_CONFIG_CORS_ALLOW_ALL_DEFAULT_VALUE: &'static str = "false";
52
53    pub const RWS_CONFIG_CORS_ALLOW_ORIGINS: &'static str = "RWS_CONFIG_CORS_ALLOW_ORIGINS";
54    pub const RWS_CONFIG_CORS_ALLOW_ORIGINS_DEFAULT_VALUE: &'static str = "";
55
56    pub const RWS_CONFIG_CORS_ALLOW_CREDENTIALS: &'static str = "RWS_CONFIG_CORS_ALLOW_CREDENTIALS";
57    pub const RWS_CONFIG_CORS_ALLOW_CREDENTIALS_DEFAULT_VALUE: &'static str = "";
58
59    pub const RWS_CONFIG_CORS_ALLOW_HEADERS: &'static str = "RWS_CONFIG_CORS_ALLOW_HEADERS";
60    pub const RWS_CONFIG_CORS_ALLOW_HEADERS_DEFAULT_VALUE: &'static str = "";
61
62    pub const RWS_CONFIG_CORS_ALLOW_METHODS: &'static str = "RWS_CONFIG_CORS_ALLOW_METHODS";
63    pub const RWS_CONFIG_CORS_ALLOW_METHODS_DEFAULT_VALUE: &'static str = "";
64
65    pub const RWS_CONFIG_CORS_EXPOSE_HEADERS: &'static str = "RWS_CONFIG_CORS_EXPOSE_HEADERS";
66    pub const RWS_CONFIG_CORS_EXPOSE_HEADERS_DEFAULT_VALUE: &'static str = "";
67
68    pub const RWS_CONFIG_CORS_MAX_AGE: &'static str = "RWS_CONFIG_CORS_MAX_AGE";
69    pub const RWS_CONFIG_CORS_MAX_AGE_DEFAULT_VALUE: &'static str = "86400";
70
71    pub const RWS_CONFIG_REQUEST_ALLOCATION_SIZE_IN_BYTES: &'static str = "RWS_CONFIG_REQUEST_ALLOCATION_SIZE_IN_BYTES";
72    pub const RWS_CONFIG_REQUEST_ALLOCATION_SIZE_IN_BYTES_DEFAULT_VALUE: &'static str = "10000";
73
74    /// Maximum accepted request body size in bytes, checked against `Content-Length`
75    /// before any body bytes are read off the socket. `0` (the default) means
76    /// unlimited — this is opt-in, not a default-on behavior change. Requests whose
77    /// declared body exceeds this get `413 Payload Too Large` and the connection is
78    /// closed (not kept alive), since bytes the server chose not to read are still
79    /// queued on the client's side of the socket.
80    pub const RWS_CONFIG_MAX_BODY_SIZE_IN_BYTES: &'static str = "RWS_CONFIG_MAX_BODY_SIZE_IN_BYTES";
81    pub const RWS_CONFIG_MAX_BODY_SIZE_IN_BYTES_DEFAULT_VALUE: &'static str = "0";
82
83    /// Serve this file (e.g. `"index.html"`) for a `GET`/`HEAD` request that doesn't
84    /// match any real file/directory, instead of `404` — the standard client-side-router
85    /// ("SPA") fallback pattern needed for deep links in a React Router / Vue Router /
86    /// etc. app. Empty (default) disables it entirely. See
87    /// `crate::app::controller::static_resource` for the matching heuristic (skips
88    /// paths that look like a missed static asset, i.e. the last segment has a `.`).
89    pub const RWS_CONFIG_SPA_FALLBACK: &'static str = "RWS_CONFIG_SPA_FALLBACK";
90    pub const RWS_CONFIG_SPA_FALLBACK_DEFAULT_VALUE: &'static str = "";
91
92    /// Comma-separated path prefixes (e.g. `"/api,/healthz"`) that never receive the
93    /// SPA fallback even when `RWS_CONFIG_SPA_FALLBACK` is set — so a real API `404`
94    /// isn't silently rewritten into a `200`. Empty (default) excludes nothing.
95    pub const RWS_CONFIG_SPA_FALLBACK_EXCLUDE_PREFIXES: &'static str = "RWS_CONFIG_SPA_FALLBACK_EXCLUDE_PREFIXES";
96    pub const RWS_CONFIG_SPA_FALLBACK_EXCLUDE_PREFIXES_DEFAULT_VALUE: &'static str = "";
97
98    pub const RWS_CONFIG_TLS_CERT_FILE: &'static str = "RWS_CONFIG_TLS_CERT_FILE";
99    pub const RWS_CONFIG_TLS_CERT_FILE_DEFAULT_VALUE: &'static str = "";
100
101    pub const RWS_CONFIG_TLS_KEY_FILE: &'static str = "RWS_CONFIG_TLS_KEY_FILE";
102    pub const RWS_CONFIG_TLS_KEY_FILE_DEFAULT_VALUE: &'static str = "";
103
104    /// Path to a PEM-encoded CA certificate used to verify client certificates (mTLS).
105    /// When set, the TLS handshake requires a valid client certificate signed by this CA.
106    /// Connections without a valid cert are rejected at the TLS layer (before any HTTP processing).
107    pub const RWS_CONFIG_TLS_CLIENT_CA_FILE: &'static str = "RWS_CONFIG_TLS_CLIENT_CA_FILE";
108    pub const RWS_CONFIG_TLS_CLIENT_CA_FILE_DEFAULT_VALUE: &'static str = "";
109
110    /// Directory containing Tera HTML templates (default: `"templates"`). Requires `tera` feature.
111    pub const RWS_CONFIG_TEMPLATE_DIR: &'static str = "RWS_CONFIG_TEMPLATE_DIR";
112    pub const RWS_CONFIG_TEMPLATE_DIR_DEFAULT_VALUE: &'static str = "templates";
113
114    /// When non-empty, a plain-HTTP listener on this port redirects all requests to HTTPS.
115    /// Set to e.g. `"80"` when running on standard ports. Requires TLS to be configured.
116    pub const RWS_CONFIG_HTTP_REDIRECT_PORT: &'static str = "RWS_CONFIG_HTTP_REDIRECT_PORT";
117    pub const RWS_CONFIG_HTTP_REDIRECT_PORT_DEFAULT_VALUE: &'static str = "";
118
119    // ── ACME (Automatic Certificate Management Environment) ───────────────────
120
121    /// Comma-separated list of domain names to obtain a certificate for.
122    /// Setting this activates ACME at startup. Example: `"example.com,www.example.com"`
123    pub const RWS_CONFIG_ACME_DOMAINS: &'static str = "RWS_CONFIG_ACME_DOMAINS";
124    pub const RWS_CONFIG_ACME_DOMAINS_DEFAULT_VALUE: &'static str = "";
125
126    /// Contact email sent to the CA. Recommended but not required.
127    pub const RWS_CONFIG_ACME_EMAIL: &'static str = "RWS_CONFIG_ACME_EMAIL";
128    pub const RWS_CONFIG_ACME_EMAIL_DEFAULT_VALUE: &'static str = "";
129
130    /// Set to `"true"` to use the Let's Encrypt staging environment (for testing).
131    pub const RWS_CONFIG_ACME_STAGING: &'static str = "RWS_CONFIG_ACME_STAGING";
132    pub const RWS_CONFIG_ACME_STAGING_DEFAULT_VALUE: &'static str = "false";
133
134    /// Custom ACME directory URL. Defaults to Let's Encrypt production.
135    pub const RWS_CONFIG_ACME_DIRECTORY: &'static str = "RWS_CONFIG_ACME_DIRECTORY";
136    pub const RWS_CONFIG_ACME_DIRECTORY_DEFAULT_VALUE: &'static str = "";
137
138    /// Where to write the provisioned certificate chain (PEM). Defaults to `RWS_CONFIG_TLS_CERT_FILE`,
139    /// or `cert.pem` in the working directory when that is empty too.
140    pub const RWS_CONFIG_ACME_CERT_PATH: &'static str = "RWS_CONFIG_ACME_CERT_PATH";
141    pub const RWS_CONFIG_ACME_CERT_PATH_DEFAULT_VALUE: &'static str = "";
142
143    /// Where to write the certificate's private key (PEM). Defaults to `RWS_CONFIG_TLS_KEY_FILE`,
144    /// or `key.pem` in the working directory when that is empty too.
145    pub const RWS_CONFIG_ACME_KEY_PATH: &'static str = "RWS_CONFIG_ACME_KEY_PATH";
146    pub const RWS_CONFIG_ACME_KEY_PATH_DEFAULT_VALUE: &'static str = "";
147
148    /// Port for the temporary HTTP-01 challenge server (default 80).
149    /// Must be reachable from the internet on port 80. Not used with DNS-01.
150    pub const RWS_CONFIG_ACME_CHALLENGE_PORT: &'static str = "RWS_CONFIG_ACME_CHALLENGE_PORT";
151    pub const RWS_CONFIG_ACME_CHALLENGE_PORT_DEFAULT_VALUE: &'static str = "80";
152
153    /// Renew when fewer than this many days remain on the certificate (default 30).
154    pub const RWS_CONFIG_ACME_RENEW_BEFORE_DAYS: &'static str = "RWS_CONFIG_ACME_RENEW_BEFORE_DAYS";
155    /// Empty: renew at two-thirds of the certificate's lifetime (or the CA's
156    /// renewal window), not a fixed number of days before expiry.
157    pub const RWS_CONFIG_ACME_RENEW_BEFORE_DAYS_DEFAULT_VALUE: &'static str = "";
158    pub const RWS_CONFIG_ACME_PROFILE: &'static str = "RWS_CONFIG_ACME_PROFILE";
159
160    /// Path to persist the ACME account key between restarts (default `acme_account.key`).
161    pub const RWS_CONFIG_ACME_ACCOUNT_KEY_PATH: &'static str = "RWS_CONFIG_ACME_ACCOUNT_KEY_PATH";
162    pub const RWS_CONFIG_ACME_ACCOUNT_KEY_PATH_DEFAULT_VALUE: &'static str = "acme_account.key";
163
164    /// PEM file of extra root CAs to trust when talking to the ACME directory,
165    /// on top of the public Web PKI roots — for a private ACME CA (step-ca,
166    /// Pebble, an internal Boulder). Empty means public roots only.
167    pub const RWS_CONFIG_ACME_CA_FILE: &'static str = "RWS_CONFIG_ACME_CA_FILE";
168    pub const RWS_CONFIG_ACME_CA_FILE_DEFAULT_VALUE: &'static str = "";
169
170    /// Seconds between certificate-expiry checks in the renewal loop (default 12h).
171    /// After a failed renewal the next attempt comes after at most one hour.
172    pub const RWS_CONFIG_ACME_RENEW_CHECK_INTERVAL_SECS: &'static str = "RWS_CONFIG_ACME_RENEW_CHECK_INTERVAL_SECS";
173    pub const RWS_CONFIG_ACME_RENEW_CHECK_INTERVAL_SECS_DEFAULT_VALUE: &'static str = "43200";
174
175    /// How to prove control of the domains: `http-01` (default),
176    /// `tls-alpn-01` (on the HTTPS port) or `dns-01` (needed for wildcards).
177    pub const RWS_CONFIG_ACME_CHALLENGE: &'static str = "RWS_CONFIG_ACME_CHALLENGE";
178    /// DNS-01: `rfc2136` (a DNS UPDATE to `RWS_CONFIG_ACME_DNS_SERVER`) or
179    /// `exec` (a program that adds and removes the record).
180    pub const RWS_CONFIG_ACME_DNS_PROVIDER: &'static str = "RWS_CONFIG_ACME_DNS_PROVIDER";
181    /// DNS-01 `rfc2136`: the primary server, `host[:port]` (port 53).
182    pub const RWS_CONFIG_ACME_DNS_SERVER: &'static str = "RWS_CONFIG_ACME_DNS_SERVER";
183    /// DNS-01: the zone holding the records; found by SOA lookup when empty.
184    pub const RWS_CONFIG_ACME_DNS_ZONE: &'static str = "RWS_CONFIG_ACME_DNS_ZONE";
185    /// DNS-01 `rfc2136`: the TSIG key name the server knows.
186    pub const RWS_CONFIG_ACME_DNS_TSIG_KEY: &'static str = "RWS_CONFIG_ACME_DNS_TSIG_KEY";
187    /// DNS-01 `rfc2136`: the TSIG secret, base64 — never printed.
188    pub const RWS_CONFIG_ACME_DNS_TSIG_SECRET: &'static str = "RWS_CONFIG_ACME_DNS_TSIG_SECRET";
189    /// DNS-01 `rfc2136`: `hmac-sha256` (default) or `hmac-sha512`.
190    pub const RWS_CONFIG_ACME_DNS_TSIG_ALGORITHM: &'static str = "RWS_CONFIG_ACME_DNS_TSIG_ALGORITHM";
191    /// DNS-01 `exec`: the program run as `<program> present|cleanup <fqdn> <value>`.
192    pub const RWS_CONFIG_ACME_DNS_EXEC: &'static str = "RWS_CONFIG_ACME_DNS_EXEC";
193    /// DNS-01: how long to wait for the record to be visible (default 120).
194    pub const RWS_CONFIG_ACME_DNS_PROPAGATION_SECS: &'static str = "RWS_CONFIG_ACME_DNS_PROPAGATION_SECS";
195    /// DNS-01 cloudflare: the API token (DNS edit) — never printed.
196    pub const RWS_CONFIG_ACME_DNS_API_TOKEN: &'static str = "RWS_CONFIG_ACME_DNS_API_TOKEN";
197    /// DNS-01 cloudflare / route53: the API base URL instead of the
198    /// provider's own (a proxy, a test double).
199    pub const RWS_CONFIG_ACME_DNS_API_URL: &'static str = "RWS_CONFIG_ACME_DNS_API_URL";
200    /// DNS-01 cloudflare / route53: the zone's id, instead of looking it up.
201    pub const RWS_CONFIG_ACME_DNS_ZONE_ID: &'static str = "RWS_CONFIG_ACME_DNS_ZONE_ID";
202    /// Redis (`host:port`) shared by replicas: one orders, all install.
203    pub const RWS_CONFIG_ACME_REDIS: &'static str = "RWS_CONFIG_ACME_REDIS";
204    /// Password for `RWS_CONFIG_ACME_REDIS` — never printed.
205    pub const RWS_CONFIG_ACME_REDIS_PASSWORD: &'static str = "RWS_CONFIG_ACME_REDIS_PASSWORD";
206    /// Key prefix in that Redis (default `rws:acme`).
207    pub const RWS_CONFIG_ACME_REDIS_PREFIX: &'static str = "RWS_CONFIG_ACME_REDIS_PREFIX";
208    /// `true`: order a certificate at the first TLS handshake for a name
209    /// `RWS_CONFIG_ACME_ON_DEMAND_ASK` allows (since 18.4.0).
210    pub const RWS_CONFIG_ACME_ON_DEMAND: &'static str = "RWS_CONFIG_ACME_ON_DEMAND";
211    /// On-demand TLS: URL asked `?domain=<name>` before each order; 2xx allows it.
212    pub const RWS_CONFIG_ACME_ON_DEMAND_ASK: &'static str = "RWS_CONFIG_ACME_ON_DEMAND_ASK";
213    /// `[admin] listen`: `host:port` of the admin API (since 18.10.0); unset = off.
214    pub const RWS_CONFIG_ADMIN_LISTEN: &'static str = "RWS_CONFIG_ADMIN_LISTEN";
215    /// `[admin] token_env`: the environment variable holding the admin bearer
216    /// token (default `RWS_ADMIN_TOKEN`).
217    pub const RWS_CONFIG_ADMIN_TOKEN_ENV: &'static str = "RWS_CONFIG_ADMIN_TOKEN_ENV";
218    /// `[admin] allowed_origins`: browser origins allowed besides the admin
219    /// listener's own (comma list).
220    pub const RWS_CONFIG_ADMIN_ALLOWED_ORIGINS: &'static str = "RWS_CONFIG_ADMIN_ALLOWED_ORIGINS";
221
222
223    pub const RWS_DEFAULT_IP: &'static str = "127.0.0.1";
224    pub const RWS_DEFAULT_PORT: &'static i32 = &7878;
225    pub const RWS_DEFAULT_THREAD_COUNT: &'static i32 = &200;
226    pub const RWS_DEFAULT_REQUEST_ALLOCATION_SIZE_IN_BYTES: &'static i64 = &10000;
227
228
229}
230
231pub fn bootstrap() {
232    read_system_environment_variables();
233    override_environment_variables_from_config(None);
234    override_environment_variables_from_command_line_args();
235    crate::server_config::invalidate();
236}
237
238pub fn set_default_values() {
239    println!("  Initializing default values");
240
241    let is_var_set = env::var(Config::RWS_CONFIG_IP).is_ok();
242    if !is_var_set {
243        env::set_var(Config::RWS_CONFIG_IP, Config::RWS_CONFIG_IP_DEFAULT_VALUE);
244        println!("    Default value  for '{}' is '{}'", Config::RWS_CONFIG_IP, Config::RWS_CONFIG_IP_DEFAULT_VALUE);
245    } else {
246        println!("    There is an environment variable  for '{}', default value won't be set", Config::RWS_CONFIG_IP);
247    }
248
249
250    let is_var_set = env::var(Config::RWS_CONFIG_PORT).is_ok();
251    if !is_var_set {
252        env::set_var(Config::RWS_CONFIG_PORT, Config::RWS_CONFIG_PORT_DEFAULT_VALUE);
253        println!("    Default value  for '{}' is '{}'", Config::RWS_CONFIG_PORT, Config::RWS_CONFIG_PORT_DEFAULT_VALUE);
254    } else {
255        println!("    There is an environment variable  for '{}', default value won't be set", Config::RWS_CONFIG_PORT);
256    }
257
258    let is_var_set = env::var(Config::RWS_CONFIG_REQUEST_ALLOCATION_SIZE_IN_BYTES).is_ok();
259    if !is_var_set {
260        env::set_var(Config::RWS_CONFIG_REQUEST_ALLOCATION_SIZE_IN_BYTES, Config::RWS_CONFIG_REQUEST_ALLOCATION_SIZE_IN_BYTES_DEFAULT_VALUE);
261        println!("    Default value  for '{}' is '{}'", Config::RWS_CONFIG_REQUEST_ALLOCATION_SIZE_IN_BYTES, Config::RWS_CONFIG_REQUEST_ALLOCATION_SIZE_IN_BYTES_DEFAULT_VALUE);
262    } else {
263        println!("    There is an environment variable  for '{}', default value won't be set", Config::RWS_CONFIG_REQUEST_ALLOCATION_SIZE_IN_BYTES);
264    }
265
266
267    let is_var_set = env::var(Config::RWS_CONFIG_THREAD_COUNT).is_ok();
268    if !is_var_set {
269        env::set_var(Config::RWS_CONFIG_THREAD_COUNT, Config::RWS_CONFIG_THREAD_COUNT_DEFAULT_VALUE);
270        println!("    Default value  for '{}' is '{}'", Config::RWS_CONFIG_THREAD_COUNT, Config::RWS_CONFIG_THREAD_COUNT_DEFAULT_VALUE);
271    } else {
272        println!("    There is an environment variable  for '{}', default value won't be set", Config::RWS_CONFIG_THREAD_COUNT);
273    }
274
275    let is_var_set = env::var(Config::RWS_CONFIG_CORS_ALLOW_ALL).is_ok();
276    if !is_var_set {
277        env::set_var(Config::RWS_CONFIG_CORS_ALLOW_ALL, Config::RWS_CONFIG_CORS_ALLOW_ALL_DEFAULT_VALUE);
278        println!("    Default value  for '{}' is '{}'", Config::RWS_CONFIG_CORS_ALLOW_ALL, Config::RWS_CONFIG_CORS_ALLOW_ALL_DEFAULT_VALUE);
279    } else {
280        println!("    There is an environment variable  for '{}', default value won't be set", Config::RWS_CONFIG_CORS_ALLOW_ALL);
281    }
282
283
284    let is_var_set = env::var(Config::RWS_CONFIG_CORS_ALLOW_ORIGINS).is_ok();
285    if !is_var_set {
286        env::set_var(Config::RWS_CONFIG_CORS_ALLOW_ORIGINS, Config::RWS_CONFIG_CORS_ALLOW_ORIGINS_DEFAULT_VALUE);
287        println!("    Default value  for '{}' is '{}'", Config::RWS_CONFIG_CORS_ALLOW_ORIGINS, Config::RWS_CONFIG_CORS_ALLOW_ORIGINS_DEFAULT_VALUE);
288    } else {
289        println!("    There is an environment variable  for '{}', default value won't be set", Config::RWS_CONFIG_CORS_ALLOW_ORIGINS);
290    }
291
292    let is_var_set = env::var(Config::RWS_CONFIG_CORS_ALLOW_CREDENTIALS).is_ok();
293    if !is_var_set {
294        env::set_var(Config::RWS_CONFIG_CORS_ALLOW_CREDENTIALS, Config::RWS_CONFIG_CORS_ALLOW_CREDENTIALS_DEFAULT_VALUE);
295        println!("    Default value  for '{}' is '{}'", Config::RWS_CONFIG_CORS_ALLOW_CREDENTIALS, Config::RWS_CONFIG_CORS_ALLOW_CREDENTIALS_DEFAULT_VALUE);
296    } else {
297        println!("    There is an environment variable  for '{}', default value won't be set", Config::RWS_CONFIG_CORS_ALLOW_CREDENTIALS);
298    }
299
300    let is_var_set = env::var(Config::RWS_CONFIG_CORS_ALLOW_HEADERS).is_ok();
301    if !is_var_set {
302        env::set_var(Config::RWS_CONFIG_CORS_ALLOW_HEADERS, Config::RWS_CONFIG_CORS_ALLOW_HEADERS_DEFAULT_VALUE);
303        println!("    Default value  for '{}' is '{}'", Config::RWS_CONFIG_CORS_ALLOW_HEADERS, Config::RWS_CONFIG_CORS_ALLOW_HEADERS_DEFAULT_VALUE);
304    } else {
305        println!("    There is an environment variable  for '{}', default value won't be set", Config::RWS_CONFIG_CORS_ALLOW_HEADERS);
306    }
307
308
309    let is_var_set = env::var(Config::RWS_CONFIG_CORS_ALLOW_METHODS).is_ok();
310    if !is_var_set {
311        env::set_var(Config::RWS_CONFIG_CORS_ALLOW_METHODS, Config::RWS_CONFIG_CORS_ALLOW_METHODS_DEFAULT_VALUE);
312        println!("    Default value  for '{}' is '{}'", Config::RWS_CONFIG_CORS_ALLOW_METHODS, Config::RWS_CONFIG_CORS_ALLOW_METHODS_DEFAULT_VALUE);
313    } else {
314        println!("    There is an environment variable  for '{}', default value won't be set", Config::RWS_CONFIG_CORS_ALLOW_METHODS);
315    }
316
317    let is_var_set = env::var(Config::RWS_CONFIG_CORS_EXPOSE_HEADERS).is_ok();
318    if !is_var_set {
319        env::set_var(Config::RWS_CONFIG_CORS_EXPOSE_HEADERS, Config::RWS_CONFIG_CORS_EXPOSE_HEADERS_DEFAULT_VALUE);
320        println!("    Default value  for '{}' is '{}'", Config::RWS_CONFIG_CORS_EXPOSE_HEADERS, Config::RWS_CONFIG_CORS_EXPOSE_HEADERS_DEFAULT_VALUE);
321    } else {
322        println!("    There is an environment variable  for '{}', default value won't be set", Config::RWS_CONFIG_CORS_EXPOSE_HEADERS);
323    }
324
325    let is_var_set = env::var(Config::RWS_CONFIG_CORS_MAX_AGE).is_ok();
326    if !is_var_set {
327        env::set_var(Config::RWS_CONFIG_CORS_MAX_AGE, Config::RWS_CONFIG_CORS_MAX_AGE_DEFAULT_VALUE);
328        println!("    Default value  for '{}' is '{}'", Config::RWS_CONFIG_CORS_MAX_AGE, Config::RWS_CONFIG_CORS_MAX_AGE_DEFAULT_VALUE);
329    } else {
330        println!("    There is an environment variable  for '{}', default value won't be set", Config::RWS_CONFIG_CORS_MAX_AGE);
331    }
332
333
334    let is_var_set = env::var(Config::RWS_CONFIG_TLS_CERT_FILE).is_ok();
335    if !is_var_set {
336        env::set_var(Config::RWS_CONFIG_TLS_CERT_FILE, Config::RWS_CONFIG_TLS_CERT_FILE_DEFAULT_VALUE);
337        println!("    Default value  for '{}' is '{}'", Config::RWS_CONFIG_TLS_CERT_FILE, Config::RWS_CONFIG_TLS_CERT_FILE_DEFAULT_VALUE);
338    } else {
339        println!("    There is an environment variable  for '{}', default value won't be set", Config::RWS_CONFIG_TLS_CERT_FILE);
340    }
341
342    let is_var_set = env::var(Config::RWS_CONFIG_TLS_KEY_FILE).is_ok();
343    if !is_var_set {
344        env::set_var(Config::RWS_CONFIG_TLS_KEY_FILE, Config::RWS_CONFIG_TLS_KEY_FILE_DEFAULT_VALUE);
345        println!("    Default value  for '{}' is '{}'", Config::RWS_CONFIG_TLS_KEY_FILE, Config::RWS_CONFIG_TLS_KEY_FILE_DEFAULT_VALUE);
346    } else {
347        println!("    There is an environment variable  for '{}', default value won't be set", Config::RWS_CONFIG_TLS_KEY_FILE);
348    }
349
350    let is_var_set = env::var(Config::RWS_CONFIG_HTTP_REDIRECT_PORT).is_ok();
351    if !is_var_set {
352        env::set_var(Config::RWS_CONFIG_HTTP_REDIRECT_PORT, Config::RWS_CONFIG_HTTP_REDIRECT_PORT_DEFAULT_VALUE);
353        println!("    Default value  for '{}' is '{}'", Config::RWS_CONFIG_HTTP_REDIRECT_PORT, Config::RWS_CONFIG_HTTP_REDIRECT_PORT_DEFAULT_VALUE);
354    } else {
355        println!("    There is an environment variable  for '{}', default value won't be set", Config::RWS_CONFIG_HTTP_REDIRECT_PORT);
356    }
357
358    let is_var_set = env::var(Config::RWS_CONFIG_LOG_FORMAT).is_ok();
359    if !is_var_set {
360        env::set_var(Config::RWS_CONFIG_LOG_FORMAT, Config::RWS_CONFIG_LOG_FORMAT_DEFAULT_VALUE);
361        println!("    Default value  for '{}' is '{}'", Config::RWS_CONFIG_LOG_FORMAT, Config::RWS_CONFIG_LOG_FORMAT_DEFAULT_VALUE);
362    } else {
363        println!("    There is an environment variable  for '{}', default value won't be set", Config::RWS_CONFIG_LOG_FORMAT);
364    }
365
366    crate::server_config::invalidate();
367    println!("  End of initializing default values\n");
368}
369
370
371pub fn get_ip_port_thread_count() -> (String, i32, i32) {
372    let mut ip : String = Config::RWS_CONFIG_IP_DEFAULT_VALUE.to_string();
373    let mut port: i32 = *Config::RWS_DEFAULT_PORT;
374    let mut thread_count: i32 = *Config::RWS_DEFAULT_THREAD_COUNT;
375
376    let boxed_ip = env::var(Config::RWS_CONFIG_IP);
377    if boxed_ip.is_ok() {
378        ip = boxed_ip.unwrap()
379    }
380
381    let boxed_port = env::var(Config::RWS_CONFIG_PORT);
382    if boxed_port.is_ok() {
383        let _port = boxed_port.unwrap();
384        let boxed_parse = _port.parse::<i32>();
385        if boxed_parse.is_ok() {
386            port = boxed_parse.unwrap();
387        } else {
388            eprintln!("unable to parse port value, expected number, got {}, variable: {}",
389                      _port, Config::RWS_CONFIG_PORT);
390        }
391    } else {
392        eprintln!("unable to parse port value, variable: {}", Config::RWS_CONFIG_PORT);
393    }
394
395    let boxed_thread_count = env::var(Config::RWS_CONFIG_THREAD_COUNT);
396    if boxed_thread_count.is_ok() {
397        let _thread_count = boxed_thread_count.unwrap();
398        let boxed_parse = _thread_count.parse();
399        if boxed_parse.is_ok() {
400            thread_count = boxed_parse.unwrap()
401        } else {
402            eprintln!("unable to parse thread count value, expected number, got {}, variable: {}",
403                      thread_count, Config::RWS_CONFIG_THREAD_COUNT);
404        }
405
406    } else {
407        eprintln!("unable to parse thread count value, variable: {}", Config::RWS_CONFIG_THREAD_COUNT);
408    }
409
410    (ip, port, thread_count)
411}
412
413/// Read all `[[virtual_host]]` entries from config / env vars.
414///
415/// Each entry must have `RWS_CONFIG_VIRTUAL_HOST_{N}_DOMAIN` set; reading
416/// stops at the first missing index.  `cert_file` and `key_file` default to
417/// empty string if omitted.
418pub fn get_virtual_hosts() -> Vec<VirtualHostConfig> {
419    let mut hosts = Vec::new();
420    let mut i = 0usize;
421    loop {
422        match env::var(format!("RWS_CONFIG_VIRTUAL_HOST_{}_DOMAIN", i)) {
423            Err(_) => break,
424            Ok(domain) => {
425                let cert_file = env::var(format!("RWS_CONFIG_VIRTUAL_HOST_{}_CERT_FILE", i))
426                    .unwrap_or_default();
427                let key_file = env::var(format!("RWS_CONFIG_VIRTUAL_HOST_{}_KEY_FILE", i))
428                    .unwrap_or_default();
429                hosts.push(VirtualHostConfig { domain, cert_file, key_file });
430                i += 1;
431            }
432        }
433    }
434    hosts
435}
436
437pub fn get_request_allocation_size() -> i64 {
438    let mut request_allocation_size: i64 = *Config::RWS_DEFAULT_REQUEST_ALLOCATION_SIZE_IN_BYTES;
439
440    let boxed_port = env::var(Config::RWS_CONFIG_REQUEST_ALLOCATION_SIZE_IN_BYTES);
441    if boxed_port.is_ok() {
442        let _request_allocation_size = boxed_port.unwrap();
443        let boxed_parse = _request_allocation_size.parse::<i64>();
444        if boxed_parse.is_ok() {
445            request_allocation_size = boxed_parse.unwrap();
446        } else {
447            eprintln!("unable to parse port value, expected number, got {}, variable: {}",
448                      _request_allocation_size, Config::RWS_CONFIG_REQUEST_ALLOCATION_SIZE_IN_BYTES);
449        }
450    } else {
451        eprintln!("unable to parse request allocation size value, variable: {}", Config::RWS_CONFIG_REQUEST_ALLOCATION_SIZE_IN_BYTES);
452    }
453
454
455    request_allocation_size
456}
457
458/// Maximum accepted request body size in bytes. `0` means unlimited (the default —
459/// enforcing a cap is opt-in). Read fresh on every call, so `RWS_CONFIG_MAX_BODY_SIZE_IN_BYTES`
460/// takes effect immediately, the same as [`get_request_allocation_size`].
461pub fn get_max_body_size() -> u64 {
462    env::var(Config::RWS_CONFIG_MAX_BODY_SIZE_IN_BYTES)
463        .ok()
464        .and_then(|v| v.parse::<u64>().ok())
465        .unwrap_or(0)
466}
467
468/// The configured SPA-fallback file (e.g. `"index.html"`), or `None` if
469/// `RWS_CONFIG_SPA_FALLBACK` is unset/empty (the default — disabled). Read
470/// fresh on every call, so it can be toggled via `rws.config.toml` + `SIGHUP`
471/// without a restart, the same as [`get_max_body_size`].
472pub fn get_spa_fallback() -> Option<String> {
473    env::var(Config::RWS_CONFIG_SPA_FALLBACK).ok().filter(|v| !v.is_empty())
474}
475
476/// Path prefixes that never receive the SPA fallback (`RWS_CONFIG_SPA_FALLBACK_EXCLUDE_PREFIXES`,
477/// comma-separated), even when the fallback is configured. Empty (default) excludes nothing.
478pub fn get_spa_fallback_exclude_prefixes() -> Vec<String> {
479    env::var(Config::RWS_CONFIG_SPA_FALLBACK_EXCLUDE_PREFIXES)
480        .ok()
481        .map(|v| v.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect())
482        .unwrap_or_default()
483}
484