From b054733854ebee3db5811c9b26d0f0e08ab62c18 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 31 Jul 2022 21:12:19 +0200 Subject: [PATCH] document settings crate (#271) --- actix-settings/examples/actix.rs | 2 +- actix-settings/src/defaults.toml | 6 +- actix-settings/src/error.rs | 20 ++- actix-settings/src/lib.rs | 137 +++++++++++++++--- actix-settings/src/parse.rs | 2 + actix-settings/src/settings/address.rs | 4 + actix-settings/src/settings/backlog.rs | 11 ++ actix-settings/src/settings/keep_alive.rs | 13 ++ .../src/settings/max_connection_rate.rs | 8 + .../src/settings/max_connections.rs | 8 + actix-settings/src/settings/mod.rs | 27 +++- actix-settings/src/settings/mode.rs | 4 + actix-settings/src/settings/num_workers.rs | 7 + actix-settings/src/settings/timeout.rs | 24 ++- actix-settings/src/settings/tls.rs | 7 + 15 files changed, 250 insertions(+), 30 deletions(-) diff --git a/actix-settings/examples/actix.rs b/actix-settings/examples/actix.rs index 46388082d..44988b198 100644 --- a/actix-settings/examples/actix.rs +++ b/actix-settings/examples/actix.rs @@ -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; diff --git a/actix-settings/src/defaults.toml b/actix-settings/src/defaults.toml index a73be1edb..7a393d92e 100644 --- a/actix-settings/src/defaults.toml +++ b/actix-settings/src/defaults.toml @@ -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" diff --git a/actix-settings/src/error.rs b/actix-settings/src/error.rs index 2e097a0a2..009962b18 100644 --- a/actix-settings/src/error.rs +++ b/actix-settings/src/error.rs @@ -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`. pub type AtResult = std::result::Result; -#[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), } diff --git a/actix-settings/src/lib.rs b/actix-settings/src/lib.rs index 7185c9273..857200c84 100644 --- a/actix-settings/src/lib.rs +++ b/actix-settings/src/lib.rs @@ -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 { + /// 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; +/// 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 BasicSettings 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

(filepath: P) -> AtResult where P: AsRef, @@ -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::(&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::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 { 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

(filepath: P) -> AtResult<()> where P: AsRef, { 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(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(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 { diff --git a/actix-settings/src/parse.rs b/actix-settings/src/parse.rs index b13493bfa..9a6c7f59c 100644 --- a/actix-settings/src/parse.rs +++ b/actix-settings/src/parse.rs @@ -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; } diff --git a/actix-settings/src/settings/address.rs b/actix-settings/src/settings/address.rs index 42224143f..c07fff1b6 100644 --- a/actix-settings/src/settings/address.rs +++ b/actix-settings/src/settings/address.rs @@ -37,9 +37,13 @@ static ADDR_LIST_REGEX: Lazy = 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, } diff --git a/actix-settings/src/settings/backlog.rs b/actix-settings/src/settings/backlog.rs index c4f10f0ac..500c20f13 100644 --- a/actix-settings/src/settings/backlog.rs +++ b/actix-settings/src/settings/backlog.rs @@ -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), } diff --git a/actix-settings/src/settings/keep_alive.rs b/actix-settings/src/settings/keep_alive.rs index 0bb05016c..96e1ed476 100644 --- a/actix-settings/src/settings/keep_alive.rs +++ b/actix-settings/src/settings/keep_alive.rs @@ -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), } diff --git a/actix-settings/src/settings/max_connection_rate.rs b/actix-settings/src/settings/max_connection_rate.rs index 21c9e4bb7..d6706783a 100644 --- a/actix-settings/src/settings/max_connection_rate.rs +++ b/actix-settings/src/settings/max_connection_rate.rs @@ -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), } diff --git a/actix-settings/src/settings/max_connections.rs b/actix-settings/src/settings/max_connections.rs index b57dc2a46..f0ffdcdf3 100644 --- a/actix-settings/src/settings/max_connections.rs +++ b/actix-settings/src/settings/max_connections.rs @@ -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), } diff --git a/actix-settings/src/settings/mod.rs b/actix-settings/src/settings/mod.rs index 77f83d1ac..e3ff645ea 100644 --- a/actix-settings/src/settings/mod.rs +++ b/actix-settings/src/settings/mod.rs @@ -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

, - 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, } diff --git a/actix-settings/src/settings/mode.rs b/actix-settings/src/settings/mode.rs index 5bec38a3c..3833da38a 100644 --- a/actix-settings/src/settings/mode.rs +++ b/actix-settings/src/settings/mode.rs @@ -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, } diff --git a/actix-settings/src/settings/num_workers.rs b/actix-settings/src/settings/num_workers.rs index a97bb6318..ff8d00b8b 100644 --- a/actix-settings/src/settings/num_workers.rs +++ b/actix-settings/src/settings/num_workers.rs @@ -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), } diff --git a/actix-settings/src/settings/timeout.rs b/actix-settings/src/settings/timeout.rs index 125fcfd9f..1bed71396 100644 --- a/actix-settings/src/settings/timeout.rs +++ b/actix-settings/src/settings/timeout.rs @@ -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), } } diff --git a/actix-settings/src/settings/tls.rs b/actix-settings/src/settings/tls.rs index 043913b70..fd0a63eb3 100644 --- a/actix-settings/src/settings/tls.rs +++ b/actix-settings/src/settings/tls.rs @@ -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, }