mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-27 09:12:57 +01:00
document settings crate (#271)
This commit is contained in:
parent
ab3f591307
commit
b054733854
@ -62,7 +62,7 @@ async fn main() -> std::io::Result<()> {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Initialize the logging infrastructure
|
||||
/// Initialize the logging infrastructure.
|
||||
fn init_logger(settings: &Settings) {
|
||||
if !settings.actix.enable_log {
|
||||
return;
|
||||
|
@ -21,19 +21,19 @@ num-workers = "default"
|
||||
# Takes a string value: Either "default", or an integer N > 0 e.g. "6".
|
||||
backlog = "default"
|
||||
|
||||
# Sets the maximum per-worker number of concurrent connections. All socket listeners
|
||||
# Sets the per-worker maximum number of concurrent connections. All socket listeners
|
||||
# will stop accepting connections when this limit is reached for each worker.
|
||||
# By default max connections is set to a 25k.
|
||||
# Takes a string value: Either "default", or an integer N > 0 e.g. "6".
|
||||
max-connections = "default"
|
||||
|
||||
# Sets the maximum per-worker concurrent connection establish process. All listeners
|
||||
# Sets the per-worker maximum concurrent connection establish process. All listeners
|
||||
# will stop accepting connections when this limit is reached. It can be used to limit
|
||||
# the global TLS CPU usage. By default max connections is set to a 256.
|
||||
# Takes a string value: Either "default", or an integer N > 0 e.g. "6".
|
||||
max-connection-rate = "default"
|
||||
|
||||
# Set server keep-alive setting. By default keep alive is set to 5 seconds.
|
||||
# Set server keep-alive preference. By default keep alive is set to 5 seconds.
|
||||
# Takes a string value: Either "default", "disabled", "os",
|
||||
# or a string of the format "N seconds" where N is an integer > 0 e.g. "6 seconds".
|
||||
keep-alive = "default"
|
||||
|
@ -2,12 +2,20 @@ use std::{env::VarError, io, num::ParseIntError, path::PathBuf, str::ParseBoolEr
|
||||
|
||||
use toml::de::Error as TomlError;
|
||||
|
||||
/// Convenience type alias for `Result<T, AtError>`.
|
||||
pub type AtResult<T> = std::result::Result<T, AtError>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// Errors that can be returned from methods in this crate.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AtError {
|
||||
/// Environment variable does not exists or is invalid.
|
||||
EnvVarError(VarError),
|
||||
|
||||
/// File already exists on disk.
|
||||
FileExists(PathBuf),
|
||||
|
||||
/// Invalid value.
|
||||
#[allow(missing_docs)]
|
||||
InvalidValue {
|
||||
expected: &'static str,
|
||||
got: String,
|
||||
@ -15,10 +23,20 @@ pub enum AtError {
|
||||
line: u32,
|
||||
column: u32,
|
||||
},
|
||||
|
||||
/// I/O error.
|
||||
IoError(ioe::IoError),
|
||||
|
||||
/// Value is not a boolean.
|
||||
ParseBoolError(ParseBoolError),
|
||||
|
||||
/// Value is not an integer.
|
||||
ParseIntError(ParseIntError),
|
||||
|
||||
/// Value is not an address.
|
||||
ParseAddressError(String),
|
||||
|
||||
/// Error deserializing as TOML.
|
||||
TomlError(TomlError),
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,68 @@
|
||||
//! Easily manage Actix Web's settings from a TOML file and environment variables.
|
||||
//!
|
||||
//! To get started add a [`Settings::parse_toml("./Server.toml")`](Settings::parse_toml) call to the
|
||||
//! top of your main function. This will create a template file with descriptions of all the
|
||||
//! configurable settings. You can change or remove anything in that file and it will be picked up
|
||||
//! the next time you run your application.
|
||||
//!
|
||||
//! Overriding parts of the file can be done from values using [`Settings::override_field`] or from
|
||||
//! the environment using [`Settings::override_field_with_env_var`].
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! See examples folder on GitHub for complete example.
|
||||
//!
|
||||
//! ```ignore
|
||||
//! # use actix_web::{
|
||||
//! # get,
|
||||
//! # middleware::{Compress, Condition, Logger},
|
||||
//! # web, App, HttpServer,
|
||||
//! # };
|
||||
//! use actix_settings::{ApplySettings as _, Mode, Settings};
|
||||
//!
|
||||
//! #[actix_web::main]
|
||||
//! async fn main() -> std::io::Result<()> {
|
||||
//! let mut settings = Settings::parse_toml("./Server.toml")
|
||||
//! .expect("Failed to parse `Settings` from Server.toml");
|
||||
//!
|
||||
//! // If the environment variable `$APPLICATION__HOSTS` is set,
|
||||
//! // have its value override the `settings.actix.hosts` setting:
|
||||
//! Settings::override_field_with_env_var(&mut settings.actix.hosts, "APPLICATION__HOSTS")?;
|
||||
//!
|
||||
//! init_logger(&settings);
|
||||
//!
|
||||
//! HttpServer::new({
|
||||
//! // clone settings into each worker thread
|
||||
//! let settings = settings.clone();
|
||||
//!
|
||||
//! move || {
|
||||
//! App::new()
|
||||
//! // Include this `.wrap()` call for compression settings to take effect
|
||||
//! .wrap(Condition::new(
|
||||
//! settings.actix.enable_compression,
|
||||
//! Compress::default(),
|
||||
//! ))
|
||||
//!
|
||||
//! // add request logger
|
||||
//! .wrap(Logger::default())
|
||||
//!
|
||||
//! // make `Settings` available to handlers
|
||||
//! .app_data(web::Data::new(settings.clone()))
|
||||
//!
|
||||
//! // add request handlers as normal
|
||||
//! .service(index)
|
||||
//! }
|
||||
//! })
|
||||
//! // apply the `Settings` to Actix Web's `HttpServer`
|
||||
//! .apply_settings(&settings)
|
||||
//! .run()
|
||||
//! .await
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible, missing_debug_implementations)]
|
||||
// #![warn(missing_docs)]
|
||||
#![warn(future_incompatible, missing_docs, missing_debug_implementations)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
@ -37,15 +96,21 @@ pub use self::settings::{
|
||||
NumWorkers, Timeout, Tls,
|
||||
};
|
||||
|
||||
/// Wrapper for server and application-specific settings.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
|
||||
#[serde(bound = "A: Deserialize<'de>")]
|
||||
pub struct BasicSettings<A> {
|
||||
/// Actix Web server settings.
|
||||
pub actix: ActixSettings,
|
||||
|
||||
/// Application-specific settings.
|
||||
pub application: A,
|
||||
}
|
||||
|
||||
/// Convenience type alias for [`BasicSettings`] with no defined application-specific settings.
|
||||
pub type Settings = BasicSettings<NoSettings>;
|
||||
|
||||
/// Marker type representing no defined application-specific settings.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
|
||||
#[non_exhaustive]
|
||||
pub struct NoSettings {/* NOTE: turning this into a unit struct will cause deserialization failures. */}
|
||||
@ -54,14 +119,16 @@ impl<A> BasicSettings<A>
|
||||
where
|
||||
A: de::DeserializeOwned,
|
||||
{
|
||||
/// NOTE **DO NOT** mess with the ordering of the tables in this template.
|
||||
/// Especially the `[application]` table needs to be last in order
|
||||
/// for some tests to keep working.
|
||||
// NOTE **DO NOT** mess with the ordering of the tables in the default template.
|
||||
// Especially the `[application]` table needs to be last in order
|
||||
// for some tests to keep working.
|
||||
/// Default settings file contents.
|
||||
pub(crate) const DEFAULT_TOML_TEMPLATE: &'static str = include_str!("./defaults.toml");
|
||||
|
||||
/// Parse an instance of `Self` from a `TOML` file located at `filepath`.
|
||||
/// If the file doesn't exist, it is generated from the default `TOML`
|
||||
/// template, after which the newly generated file is read in and parsed.
|
||||
/// Parse an instance of `Self` from a TOML file located at `filepath`.
|
||||
///
|
||||
/// If the file doesn't exist, it is generated from the default TOML template, after which the
|
||||
/// newly generated file is read in and parsed.
|
||||
pub fn parse_toml<P>(filepath: P) -> AtResult<Self>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
@ -73,43 +140,62 @@ where
|
||||
}
|
||||
|
||||
let mut f = File::open(filepath)?;
|
||||
// TODO: don't bail on metadata fail
|
||||
let mut contents = String::with_capacity(f.metadata()?.len() as usize);
|
||||
f.read_to_string(&mut contents)?;
|
||||
|
||||
Ok(toml::from_str::<Self>(&contents)?)
|
||||
}
|
||||
|
||||
/// Parse an instance of `Self` straight from the default `TOML` template.
|
||||
/// Parse an instance of `Self` straight from the default TOML template.
|
||||
// TODO: make infallible
|
||||
// TODO: consider "template" rename
|
||||
pub fn from_default_template() -> AtResult<Self> {
|
||||
Self::from_template(Self::DEFAULT_TOML_TEMPLATE)
|
||||
}
|
||||
|
||||
/// Parse an instance of `Self` straight from the default `TOML` template.
|
||||
/// Parse an instance of `Self` straight from the default TOML template.
|
||||
// TODO: consider "template" rename
|
||||
pub fn from_template(template: &str) -> AtResult<Self> {
|
||||
Ok(toml::from_str(template)?)
|
||||
}
|
||||
|
||||
/// Write the default `TOML` template to a new file, to be located
|
||||
/// at `filepath`. Return a `Error::FileExists(_)` error if a
|
||||
/// file already exists at that location.
|
||||
/// Writes the default TOML template to a new file, located at `filepath`.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns a [`FileExists`](crate::AtError::FileExists) error if a file already exists at that
|
||||
/// location.
|
||||
pub fn write_toml_file<P>(filepath: P) -> AtResult<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let filepath = filepath.as_ref();
|
||||
let contents = Self::DEFAULT_TOML_TEMPLATE.trim();
|
||||
|
||||
if filepath.exists() {
|
||||
return Err(AtError::FileExists(filepath.to_path_buf()));
|
||||
}
|
||||
|
||||
let mut file = File::create(filepath)?;
|
||||
file.write_all(contents.as_bytes())?;
|
||||
file.write_all(Self::DEFAULT_TOML_TEMPLATE.trim().as_bytes())?;
|
||||
file.flush()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Attempts to parse `value` and override the referenced `field`.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_settings::{Settings, Mode};
|
||||
///
|
||||
/// # fn inner() -> actix_settings::AtResult<()> {
|
||||
/// let mut settings = Settings::from_default_template()?;
|
||||
/// assert_eq!(settings.actix.mode, Mode::Development);
|
||||
///
|
||||
/// Settings::override_field(&mut settings.actix.mode, "production")?;
|
||||
/// assert_eq!(settings.actix.mode, Mode::Production);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn override_field<F, V>(field: &mut F, value: V) -> AtResult<()>
|
||||
where
|
||||
F: Parse,
|
||||
@ -119,6 +205,22 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Attempts to read an environment variable, parse it, and override the referenced `field`.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_settings::{Settings, Mode};
|
||||
///
|
||||
/// std::env::set_var("OVERRIDE__MODE", "production");
|
||||
///
|
||||
/// # fn inner() -> actix_settings::AtResult<()> {
|
||||
/// let mut settings = Settings::from_default_template()?;
|
||||
/// assert_eq!(settings.actix.mode, Mode::Development);
|
||||
///
|
||||
/// Settings::override_field_with_env_var(&mut settings.actix.mode, "OVERRIDE__MODE")?;
|
||||
/// assert_eq!(settings.actix.mode, Mode::Production);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn override_field_with_env_var<F, N>(field: &mut F, var_name: N) -> AtResult<()>
|
||||
where
|
||||
F: Parse,
|
||||
@ -132,6 +234,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait for applying parsed settings to the server object.
|
||||
pub trait ApplySettings {
|
||||
/// Apply a [`BasicSettings`] value to `self`.
|
||||
///
|
||||
@ -200,9 +303,9 @@ where
|
||||
self = match settings.actix.client_timeout {
|
||||
Timeout::Default => self,
|
||||
Timeout::Milliseconds(n) => {
|
||||
self.client_disconnect_timeout(Duration::from_millis(n as u64))
|
||||
self.client_request_timeout(Duration::from_millis(n as u64))
|
||||
}
|
||||
Timeout::Seconds(n) => self.client_disconnect_timeout(Duration::from_secs(n as u64)),
|
||||
Timeout::Seconds(n) => self.client_request_timeout(Duration::from_secs(n as u64)),
|
||||
};
|
||||
|
||||
self = match settings.actix.client_shutdown {
|
||||
|
@ -2,7 +2,9 @@ use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
use crate::AtError;
|
||||
|
||||
/// A specialized `FromStr` trait that returns [`AtError`] errors
|
||||
pub trait Parse: Sized {
|
||||
/// Parse `Self` from `string`.
|
||||
fn parse(string: &str) -> Result<Self, AtError>;
|
||||
}
|
||||
|
||||
|
@ -37,9 +37,13 @@ static ADDR_LIST_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
.expect("Failed to compile regex: ADDRS_REGEX")
|
||||
});
|
||||
|
||||
/// A host/port pair for the server to bind to.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
|
||||
pub struct Address {
|
||||
/// Host part of address.
|
||||
pub host: String,
|
||||
|
||||
/// Port part of address.
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,20 @@ use serde::de;
|
||||
|
||||
use crate::{AtError, AtResult, Parse};
|
||||
|
||||
/// The maximum number of pending connections.
|
||||
///
|
||||
/// This refers to the number of clients that can be waiting to be served. Exceeding this number
|
||||
/// results in the client getting an error when attempting to connect. It should only affect servers
|
||||
/// under significant load.
|
||||
///
|
||||
/// Generally set in the 64–2048 range. The default value is 2048. Takes a string value: Either
|
||||
/// "default", or an integer N > 0 e.g. "6".
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Backlog {
|
||||
/// The default number of connections. See struct docs.
|
||||
Default,
|
||||
|
||||
/// A specific number of connections.
|
||||
Manual(usize),
|
||||
}
|
||||
|
||||
|
@ -6,11 +6,24 @@ use serde::de;
|
||||
|
||||
use crate::{AtError, AtResult, Parse};
|
||||
|
||||
/// The server keep-alive preference.
|
||||
///
|
||||
/// By default keep alive is set to 5 seconds. Takes a string value: Either "default", "disabled",
|
||||
/// "os", or a string of the format "N seconds" where N is an integer > 0 e.g. "6 seconds".
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum KeepAlive {
|
||||
/// The default keep-alive as defined by Actix Web.
|
||||
Default,
|
||||
|
||||
/// Disable keep-alive.
|
||||
Disabled,
|
||||
|
||||
/// Let the OS determine keep-alive duration.
|
||||
///
|
||||
/// Note: this is usually quite long.
|
||||
Os,
|
||||
|
||||
/// A specific keep-alive duration (in seconds).
|
||||
Seconds(usize),
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,17 @@ use serde::de;
|
||||
|
||||
use crate::{AtError, AtResult, Parse};
|
||||
|
||||
/// The maximum per-worker concurrent TLS connection limit.
|
||||
///
|
||||
/// All listeners will stop accepting connections when this limit is reached. It can be used to
|
||||
/// limit the global TLS CPU usage. By default max connections is set to a 256. Takes a string
|
||||
/// value: Either "default", or an integer N > 0 e.g. "6".
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum MaxConnectionRate {
|
||||
/// The default connection limit. See struct docs.
|
||||
Default,
|
||||
|
||||
/// A specific connection limit.
|
||||
Manual(usize),
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,17 @@ use serde::de;
|
||||
|
||||
use crate::{AtError, AtResult, Parse};
|
||||
|
||||
/// The maximum per-worker number of concurrent connections.
|
||||
///
|
||||
/// All socket listeners will stop accepting connections when this limit is reached for each worker.
|
||||
/// By default max connections is set to a 25k. Takes a string value: Either "default", or an
|
||||
/// integer N > 0 e.g. "6".
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum MaxConnections {
|
||||
/// The default number of connections. See struct docs.
|
||||
Default,
|
||||
|
||||
/// A specific number of connections.
|
||||
Manual(usize),
|
||||
}
|
||||
|
||||
|
@ -24,17 +24,42 @@ pub use self::tls::Tls;
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct ActixSettings {
|
||||
/// List of addresses for the server to bind to.
|
||||
pub hosts: Vec<Address>,
|
||||
pub mode: mode::Mode,
|
||||
|
||||
/// Marker of intended deployment environment.
|
||||
pub mode: Mode,
|
||||
|
||||
/// True if the [`Compress`](actix_web::middleware::Compress) middleware should be enabled.
|
||||
pub enable_compression: bool,
|
||||
|
||||
/// True if the [`Logger`](actix_web::middleware::Logger) middleware should be enabled.
|
||||
pub enable_log: bool,
|
||||
|
||||
/// The number of workers that the server should start.
|
||||
pub num_workers: NumWorkers,
|
||||
|
||||
/// The maximum number of pending connections.
|
||||
pub backlog: Backlog,
|
||||
|
||||
/// The per-worker maximum number of concurrent connections.
|
||||
pub max_connections: MaxConnections,
|
||||
|
||||
/// The per-worker maximum concurrent TLS connection limit.
|
||||
pub max_connection_rate: MaxConnectionRate,
|
||||
|
||||
/// Server keep-alive preference.
|
||||
pub keep_alive: KeepAlive,
|
||||
|
||||
/// Timeout duration for reading client request header.
|
||||
pub client_timeout: Timeout,
|
||||
|
||||
/// Timeout duration for connection shutdown.
|
||||
pub client_shutdown: Timeout,
|
||||
|
||||
/// Timeout duration for graceful worker shutdown.
|
||||
pub shutdown_timeout: Timeout,
|
||||
|
||||
/// TLS (HTTPS) configuration.
|
||||
pub tls: Tls,
|
||||
}
|
||||
|
@ -2,10 +2,14 @@ use serde::Deserialize;
|
||||
|
||||
use crate::{AtResult, Parse};
|
||||
|
||||
/// Marker of intended deployment environment.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Mode {
|
||||
/// Marks development environment.
|
||||
Development,
|
||||
|
||||
/// Marks production environment.
|
||||
Production,
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,16 @@ use serde::de;
|
||||
|
||||
use crate::{AtError, AtResult, Parse};
|
||||
|
||||
/// The number of workers that the server should start.
|
||||
///
|
||||
/// By default the number of available logical cpu cores is used. Takes a string value: Either
|
||||
/// "default", or an integer N > 0 e.g. "6".
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum NumWorkers {
|
||||
/// The default number of workers. See struct docs.
|
||||
Default,
|
||||
|
||||
/// A specific number of workers.
|
||||
Manual(usize),
|
||||
}
|
||||
|
||||
|
@ -6,10 +6,16 @@ use serde::de;
|
||||
|
||||
use crate::{AtError, AtResult, Parse};
|
||||
|
||||
/// A timeout duration in milliseconds or seconds.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Timeout {
|
||||
/// The default timeout. Depends on context.
|
||||
Default,
|
||||
|
||||
/// Timeout in milliseconds.
|
||||
Milliseconds(usize),
|
||||
|
||||
/// Timeout in seconds.
|
||||
Seconds(usize),
|
||||
}
|
||||
|
||||
@ -34,18 +40,22 @@ impl Parse for Timeout {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
match string {
|
||||
"default" => Ok(Timeout::Default),
|
||||
|
||||
string if !FMT.is_match(string) => invalid_value!(string),
|
||||
|
||||
string => match (DIGITS.find(string), UNIT.find(string)) {
|
||||
(None, _) => invalid_value!(string),
|
||||
(_, None) => invalid_value!(string),
|
||||
(Some(dmatch), Some(umatch)) => {
|
||||
let digits = &string[dmatch.start()..dmatch.end()];
|
||||
let unit = &string[umatch.start()..umatch.end()];
|
||||
(None, _) | (_, None) => invalid_value!(string),
|
||||
|
||||
(Some(digits), Some(unit)) => {
|
||||
let digits = &string[digits.range()];
|
||||
let unit = &string[unit.range()];
|
||||
|
||||
match (digits.parse(), unit) {
|
||||
(Ok(v), "milliseconds") => Ok(Timeout::Milliseconds(v)),
|
||||
(Ok(v), "seconds") => Ok(Timeout::Seconds(v)),
|
||||
(Ok(n), "milliseconds") => Ok(Timeout::Milliseconds(n)),
|
||||
(Ok(n), "seconds") => Ok(Timeout::Seconds(n)),
|
||||
_ => invalid_value!(string),
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,17 @@ use std::path::PathBuf;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
/// TLS (HTTPS) configuration.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[doc(alias = "ssl", alias = "https")]
|
||||
pub struct Tls {
|
||||
/// Tru if accepting TLS connections should be enabled.
|
||||
pub enabled: bool,
|
||||
|
||||
/// Path to certificate `.pem` file.
|
||||
pub certificate: PathBuf,
|
||||
|
||||
/// Path to private key `.pem` file.
|
||||
pub private_key: PathBuf,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user