Skip to main content

rws_http/entry_point/
mod.rs

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