mirror of https://github.com/rwf2/Rocket.git
Revamp configuration.
This commit completely overhauls Rocket's configuration systems, basing it on the new Figment library. It includes many breaking changes pertaining to configuration. They are: * "Environments" are replaced by "profiles". * 'ROCKET_PROFILE' takes the place of 'ROCKET_ENV'. * Profile names are now arbitrary, but 'debug' and 'release' are given special treatment as default profiles for the debug and release compilation profiles. * A 'default' profile now sits along-side the meta 'global' profile. * The concept of "extras" is no longer present; users can extract any values they want from the configured 'Figment'. * The 'Poolable' trait takes an '&Config'. * The 'secrets' feature is disabled by default. * It is a hard error if 'secrets' is enabled under the 'release' profile and no 'secret_key' is configured. * 'ConfigBuilder' no longer exists: all fields of 'Config' are public with public constructors for each type. * 'keep_alive' is disabled with '0', not 'false' or 'off'. * Inlined error variants into the 'Error' structure. * 'LoggingLevel' is now 'LogLevel'. * Limits can now be specified in SI units: "1 MiB". The summary of other changes are: * The default config file can be configured with 'ROCKET_CONFIG'. * HTTP/1 and HTTP/2 keep-alive configuration is restored. * 'ctrlc' is now a recognized config option. * 'serde' is now a core dependency. * TLS misconfiguration errors are improved. * Several example use '_' as the return type of '#[launch]' fns. * 'AdHoc::config()' was added for simple config extraction. * Added more documentation for using 'Limits'. * Launch information is no longer treated specially. * The configuration guide was rewritten. Resolves #852. Resolves #209. Closes #1404. Closes #652.
This commit is contained in:
parent
8da034ab83
commit
1fb061496d
|
@ -71,13 +71,13 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStrea
|
|||
let databases = quote_spanned!(span => ::rocket_contrib::databases);
|
||||
let request = quote!(::rocket::request);
|
||||
|
||||
let generated_types = quote_spanned! { span =>
|
||||
let request_guard_type = quote_spanned! { span =>
|
||||
/// The request guard type.
|
||||
#vis struct #guard_type(#databases::Connection<Self, #conn_type>);
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
#generated_types
|
||||
#request_guard_type
|
||||
|
||||
impl #guard_type {
|
||||
/// Returns a fairing that initializes the associated database
|
||||
|
@ -110,8 +110,8 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStrea
|
|||
impl<'a, 'r> #request::FromRequest<'a, 'r> for #guard_type {
|
||||
type Error = ();
|
||||
|
||||
async fn from_request(request: &'a #request::Request<'r>) -> #request::Outcome<Self, ()> {
|
||||
<#databases::Connection<Self, #conn_type>>::from_request(request).await.map(Self)
|
||||
async fn from_request(req: &'a #request::Request<'r>) -> #request::Outcome<Self, ()> {
|
||||
<#databases::Connection<Self, #conn_type>>::from_request(req).await.map(Self)
|
||||
}
|
||||
}
|
||||
}.into())
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
error: unexpected end of input, expected literal
|
||||
error: unexpected end of input, expected string literal
|
||||
--> $DIR/database-syntax.rs:6:1
|
||||
|
|
||||
6 | #[database]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
error: unexpected end of input, expected literal
|
||||
error: unexpected end of input, expected string literal
|
||||
--> $DIR/database-syntax.rs:6:1
|
||||
|
|
||||
6 | #[database]
|
||||
|
|
|
@ -14,7 +14,10 @@ edition = "2018"
|
|||
[features]
|
||||
# Internal use only.
|
||||
templates = ["serde", "serde_json", "glob", "notify"]
|
||||
databases = ["r2d2", "tokio/blocking", "tokio/rt-threaded", "rocket_contrib_codegen/database_attribute"]
|
||||
databases = [
|
||||
"serde", "r2d2", "tokio/blocking", "tokio/rt-threaded",
|
||||
"rocket_contrib_codegen/database_attribute"
|
||||
]
|
||||
|
||||
# User-facing features.
|
||||
default = ["json", "serve"]
|
||||
|
|
|
@ -40,7 +40,9 @@
|
|||
//! See [Provided](#provided) for a list of supported database and their
|
||||
//! associated feature name.
|
||||
//!
|
||||
//! In `Rocket.toml` or the equivalent via environment variables:
|
||||
//! In whichever configuration source you choose, configure a `databases`
|
||||
//! dictionary with an internal dictionary for each database, here `sqlite_logs`
|
||||
//! in a TOML source:
|
||||
//!
|
||||
//! ```toml
|
||||
//! [global.databases]
|
||||
|
@ -97,8 +99,9 @@
|
|||
//!
|
||||
//! ## Configuration
|
||||
//!
|
||||
//! Databases can be configured via various mechanisms: `Rocket.toml`,
|
||||
//! procedurally via `rocket::custom()`, or via environment variables.
|
||||
//! Databases can be configured as any other values. Using the default
|
||||
//! configuration provider, either via `Rocket.toml` or environment variables.
|
||||
//! You can also use a custom provider.
|
||||
//!
|
||||
//! ### `Rocket.toml`
|
||||
//!
|
||||
|
@ -138,31 +141,23 @@
|
|||
//! The example below does just this:
|
||||
//!
|
||||
//! ```rust
|
||||
//! #[macro_use] extern crate rocket;
|
||||
//! # #[cfg(feature = "diesel_sqlite_pool")] {
|
||||
//! use rocket::figment::{value::{Map, Value}, util::map};
|
||||
//!
|
||||
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
||||
//! # mod test {
|
||||
//! use std::collections::HashMap;
|
||||
//! use rocket::config::{Config, Environment, Value};
|
||||
//! #[rocket::launch]
|
||||
//! fn rocket() -> _ {
|
||||
//! let db: Map<_, Value> = map! {
|
||||
//! "url" => "db.sqlite".into(),
|
||||
//! "pool_size" => 10.into()
|
||||
//! };
|
||||
//!
|
||||
//! #[launch]
|
||||
//! fn rocket() -> rocket::Rocket {
|
||||
//! let mut database_config = HashMap::new();
|
||||
//! let mut databases = HashMap::new();
|
||||
//! let figment = rocket::Config::figment()
|
||||
//! .merge(("databases", map!["my_db" => db]));
|
||||
//!
|
||||
//! // This is the same as the following TOML:
|
||||
//! // my_db = { url = "database.sqlite" }
|
||||
//! database_config.insert("url", Value::from("database.sqlite"));
|
||||
//! databases.insert("my_db", Value::from(database_config));
|
||||
//!
|
||||
//! let config = Config::build(Environment::Development)
|
||||
//! .extra("databases", databases)
|
||||
//! .finalize()
|
||||
//! .unwrap();
|
||||
//!
|
||||
//! rocket::custom(config)
|
||||
//! rocket::custom(figment)
|
||||
//! }
|
||||
//! # } fn main() {}
|
||||
//! # rocket();
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Environment Variables
|
||||
|
@ -246,33 +241,22 @@
|
|||
//! # #[macro_use] extern crate rocket;
|
||||
//! # #[macro_use] extern crate rocket_contrib;
|
||||
//! #
|
||||
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
||||
//! # mod test {
|
||||
//! # use std::collections::HashMap;
|
||||
//! # use rocket::config::{Config, Environment, Value};
|
||||
//! #
|
||||
//! # #[cfg(feature = "diesel_sqlite_pool")] {
|
||||
//! # use rocket::figment::{value::{Map, Value}, util::map};
|
||||
//! use rocket_contrib::databases::diesel;
|
||||
//!
|
||||
//! #[database("my_db")]
|
||||
//! struct MyDatabase(diesel::SqliteConnection);
|
||||
//!
|
||||
//! #[launch]
|
||||
//! fn rocket() -> rocket::Rocket {
|
||||
//! # let mut db_config = HashMap::new();
|
||||
//! # let mut databases = HashMap::new();
|
||||
//! #
|
||||
//! # db_config.insert("url", Value::from("database.sqlite"));
|
||||
//! # db_config.insert("pool_size", Value::from(10));
|
||||
//! # databases.insert("my_db", Value::from(db_config));
|
||||
//! #
|
||||
//! # let config = Config::build(Environment::Development)
|
||||
//! # .extra("databases", databases)
|
||||
//! # .finalize()
|
||||
//! # .unwrap();
|
||||
//! #
|
||||
//! rocket::custom(config).attach(MyDatabase::fairing())
|
||||
//! fn rocket() -> _ {
|
||||
//! # let db: Map<_, Value> = map![
|
||||
//! # "url" => "db.sqlite".into(), "pool_size" => 10.into()
|
||||
//! # ];
|
||||
//! # let figment = rocket::Config::figment().merge(("databases", map!["my_db" => db]));
|
||||
//! rocket::custom(figment).attach(MyDatabase::fairing())
|
||||
//! }
|
||||
//! # } fn main() {}
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Handlers
|
||||
|
@ -376,22 +360,23 @@
|
|||
|
||||
pub extern crate r2d2;
|
||||
|
||||
#[cfg(any(feature = "diesel_sqlite_pool",
|
||||
feature = "diesel_postgres_pool",
|
||||
feature = "diesel_mysql_pool"))]
|
||||
#[cfg(any(
|
||||
feature = "diesel_sqlite_pool",
|
||||
feature = "diesel_postgres_pool",
|
||||
feature = "diesel_mysql_pool"
|
||||
))]
|
||||
pub extern crate diesel;
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
|
||||
use rocket::config::{self, Value};
|
||||
use rocket::fairing::{AdHoc, Fairing};
|
||||
use rocket::request::{Request, Outcome, FromRequest};
|
||||
use rocket::outcome::IntoOutcome;
|
||||
use rocket::http::Status;
|
||||
|
||||
use rocket::tokio::sync::{OwnedSemaphorePermit, Semaphore, Mutex};
|
||||
use rocket::tokio::time::timeout;
|
||||
|
||||
use self::r2d2::ManageConnection;
|
||||
|
||||
|
@ -425,7 +410,7 @@ use self::r2d2::ManageConnection;
|
|||
/// [`database_config`]`("my_database", &config)`:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// DatabaseConfig {
|
||||
/// Config {
|
||||
/// url: "dummy_db.sqlite",
|
||||
/// pool_size: 10,
|
||||
/// extras: {
|
||||
|
@ -434,16 +419,51 @@ use self::r2d2::ManageConnection;
|
|||
/// },
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DatabaseConfig<'a> {
|
||||
/// The connection URL specified in the Rocket configuration.
|
||||
pub url: &'a str,
|
||||
/// The size of the pool to be initialized. Defaults to the number of
|
||||
/// Rocket workers.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct Config {
|
||||
/// Connection URL specified in the Rocket configuration.
|
||||
pub url: String,
|
||||
/// Initial pool size. Defaults to the number of Rocket workers.
|
||||
pub pool_size: u32,
|
||||
/// Any extra options that are included in the configuration, **excluding**
|
||||
/// the url and pool_size.
|
||||
pub extras: rocket::config::Map<String, Value>,
|
||||
/// How long to wait, in seconds, for a new connection before timing out.
|
||||
/// Defaults to `5`.
|
||||
// FIXME: Use `time`.
|
||||
pub timeout: u8,
|
||||
}
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
use rocket::figment::{Figment, Error, providers::Serialized};
|
||||
|
||||
impl Config {
|
||||
/// Retrieves the database configuration for the database named `name`.
|
||||
///
|
||||
/// This function is primarily used by the code generated by the `#[database]`
|
||||
/// attribute.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Consider the following configuration:
|
||||
///
|
||||
/// ```toml
|
||||
/// [global.databases]
|
||||
/// my_db = { url = "db/db.sqlite", pool_size = 25 }
|
||||
/// my_other_db = { url = "mysql://root:root@localhost/database" }
|
||||
/// ```
|
||||
///
|
||||
/// The following example uses `database_config` to retrieve the configurations
|
||||
/// for the `my_db` and `my_other_db` databases:
|
||||
///
|
||||
/// ```rust
|
||||
///
|
||||
/// ```
|
||||
pub fn from(cargo: &rocket::Cargo, db: &str) -> Result<Config, Error> {
|
||||
let db_key = format!("databases.{}", db);
|
||||
let key = |name: &str| format!("{}.{}", db_key, name);
|
||||
Figment::from(cargo.figment())
|
||||
.merge(Serialized::default(&key("pool_size"), cargo.config().workers))
|
||||
.merge(Serialized::default(&key("timeout"), 5))
|
||||
.extract_inner::<Self>(&db_key)
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around `r2d2::Error`s or a custom database error type.
|
||||
|
@ -458,143 +478,6 @@ pub enum DbError<T> {
|
|||
PoolError(r2d2::Error),
|
||||
}
|
||||
|
||||
/// Error returned on invalid database configurations.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ConfigError {
|
||||
/// The `databases` configuration key is missing or is empty.
|
||||
MissingTable,
|
||||
/// The requested database configuration key is missing from the active
|
||||
/// configuration.
|
||||
MissingKey,
|
||||
/// The configuration associated with the key isn't a
|
||||
/// [`Table`](rocket::config::Table).
|
||||
MalformedConfiguration,
|
||||
/// The required `url` key is missing.
|
||||
MissingUrl,
|
||||
/// The value for `url` isn't a string.
|
||||
MalformedUrl,
|
||||
/// The `pool_size` exceeds `u32::max_value()` or is negative.
|
||||
InvalidPoolSize(i64),
|
||||
}
|
||||
|
||||
/// Retrieves the database configuration for the database named `name`.
|
||||
///
|
||||
/// This function is primarily used by the code generated by the `#[database]`
|
||||
/// attribute.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Consider the following configuration:
|
||||
///
|
||||
/// ```toml
|
||||
/// [global.databases]
|
||||
/// my_db = { url = "db/db.sqlite", pool_size = 25 }
|
||||
/// my_other_db = { url = "mysql://root:root@localhost/database" }
|
||||
/// ```
|
||||
///
|
||||
/// The following example uses `database_config` to retrieve the configurations
|
||||
/// for the `my_db` and `my_other_db` databases:
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// # extern crate rocket_contrib;
|
||||
/// #
|
||||
/// # use std::{collections::BTreeMap, mem::drop};
|
||||
/// # use rocket::{fairing::AdHoc, config::{Config, Environment, Value}};
|
||||
/// use rocket_contrib::databases::{database_config, ConfigError};
|
||||
///
|
||||
/// # let mut databases = BTreeMap::new();
|
||||
/// #
|
||||
/// # let mut my_db = BTreeMap::new();
|
||||
/// # my_db.insert("url".to_string(), Value::from("db/db.sqlite"));
|
||||
/// # my_db.insert("pool_size".to_string(), Value::from(25));
|
||||
/// #
|
||||
/// # let mut my_other_db = BTreeMap::new();
|
||||
/// # my_other_db.insert("url".to_string(),
|
||||
/// # Value::from("mysql://root:root@localhost/database"));
|
||||
/// #
|
||||
/// # databases.insert("my_db".to_string(), Value::from(my_db));
|
||||
/// # databases.insert("my_other_db".to_string(), Value::from(my_other_db));
|
||||
/// #
|
||||
/// # let config = Config::build(Environment::Development)
|
||||
/// # .extra("databases", databases)
|
||||
/// # .expect("custom config okay");
|
||||
/// #
|
||||
/// # rocket::custom(config).attach(AdHoc::on_attach("Testing", |mut rocket| async {
|
||||
/// # {
|
||||
/// let rocket_config = rocket.config().await;
|
||||
/// let config = database_config("my_db", rocket_config).unwrap();
|
||||
/// assert_eq!(config.url, "db/db.sqlite");
|
||||
/// assert_eq!(config.pool_size, 25);
|
||||
///
|
||||
/// let other_config = database_config("my_other_db", rocket_config).unwrap();
|
||||
/// assert_eq!(other_config.url, "mysql://root:root@localhost/database");
|
||||
///
|
||||
/// let error = database_config("invalid_db", rocket_config).unwrap_err();
|
||||
/// assert_eq!(error, ConfigError::MissingKey);
|
||||
/// # }
|
||||
/// #
|
||||
/// # Ok(rocket)
|
||||
/// # }));
|
||||
/// ```
|
||||
pub fn database_config<'a>(
|
||||
name: &str,
|
||||
from: &'a config::Config
|
||||
) -> Result<DatabaseConfig<'a>, ConfigError> {
|
||||
// Find the first `databases` config that's a table with a key of 'name'
|
||||
// equal to `name`.
|
||||
let connection_config = from.get_table("databases")
|
||||
.map_err(|_| ConfigError::MissingTable)?
|
||||
.get(name)
|
||||
.ok_or(ConfigError::MissingKey)?
|
||||
.as_table()
|
||||
.ok_or(ConfigError::MalformedConfiguration)?;
|
||||
|
||||
let maybe_url = connection_config.get("url")
|
||||
.ok_or(ConfigError::MissingUrl)?;
|
||||
|
||||
let url = maybe_url.as_str().ok_or(ConfigError::MalformedUrl)?;
|
||||
|
||||
let pool_size = connection_config.get("pool_size")
|
||||
.and_then(Value::as_integer)
|
||||
.unwrap_or(from.workers as i64);
|
||||
|
||||
if pool_size < 1 || pool_size > u32::max_value() as i64 {
|
||||
return Err(ConfigError::InvalidPoolSize(pool_size));
|
||||
}
|
||||
|
||||
let mut extras = connection_config.clone();
|
||||
extras.remove("url");
|
||||
extras.remove("pool_size");
|
||||
|
||||
Ok(DatabaseConfig { url, pool_size: pool_size as u32, extras: extras })
|
||||
}
|
||||
|
||||
impl<'a> Display for ConfigError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ConfigError::MissingTable => {
|
||||
write!(f, "A table named `databases` was not found for this configuration")
|
||||
},
|
||||
ConfigError::MissingKey => {
|
||||
write!(f, "An entry in the `databases` table was not found for this key")
|
||||
},
|
||||
ConfigError::MalformedConfiguration => {
|
||||
write!(f, "The configuration for this database is malformed")
|
||||
}
|
||||
ConfigError::MissingUrl => {
|
||||
write!(f, "The connection URL is missing for this database")
|
||||
},
|
||||
ConfigError::MalformedUrl => {
|
||||
write!(f, "The specified connection URL is malformed")
|
||||
},
|
||||
ConfigError::InvalidPoolSize(invalid_size) => {
|
||||
write!(f, "'{}' is not a valid value for `pool_size`", invalid_size)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait implemented by `r2d2`-based database adapters.
|
||||
///
|
||||
/// # Provided Implementations
|
||||
|
@ -629,7 +512,7 @@ impl<'a> Display for ConfigError {
|
|||
/// `Poolable` for `foo::Connection`:
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket_contrib::databases::{r2d2, DbError, DatabaseConfig, Poolable};
|
||||
/// use rocket_contrib::databases::{r2d2, DbError, Config, Poolable};
|
||||
/// # mod foo {
|
||||
/// # use std::fmt;
|
||||
/// # use rocket_contrib::databases::r2d2;
|
||||
|
@ -661,8 +544,8 @@ impl<'a> Display for ConfigError {
|
|||
/// type Manager = foo::ConnectionManager;
|
||||
/// type Error = DbError<foo::Error>;
|
||||
///
|
||||
/// fn pool(config: DatabaseConfig) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
|
||||
/// let manager = foo::ConnectionManager::new(config.url)
|
||||
/// fn pool(config: &Config) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
|
||||
/// let manager = foo::ConnectionManager::new(&config.url)
|
||||
/// .map_err(DbError::Custom)?;
|
||||
///
|
||||
/// r2d2::Pool::builder()
|
||||
|
@ -692,7 +575,7 @@ pub trait Poolable: Send + Sized + 'static {
|
|||
|
||||
/// Creates an `r2d2` connection pool for `Manager::Connection`, returning
|
||||
/// the pool on success.
|
||||
fn pool(config: DatabaseConfig<'_>) -> Result<r2d2::Pool<Self::Manager>, Self::Error>;
|
||||
fn pool(config: &Config) -> Result<r2d2::Pool<Self::Manager>, Self::Error>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "diesel_sqlite_pool")]
|
||||
|
@ -700,8 +583,8 @@ impl Poolable for diesel::SqliteConnection {
|
|||
type Manager = diesel::r2d2::ConnectionManager<diesel::SqliteConnection>;
|
||||
type Error = r2d2::Error;
|
||||
|
||||
fn pool(config: DatabaseConfig<'_>) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
|
||||
let manager = diesel::r2d2::ConnectionManager::new(config.url);
|
||||
fn pool(config: &Config) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
|
||||
let manager = diesel::r2d2::ConnectionManager::new(&config.url);
|
||||
r2d2::Pool::builder().max_size(config.pool_size).build(manager)
|
||||
}
|
||||
}
|
||||
|
@ -711,8 +594,8 @@ impl Poolable for diesel::PgConnection {
|
|||
type Manager = diesel::r2d2::ConnectionManager<diesel::PgConnection>;
|
||||
type Error = r2d2::Error;
|
||||
|
||||
fn pool(config: DatabaseConfig<'_>) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
|
||||
let manager = diesel::r2d2::ConnectionManager::new(config.url);
|
||||
fn pool(config: &Config) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
|
||||
let manager = diesel::r2d2::ConnectionManager::new(&config.url);
|
||||
r2d2::Pool::builder().max_size(config.pool_size).build(manager)
|
||||
}
|
||||
}
|
||||
|
@ -722,8 +605,8 @@ impl Poolable for diesel::MysqlConnection {
|
|||
type Manager = diesel::r2d2::ConnectionManager<diesel::MysqlConnection>;
|
||||
type Error = r2d2::Error;
|
||||
|
||||
fn pool(config: DatabaseConfig<'_>) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
|
||||
let manager = diesel::r2d2::ConnectionManager::new(config.url);
|
||||
fn pool(config: &Config) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
|
||||
let manager = diesel::r2d2::ConnectionManager::new(&config.url);
|
||||
r2d2::Pool::builder().max_size(config.pool_size).build(manager)
|
||||
}
|
||||
}
|
||||
|
@ -734,7 +617,7 @@ impl Poolable for postgres::Client {
|
|||
type Manager = r2d2_postgres::PostgresConnectionManager<postgres::tls::NoTls>;
|
||||
type Error = DbError<postgres::Error>;
|
||||
|
||||
fn pool(config: DatabaseConfig<'_>) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
|
||||
fn pool(config: &Config) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
|
||||
let manager = r2d2_postgres::PostgresConnectionManager::new(
|
||||
config.url.parse().map_err(DbError::Custom)?,
|
||||
postgres::tls::NoTls,
|
||||
|
@ -750,8 +633,8 @@ impl Poolable for mysql::Conn {
|
|||
type Manager = r2d2_mysql::MysqlConnectionManager;
|
||||
type Error = r2d2::Error;
|
||||
|
||||
fn pool(config: DatabaseConfig<'_>) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
|
||||
let opts = mysql::OptsBuilder::from_opts(config.url);
|
||||
fn pool(config: &Config) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
|
||||
let opts = mysql::OptsBuilder::from_opts(&config.url);
|
||||
let manager = r2d2_mysql::MysqlConnectionManager::new(opts);
|
||||
r2d2::Pool::builder().max_size(config.pool_size).build(manager)
|
||||
}
|
||||
|
@ -762,9 +645,8 @@ impl Poolable for rusqlite::Connection {
|
|||
type Manager = r2d2_sqlite::SqliteConnectionManager;
|
||||
type Error = r2d2::Error;
|
||||
|
||||
fn pool(config: DatabaseConfig<'_>) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
|
||||
let manager = r2d2_sqlite::SqliteConnectionManager::file(config.url);
|
||||
|
||||
fn pool(config: &Config) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
|
||||
let manager = r2d2_sqlite::SqliteConnectionManager::file(&*config.url);
|
||||
r2d2::Pool::builder().max_size(config.pool_size).build(manager)
|
||||
}
|
||||
}
|
||||
|
@ -774,8 +656,8 @@ impl Poolable for memcache::Client {
|
|||
type Manager = r2d2_memcache::MemcacheConnectionManager;
|
||||
type Error = DbError<memcache::MemcacheError>;
|
||||
|
||||
fn pool(config: DatabaseConfig<'_>) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
|
||||
let manager = r2d2_memcache::MemcacheConnectionManager::new(config.url);
|
||||
fn pool(config: &Config) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
|
||||
let manager = r2d2_memcache::MemcacheConnectionManager::new(&*config.url);
|
||||
r2d2::Pool::builder().max_size(config.pool_size).build(manager).map_err(DbError::PoolError)
|
||||
}
|
||||
}
|
||||
|
@ -786,11 +668,23 @@ impl Poolable for memcache::Client {
|
|||
/// types are properly checked.
|
||||
#[doc(hidden)]
|
||||
pub struct ConnectionPool<K, C: Poolable> {
|
||||
config: Config,
|
||||
pool: r2d2::Pool<C::Manager>,
|
||||
semaphore: Arc<Semaphore>,
|
||||
_marker: PhantomData<fn() -> K>,
|
||||
}
|
||||
|
||||
impl<K, C: Poolable> Clone for ConnectionPool<K, C> {
|
||||
fn clone(&self) -> Self {
|
||||
ConnectionPool {
|
||||
config: self.config.clone(),
|
||||
pool: self.pool.clone(),
|
||||
semaphore: self.semaphore.clone(),
|
||||
_marker: PhantomData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unstable internal details of generated code for the #[database] attribute.
|
||||
///
|
||||
/// This type is implemented here instead of in generated code to ensure all
|
||||
|
@ -816,30 +710,31 @@ async fn run_blocking<F, R>(job: F) -> R
|
|||
}
|
||||
|
||||
impl<K: 'static, C: Poolable> ConnectionPool<K, C> {
|
||||
pub fn fairing(fairing_name: &'static str, config_name: &'static str) -> impl Fairing {
|
||||
pub fn fairing(fairing_name: &'static str, db_name: &'static str) -> impl Fairing {
|
||||
AdHoc::on_attach(fairing_name, move |mut rocket| async move {
|
||||
let config = database_config(config_name, rocket.config().await);
|
||||
let pool = config.map(|c| (c.pool_size, C::pool(c)));
|
||||
let config = match Config::from(rocket.inspect().await, db_name) {
|
||||
Ok(config) => config,
|
||||
Err(config_error) => {
|
||||
rocket::error!("database configuration error for '{}'", db_name);
|
||||
error_!("{}", config_error);
|
||||
return Err(rocket);
|
||||
}
|
||||
};
|
||||
|
||||
match pool {
|
||||
Ok((size, Ok(pool))) => {
|
||||
match C::pool(&config) {
|
||||
Ok(pool) => {
|
||||
let pool_size = config.pool_size;
|
||||
let managed = ConnectionPool::<K, C> {
|
||||
pool,
|
||||
semaphore: Arc::new(Semaphore::new(size as usize)),
|
||||
config, pool,
|
||||
semaphore: Arc::new(Semaphore::new(pool_size as usize)),
|
||||
_marker: PhantomData,
|
||||
};
|
||||
|
||||
Ok(rocket.manage(managed))
|
||||
},
|
||||
Err(config_error) => {
|
||||
rocket::logger::error(
|
||||
&format!("Database configuration failure: '{}'", config_name));
|
||||
rocket::logger::error_(&config_error.to_string());
|
||||
Err(rocket)
|
||||
},
|
||||
Ok((_, Err(pool_error))) => {
|
||||
rocket::logger::error(
|
||||
&format!("Failed to initialize pool for '{}'", config_name));
|
||||
rocket::logger::error_(&format!("{:?}", pool_error));
|
||||
Err(pool_error) => {
|
||||
rocket::error!("failed to initialize pool for '{}'", db_name);
|
||||
error_!("{:?}", pool_error);
|
||||
Err(rocket)
|
||||
},
|
||||
}
|
||||
|
@ -847,28 +742,24 @@ impl<K: 'static, C: Poolable> ConnectionPool<K, C> {
|
|||
}
|
||||
|
||||
async fn get(&self) -> Result<Connection<K, C>, ()> {
|
||||
// TODO: Make timeout configurable.
|
||||
let permit = match tokio::time::timeout(
|
||||
std::time::Duration::from_secs(5),
|
||||
self.semaphore.clone().acquire_owned()
|
||||
).await {
|
||||
let duration = std::time::Duration::from_secs(self.config.timeout as u64);
|
||||
let permit = match timeout(duration, self.semaphore.clone().acquire_owned()).await {
|
||||
Ok(p) => p,
|
||||
Err(_) => {
|
||||
error_!("Failed to get a database connection within the timeout.");
|
||||
error_!("database connection retrieval timed out");
|
||||
return Err(());
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Make timeout configurable.
|
||||
let pool = self.pool.clone();
|
||||
match run_blocking(move || pool.get_timeout(std::time::Duration::from_secs(5))).await {
|
||||
match run_blocking(move || pool.get_timeout(duration)).await {
|
||||
Ok(c) => Ok(Connection {
|
||||
connection: Arc::new(Mutex::new(Some(c))),
|
||||
permit: Some(permit),
|
||||
_marker: PhantomData,
|
||||
}),
|
||||
Err(e) => {
|
||||
error_!("Failed to get a database connection: {}", e);
|
||||
error_!("failed to get a database connection: {}", e);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
@ -878,12 +769,14 @@ impl<K: 'static, C: Poolable> ConnectionPool<K, C> {
|
|||
pub async fn get_one(cargo: &rocket::Cargo) -> Option<Connection<K, C>> {
|
||||
match cargo.state::<Self>() {
|
||||
Some(pool) => pool.get().await.ok(),
|
||||
None => {
|
||||
error_!("Database fairing was not attached for {}", std::any::type_name::<K>());
|
||||
None
|
||||
}
|
||||
None => None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn get_pool(cargo: &rocket::Cargo) -> Option<Self> {
|
||||
cargo.state::<Self>().map(|pool| pool.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: 'static, C: Poolable> Connection<K, C> {
|
||||
|
@ -911,7 +804,8 @@ impl<K, C: Poolable> Drop for Connection<K, C> {
|
|||
if let Some(conn) = connection.take() {
|
||||
drop(conn);
|
||||
}
|
||||
// NB: Explicitly dropping the permit here so that it's only
|
||||
|
||||
// Explicitly dropping the permit here so that it's only
|
||||
// released after the connection is.
|
||||
drop(permit);
|
||||
})
|
||||
|
@ -934,201 +828,3 @@ impl<'a, 'r, K: 'static, C: Poolable> FromRequest<'a, 'r> for Connection<K, C> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeMap;
|
||||
use rocket::{Config, config::{Environment, Value}};
|
||||
use super::{ConfigError::*, database_config};
|
||||
|
||||
#[test]
|
||||
fn no_database_entry_in_config_returns_error() {
|
||||
let config = Config::build(Environment::Development)
|
||||
.finalize()
|
||||
.unwrap();
|
||||
let database_config_result = database_config("dummy_db", &config);
|
||||
|
||||
assert_eq!(Err(MissingTable), database_config_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_matching_connection_returns_error() {
|
||||
// Laboriously setup the config extras
|
||||
let mut database_extra = BTreeMap::new();
|
||||
let mut connection_config = BTreeMap::new();
|
||||
connection_config.insert("url".to_string(), Value::from("dummy_db.sqlite"));
|
||||
connection_config.insert("pool_size".to_string(), Value::from(10));
|
||||
database_extra.insert("dummy_db".to_string(), Value::from(connection_config));
|
||||
|
||||
let config = Config::build(Environment::Development)
|
||||
.extra("databases", database_extra)
|
||||
.finalize()
|
||||
.unwrap();
|
||||
|
||||
let database_config_result = database_config("real_db", &config);
|
||||
|
||||
assert_eq!(Err(MissingKey), database_config_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn incorrectly_structured_config_returns_error() {
|
||||
let mut database_extra = BTreeMap::new();
|
||||
let connection_config = vec!["url", "dummy_db.slqite"];
|
||||
database_extra.insert("dummy_db".to_string(), Value::from(connection_config));
|
||||
|
||||
let config = Config::build(Environment::Development)
|
||||
.extra("databases", database_extra)
|
||||
.finalize()
|
||||
.unwrap();
|
||||
|
||||
let database_config_result = database_config("dummy_db", &config);
|
||||
|
||||
assert_eq!(Err(MalformedConfiguration), database_config_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_connection_string_returns_error() {
|
||||
let mut database_extra = BTreeMap::new();
|
||||
let connection_config: BTreeMap<String, Value> = BTreeMap::new();
|
||||
database_extra.insert("dummy_db", connection_config);
|
||||
|
||||
let config = Config::build(Environment::Development)
|
||||
.extra("databases", database_extra)
|
||||
.finalize()
|
||||
.unwrap();
|
||||
|
||||
let database_config_result = database_config("dummy_db", &config);
|
||||
|
||||
assert_eq!(Err(MissingUrl), database_config_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_connection_string_returns_error() {
|
||||
let mut database_extra = BTreeMap::new();
|
||||
let mut connection_config = BTreeMap::new();
|
||||
connection_config.insert("url".to_string(), Value::from(42));
|
||||
database_extra.insert("dummy_db", connection_config);
|
||||
|
||||
let config = Config::build(Environment::Development)
|
||||
.extra("databases", database_extra)
|
||||
.finalize()
|
||||
.unwrap();
|
||||
|
||||
let database_config_result = database_config("dummy_db", &config);
|
||||
|
||||
assert_eq!(Err(MalformedUrl), database_config_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn negative_pool_size_returns_error() {
|
||||
let mut database_extra = BTreeMap::new();
|
||||
let mut connection_config = BTreeMap::new();
|
||||
connection_config.insert("url".to_string(), Value::from("dummy_db.sqlite"));
|
||||
connection_config.insert("pool_size".to_string(), Value::from(-1));
|
||||
database_extra.insert("dummy_db", connection_config);
|
||||
|
||||
let config = Config::build(Environment::Development)
|
||||
.extra("databases", database_extra)
|
||||
.finalize()
|
||||
.unwrap();
|
||||
|
||||
let database_config_result = database_config("dummy_db", &config);
|
||||
|
||||
assert_eq!(Err(InvalidPoolSize(-1)), database_config_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_size_beyond_u32_max_returns_error() {
|
||||
let mut database_extra = BTreeMap::new();
|
||||
let mut connection_config = BTreeMap::new();
|
||||
let over_max = (u32::max_value()) as i64 + 1;
|
||||
connection_config.insert("url".to_string(), Value::from("dummy_db.sqlite"));
|
||||
connection_config.insert("pool_size".to_string(), Value::from(over_max));
|
||||
database_extra.insert("dummy_db", connection_config);
|
||||
|
||||
let config = Config::build(Environment::Development)
|
||||
.extra("databases", database_extra)
|
||||
.finalize()
|
||||
.unwrap();
|
||||
|
||||
let database_config_result = database_config("dummy_db", &config);
|
||||
|
||||
// The size of `0` is an overflow wrap-around
|
||||
assert_eq!(Err(InvalidPoolSize(over_max)), database_config_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn happy_path_database_config() {
|
||||
let url = "dummy_db.sqlite";
|
||||
let pool_size = 10;
|
||||
|
||||
let mut database_extra = BTreeMap::new();
|
||||
let mut connection_config = BTreeMap::new();
|
||||
connection_config.insert("url".to_string(), Value::from(url));
|
||||
connection_config.insert("pool_size".to_string(), Value::from(pool_size));
|
||||
database_extra.insert("dummy_db", connection_config);
|
||||
|
||||
let config = Config::build(Environment::Development)
|
||||
.extra("databases", database_extra)
|
||||
.finalize()
|
||||
.unwrap();
|
||||
|
||||
let database_config = database_config("dummy_db", &config).unwrap();
|
||||
|
||||
assert_eq!(url, database_config.url);
|
||||
assert_eq!(pool_size, database_config.pool_size);
|
||||
assert_eq!(0, database_config.extras.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extras_do_not_contain_required_keys() {
|
||||
let url = "dummy_db.sqlite";
|
||||
let pool_size = 10;
|
||||
|
||||
let mut database_extra = BTreeMap::new();
|
||||
let mut connection_config = BTreeMap::new();
|
||||
connection_config.insert("url".to_string(), Value::from(url));
|
||||
connection_config.insert("pool_size".to_string(), Value::from(pool_size));
|
||||
database_extra.insert("dummy_db", connection_config);
|
||||
|
||||
let config = Config::build(Environment::Development)
|
||||
.extra("databases", database_extra)
|
||||
.finalize()
|
||||
.unwrap();
|
||||
|
||||
let database_config = database_config("dummy_db", &config).unwrap();
|
||||
|
||||
assert_eq!(url, database_config.url);
|
||||
assert_eq!(pool_size, database_config.pool_size);
|
||||
assert_eq!(false, database_config.extras.contains_key("url"));
|
||||
assert_eq!(false, database_config.extras.contains_key("pool_size"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extra_values_are_placed_in_extras_map() {
|
||||
let url = "dummy_db.sqlite";
|
||||
let pool_size = 10;
|
||||
let tls_cert = "certs.pem";
|
||||
let tls_key = "key.pem";
|
||||
|
||||
let mut database_extra = BTreeMap::new();
|
||||
let mut connection_config = BTreeMap::new();
|
||||
connection_config.insert("url".to_string(), Value::from(url));
|
||||
connection_config.insert("pool_size".to_string(), Value::from(pool_size));
|
||||
connection_config.insert("certs".to_string(), Value::from(tls_cert));
|
||||
connection_config.insert("key".to_string(), Value::from(tls_key));
|
||||
database_extra.insert("dummy_db", connection_config);
|
||||
|
||||
let config = Config::build(Environment::Development)
|
||||
.extra("databases", database_extra)
|
||||
.finalize()
|
||||
.unwrap();
|
||||
|
||||
let database_config = database_config("dummy_db", &config).unwrap();
|
||||
|
||||
assert_eq!(url, database_config.url);
|
||||
assert_eq!(pool_size, database_config.pool_size);
|
||||
assert_eq!(true, database_config.extras.contains_key("certs"));
|
||||
assert_eq!(true, database_config.extras.contains_key("key"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -203,7 +203,7 @@ impl Fairing for SpaceHelmet {
|
|||
|
||||
fn on_launch(&self, cargo: &Cargo) {
|
||||
if cargo.config().tls_enabled()
|
||||
&& !cargo.config().environment.is_dev()
|
||||
&& cargo.figment().profile() != rocket::Config::DEBUG_PROFILE
|
||||
&& !self.is_enabled::<Hsts>()
|
||||
{
|
||||
warn_!("Space Helmet: deploying with TLS without enabling HSTS.");
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use crate::templates::{DEFAULT_TEMPLATE_DIR, Context, Engines};
|
||||
|
||||
use rocket::Rocket;
|
||||
use rocket::config::ConfigError;
|
||||
use rocket::fairing::{Fairing, Info, Kind};
|
||||
|
||||
pub(crate) use self::context::ContextManager;
|
||||
|
@ -152,19 +151,31 @@ impl Fairing for TemplateFairing {
|
|||
/// template engines. In debug mode, the `ContextManager::new` method
|
||||
/// initializes a directory watcher for auto-reloading of templates.
|
||||
async fn on_attach(&self, mut rocket: Rocket) -> Result<Rocket, Rocket> {
|
||||
let config = rocket.config().await;
|
||||
let mut template_root = config.root_relative(DEFAULT_TEMPLATE_DIR);
|
||||
match config.get_str("template_dir") {
|
||||
Ok(dir) => template_root = config.root_relative(dir),
|
||||
Err(ConfigError::Missing(_)) => { /* ignore missing */ }
|
||||
use rocket::figment::{Source, value::magic::RelativePathBuf};
|
||||
|
||||
let configured_dir = rocket.figment().await
|
||||
.extract_inner::<RelativePathBuf>("template_dir")
|
||||
.map(|path| path.relative());
|
||||
|
||||
let path = match configured_dir {
|
||||
Ok(dir) => dir,
|
||||
Err(e) if e.missing() => DEFAULT_TEMPLATE_DIR.into(),
|
||||
Err(e) => {
|
||||
e.pretty_print();
|
||||
warn_!("Using default templates directory '{:?}'", template_root);
|
||||
rocket::config::pretty_print_error(e);
|
||||
return Err(rocket);
|
||||
}
|
||||
};
|
||||
|
||||
match Context::initialize(template_root) {
|
||||
let root = Source::from(&*path);
|
||||
match Context::initialize(path) {
|
||||
Some(mut ctxt) => {
|
||||
use rocket::{logger::PaintExt, yansi::Paint};
|
||||
use crate::templates::Engines;
|
||||
|
||||
info!("{}{}", Paint::emoji("📐 "), Paint::magenta("Templating:"));
|
||||
info_!("directory: {}", Paint::white(root));
|
||||
info_!("engines: {:?}", Paint::white(Engines::ENABLED_EXTENSIONS));
|
||||
|
||||
(self.custom_callback)(&mut ctxt.engines);
|
||||
Ok(rocket.manage(ContextManager::new(ctxt)))
|
||||
}
|
||||
|
|
|
@ -12,9 +12,8 @@ mod databases_tests {
|
|||
#[cfg(all(feature = "databases", feature = "sqlite_pool"))]
|
||||
#[cfg(test)]
|
||||
mod rusqlite_integration_test {
|
||||
use rocket::config::{Config, Environment, Value, Map};
|
||||
use rocket_contrib::databases::rusqlite;
|
||||
use rocket_contrib::database;
|
||||
use rocket_contrib::databases::rusqlite;
|
||||
|
||||
use rusqlite::types::ToSql;
|
||||
|
||||
|
@ -27,18 +26,19 @@ mod rusqlite_integration_test {
|
|||
|
||||
#[rocket::async_test]
|
||||
async fn test_db() {
|
||||
let mut test_db: Map<String, Value> = Map::new();
|
||||
let mut test_db_opts: Map<String, Value> = Map::new();
|
||||
test_db_opts.insert("url".into(), Value::String(":memory:".into()));
|
||||
test_db.insert("test_db".into(), Value::Table(test_db_opts.clone()));
|
||||
test_db.insert("test_db_2".into(), Value::Table(test_db_opts));
|
||||
let config = Config::build(Environment::Development)
|
||||
.extra("databases", Value::Table(test_db))
|
||||
.finalize()
|
||||
.unwrap();
|
||||
use rocket::figment::{Figment, util::map};
|
||||
|
||||
let mut rocket = rocket::custom(config).attach(SqliteDb::fairing()).attach(SqliteDb2::fairing());
|
||||
let conn = SqliteDb::get_one(rocket.inspect().await).await.expect("unable to get connection");
|
||||
let options = map!["url" => ":memory:"];
|
||||
let config = Figment::from(rocket::Config::default())
|
||||
.merge(("databases", map!["test_db" => &options]))
|
||||
.merge(("databases", map!["test_db_2" => &options]));
|
||||
|
||||
let mut rocket = rocket::custom(config)
|
||||
.attach(SqliteDb::fairing())
|
||||
.attach(SqliteDb2::fairing());
|
||||
|
||||
let conn = SqliteDb::get_one(rocket.inspect().await).await
|
||||
.expect("unable to get connection");
|
||||
|
||||
// Rusqlite's `transaction()` method takes `&mut self`; this tests that
|
||||
// the &mut method can be called inside the closure passed to `run()`.
|
||||
|
|
|
@ -6,7 +6,7 @@ mod templates_tests {
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use rocket::{Rocket, http::RawStr};
|
||||
use rocket::config::{Config, Environment};
|
||||
use rocket::config::Config;
|
||||
use rocket_contrib::templates::{Template, Metadata};
|
||||
|
||||
#[get("/<engine>/<name>")]
|
||||
|
@ -27,11 +27,8 @@ mod templates_tests {
|
|||
}
|
||||
|
||||
fn rocket() -> Rocket {
|
||||
let config = Config::build(Environment::Development)
|
||||
.extra("template_dir", template_root().to_str().expect("template directory"))
|
||||
.expect("valid configuration");
|
||||
|
||||
rocket::custom(config).attach(Template::fairing())
|
||||
rocket::custom(Config::figment().merge(("template_dir", template_root())))
|
||||
.attach(Template::fairing())
|
||||
.mount("/", routes![template_check, is_reloading])
|
||||
}
|
||||
|
||||
|
|
|
@ -41,12 +41,12 @@ fn get0b(_n: u8) { }
|
|||
|
||||
#[test]
|
||||
fn test_rank_collision() {
|
||||
use rocket::error::LaunchErrorKind;
|
||||
use rocket::error::ErrorKind;
|
||||
|
||||
let rocket = rocket::ignite().mount("/", routes![get0, get0b]);
|
||||
let client_result = Client::tracked(rocket);
|
||||
match client_result.as_ref().map_err(|e| e.kind()) {
|
||||
Err(LaunchErrorKind::Collision(..)) => { /* o.k. */ },
|
||||
Err(ErrorKind::Collision(..)) => { /* o.k. */ },
|
||||
Ok(_) => panic!("client succeeded unexpectedly"),
|
||||
Err(e) => panic!("expected collision, got {}", e)
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ error: invalid path URI: expected token / but found a at index 0
|
|||
|
|
||||
= help: expected path in origin form: "/path/<param>"
|
||||
|
||||
error: invalid path URI: expected token / but none was found at index 0
|
||||
error: invalid path URI: unexpected EOF: expected token / at index 0
|
||||
--> $DIR/route-path-bad-syntax.rs:8:8
|
||||
|
|
||||
8 | #[get("")]
|
||||
|
|
|
@ -42,17 +42,17 @@ error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Path, _>` is n
|
|||
|
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `i32: FromUriParam<rocket::http::uri::Path, Option<{integer}>>` is not satisfied
|
||||
error[E0277]: the trait bound `i32: FromUriParam<rocket::http::uri::Path, std::option::Option<{integer}>>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:53:26
|
||||
|
|
||||
53 | uri!(optionals: id = Some(10), name = Ok("bob".into()));
|
||||
| ^^^^^^^^ the trait `FromUriParam<rocket::http::uri::Path, Option<{integer}>>` is not implemented for `i32`
|
||||
| ^^^^^^^^ the trait `FromUriParam<rocket::http::uri::Path, std::option::Option<{integer}>>` is not implemented for `i32`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<i32 as FromUriParam<P, &'x i32>>
|
||||
<i32 as FromUriParam<P, &'x mut i32>>
|
||||
<i32 as FromUriParam<P, i32>>
|
||||
= note: required because of the requirements on the impl of `FromUriParam<rocket::http::uri::Path, Option<{integer}>>` for `Option<i32>`
|
||||
= note: required because of the requirements on the impl of `FromUriParam<rocket::http::uri::Path, std::option::Option<{integer}>>` for `std::option::Option<i32>`
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `std::string::String: FromUriParam<rocket::http::uri::Path, std::result::Result<_, _>>` is not satisfied
|
||||
|
|
|
@ -5,7 +5,7 @@ error: invalid path URI: expected token / but found a at index 0
|
|||
5 | #[get("a")]
|
||||
| ^^^
|
||||
|
||||
error: invalid path URI: expected token / but none was found at index 0
|
||||
error: invalid path URI: unexpected EOF: expected token / at index 0
|
||||
--- help: expected path in origin form: "/path/<param>"
|
||||
--> $DIR/route-path-bad-syntax.rs:8:7
|
||||
|
|
||||
|
|
|
@ -22,7 +22,7 @@ private-cookies = ["cookie/private", "cookie/key-expansion"]
|
|||
[dependencies]
|
||||
smallvec = "1.0"
|
||||
percent-encoding = "2"
|
||||
hyper = { version = "0.13.0", default-features = false }
|
||||
hyper = { version = "0.13.0", default-features = false, features = ["runtime"] }
|
||||
http = "0.2"
|
||||
mime = "0.3.13"
|
||||
time = "0.2.11"
|
||||
|
@ -36,16 +36,13 @@ ref-cast = "1.0"
|
|||
uncased = "0.9"
|
||||
parking_lot = "0.11"
|
||||
either = "1"
|
||||
pear = "0.2"
|
||||
|
||||
[dependencies.cookie]
|
||||
git = "https://github.com/SergioBenitez/cookie-rs.git"
|
||||
rev = "9675944"
|
||||
rev = "1c3ca83"
|
||||
features = ["percent-encode"]
|
||||
|
||||
[dependencies.pear]
|
||||
git = "https://github.com/SergioBenitez/Pear.git"
|
||||
rev = "4b68055"
|
||||
|
||||
[dev-dependencies]
|
||||
rocket = { version = "0.5.0-dev", path = "../lib" }
|
||||
|
||||
|
|
|
@ -20,9 +20,16 @@ mod key {
|
|||
pub struct Key;
|
||||
|
||||
impl Key {
|
||||
pub fn from(_: &[u8]) -> Self { Key }
|
||||
pub fn derive_from(_: &[u8]) -> Self { Key }
|
||||
pub fn generate() -> Self { Key }
|
||||
pub fn try_generate() -> Option<Self> { Some(Key) }
|
||||
pub fn derive_from(_bytes: &[u8]) -> Self { Key }
|
||||
}
|
||||
|
||||
impl PartialEq for Key {
|
||||
fn eq(&self, _: &Self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,7 +128,7 @@ mod key {
|
|||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// #
|
||||
/// # #[cfg(feature = "private-cookies")] {
|
||||
/// use rocket::http::Status;
|
||||
/// use rocket::outcome::IntoOutcome;
|
||||
/// use rocket::request::{self, Request, FromRequest};
|
||||
|
@ -141,6 +148,7 @@ mod key {
|
|||
/// .or_forward(())
|
||||
/// }
|
||||
/// }
|
||||
/// # }
|
||||
/// # fn main() { }
|
||||
/// ```
|
||||
///
|
||||
|
|
|
@ -10,8 +10,8 @@ use hyper::server::accept::Accept;
|
|||
|
||||
use log::{debug, error};
|
||||
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::time::Delay;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
// TODO.async: 'Listener' and 'Connection' provide common enough functionality
|
||||
|
@ -32,10 +32,10 @@ pub trait Connection: AsyncRead + AsyncWrite {
|
|||
fn remote_addr(&self) -> Option<SocketAddr>;
|
||||
}
|
||||
|
||||
/// This is a genericized version of hyper's AddrIncoming that is intended to be
|
||||
/// This is a generic version of hyper's AddrIncoming that is intended to be
|
||||
/// usable with listeners other than a plain TCP stream, e.g. TLS and/or Unix
|
||||
/// sockets. It does this by bridging the `Listener` trait to what hyper wants
|
||||
/// (an Accept). This type is internal to Rocket.
|
||||
/// sockets. It does so by bridging the `Listener` trait to what hyper wants (an
|
||||
/// Accept). This type is internal to Rocket.
|
||||
#[must_use = "streams do nothing unless polled"]
|
||||
pub struct Incoming<L> {
|
||||
listener: L,
|
||||
|
|
|
@ -1,70 +1,49 @@
|
|||
use std::fs;
|
||||
use std::io;
|
||||
use std::future::Future;
|
||||
use std::io::{self, BufReader};
|
||||
use std::net::SocketAddr;
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use rustls::internal::pemfile;
|
||||
use rustls::{Certificate, PrivateKey, ServerConfig};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
use tokio_rustls::{TlsAcceptor, server::TlsStream};
|
||||
use tokio_rustls::{TlsAcceptor, Accept, server::TlsStream};
|
||||
use tokio_rustls::rustls;
|
||||
|
||||
pub use rustls::internal::pemfile;
|
||||
pub use rustls::{Certificate, PrivateKey, ServerConfig};
|
||||
|
||||
use crate::listener::{Connection, Listener};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Io(io::Error),
|
||||
BadCerts,
|
||||
BadKeyCount,
|
||||
BadKey,
|
||||
fn load_certs(reader: &mut dyn io::BufRead) -> io::Result<Vec<Certificate>> {
|
||||
pemfile::certs(reader)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "invalid certificate"))
|
||||
}
|
||||
|
||||
// TODO.async: consider using async fs operations
|
||||
pub fn load_certs<P: AsRef<Path>>(path: P) -> Result<Vec<rustls::Certificate>, Error> {
|
||||
let certfile = fs::File::open(path.as_ref()).map_err(|e| Error::Io(e))?;
|
||||
let mut reader = BufReader::new(certfile);
|
||||
pemfile::certs(&mut reader).map_err(|_| Error::BadCerts)
|
||||
}
|
||||
|
||||
pub fn load_private_key<P: AsRef<Path>>(path: P) -> Result<rustls::PrivateKey, Error> {
|
||||
use std::io::Seek;
|
||||
use std::io::BufRead;
|
||||
|
||||
let keyfile = fs::File::open(path.as_ref()).map_err(Error::Io)?;
|
||||
let mut reader = BufReader::new(keyfile);
|
||||
fn load_private_key(reader: &mut dyn io::BufRead) -> io::Result<PrivateKey> {
|
||||
use std::io::{Cursor, Error, Read, ErrorKind::Other};
|
||||
|
||||
// "rsa" (PKCS1) PEM files have a different first-line header than PKCS8
|
||||
// PEM files, use that to determine the parse function to use.
|
||||
let mut first_line = String::new();
|
||||
reader.read_line(&mut first_line).map_err(Error::Io)?;
|
||||
reader.seek(io::SeekFrom::Start(0)).map_err(Error::Io)?;
|
||||
reader.read_line(&mut first_line)?;
|
||||
|
||||
let private_keys_fn = match first_line.trim_end() {
|
||||
"-----BEGIN RSA PRIVATE KEY-----" => pemfile::rsa_private_keys,
|
||||
"-----BEGIN PRIVATE KEY-----" => pemfile::pkcs8_private_keys,
|
||||
_ => return Err(Error::BadKey),
|
||||
_ => return Err(Error::new(Other, "invalid key header"))
|
||||
};
|
||||
|
||||
let key = private_keys_fn(&mut reader)
|
||||
.map_err(|_| Error::BadKey)
|
||||
let key = private_keys_fn(&mut Cursor::new(first_line).chain(reader))
|
||||
.map_err(|_| Error::new(Other, "invalid key file"))
|
||||
.and_then(|mut keys| match keys.len() {
|
||||
0 => Err(Error::BadKey),
|
||||
0 => Err(Error::new(Other, "no valid keys found; is the file malformed?")),
|
||||
1 => Ok(keys.remove(0)),
|
||||
_ => Err(Error::BadKeyCount),
|
||||
n => Err(Error::new(Other, format!("expected 1 key, found {}", n))),
|
||||
})?;
|
||||
|
||||
// Ensure we can use the key.
|
||||
if rustls::sign::RSASigningKey::new(&key).is_err() {
|
||||
Err(Error::BadKey)
|
||||
} else {
|
||||
Ok(key)
|
||||
}
|
||||
rustls::sign::RSASigningKey::new(&key)
|
||||
.map_err(|_| Error::new(Other, "key parsed but is unusable"))
|
||||
.map(|_| key)
|
||||
}
|
||||
|
||||
pub struct TlsListener {
|
||||
|
@ -75,7 +54,7 @@ pub struct TlsListener {
|
|||
|
||||
enum TlsListenerState {
|
||||
Listening,
|
||||
Accepting(Pin<Box<dyn Future<Output=Result<TlsStream<TcpStream>, io::Error>> + Send>>),
|
||||
Accepting(Accept<TcpStream>),
|
||||
}
|
||||
|
||||
impl Listener for TlsListener {
|
||||
|
@ -85,22 +64,21 @@ impl Listener for TlsListener {
|
|||
self.listener.local_addr().ok()
|
||||
}
|
||||
|
||||
fn poll_accept(&mut self, cx: &mut Context<'_>) -> Poll<Result<Self::Connection, io::Error>> {
|
||||
fn poll_accept(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<Self::Connection>> {
|
||||
loop {
|
||||
match &mut self.state {
|
||||
match self.state {
|
||||
TlsListenerState::Listening => {
|
||||
match self.listener.poll_accept(cx) {
|
||||
Poll::Pending => return Poll::Pending,
|
||||
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
|
||||
Poll::Ready(Ok((stream, _addr))) => {
|
||||
self.state = TlsListenerState::Accepting(Box::pin(self.acceptor.accept(stream)));
|
||||
}
|
||||
Poll::Ready(Err(e)) => {
|
||||
return Poll::Ready(Err(e));
|
||||
let fut = self.acceptor.accept(stream);
|
||||
self.state = TlsListenerState::Accepting(fut);
|
||||
}
|
||||
}
|
||||
}
|
||||
TlsListenerState::Accepting(fut) => {
|
||||
match fut.as_mut().poll(cx) {
|
||||
TlsListenerState::Accepting(ref mut fut) => {
|
||||
match Pin::new(fut).poll(cx) {
|
||||
Poll::Pending => return Poll::Pending,
|
||||
Poll::Ready(result) => {
|
||||
self.state = TlsListenerState::Listening;
|
||||
|
@ -113,11 +91,21 @@ impl Listener for TlsListener {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn bind_tls(
|
||||
pub async fn bind_tls<C: io::BufRead + Send, K: io::BufRead + Send>(
|
||||
address: SocketAddr,
|
||||
cert_chain: Vec<Certificate>,
|
||||
key: PrivateKey
|
||||
mut cert_chain: C,
|
||||
mut private_key: K,
|
||||
) -> io::Result<TlsListener> {
|
||||
let cert_chain = load_certs(&mut cert_chain).map_err(|e| {
|
||||
let msg = format!("malformed TLS certificate chain: {}", e);
|
||||
io::Error::new(e.kind(), msg)
|
||||
})?;
|
||||
|
||||
let key = load_private_key(&mut private_key).map_err(|e| {
|
||||
let msg = format!("malformed TLS private key: {}", e);
|
||||
io::Error::new(e.kind(), msg)
|
||||
})?;
|
||||
|
||||
let listener = TcpListener::bind(address).await?;
|
||||
|
||||
let client_auth = rustls::NoClientAuth::new();
|
||||
|
|
|
@ -19,7 +19,7 @@ edition = "2018"
|
|||
all-features = true
|
||||
|
||||
[features]
|
||||
default = ["secrets"]
|
||||
default = []
|
||||
tls = ["rocket_http/tls"]
|
||||
secrets = ["rocket_http/private-cookies"]
|
||||
|
||||
|
@ -29,7 +29,6 @@ rocket_http = { version = "0.5.0-dev", path = "../http" }
|
|||
futures = "0.3.0"
|
||||
yansi = "0.5"
|
||||
log = { version = "0.4", features = ["std"] }
|
||||
toml = "0.5"
|
||||
num_cpus = "1.0"
|
||||
state = "0.4.1"
|
||||
time = "0.2.11"
|
||||
|
@ -39,12 +38,12 @@ atty = "0.2"
|
|||
async-trait = "0.1"
|
||||
ref-cast = "1.0"
|
||||
atomic = "0.5"
|
||||
ubyte = "0.10"
|
||||
parking_lot = "0.11"
|
||||
|
||||
[dependencies.pear]
|
||||
git = "https://github.com/SergioBenitez/Pear.git"
|
||||
rev = "4b68055"
|
||||
ubyte = {version = "0.10", features = ["serde"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
figment = { version = "0.9.2", features = ["toml", "env"] }
|
||||
rand = "0.7"
|
||||
either = "1"
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "0.2.9"
|
||||
|
@ -56,8 +55,7 @@ version_check = "0.9.1"
|
|||
|
||||
[dev-dependencies]
|
||||
bencher = "0.1"
|
||||
# TODO: Find a way to not depend on this.
|
||||
lazy_static = "1.0"
|
||||
figment = { version = "0.9.2", features = ["test"] }
|
||||
|
||||
[[bench]]
|
||||
name = "format-routing"
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#[macro_use] extern crate bencher;
|
||||
|
||||
use rocket::local::blocking::Client;
|
||||
use rocket::config::{Environment, Config, LoggingLevel};
|
||||
|
||||
#[get("/", format = "application/json")]
|
||||
fn get() -> &'static str { "get" }
|
||||
|
@ -11,8 +10,8 @@ fn get() -> &'static str { "get" }
|
|||
fn post() -> &'static str { "post" }
|
||||
|
||||
fn rocket() -> rocket::Rocket {
|
||||
let config = Config::build(Environment::Production).log_level(LoggingLevel::Off);
|
||||
rocket::custom(config.unwrap()).mount("/", routes![get, post])
|
||||
rocket::custom(rocket::Config::figment().merge(("log_level", "off")))
|
||||
.mount("/", routes![get, post])
|
||||
}
|
||||
|
||||
use bencher::Bencher;
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
#[macro_use] extern crate rocket;
|
||||
#[macro_use] extern crate bencher;
|
||||
|
||||
use rocket::config::{Environment, Config, LoggingLevel};
|
||||
|
||||
#[get("/", format = "application/json", rank = 1)]
|
||||
fn get() -> &'static str { "json" }
|
||||
|
||||
|
@ -22,8 +20,7 @@ fn post2() -> &'static str { "html" }
|
|||
fn post3() -> &'static str { "plain" }
|
||||
|
||||
fn rocket() -> rocket::Rocket {
|
||||
let config = Config::build(Environment::Production).log_level(LoggingLevel::Off);
|
||||
rocket::custom(config.unwrap())
|
||||
rocket::custom(rocket::Config::figment().merge(("log_level", "off")))
|
||||
.mount("/", routes![get, get2, get3])
|
||||
.mount("/", routes![post, post2, post3])
|
||||
}
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
#[macro_use] extern crate rocket;
|
||||
#[macro_use] extern crate bencher;
|
||||
|
||||
use rocket::config::{Environment, Config, LoggingLevel};
|
||||
use rocket::http::RawStr;
|
||||
|
||||
#[get("/")]
|
||||
fn hello_world() -> &'static str { "Hello, world!" }
|
||||
|
||||
#[get("/")]
|
||||
fn get_index() -> &'static str { "index" }
|
||||
|
||||
#[put("/")]
|
||||
fn put_index() -> &'static str { "index" }
|
||||
|
||||
|
@ -29,15 +25,15 @@ fn index_c() -> &'static str { "index" }
|
|||
fn index_dyn_a(_a: &RawStr) -> &'static str { "index" }
|
||||
|
||||
fn hello_world_rocket() -> rocket::Rocket {
|
||||
let config = Config::build(Environment::Production).log_level(LoggingLevel::Off);
|
||||
rocket::custom(config.unwrap()).mount("/", routes![hello_world])
|
||||
let config = rocket::Config::figment().merge(("log_level", "off"));
|
||||
rocket::custom(config).mount("/", routes![hello_world])
|
||||
}
|
||||
|
||||
fn rocket() -> rocket::Rocket {
|
||||
let config = Config::build(Environment::Production).log_level(LoggingLevel::Off);
|
||||
rocket::custom(config.unwrap())
|
||||
.mount("/", routes![get_index, put_index, post_index, index_a,
|
||||
index_b, index_c, index_dyn_a])
|
||||
hello_world_rocket()
|
||||
.mount("/", routes![
|
||||
put_index, post_index, index_a, index_b, index_c, index_dyn_a
|
||||
])
|
||||
}
|
||||
|
||||
use bencher::Bencher;
|
||||
|
|
|
@ -1,387 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::config::{Result, Config, Value, Environment, LoggingLevel};
|
||||
use crate::data::Limits;
|
||||
|
||||
/// Structure following the builder pattern for building `Config` structures.
|
||||
#[derive(Clone)]
|
||||
pub struct ConfigBuilder {
|
||||
/// The environment that this configuration corresponds to.
|
||||
pub environment: Environment,
|
||||
/// The address to serve on.
|
||||
pub address: String,
|
||||
/// The port to serve on.
|
||||
pub port: u16,
|
||||
/// The number of workers to run in parallel.
|
||||
pub workers: u16,
|
||||
/// Keep-alive timeout in seconds or disabled if 0.
|
||||
pub keep_alive: u32,
|
||||
/// How much information to log.
|
||||
pub log_level: LoggingLevel,
|
||||
/// The secret key.
|
||||
pub secret_key: Option<String>,
|
||||
/// TLS configuration (path to certificates file, path to private key file).
|
||||
pub tls: Option<(String, String)>,
|
||||
/// Size limits.
|
||||
pub limits: Limits,
|
||||
/// Any extra parameters that aren't part of Rocket's config.
|
||||
pub extras: HashMap<String, Value>,
|
||||
/// The root directory of this config, if any.
|
||||
pub root: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl ConfigBuilder {
|
||||
/// Create a new `ConfigBuilder` instance using the default parameters from
|
||||
/// the given `environment`.
|
||||
///
|
||||
/// This method is typically called indirectly via [`Config::build()`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::{Config, Environment};
|
||||
///
|
||||
/// let config = Config::build(Environment::Staging)
|
||||
/// .address("127.0.0.1")
|
||||
/// .port(700)
|
||||
/// .workers(12)
|
||||
/// .finalize();
|
||||
///
|
||||
/// # assert!(config.is_ok());
|
||||
/// ```
|
||||
pub fn new(environment: Environment) -> ConfigBuilder {
|
||||
let config = Config::new(environment);
|
||||
ConfigBuilder {
|
||||
environment: config.environment,
|
||||
address: config.address,
|
||||
port: config.port,
|
||||
workers: config.workers,
|
||||
keep_alive: config.keep_alive.unwrap_or(0),
|
||||
log_level: config.log_level,
|
||||
secret_key: None,
|
||||
tls: None,
|
||||
limits: config.limits,
|
||||
extras: config.extras,
|
||||
root: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the `address` in the configuration being built.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::{Config, Environment};
|
||||
///
|
||||
/// let config = Config::build(Environment::Staging)
|
||||
/// .address("127.0.0.1")
|
||||
/// .unwrap();
|
||||
///
|
||||
/// assert_eq!(config.address.as_str(), "127.0.0.1");
|
||||
/// ```
|
||||
pub fn address<A: Into<String>>(mut self, address: A) -> Self {
|
||||
self.address = address.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `port` in the configuration being built.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::{Config, Environment};
|
||||
///
|
||||
/// let config = Config::build(Environment::Staging)
|
||||
/// .port(1329)
|
||||
/// .unwrap();
|
||||
///
|
||||
/// assert_eq!(config.port, 1329);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn port(mut self, port: u16) -> Self {
|
||||
self.port = port;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets `workers` in the configuration being built.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::{Config, Environment};
|
||||
///
|
||||
/// let config = Config::build(Environment::Staging)
|
||||
/// .workers(64)
|
||||
/// .unwrap();
|
||||
///
|
||||
/// assert_eq!(config.workers, 64);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn workers(mut self, workers: u16) -> Self {
|
||||
self.workers = workers;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the keep-alive timeout to `timeout` seconds. If `timeout` is `0`,
|
||||
/// keep-alive is disabled.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::{Config, Environment};
|
||||
///
|
||||
/// let config = Config::build(Environment::Staging)
|
||||
/// .keep_alive(10)
|
||||
/// .unwrap();
|
||||
///
|
||||
/// assert_eq!(config.keep_alive, Some(10));
|
||||
///
|
||||
/// let config = Config::build(Environment::Staging)
|
||||
/// .keep_alive(0)
|
||||
/// .unwrap();
|
||||
///
|
||||
/// assert_eq!(config.keep_alive, None);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn keep_alive(mut self, timeout: u32) -> Self {
|
||||
self.keep_alive = timeout;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `log_level` in the configuration being built.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::{Config, Environment, LoggingLevel};
|
||||
///
|
||||
/// let config = Config::build(Environment::Staging)
|
||||
/// .log_level(LoggingLevel::Critical)
|
||||
/// .unwrap();
|
||||
///
|
||||
/// assert_eq!(config.log_level, LoggingLevel::Critical);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn log_level(mut self, log_level: LoggingLevel) -> Self {
|
||||
self.log_level = log_level;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `secret_key` in the configuration being built.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::{Config, Environment, LoggingLevel};
|
||||
///
|
||||
/// let key = "8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg=";
|
||||
/// let mut config = Config::build(Environment::Staging)
|
||||
/// .secret_key(key)
|
||||
/// .unwrap();
|
||||
/// ```
|
||||
pub fn secret_key<K: Into<String>>(mut self, key: K) -> Self {
|
||||
self.secret_key = Some(key.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `limits` in the configuration being built.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::{Config, Environment};
|
||||
/// use rocket::data::{Limits, ToByteUnit};
|
||||
///
|
||||
/// let mut config = Config::build(Environment::Staging)
|
||||
/// .limits(Limits::new().limit("json", 5.mebibytes()))
|
||||
/// .unwrap();
|
||||
/// ```
|
||||
pub fn limits(mut self, limits: Limits) -> Self {
|
||||
self.limits = limits;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the TLS configuration in the configuration being built.
|
||||
///
|
||||
/// Certificates are read from `certs_path`. The certificate chain must be
|
||||
/// in X.509 PEM format. The private key is read from `key_path`. The
|
||||
/// private key must be an RSA key in either PKCS#1 or PKCS#8 PEM format.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::{Config, Environment};
|
||||
///
|
||||
/// let mut config = Config::build(Environment::Staging)
|
||||
/// .tls("/path/to/certs.pem", "/path/to/key.pem")
|
||||
/// # ; /*
|
||||
/// .unwrap();
|
||||
/// # */
|
||||
/// ```
|
||||
pub fn tls<C, K>(mut self, certs_path: C, key_path: K) -> Self
|
||||
where C: Into<String>, K: Into<String>
|
||||
{
|
||||
self.tls = Some((certs_path.into(), key_path.into()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `environment` in the configuration being built.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::{Config, Environment};
|
||||
///
|
||||
/// let config = Config::build(Environment::Staging)
|
||||
/// .environment(Environment::Production)
|
||||
/// .unwrap();
|
||||
///
|
||||
/// assert_eq!(config.environment, Environment::Production);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn environment(mut self, env: Environment) -> Self {
|
||||
self.environment = env;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `root` in the configuration being built.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use std::path::Path;
|
||||
/// use rocket::config::{Config, Environment};
|
||||
///
|
||||
/// let config = Config::build(Environment::Staging)
|
||||
/// .root("/my_app/dir")
|
||||
/// .unwrap();
|
||||
///
|
||||
/// assert_eq!(config.root().unwrap(), Path::new("/my_app/dir"));
|
||||
/// ```
|
||||
pub fn root<P: AsRef<Path>>(mut self, path: P) -> Self {
|
||||
self.root = Some(path.as_ref().to_path_buf());
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds an extra configuration parameter with `name` and `value` to the
|
||||
/// configuration being built. The value can be any type that implements
|
||||
/// `Into<Value>` including `&str`, `String`, `Vec<V: Into<Value>>`,
|
||||
/// `HashMap<S: Into<String>, V: Into<Value>>`, and most integer and float
|
||||
/// types.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::{Config, Environment};
|
||||
///
|
||||
/// let config = Config::build(Environment::Staging)
|
||||
/// .extra("pi", 3.14)
|
||||
/// .extra("custom_dir", "/a/b/c")
|
||||
/// .unwrap();
|
||||
///
|
||||
/// assert_eq!(config.get_float("pi"), Ok(3.14));
|
||||
/// assert_eq!(config.get_str("custom_dir"), Ok("/a/b/c"));
|
||||
/// ```
|
||||
pub fn extra<V: Into<Value>>(mut self, name: &str, value: V) -> Self {
|
||||
self.extras.insert(name.into(), value.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Return the `Config` structure that was being built by this builder.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If the address or secret key fail to parse, returns a `BadType` error.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::{Config, Environment};
|
||||
///
|
||||
/// let config = Config::build(Environment::Staging)
|
||||
/// .address("127.0.0.1")
|
||||
/// .port(700)
|
||||
/// .workers(12)
|
||||
/// .keep_alive(0)
|
||||
/// .finalize();
|
||||
///
|
||||
/// assert!(config.is_ok());
|
||||
///
|
||||
/// let config = Config::build(Environment::Staging)
|
||||
/// .address("123.123.123.123.123 whoops!")
|
||||
/// .finalize();
|
||||
///
|
||||
/// assert!(config.is_err());
|
||||
/// ```
|
||||
pub fn finalize(self) -> Result<Config> {
|
||||
let mut config = Config::new(self.environment);
|
||||
config.set_address(self.address)?;
|
||||
config.set_port(self.port);
|
||||
config.set_workers(self.workers);
|
||||
config.set_keep_alive(self.keep_alive);
|
||||
config.set_log_level(self.log_level);
|
||||
config.set_extras(self.extras);
|
||||
config.set_limits(self.limits);
|
||||
|
||||
if let Some(root) = self.root {
|
||||
config.set_root(root);
|
||||
}
|
||||
|
||||
if let Some((certs_path, key_path)) = self.tls {
|
||||
config.set_tls(&certs_path, &key_path)?;
|
||||
}
|
||||
|
||||
if let Some(key) = self.secret_key {
|
||||
config.set_secret_key(key)?;
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Return the `Config` structure that was being built by this builder.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the supplied address, secret key, or TLS configuration fail to
|
||||
/// parse.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::{Config, Environment};
|
||||
///
|
||||
/// let config = Config::build(Environment::Staging)
|
||||
/// .address("127.0.0.1")
|
||||
/// .unwrap();
|
||||
///
|
||||
/// assert_eq!(config.address.as_str(), "127.0.0.1");
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn unwrap(self) -> Config {
|
||||
self.finalize().expect("ConfigBuilder::unwrap() failed")
|
||||
}
|
||||
|
||||
/// Returns the `Config` structure that was being built by this builder.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the supplied address, secret key, or TLS configuration fail to
|
||||
/// parse. If a panic occurs, the error message `msg` is printed.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::{Config, Environment};
|
||||
///
|
||||
/// let config = Config::build(Environment::Staging)
|
||||
/// .address("127.0.0.1")
|
||||
/// .expect("the configuration is bad!");
|
||||
///
|
||||
/// assert_eq!(config.address.as_str(), "127.0.0.1");
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn expect(self, msg: &str) -> Config {
|
||||
self.finalize().expect(msg)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,128 +0,0 @@
|
|||
use std::fmt;
|
||||
|
||||
#[cfg(feature = "tls")] use crate::http::tls::{Certificate, PrivateKey};
|
||||
|
||||
use crate::http::private::cookie::Key;
|
||||
use crate::config::{Result, Config, Value, ConfigError, LoggingLevel};
|
||||
use crate::data::Limits;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum SecretKey {
|
||||
Generated(Key),
|
||||
Provided(Key)
|
||||
}
|
||||
|
||||
impl SecretKey {
|
||||
#[inline]
|
||||
pub(crate) fn inner(&self) -> &Key {
|
||||
match *self {
|
||||
SecretKey::Generated(ref key) | SecretKey::Provided(ref key) => key
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn is_generated(&self) -> bool {
|
||||
match *self {
|
||||
#[cfg(feature = "secrets")]
|
||||
SecretKey::Generated(_) => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SecretKey {
|
||||
#[cfg(feature = "secrets")]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
SecretKey::Generated(_) => write!(f, "generated"),
|
||||
SecretKey::Provided(_) => write!(f, "provided"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "secrets"))]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
"private-cookies disabled".fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
#[derive(Clone)]
|
||||
pub struct TlsConfig {
|
||||
pub certs: Vec<Certificate>,
|
||||
pub key: PrivateKey
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tls"))]
|
||||
#[derive(Clone)]
|
||||
pub struct TlsConfig;
|
||||
|
||||
pub fn str<'a>(conf: &Config, name: &str, v: &'a Value) -> Result<&'a str> {
|
||||
v.as_str().ok_or_else(|| conf.bad_type(name, v.type_str(), "a string"))
|
||||
}
|
||||
|
||||
pub fn u64(conf: &Config, name: &str, value: &Value) -> Result<u64> {
|
||||
match value.as_integer() {
|
||||
Some(x) if x >= 0 => Ok(x as u64),
|
||||
_ => Err(conf.bad_type(name, value.type_str(), "an unsigned integer"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn u16(conf: &Config, name: &str, value: &Value) -> Result<u16> {
|
||||
match value.as_integer() {
|
||||
Some(x) if x >= 0 && x <= (u16::max_value() as i64) => Ok(x as u16),
|
||||
_ => Err(conf.bad_type(name, value.type_str(), "a 16-bit unsigned integer"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn u32(conf: &Config, name: &str, value: &Value) -> Result<u32> {
|
||||
match value.as_integer() {
|
||||
Some(x) if x >= 0 && x <= (u32::max_value() as i64) => Ok(x as u32),
|
||||
_ => Err(conf.bad_type(name, value.type_str(), "a 32-bit unsigned integer"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_level(conf: &Config,
|
||||
name: &str,
|
||||
value: &Value
|
||||
) -> Result<LoggingLevel> {
|
||||
str(conf, name, value)
|
||||
.and_then(|s| s.parse().map_err(|e| conf.bad_type(name, value.type_str(), e)))
|
||||
}
|
||||
|
||||
pub fn tls_config<'v>(conf: &Config,
|
||||
name: &str,
|
||||
value: &'v Value,
|
||||
) -> Result<(&'v str, &'v str)> {
|
||||
let (mut certs_path, mut key_path) = (None, None);
|
||||
let table = value.as_table()
|
||||
.ok_or_else(|| conf.bad_type(name, value.type_str(), "a table"))?;
|
||||
|
||||
let env = conf.environment;
|
||||
for (key, value) in table {
|
||||
match key.as_str() {
|
||||
"certs" => certs_path = Some(str(conf, "tls.certs", value)?),
|
||||
"key" => key_path = Some(str(conf, "tls.key", value)?),
|
||||
_ => return Err(ConfigError::UnknownKey(format!("{}.tls.{}", env, key)))
|
||||
}
|
||||
}
|
||||
|
||||
if let (Some(certs), Some(key)) = (certs_path, key_path) {
|
||||
Ok((certs, key))
|
||||
} else {
|
||||
Err(conf.bad_type(name, "a table with missing entries",
|
||||
"a table with `certs` and `key` entries"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn limits(conf: &Config, name: &str, value: &Value) -> Result<Limits> {
|
||||
let table = value.as_table()
|
||||
.ok_or_else(|| conf.bad_type(name, value.type_str(), "a table"))?;
|
||||
|
||||
let mut limits = Limits::default();
|
||||
for (key, val) in table {
|
||||
let val = u64(conf, &format!("limits.{}", key), val)?;
|
||||
limits = limits.limit(key.as_str(), val.into());
|
||||
}
|
||||
|
||||
Ok(limits)
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
use super::ConfigError;
|
||||
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use std::env;
|
||||
|
||||
use self::Environment::*;
|
||||
|
||||
pub const CONFIG_ENV: &str = "ROCKET_ENV";
|
||||
|
||||
/// An enum corresponding to the valid configuration environments.
|
||||
#[derive(Hash, PartialEq, Eq, Debug, Clone, Copy)]
|
||||
pub enum Environment {
|
||||
/// The development environment.
|
||||
Development,
|
||||
/// The staging environment.
|
||||
Staging,
|
||||
/// The production environment.
|
||||
Production,
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
/// List of all of the possible environments.
|
||||
pub(crate) const ALL: [Environment; 3] = [Development, Staging, Production];
|
||||
|
||||
/// String of all valid environments.
|
||||
pub(crate) const VALID: &'static str = "development, staging, production";
|
||||
|
||||
/// Retrieves the "active" environment as determined by the `ROCKET_ENV`
|
||||
/// environment variable. If `ROCKET_ENV` is not set, returns `Development`
|
||||
/// when the application was compiled in `debug` mode and `Production` when
|
||||
/// the application was compiled in `release` mode.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns a `BadEnv` `ConfigError` if `ROCKET_ENV` is set and contains an
|
||||
/// invalid or unknown environment name.
|
||||
pub fn active() -> Result<Environment, ConfigError> {
|
||||
match env::var(CONFIG_ENV) {
|
||||
Ok(s) => s.parse().map_err(|_| ConfigError::BadEnv(s)),
|
||||
#[cfg(debug_assertions)]
|
||||
_ => Ok(Development),
|
||||
#[cfg(not(debug_assertions))]
|
||||
_ => Ok(Production),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is `Environment::Development`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::Environment;
|
||||
///
|
||||
/// assert!(Environment::Development.is_dev());
|
||||
/// assert!(!Environment::Production.is_dev());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn is_dev(self) -> bool {
|
||||
self == Development
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is `Environment::Staging`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::Environment;
|
||||
///
|
||||
/// assert!(Environment::Staging.is_stage());
|
||||
/// assert!(!Environment::Production.is_stage());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn is_stage(self) -> bool {
|
||||
self == Staging
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is `Environment::Production`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::Environment;
|
||||
///
|
||||
/// assert!(Environment::Production.is_prod());
|
||||
/// assert!(!Environment::Staging.is_prod());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn is_prod(self) -> bool {
|
||||
self == Production
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Environment {
|
||||
type Err = ();
|
||||
|
||||
/// Parses a configuration environment from a string. Should be used
|
||||
/// indirectly via `str`'s `parse` method.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Parsing a development environment:
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::Environment;
|
||||
///
|
||||
/// let env = "development".parse::<Environment>();
|
||||
/// assert_eq!(env.unwrap(), Environment::Development);
|
||||
///
|
||||
/// let env = "dev".parse::<Environment>();
|
||||
/// assert_eq!(env.unwrap(), Environment::Development);
|
||||
///
|
||||
/// let env = "devel".parse::<Environment>();
|
||||
/// assert_eq!(env.unwrap(), Environment::Development);
|
||||
/// ```
|
||||
///
|
||||
/// Parsing a staging environment:
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::Environment;
|
||||
///
|
||||
/// let env = "staging".parse::<Environment>();
|
||||
/// assert_eq!(env.unwrap(), Environment::Staging);
|
||||
///
|
||||
/// let env = "stage".parse::<Environment>();
|
||||
/// assert_eq!(env.unwrap(), Environment::Staging);
|
||||
/// ```
|
||||
///
|
||||
/// Parsing a production environment:
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::Environment;
|
||||
///
|
||||
/// let env = "production".parse::<Environment>();
|
||||
/// assert_eq!(env.unwrap(), Environment::Production);
|
||||
///
|
||||
/// let env = "prod".parse::<Environment>();
|
||||
/// assert_eq!(env.unwrap(), Environment::Production);
|
||||
/// ```
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let env = match s {
|
||||
"dev" | "devel" | "development" => Development,
|
||||
"stage" | "staging" => Staging,
|
||||
"prod" | "production" => Production,
|
||||
_ => return Err(()),
|
||||
};
|
||||
|
||||
Ok(env)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Environment {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
Development => write!(f, "development"),
|
||||
Staging => write!(f, "staging"),
|
||||
Production => write!(f, "production"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,206 +0,0 @@
|
|||
use std::{io, fmt};
|
||||
use std::path::PathBuf;
|
||||
use std::error::Error;
|
||||
|
||||
use yansi::Paint;
|
||||
|
||||
use super::Environment;
|
||||
use self::ConfigError::*;
|
||||
|
||||
/// The type of a configuration error.
|
||||
#[derive(Debug)]
|
||||
pub enum ConfigError {
|
||||
/// The configuration file was not found.
|
||||
NotFound,
|
||||
/// There was an I/O error while reading the configuration file.
|
||||
IoError,
|
||||
/// There was an error retrieving randomness from the OS.
|
||||
RandFailure,
|
||||
/// There was an I/O error while setting a configuration parameter.
|
||||
///
|
||||
/// Parameters: (io_error, config_param_name)
|
||||
Io(io::Error, &'static str),
|
||||
/// The path at which the configuration file was found was invalid.
|
||||
///
|
||||
/// Parameters: (path, reason)
|
||||
BadFilePath(PathBuf, &'static str),
|
||||
/// An environment specified in `ROCKET_ENV` is invalid.
|
||||
///
|
||||
/// Parameters: (environment_name)
|
||||
BadEnv(String),
|
||||
/// An environment specified as a table `[environment]` is invalid.
|
||||
///
|
||||
/// Parameters: (environment_name, filename)
|
||||
BadEntry(String, PathBuf),
|
||||
/// A config key was specified with a value of the wrong type.
|
||||
///
|
||||
/// Parameters: (entry_name, expected_type, actual_type, filename)
|
||||
BadType(String, &'static str, &'static str, Option<PathBuf>),
|
||||
/// There was a TOML parsing error.
|
||||
///
|
||||
/// Parameters: (toml_source_string, filename, error_description, line/col)
|
||||
ParseError(String, PathBuf, String, Option<(usize, usize)>),
|
||||
/// There was a TOML parsing error in a config environment variable.
|
||||
///
|
||||
/// Parameters: (env_key, env_value, error)
|
||||
BadEnvVal(String, String, String),
|
||||
/// The entry (key) is unknown.
|
||||
///
|
||||
/// Parameters: (key)
|
||||
UnknownKey(String),
|
||||
/// The entry (key) was expected but was missing.
|
||||
///
|
||||
/// Parameters: (key)
|
||||
Missing(String),
|
||||
}
|
||||
|
||||
impl ConfigError {
|
||||
/// Prints this configuration error with Rocket formatting.
|
||||
pub fn pretty_print(&self) {
|
||||
let valid_envs = Environment::VALID;
|
||||
match *self {
|
||||
NotFound => error!("config file was not found"),
|
||||
IoError => error!("failed reading the config file: IO error"),
|
||||
RandFailure => error!("failed to read randomness from the OS"),
|
||||
Io(ref error, param) => {
|
||||
error!("I/O error while setting {}:", Paint::default(param).bold());
|
||||
info_!("{}", error);
|
||||
}
|
||||
BadFilePath(ref path, reason) => {
|
||||
error!("configuration file path {} is invalid",
|
||||
Paint::default(path.display()).bold());
|
||||
info_!("{}", reason);
|
||||
}
|
||||
BadEntry(ref name, ref filename) => {
|
||||
let valid_entries = format!("{}, global", valid_envs);
|
||||
error!("{} is not a known configuration environment",
|
||||
Paint::default(format!("[{}]", name)).bold());
|
||||
info_!("in {}", Paint::default(filename.display()).bold());
|
||||
info_!("valid environments are: {}", Paint::default(valid_entries).bold());
|
||||
}
|
||||
BadEnv(ref name) => {
|
||||
error!("{} is not a valid ROCKET_ENV value", Paint::default(name).bold());
|
||||
info_!("valid environments are: {}", Paint::default(valid_envs).bold());
|
||||
}
|
||||
BadType(ref name, expected, actual, ref filename) => {
|
||||
error!("{} key could not be parsed", Paint::default(name).bold());
|
||||
if let Some(filename) = filename {
|
||||
info_!("in {}", Paint::default(filename.display()).bold());
|
||||
}
|
||||
|
||||
info_!("expected value to be {}, but found {}",
|
||||
Paint::default(expected).bold(), Paint::default(actual).bold());
|
||||
}
|
||||
ParseError(_, ref filename, ref desc, line_col) => {
|
||||
error!("config file failed to parse due to invalid TOML");
|
||||
info_!("{}", desc);
|
||||
info_!("in {}", Paint::default(filename.display()).bold());
|
||||
if let Some((line, col)) = line_col {
|
||||
info_!("at line {}, column {}",
|
||||
Paint::default(line + 1).bold(), Paint::default(col + 1).bold());
|
||||
}
|
||||
}
|
||||
BadEnvVal(ref key, ref value, ref error) => {
|
||||
error!("environment variable {} could not be parsed",
|
||||
Paint::default(format!("ROCKET_{}={}", key.to_uppercase(), value)).bold());
|
||||
info_!("{}", error);
|
||||
}
|
||||
UnknownKey(ref key) => {
|
||||
error!("the configuration key {} is unknown and disallowed in \
|
||||
this position", Paint::default(key).bold());
|
||||
}
|
||||
Missing(ref key) => {
|
||||
error!("missing configuration key: {}", Paint::default(key).bold());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is of `NotFound` variant.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::ConfigError;
|
||||
///
|
||||
/// let error = ConfigError::NotFound;
|
||||
/// assert!(error.is_not_found());
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn is_not_found(&self) -> bool {
|
||||
match *self {
|
||||
NotFound => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ConfigError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
NotFound => write!(f, "config file was not found"),
|
||||
IoError => write!(f, "I/O error while reading the config file"),
|
||||
RandFailure => write!(f, "randomness could not be retrieved from the OS"),
|
||||
Io(ref e, p) => write!(f, "I/O error while setting '{}': {}", p, e),
|
||||
BadFilePath(ref p, _) => write!(f, "{:?} is not a valid config path", p),
|
||||
BadEnv(ref e) => write!(f, "{:?} is not a valid `ROCKET_ENV` value", e),
|
||||
ParseError(..) => write!(f, "the config file contains invalid TOML"),
|
||||
UnknownKey(ref k) => write!(f, "'{}' is an unknown key", k),
|
||||
Missing(ref k) => write!(f, "missing key: '{}'", k),
|
||||
BadEntry(ref e, _) => {
|
||||
write!(f, "{:?} is not a valid `[environment]` entry", e)
|
||||
}
|
||||
BadType(ref n, e, a, _) => {
|
||||
write!(f, "type mismatch for '{}'. expected {}, found {}", n, e, a)
|
||||
}
|
||||
BadEnvVal(ref k, ref v, _) => {
|
||||
write!(f, "environment variable '{}={}' could not be parsed", k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ConfigError {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
NotFound => "config file was not found",
|
||||
IoError => "there was an I/O error while reading the config file",
|
||||
RandFailure => "randomness could not be retrieved from the OS",
|
||||
Io(..) => "an I/O error occurred while setting a configuration parameter",
|
||||
BadFilePath(..) => "the config file path is invalid",
|
||||
BadEntry(..) => "an environment specified as `[environment]` is invalid",
|
||||
BadEnv(..) => "the environment specified in `ROCKET_ENV` is invalid",
|
||||
ParseError(..) => "the config file contains invalid TOML",
|
||||
BadType(..) => "a key was specified with a value of the wrong type",
|
||||
BadEnvVal(..) => "an environment variable could not be parsed",
|
||||
UnknownKey(..) => "an unknown key was used in a disallowed position",
|
||||
Missing(..) => "an expected key was not found",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ConfigError {
|
||||
fn eq(&self, other: &ConfigError) -> bool {
|
||||
match (self, other) {
|
||||
(&NotFound, &NotFound) => true,
|
||||
(&IoError, &IoError) => true,
|
||||
(&RandFailure, &RandFailure) => true,
|
||||
(&Io(_, p1), &Io(_, p2)) => p1 == p2,
|
||||
(&BadFilePath(ref p1, _), &BadFilePath(ref p2, _)) => p1 == p2,
|
||||
(&BadEnv(ref e1), &BadEnv(ref e2)) => e1 == e2,
|
||||
(&ParseError(..), &ParseError(..)) => true,
|
||||
(&UnknownKey(ref k1), &UnknownKey(ref k2)) => k1 == k2,
|
||||
(&BadEntry(ref e1, _), &BadEntry(ref e2, _)) => e1 == e2,
|
||||
(&BadType(ref n1, e1, a1, _), &BadType(ref n2, e2, a2, _)) => {
|
||||
n1 == n2 && e1 == e2 && a1 == a2
|
||||
}
|
||||
(&BadEnvVal(ref k1, ref v1, _), &BadEnvVal(ref k2, ref v2, _)) => {
|
||||
k1 == k2 && v1 == v2
|
||||
}
|
||||
(&Missing(ref k1), &Missing(ref k2)) => k1 == k2,
|
||||
(&NotFound, _) | (&IoError, _) | (&RandFailure, _) | (&Io(..), _)
|
||||
| (&BadFilePath(..), _) | (&BadEnv(..), _) | (&ParseError(..), _)
|
||||
| (&UnknownKey(..), _) | (&BadEntry(..), _) | (&BadType(..), _)
|
||||
| (&BadEnvVal(..), _) | (&Missing(..), _) => false
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,218 @@
|
|||
use std::fmt;
|
||||
use std::ops::Deref;
|
||||
|
||||
use serde::{de, ser, Deserialize, Serialize};
|
||||
|
||||
use crate::http::private::cookie::Key;
|
||||
use crate::request::{Outcome, Request, FromRequest};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
enum Kind {
|
||||
Zero,
|
||||
Generated,
|
||||
Provided
|
||||
}
|
||||
|
||||
/// A cryptographically secure secret key.
|
||||
///
|
||||
/// A `SecretKey` is primarily used by [private cookies]. See the [configuration
|
||||
/// guide] for further details. It can be configured from 256-bit random
|
||||
/// material or a 512-bit master key, each as either a base64-encoded string or
|
||||
/// raw bytes. When compiled in debug mode with the `secrets` feature enabled, a
|
||||
/// key set a `0` is automatically regenerated from the OS's random source if
|
||||
/// available.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use rocket::figment::Figment;
|
||||
/// let figment = Figment::from(rocket::Config::default())
|
||||
/// .merge(("secret_key", "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk="));
|
||||
///
|
||||
/// assert!(!rocket::Config::from(figment).secret_key.is_zero());
|
||||
///
|
||||
/// let figment = Figment::from(rocket::Config::default())
|
||||
/// .merge(("secret_key", vec![0u8; 64]));
|
||||
///
|
||||
/// # /* as far as I can tell, there's no way to test this properly
|
||||
/// # https://github.com/rust-lang/cargo/issues/6570
|
||||
/// # https://github.com/rust-lang/cargo/issues/4737
|
||||
/// # https://github.com/rust-lang/rust/issues/43031
|
||||
/// assert!(!rocket::Config::from(figment).secret_key.is_zero());
|
||||
/// # */
|
||||
/// ```
|
||||
///
|
||||
/// [private cookies]: https://rocket.rs/v0.5/guide/requests/#private-cookies
|
||||
/// [configuration guide]: https://rocket.rs/v0.5/guide/configuration/#secret-key
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub struct SecretKey {
|
||||
key: Key,
|
||||
kind: Kind,
|
||||
}
|
||||
|
||||
impl SecretKey {
|
||||
/// Returns a secret key that is all zeroes.
|
||||
pub(crate) fn zero() -> SecretKey {
|
||||
SecretKey { key: Key::from(&[0; 64]), kind: Kind::Zero }
|
||||
}
|
||||
|
||||
/// Creates a `SecretKey` from a 512-bit `master` key. For security,
|
||||
/// `master` _must_ be cryptographically random.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `master` < 64 bytes.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::SecretKey;
|
||||
///
|
||||
/// # let master = vec![0u8; 64];
|
||||
/// let key = SecretKey::from(&master);
|
||||
/// ```
|
||||
pub fn from(master: &[u8]) -> SecretKey {
|
||||
let kind = match master.iter().all(|&b| b == 0) {
|
||||
true => Kind::Zero,
|
||||
false => Kind::Provided
|
||||
};
|
||||
|
||||
SecretKey { key: Key::from(master), kind }
|
||||
}
|
||||
|
||||
/// Derives a `SecretKey` from 256 bits of cryptographically random
|
||||
/// `material`. For security, `material` _must_ be cryptographically random.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `material` < 32 bytes.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::SecretKey;
|
||||
///
|
||||
/// # let material = vec![0u8; 32];
|
||||
/// let key = SecretKey::derive_from(&material);
|
||||
/// ```
|
||||
pub fn derive_from(material: &[u8]) -> SecretKey {
|
||||
SecretKey { key: Key::derive_from(material), kind: Kind::Provided }
|
||||
}
|
||||
|
||||
/// Attempts to generate a `SecretKey` from randomness retrieved from the
|
||||
/// OS. If randomness from the OS isn't available, returns `None`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::SecretKey;
|
||||
///
|
||||
/// let key = SecretKey::generate();
|
||||
/// ```
|
||||
pub fn generate() -> Option<SecretKey> {
|
||||
Some(SecretKey { key: Key::try_generate()?, kind: Kind::Generated })
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is the `0`-key.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::SecretKey;
|
||||
///
|
||||
/// let master = vec![0u8; 64];
|
||||
/// let key = SecretKey::from(&master);
|
||||
/// assert!(key.is_zero());
|
||||
/// ```
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.kind == Kind::Zero
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl Deref for SecretKey {
|
||||
type Target = Key;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.key
|
||||
}
|
||||
}
|
||||
|
||||
#[crate::async_trait]
|
||||
impl<'a, 'r> FromRequest<'a, 'r> for &'a SecretKey {
|
||||
type Error = std::convert::Infallible;
|
||||
|
||||
async fn from_request(req: &'a Request<'r>) -> Outcome<Self, Self::Error> {
|
||||
Outcome::Success(&req.state.config.secret_key)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for SecretKey {
|
||||
fn serialize<S: ser::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
|
||||
// We encode as "zero" to avoid leaking the key.
|
||||
ser.serialize_bytes(&[0; 32][..])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SecretKey {
|
||||
fn deserialize<D: de::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
|
||||
use {binascii::{b64decode, hex2bin}, de::Unexpected::Str};
|
||||
|
||||
struct Visitor;
|
||||
|
||||
impl<'de> de::Visitor<'de> for Visitor {
|
||||
type Value = SecretKey;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("256-bit base64 or hex string, or 32-byte slice")
|
||||
}
|
||||
|
||||
fn visit_str<E: de::Error>(self, val: &str) -> Result<SecretKey, E> {
|
||||
let e = |s| E::invalid_value(Str(s), &"256-bit base64 or hex");
|
||||
|
||||
// `binascii` requires a more space than actual output for padding
|
||||
let mut buf = [0u8; 96];
|
||||
let bytes = match val.len() {
|
||||
44 | 88 => b64decode(val.as_bytes(), &mut buf).map_err(|_| e(val))?,
|
||||
64 => hex2bin(val.as_bytes(), &mut buf).map_err(|_| e(val))?,
|
||||
n => Err(E::invalid_length(n, &"44 or 88 for base64, 64 for hex"))?
|
||||
};
|
||||
|
||||
self.visit_bytes(bytes)
|
||||
}
|
||||
|
||||
fn visit_bytes<E: de::Error>(self, bytes: &[u8]) -> Result<SecretKey, E> {
|
||||
if bytes.len() < 32 {
|
||||
Err(E::invalid_length(bytes.len(), &"at least 32"))
|
||||
} else if bytes.iter().all(|b| *b == 0) {
|
||||
Ok(SecretKey::zero())
|
||||
} else if bytes.len() >= 64 {
|
||||
Ok(SecretKey::from(bytes))
|
||||
} else {
|
||||
Ok(SecretKey::derive_from(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where A: de::SeqAccess<'de>
|
||||
{
|
||||
let mut bytes = Vec::with_capacity(seq.size_hint().unwrap_or(0));
|
||||
while let Some(byte) = seq.next_element()? {
|
||||
bytes.push(byte);
|
||||
}
|
||||
|
||||
self.visit_bytes(&bytes)
|
||||
}
|
||||
}
|
||||
|
||||
de.deserialize_any(Visitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for SecretKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.kind {
|
||||
Kind::Zero => f.write_str("[zero]"),
|
||||
Kind::Generated => f.write_str("[generated]"),
|
||||
Kind::Provided => f.write_str("[provided]"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
use figment::value::magic::{Either, RelativePathBuf};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// TLS configuration: a certificate chain and a private key.
|
||||
///
|
||||
/// Both `certs` and `key` can be configured as a path or as raw bytes. `certs`
|
||||
/// must be a DER-encoded X.509 TLS certificate chain, while `key` must be a
|
||||
/// DER-encoded ASN.1 key in either PKCS#8 or PKCS#1 format.
|
||||
///
|
||||
/// The following example illustrates manual configuration:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use rocket::figment::Figment;
|
||||
/// let figment = Figment::from(rocket::Config::default())
|
||||
/// .merge(("tls.certs", "strings/are/paths/certs.pem"))
|
||||
/// .merge(("tls.key", vec![0; 32]));
|
||||
///
|
||||
/// let config = rocket::Config::from(figment);
|
||||
/// let tls_config = config.tls.as_ref().unwrap();
|
||||
/// assert!(tls_config.certs().is_left());
|
||||
/// assert!(tls_config.key().is_right());
|
||||
/// ```
|
||||
///
|
||||
/// When a path is configured in a file source, such as `Rocket.toml`, relative
|
||||
/// paths are interpreted as being relative to the source file's directory.
|
||||
#[derive(PartialEq, Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct TlsConfig {
|
||||
/// Path or raw bytes for the DER-encoded X.509 TLS certificate chain.
|
||||
pub(crate) certs: Either<RelativePathBuf, Vec<u8>>,
|
||||
/// Path or raw bytes to DER-encoded ASN.1 key in either PKCS#8 or PKCS#1
|
||||
/// format.
|
||||
pub(crate) key: Either<RelativePathBuf, Vec<u8>>,
|
||||
}
|
||||
|
||||
impl TlsConfig {
|
||||
/// Constructs a `TlsConfig` from paths to a `certs` certificate-chain
|
||||
/// a `key` private-key. This method does no validation; it simply creates a
|
||||
/// structure suitable for passing into a [`Config`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::TlsConfig;
|
||||
///
|
||||
/// let tls_config = TlsConfig::from_paths("/ssl/certs.pem", "/ssl/key.pem");
|
||||
/// ```
|
||||
pub fn from_paths<C, K>(certs: C, key: K) -> Self
|
||||
where C: AsRef<std::path::Path>, K: AsRef<std::path::Path>
|
||||
{
|
||||
TlsConfig {
|
||||
certs: Either::Left(certs.as_ref().to_path_buf().into()),
|
||||
key: Either::Left(key.as_ref().to_path_buf().into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a `TlsConfig` from byte buffers to a `certs`
|
||||
/// certificate-chain a `key` private-key. This method does no validation;
|
||||
/// it simply creates a structure suitable for passing into a [`Config`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::TlsConfig;
|
||||
///
|
||||
/// # let certs_buf = &[];
|
||||
/// # let key_buf = &[];
|
||||
/// let tls_config = TlsConfig::from_bytes(certs_buf, key_buf);
|
||||
/// ```
|
||||
pub fn from_bytes(certs: &[u8], key: &[u8]) -> Self {
|
||||
TlsConfig {
|
||||
certs: Either::Right(certs.to_vec().into()),
|
||||
key: Either::Right(key.to_vec().into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value of the `certs` parameter.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use rocket::figment::Figment;
|
||||
/// let figment = Figment::from(rocket::Config::default())
|
||||
/// .merge(("tls.certs", vec![0; 32]))
|
||||
/// .merge(("tls.key", "/etc/ssl/key.pem"));
|
||||
///
|
||||
/// let config = rocket::Config::from(figment);
|
||||
/// let tls_config = config.tls.as_ref().unwrap();
|
||||
/// let cert_bytes = tls_config.certs().right().unwrap();
|
||||
/// assert!(cert_bytes.iter().all(|&b| b == 0));
|
||||
/// ```
|
||||
pub fn certs(&self) -> either::Either<std::path::PathBuf, &[u8]> {
|
||||
match &self.certs {
|
||||
Either::Left(path) => either::Either::Left(path.relative()),
|
||||
Either::Right(bytes) => either::Either::Right(&bytes),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value of the `key` parameter.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use rocket::figment::Figment;
|
||||
/// # use std::path::Path;
|
||||
/// let figment = Figment::from(rocket::Config::default())
|
||||
/// .merge(("tls.certs", vec![0; 32]))
|
||||
/// .merge(("tls.key", "/etc/ssl/key.pem"));
|
||||
///
|
||||
/// let config = rocket::Config::from(figment);
|
||||
/// let tls_config = config.tls.as_ref().unwrap();
|
||||
/// let key_path = tls_config.key().left().unwrap();
|
||||
/// assert_eq!(key_path, Path::new("/etc/ssl/key.pem"));
|
||||
/// ```
|
||||
pub fn key(&self) -> either::Either<std::path::PathBuf, &[u8]> {
|
||||
match &self.key {
|
||||
Either::Left(path) => either::Either::Left(path.relative()),
|
||||
Either::Right(bytes) => either::Either::Right(&bytes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
type Reader = Box<dyn std::io::BufRead + Sync + Send>;
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
impl TlsConfig {
|
||||
pub(crate) fn to_readers(&self) -> std::io::Result<(Reader, Reader)> {
|
||||
use std::{io::{self, Error}, fs};
|
||||
use yansi::Paint;
|
||||
|
||||
fn to_reader(value: &Either<RelativePathBuf, Vec<u8>>) -> io::Result<Reader> {
|
||||
match value {
|
||||
Either::Left(path) => {
|
||||
let path = path.relative();
|
||||
let file = fs::File::open(&path).map_err(move |e| {
|
||||
Error::new(e.kind(), format!("error reading TLS file `{}`: {}",
|
||||
Paint::white(figment::Source::File(path)), e))
|
||||
})?;
|
||||
|
||||
Ok(Box::new(io::BufReader::new(file)))
|
||||
}
|
||||
Either::Right(vec) => Ok(Box::new(io::Cursor::new(vec.clone()))),
|
||||
}
|
||||
}
|
||||
|
||||
Ok((to_reader(&self.certs)?, to_reader(&self.key)?))
|
||||
}
|
||||
}
|
|
@ -1,172 +0,0 @@
|
|||
use std::fmt;
|
||||
use std::result::Result as StdResult;
|
||||
|
||||
use crate::config::Value;
|
||||
|
||||
use pear::macros::{parse, parser, switch};
|
||||
use pear::parsers::*;
|
||||
use pear::combinators::*;
|
||||
|
||||
type Input<'a> = pear::input::Pear<&'a str>;
|
||||
type Result<'a, T> = pear::input::Result<T, Input<'a>>;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_whitespace(&byte: &char) -> bool {
|
||||
byte.is_ascii_whitespace()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_not_separator(&byte: &char) -> bool {
|
||||
match byte {
|
||||
',' | '{' | '}' | '[' | ']' => false,
|
||||
_ => true
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Be more permissive here?
|
||||
#[inline(always)]
|
||||
fn is_ident_char(&byte: &char) -> bool {
|
||||
byte.is_ascii_alphanumeric() || byte == '_' || byte == '-'
|
||||
}
|
||||
|
||||
#[parser]
|
||||
fn array<'a>(input: &mut Input<'a>) -> Result<'a, Value> {
|
||||
Value::Array(delimited_collect('[', value, ',', ']')?)
|
||||
}
|
||||
|
||||
#[parser]
|
||||
fn key<'a>(input: &mut Input<'a>) -> Result<'a, String> {
|
||||
take_some_while(is_ident_char)?.to_string()
|
||||
}
|
||||
|
||||
#[parser]
|
||||
fn key_value<'a>(input: &mut Input<'a>) -> Result<'a, (String, Value)> {
|
||||
let key = (surrounded(key, is_whitespace)?, eat('=')?).0.to_string();
|
||||
(key, surrounded(value, is_whitespace)?)
|
||||
}
|
||||
|
||||
#[parser]
|
||||
fn table<'a>(input: &mut Input<'a>) -> Result<'a, Value> {
|
||||
Value::Table(delimited_collect('{', key_value, ',', '}')?)
|
||||
}
|
||||
|
||||
#[parser]
|
||||
fn value<'a>(input: &mut Input<'a>) -> Result<'a, Value> {
|
||||
skip_while(is_whitespace)?;
|
||||
let val = switch! {
|
||||
eat_slice("true") => Value::Boolean(true),
|
||||
eat_slice("false") => Value::Boolean(false),
|
||||
peek('{') => table()?,
|
||||
peek('[') => array()?,
|
||||
peek('"') => Value::String(delimited('"', |_| true, '"')?.to_string()),
|
||||
_ => {
|
||||
let value_str = take_some_while(is_not_separator)?;
|
||||
if let Ok(int) = value_str.parse::<i64>() {
|
||||
Value::Integer(int)
|
||||
} else if let Ok(float) = value_str.parse::<f64>() {
|
||||
Value::Float(float)
|
||||
} else {
|
||||
Value::String(value_str.into())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
skip_while(is_whitespace)?;
|
||||
val
|
||||
}
|
||||
|
||||
pub fn parse_simple_toml_value(input: &str) -> StdResult<Value, String> {
|
||||
parse!(value: input).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// A simple wrapper over a `Value` reference with a custom implementation of
|
||||
/// `Display`. This is used to log config values at initialization.
|
||||
pub struct LoggedValue<'a>(pub &'a Value);
|
||||
|
||||
impl fmt::Display for LoggedValue<'_> {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use crate::config::Value::*;
|
||||
match *self.0 {
|
||||
String(_) | Integer(_) | Float(_) | Boolean(_) | Datetime(_) | Array(_) => {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
Table(ref map) => {
|
||||
write!(f, "{{ ")?;
|
||||
for (i, (key, val)) in map.iter().enumerate() {
|
||||
write!(f, "{} = {}", key, LoggedValue(val))?;
|
||||
if i != map.len() - 1 { write!(f, ", ")?; }
|
||||
}
|
||||
|
||||
write!(f, " }}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use toml::map::Map;
|
||||
|
||||
use super::parse_simple_toml_value;
|
||||
use super::Value::{self, *};
|
||||
|
||||
macro_rules! assert_parse {
|
||||
($string:expr, $value:expr) => (
|
||||
match parse_simple_toml_value($string) {
|
||||
Ok(value) => assert_eq!(value, $value),
|
||||
Err(e) => panic!("{:?} failed to parse: {:?}", $string, e)
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_toml_values() {
|
||||
assert_parse!("1", Integer(1));
|
||||
assert_parse!("1.32", Float(1.32));
|
||||
assert_parse!("true", Boolean(true));
|
||||
assert_parse!("false", Boolean(false));
|
||||
assert_parse!("\"hello, WORLD!\"", String("hello, WORLD!".into()));
|
||||
assert_parse!("hi", String("hi".into()));
|
||||
assert_parse!("\"hi\"", String("hi".into()));
|
||||
|
||||
assert_parse!("[]", Array(Vec::new()));
|
||||
assert_parse!("[1]", vec![1].into());
|
||||
assert_parse!("[1, 2, 3]", vec![1, 2, 3].into());
|
||||
assert_parse!("[1.32, 2]", Array(vec![1.32.into(), 2.into()]));
|
||||
|
||||
assert_parse!("{}", Table(Map::new()));
|
||||
|
||||
assert_parse!("{a=b}", Table({
|
||||
let mut map = Map::new();
|
||||
map.insert("a".into(), "b".into());
|
||||
map
|
||||
}));
|
||||
|
||||
assert_parse!("{v=1, on=true,pi=3.14}", Table({
|
||||
let mut map = Map::new();
|
||||
map.insert("v".into(), 1.into());
|
||||
map.insert("on".into(), true.into());
|
||||
map.insert("pi".into(), 3.14.into());
|
||||
map
|
||||
}));
|
||||
|
||||
assert_parse!("{v=[1, 2, 3], v2=[a, \"b\"], on=true,pi=3.14}", Table({
|
||||
let mut map = Map::new();
|
||||
map.insert("v".into(), vec![1, 2, 3].into());
|
||||
map.insert("v2".into(), vec!["a", "b"].into());
|
||||
map.insert("on".into(), true.into());
|
||||
map.insert("pi".into(), 3.14.into());
|
||||
map
|
||||
}));
|
||||
|
||||
assert_parse!("{v=[[1], [2, 3], [4,5]]}", Table({
|
||||
let mut map = Map::new();
|
||||
let first: Value = vec![1].into();
|
||||
let second: Value = vec![2, 3].into();
|
||||
let third: Value = vec![4, 5].into();
|
||||
map.insert("v".into(), vec![first, second, third].into());
|
||||
map
|
||||
}));
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ use crate::outcome::{self, IntoOutcome};
|
|||
use crate::outcome::Outcome::*;
|
||||
use crate::http::Status;
|
||||
use crate::request::Request;
|
||||
use crate::data::{Data, ByteUnit};
|
||||
use crate::data::Data;
|
||||
|
||||
/// Type alias for the `Outcome` of a `FromTransformedData` conversion.
|
||||
pub type Outcome<S, E> = outcome::Outcome<S, (Status, E), Data>;
|
||||
|
@ -417,7 +417,7 @@ impl<'a> FromTransformedData<'a> for Data {
|
|||
}
|
||||
}
|
||||
|
||||
/// A varaint of [`FromTransformedData`] for data guards that don't require
|
||||
/// A variant of [`FromTransformedData`] for data guards that don't require
|
||||
/// transformations.
|
||||
///
|
||||
/// When transformation of incoming data isn't required, data guards should
|
||||
|
@ -502,7 +502,8 @@ impl<'a> FromTransformedData<'a> for Data {
|
|||
/// }
|
||||
///
|
||||
/// // Read the data into a String.
|
||||
/// let string = match data.open(LIMIT).stream_to_string().await {
|
||||
/// let limit = req.limits().get("person").unwrap_or(LIMIT);
|
||||
/// let string = match data.open(limit).stream_to_string().await {
|
||||
/// Ok(string) => string,
|
||||
/// Err(e) => return Outcome::Failure((Status::InternalServerError, format!("{}", e)))
|
||||
/// };
|
||||
|
@ -602,7 +603,10 @@ impl<'a, T: FromTransformedData<'a> + 'a> FromTransformedData<'a> for Option<T>
|
|||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
use crate::data::ByteUnit;
|
||||
|
||||
#[crate::async_trait]
|
||||
#[cfg(debug_assertions)]
|
||||
impl FromData for String {
|
||||
type Error = std::io::Error;
|
||||
|
||||
|
@ -615,8 +619,8 @@ impl FromData for String {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[crate::async_trait]
|
||||
#[cfg(debug_assertions)]
|
||||
impl FromData for Vec<u8> {
|
||||
type Error = std::io::Error;
|
||||
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use std::fmt;
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crate::request::{Request, FromRequest, Outcome};
|
||||
|
||||
use crate::data::{ByteUnit, ToByteUnit};
|
||||
|
||||
/// Mapping from data type to size limits.
|
||||
/// Mapping from data types to read limits.
|
||||
///
|
||||
/// A `Limits` structure contains a mapping from a given data type ("forms",
|
||||
/// "json", and so on) to the maximum size in bytes that should be accepted by a
|
||||
|
@ -12,7 +15,7 @@ use crate::data::{ByteUnit, ToByteUnit};
|
|||
///
|
||||
/// # Defaults
|
||||
///
|
||||
/// As documented in [`config`](crate::config), the default limits are as follows:
|
||||
/// The default limits are:
|
||||
///
|
||||
/// * **forms**: 32KiB
|
||||
///
|
||||
|
@ -24,38 +27,82 @@ use crate::data::{ByteUnit, ToByteUnit};
|
|||
/// use rocket::data::{Limits, ToByteUnit};
|
||||
///
|
||||
/// // Set a limit of 64KiB for forms and 3MiB for JSON.
|
||||
/// let limits = Limits::new()
|
||||
/// let limits = Limits::default()
|
||||
/// .limit("forms", 64.kibibytes())
|
||||
/// .limit("json", 3.mebibytes());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
///
|
||||
/// The configured limits can be retrieved via the `&Limits` request guard:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use std::io;
|
||||
///
|
||||
/// use rocket::data::{Data, Limits, ToByteUnit};
|
||||
/// use rocket::response::Debug;
|
||||
///
|
||||
/// #[post("/echo", data = "<data>")]
|
||||
/// async fn echo(data: Data, limits: &Limits) -> Result<String, Debug<io::Error>> {
|
||||
/// let limit = limits.get("data").unwrap_or(1.mebibytes());
|
||||
/// Ok(data.open(limit).stream_to_string().await?)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ...or via the [`Request::limits()`] method:
|
||||
///
|
||||
/// ```
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::request::Request;
|
||||
/// use rocket::data::{self, Data, FromData};
|
||||
///
|
||||
/// # struct MyType;
|
||||
/// # type MyError = ();
|
||||
/// #[rocket::async_trait]
|
||||
/// impl FromData for MyType {
|
||||
/// type Error = MyError;
|
||||
///
|
||||
/// async fn from_data(req: &Request<'_>, data: Data) -> data::Outcome<Self, MyError> {
|
||||
/// let limit = req.limits().get("my-data-type");
|
||||
/// /* .. */
|
||||
/// # unimplemented!()
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[serde(transparent)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Limits {
|
||||
// We cache this internally but don't share that fact in the API.
|
||||
pub(crate) forms: ByteUnit,
|
||||
extra: Vec<(String, ByteUnit)>
|
||||
#[serde(with = "figment::util::vec_tuple_map")]
|
||||
limits: Vec<(String, ByteUnit)>
|
||||
}
|
||||
|
||||
/// The default limits are:
|
||||
///
|
||||
/// * **forms**: 32KiB
|
||||
impl Default for Limits {
|
||||
fn default() -> Limits {
|
||||
// Default limit for forms is 32KiB.
|
||||
Limits { forms: 32.kibibytes(), extra: Vec::new() }
|
||||
Limits { limits: vec![("forms".into(), 32.kibibytes())] }
|
||||
}
|
||||
}
|
||||
|
||||
impl Limits {
|
||||
/// Construct a new `Limits` structure with the default limits set.
|
||||
/// Construct a new `Limits` structure with no limits set.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::data::{Limits, ToByteUnit};
|
||||
///
|
||||
/// let limits = Limits::new();
|
||||
/// let limits = Limits::default();
|
||||
/// assert_eq!(limits.get("forms"), Some(32.kibibytes()));
|
||||
///
|
||||
/// let limits = Limits::new();
|
||||
/// assert_eq!(limits.get("forms"), None);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Limits::default()
|
||||
Limits { limits: vec![] }
|
||||
}
|
||||
|
||||
/// Adds or replaces a limit in `self`, consuming `self` and returning a new
|
||||
|
@ -66,7 +113,7 @@ impl Limits {
|
|||
/// ```rust
|
||||
/// use rocket::data::{Limits, ToByteUnit};
|
||||
///
|
||||
/// let limits = Limits::new().limit("json", 1.mebibytes());
|
||||
/// let limits = Limits::default().limit("json", 1.mebibytes());
|
||||
///
|
||||
/// assert_eq!(limits.get("forms"), Some(32.kibibytes()));
|
||||
/// assert_eq!(limits.get("json"), Some(1.mebibytes()));
|
||||
|
@ -76,24 +123,12 @@ impl Limits {
|
|||
/// ```
|
||||
pub fn limit<S: Into<String>>(mut self, name: S, limit: ByteUnit) -> Self {
|
||||
let name = name.into();
|
||||
match name.as_str() {
|
||||
"forms" => self.forms = limit,
|
||||
_ => {
|
||||
let mut found = false;
|
||||
for tuple in &mut self.extra {
|
||||
if tuple.0 == name {
|
||||
tuple.1 = limit;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
self.extra.push((name, limit))
|
||||
}
|
||||
}
|
||||
match self.limits.iter_mut().find(|(k, _)| *k == name) {
|
||||
Some((_, v)) => *v = limit,
|
||||
None => self.limits.push((name, limit)),
|
||||
}
|
||||
|
||||
self.limits.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -104,35 +139,35 @@ impl Limits {
|
|||
/// ```rust
|
||||
/// use rocket::data::{Limits, ToByteUnit};
|
||||
///
|
||||
/// let limits = Limits::new().limit("json", 64.mebibytes());
|
||||
/// let limits = Limits::default().limit("json", 64.mebibytes());
|
||||
///
|
||||
/// assert_eq!(limits.get("forms"), Some(32.kibibytes()));
|
||||
/// assert_eq!(limits.get("json"), Some(64.mebibytes()));
|
||||
/// assert!(limits.get("msgpack").is_none());
|
||||
/// ```
|
||||
pub fn get(&self, name: &str) -> Option<ByteUnit> {
|
||||
if name == "forms" {
|
||||
return Some(self.forms);
|
||||
}
|
||||
|
||||
for &(ref key, val) in &self.extra {
|
||||
if key == name {
|
||||
return Some(val);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
self.limits.iter()
|
||||
.find(|(k, _)| *k == name)
|
||||
.map(|(_, v)| *v)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Limits {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "forms = {}", self.forms)?;
|
||||
for (key, val) in &self.extra {
|
||||
write!(f, ", {}* = {}", key, val)?;
|
||||
for (i, (k, v)) in self.limits.iter().enumerate() {
|
||||
if i != 0 { f.write_str(", ")? }
|
||||
write!(f, "{} = {}", k, v)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[crate::async_trait]
|
||||
impl<'a, 'r> FromRequest<'a, 'r> for &'r Limits {
|
||||
type Error = std::convert::Infallible;
|
||||
|
||||
async fn from_request(req: &'a Request<'r>) -> Outcome<Self, Self::Error> {
|
||||
Outcome::Success(req.limits())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,81 +7,30 @@ use yansi::Paint;
|
|||
|
||||
use crate::router::Route;
|
||||
|
||||
/// An error that occurs when running a Rocket server.
|
||||
///
|
||||
/// Errors can happen immediately upon launch ([`LaunchError`])
|
||||
/// or more rarely during the server's execution.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Launch(LaunchError),
|
||||
Run(Box<dyn std::error::Error + Send + Sync>),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Error::Launch(e) => write!(f, "Rocket failed to launch: {}", e),
|
||||
Error::Run(e) => write!(f, "error while running server: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error { }
|
||||
|
||||
/// The kind of launch error that occurred.
|
||||
///
|
||||
/// In almost every instance, a launch error occurs because of an I/O error;
|
||||
/// this is represented by the `Io` variant. A launch error may also occur
|
||||
/// because of ill-defined routes that lead to collisions or because a fairing
|
||||
/// encountered an error; these are represented by the `Collision` and
|
||||
/// `FailedFairing` variants, respectively.
|
||||
#[derive(Debug)]
|
||||
pub enum LaunchErrorKind {
|
||||
/// Binding to the provided address/port failed.
|
||||
Bind(io::Error),
|
||||
/// An I/O error occurred during launch.
|
||||
Io(io::Error),
|
||||
/// Route collisions were detected.
|
||||
Collision(Vec<(Route, Route)>),
|
||||
/// A launch fairing reported an error.
|
||||
FailedFairings(Vec<&'static str>),
|
||||
}
|
||||
|
||||
/// An error that occurs during launch.
|
||||
///
|
||||
/// A `LaunchError` is returned by [`launch()`](crate::Rocket::launch()) when
|
||||
/// launching an application fails.
|
||||
/// An `Error` is returned by [`launch()`](crate::Rocket::launch()) when
|
||||
/// launching an application fails or, more rarely, when the runtime fails after
|
||||
/// lauching.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// A value of this type panics if it is dropped without first being inspected.
|
||||
/// An _inspection_ occurs when any method is called. For instance, if
|
||||
/// `println!("Error: {}", e)` is called, where `e: LaunchError`, the
|
||||
/// `Display::fmt` method being called by `println!` results in `e` being marked
|
||||
/// as inspected; a subsequent `drop` of the value will _not_ result in a panic.
|
||||
/// The following snippet illustrates this:
|
||||
/// `println!("Error: {}", e)` is called, where `e: Error`, the `Display::fmt`
|
||||
/// method being called by `println!` results in `e` being marked as inspected;
|
||||
/// a subsequent `drop` of the value will _not_ result in a panic. The following
|
||||
/// snippet illustrates this:
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::error::Error;
|
||||
///
|
||||
/// # let _ = async {
|
||||
/// if let Err(error) = rocket::ignite().launch().await {
|
||||
/// match error {
|
||||
/// Error::Launch(error) => {
|
||||
/// // This case is only reached if launching failed. This println "inspects" the error.
|
||||
/// println!("Launch failed! Error: {}", error);
|
||||
/// // This println "inspects" the error.
|
||||
/// println!("Launch failed! Error: {}", error);
|
||||
///
|
||||
/// // This call to drop (explicit here for demonstration) will do nothing.
|
||||
/// drop(error);
|
||||
/// }
|
||||
/// Error::Run(error) => {
|
||||
/// // This case is reached if launching succeeds, but the server had a fatal error later
|
||||
/// println!("Server failed! Error: {}", error);
|
||||
/// }
|
||||
/// }
|
||||
/// // This call to drop (explicit here for demonstration) will do nothing.
|
||||
/// drop(error);
|
||||
/// }
|
||||
///
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
|
@ -100,8 +49,8 @@ pub enum LaunchErrorKind {
|
|||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// A `LaunchError` value should usually be allowed to `drop` without
|
||||
/// inspection. There are two exceptions to this suggestion.
|
||||
/// An `Error` value should usually be allowed to `drop` without inspection.
|
||||
/// There are at least two exceptions:
|
||||
///
|
||||
/// 1. If you are writing a library or high-level application on-top of
|
||||
/// Rocket, you likely want to inspect the value before it drops to avoid a
|
||||
|
@ -109,15 +58,42 @@ pub enum LaunchErrorKind {
|
|||
/// value.
|
||||
///
|
||||
/// 2. You want to display your own error messages.
|
||||
pub struct LaunchError {
|
||||
pub struct Error {
|
||||
handled: AtomicBool,
|
||||
kind: LaunchErrorKind
|
||||
kind: ErrorKind
|
||||
}
|
||||
|
||||
impl LaunchError {
|
||||
/// The kind error that occurred.
|
||||
///
|
||||
/// In almost every instance, a launch error occurs because of an I/O error;
|
||||
/// this is represented by the `Io` variant. A launch error may also occur
|
||||
/// because of ill-defined routes that lead to collisions or because a fairing
|
||||
/// encountered an error; these are represented by the `Collision` and
|
||||
/// `FailedFairing` variants, respectively.
|
||||
#[derive(Debug)]
|
||||
pub enum ErrorKind {
|
||||
/// Binding to the provided address/port failed.
|
||||
Bind(io::Error),
|
||||
/// An I/O error occurred during launch.
|
||||
Io(io::Error),
|
||||
/// An I/O error occurred in the runtime.
|
||||
Runtime(Box<dyn std::error::Error + Send + Sync>),
|
||||
/// Route collisions were detected.
|
||||
Collision(Vec<(Route, Route)>),
|
||||
/// A launch fairing reported an error.
|
||||
FailedFairings(Vec<&'static str>),
|
||||
}
|
||||
|
||||
impl From<ErrorKind> for Error {
|
||||
fn from(kind: ErrorKind) -> Self {
|
||||
Error::new(kind)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
#[inline(always)]
|
||||
pub(crate) fn new(kind: LaunchErrorKind) -> LaunchError {
|
||||
LaunchError { handled: AtomicBool::new(false), kind }
|
||||
pub(crate) fn new(kind: ErrorKind) -> Error {
|
||||
Error { handled: AtomicBool::new(false), kind }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -135,43 +111,38 @@ impl LaunchError {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::error::Error;
|
||||
/// use rocket::error::ErrorKind;
|
||||
///
|
||||
/// # let _ = async {
|
||||
/// if let Err(error) = rocket::ignite().launch().await {
|
||||
/// match error {
|
||||
/// Error::Launch(err) => println!("Found a launch error: {}", err.kind()),
|
||||
/// Error::Run(err) => println!("Error at runtime"),
|
||||
/// match error.kind() {
|
||||
/// ErrorKind::Io(e) => println!("found an i/o launch error: {}", e),
|
||||
/// e => println!("something else happened: {}", e)
|
||||
/// }
|
||||
/// }
|
||||
/// # };
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn kind(&self) -> &LaunchErrorKind {
|
||||
pub fn kind(&self) -> &ErrorKind {
|
||||
self.mark_handled();
|
||||
&self.kind
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for LaunchError {
|
||||
#[inline]
|
||||
fn from(error: io::Error) -> LaunchError {
|
||||
LaunchError::new(LaunchErrorKind::Io(error))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LaunchErrorKind {
|
||||
impl fmt::Display for ErrorKind {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
LaunchErrorKind::Bind(ref e) => write!(f, "binding failed: {}", e),
|
||||
LaunchErrorKind::Io(ref e) => write!(f, "I/O error: {}", e),
|
||||
LaunchErrorKind::Collision(_) => write!(f, "route collisions detected"),
|
||||
LaunchErrorKind::FailedFairings(_) => write!(f, "a launch fairing failed"),
|
||||
match self {
|
||||
ErrorKind::Bind(e) => write!(f, "binding failed: {}", e),
|
||||
ErrorKind::Io(e) => write!(f, "I/O error: {}", e),
|
||||
ErrorKind::Collision(_) => write!(f, "route collisions detected"),
|
||||
ErrorKind::FailedFairings(_) => write!(f, "a launch fairing failed"),
|
||||
ErrorKind::Runtime(e) => write!(f, "runtime error: {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for LaunchError {
|
||||
impl fmt::Debug for Error {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.mark_handled();
|
||||
|
@ -179,7 +150,7 @@ impl fmt::Debug for LaunchError {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LaunchError {
|
||||
impl fmt::Display for Error {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.mark_handled();
|
||||
|
@ -187,22 +158,24 @@ impl fmt::Display for LaunchError {
|
|||
}
|
||||
}
|
||||
|
||||
impl Drop for LaunchError {
|
||||
impl Drop for Error {
|
||||
fn drop(&mut self) {
|
||||
if self.was_handled() {
|
||||
return
|
||||
}
|
||||
|
||||
match *self.kind() {
|
||||
LaunchErrorKind::Bind(ref e) => {
|
||||
ErrorKind::Bind(ref e) => {
|
||||
error!("Rocket failed to bind network socket to given address/port.");
|
||||
panic!("{}", e);
|
||||
info_!("{}", e);
|
||||
panic!("aborting due to binding o error");
|
||||
}
|
||||
LaunchErrorKind::Io(ref e) => {
|
||||
ErrorKind::Io(ref e) => {
|
||||
error!("Rocket failed to launch due to an I/O error.");
|
||||
panic!("{}", e);
|
||||
info_!("{}", e);
|
||||
panic!("aborting due to i/o error");
|
||||
}
|
||||
LaunchErrorKind::Collision(ref collisions) => {
|
||||
ErrorKind::Collision(ref collisions) => {
|
||||
error!("Rocket failed to launch due to the following routing collisions:");
|
||||
for &(ref a, ref b) in collisions {
|
||||
info_!("{} {} {}", a, Paint::red("collides with").italic(), b)
|
||||
|
@ -211,13 +184,18 @@ impl Drop for LaunchError {
|
|||
info_!("Note: Collisions can usually be resolved by ranking routes.");
|
||||
panic!("route collisions detected");
|
||||
}
|
||||
LaunchErrorKind::FailedFairings(ref failures) => {
|
||||
ErrorKind::FailedFairings(ref failures) => {
|
||||
error!("Rocket failed to launch due to failing fairings:");
|
||||
for fairing in failures {
|
||||
info_!("{}", fairing);
|
||||
}
|
||||
|
||||
panic!("launch fairing failure");
|
||||
panic!("aborting due to launch fairing failure");
|
||||
}
|
||||
ErrorKind::Runtime(ref err) => {
|
||||
error!("An error occured in the runtime:");
|
||||
info_!("{}", err);
|
||||
panic!("aborting due to runtime failure");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,9 +92,8 @@ impl AdHoc {
|
|||
/// let fairing = AdHoc::on_attach("No-Op", |rocket| async { Ok(rocket) });
|
||||
/// ```
|
||||
pub fn on_attach<F, Fut>(name: &'static str, f: F) -> AdHoc
|
||||
where
|
||||
F: FnOnce(Rocket) -> Fut + Send + 'static,
|
||||
Fut: Future<Output=Result<Rocket, Rocket>> + Send + 'static,
|
||||
where F: FnOnce(Rocket) -> Fut + Send + 'static,
|
||||
Fut: Future<Output=Result<Rocket, Rocket>> + Send + 'static,
|
||||
{
|
||||
AdHoc {
|
||||
name,
|
||||
|
@ -102,6 +101,43 @@ impl AdHoc {
|
|||
}
|
||||
}
|
||||
|
||||
/// Constructs an `AdHoc` attach fairing that extracts a configuration of
|
||||
/// type `T` from the configured provider and stores it in managed state. If
|
||||
/// extractions fails, pretty-prints the error message and errors the attach
|
||||
/// fairing.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use serde::Deserialize;
|
||||
/// use rocket::fairing::AdHoc;
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Config {
|
||||
/// field: String,
|
||||
/// other: usize,
|
||||
/// /* and so on.. */
|
||||
/// }
|
||||
///
|
||||
/// let fairing = AdHoc::config::<Config>();
|
||||
/// ```
|
||||
pub fn config<'de, T>() -> AdHoc
|
||||
where T: serde::Deserialize<'de> + Send + Sync + 'static
|
||||
{
|
||||
AdHoc::on_attach(std::any::type_name::<T>(), |mut rocket| async {
|
||||
let figment = rocket.figment().await;
|
||||
let app_config = match figment.extract::<T>() {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
crate::config::pretty_print_error(e);
|
||||
return Err(rocket);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(rocket.manage(app_config))
|
||||
})
|
||||
}
|
||||
|
||||
/// Constructs an `AdHoc` launch fairing named `name`. The function `f` will
|
||||
/// be called by Rocket just prior to launching.
|
||||
///
|
||||
|
|
|
@ -55,24 +55,27 @@
|
|||
//! }
|
||||
//!
|
||||
//! #[launch]
|
||||
//! fn rocket() -> rocket::Rocket {
|
||||
//! fn rocket() -> _ {
|
||||
//! rocket::ignite().mount("/", routes![hello])
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Features
|
||||
//!
|
||||
//! The `secrets` feature, which enables [private cookies], is enabled by
|
||||
//! default. This necessitates pulling in additional dependencies. To avoid
|
||||
//! these dependencies when your application does not use private cookies,
|
||||
//! disable the `secrets` feature:
|
||||
//! There are two optional, disabled-by-default features:
|
||||
//!
|
||||
//! * **secrets:** Enables support for [private cookies].
|
||||
//! * **tls:** Enables support for [TLS].
|
||||
//!
|
||||
//! The features can be enabled in `Cargo.toml`:
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! rocket = { version = "0.5.0-dev", default-features = false }
|
||||
//! rocket = { version = "0.5.0-dev", features = ["secrets", "tls"] }
|
||||
//! ```
|
||||
//!
|
||||
//! [private cookies]: crate::http::CookieJar#private-cookies
|
||||
//! [private cookies]: https://rocket.rs/v0.5/guide/requests/#private-cookies
|
||||
//! [TLS]: https://rocket.rs/v0.5/guide/configuration/#tls
|
||||
//!
|
||||
//! ## Configuration
|
||||
//!
|
||||
|
@ -98,10 +101,13 @@ pub use async_trait::*;
|
|||
|
||||
#[macro_use] extern crate log;
|
||||
|
||||
/// These are public dependencies! Update docs if these are changed, especially
|
||||
/// figment's version number in docs.
|
||||
#[doc(hidden)]
|
||||
pub use yansi;
|
||||
pub use futures;
|
||||
pub use tokio;
|
||||
pub use figment;
|
||||
|
||||
#[doc(hidden)] #[macro_use] pub mod logger;
|
||||
#[macro_use] pub mod outcome;
|
||||
|
@ -132,6 +138,7 @@ mod rocket;
|
|||
mod codegen;
|
||||
mod ext;
|
||||
|
||||
#[doc(hidden)] pub use log::{info, warn, error, debug};
|
||||
#[doc(inline)] pub use crate::response::Response;
|
||||
#[doc(hidden)] pub use crate::codegen::{StaticRouteInfo, StaticCatcherInfo};
|
||||
#[doc(inline)] pub use crate::data::Data;
|
||||
|
@ -148,9 +155,9 @@ pub fn ignite() -> Rocket {
|
|||
}
|
||||
|
||||
/// Alias to [`Rocket::custom()`]. Creates a new instance of `Rocket` with a
|
||||
/// custom configuration.
|
||||
pub fn custom(config: Config) -> Rocket {
|
||||
Rocket::custom(config)
|
||||
/// custom configuration provider.
|
||||
pub fn custom<T: figment::Provider>(provider: T) -> Rocket {
|
||||
Rocket::custom(provider)
|
||||
}
|
||||
|
||||
// TODO.async: More thoughtful plan for async tests
|
||||
|
@ -170,6 +177,7 @@ pub fn async_test<R>(fut: impl std::future::Future<Output = R> + Send) -> R {
|
|||
pub fn async_main<R>(fut: impl std::future::Future<Output = R> + Send) -> R {
|
||||
tokio::runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
.thread_name("rocket-worker-thread")
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("create tokio runtime")
|
||||
|
|
|
@ -5,7 +5,7 @@ use parking_lot::RwLock;
|
|||
use crate::local::asynchronous::{LocalRequest, LocalResponse};
|
||||
use crate::rocket::{Rocket, Cargo};
|
||||
use crate::http::{private::cookie, Method};
|
||||
use crate::error::LaunchError;
|
||||
use crate::error::Error;
|
||||
|
||||
/// An `async` client to construct and dispatch local requests.
|
||||
///
|
||||
|
@ -57,7 +57,7 @@ impl Client {
|
|||
pub(crate) async fn _new(
|
||||
mut rocket: Rocket,
|
||||
tracked: bool
|
||||
) -> Result<Client, LaunchError> {
|
||||
) -> Result<Client, Error> {
|
||||
rocket.prelaunch_check().await?;
|
||||
let cargo = rocket.into_cargo().await;
|
||||
let cookies = RwLock::new(cookie::CookieJar::new());
|
||||
|
|
|
@ -89,7 +89,7 @@ impl<'c> LocalResponse<'c> {
|
|||
|
||||
async move {
|
||||
let response: Response<'c> = f(request).await;
|
||||
let mut cookies = CookieJar::new(request.state.config.secret_key());
|
||||
let mut cookies = CookieJar::new(&request.state.config.secret_key);
|
||||
for cookie in response.cookies() {
|
||||
cookies.add_original(cookie.into_owned());
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
|
||||
use crate::error::LaunchError;
|
||||
use crate::error::Error;
|
||||
use crate::local::{asynchronous, blocking::{LocalRequest, LocalResponse}};
|
||||
use crate::rocket::{Rocket, Cargo};
|
||||
use crate::http::Method;
|
||||
|
@ -31,7 +31,7 @@ pub struct Client {
|
|||
}
|
||||
|
||||
impl Client {
|
||||
fn _new(rocket: Rocket, tracked: bool) -> Result<Client, LaunchError> {
|
||||
fn _new(rocket: Rocket, tracked: bool) -> Result<Client, Error> {
|
||||
let mut runtime = tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.enable_all()
|
||||
|
|
|
@ -58,7 +58,7 @@ macro_rules! pub_client_impl {
|
|||
/// # Errors
|
||||
///
|
||||
/// If launching the `Rocket` instance would fail, excepting network errors,
|
||||
/// the `LaunchError` is returned.
|
||||
/// the `Error` is returned.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
#[doc = $import]
|
||||
|
@ -67,7 +67,7 @@ macro_rules! pub_client_impl {
|
|||
/// let client = Client::tracked(rocket);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub $($prefix)? fn tracked(rocket: Rocket) -> Result<Self, LaunchError> {
|
||||
pub $($prefix)? fn tracked(rocket: Rocket) -> Result<Self, Error> {
|
||||
Self::_new(rocket, true) $(.$suffix)?
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ macro_rules! pub_client_impl {
|
|||
/// # Errors
|
||||
///
|
||||
/// If launching the `Rocket` instance would fail, excepting network
|
||||
/// errors, the `LaunchError` is returned.
|
||||
/// errors, the `Error` is returned.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
#[doc = $import]
|
||||
|
@ -91,7 +91,7 @@ macro_rules! pub_client_impl {
|
|||
/// let rocket = rocket::ignite();
|
||||
/// let client = Client::untracked(rocket);
|
||||
/// ```
|
||||
pub $($prefix)? fn untracked(rocket: Rocket) -> Result<Self, LaunchError> {
|
||||
pub $($prefix)? fn untracked(rocket: Rocket) -> Result<Self, Error> {
|
||||
Self::_new(rocket, false) $(.$suffix)?
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,7 @@ macro_rules! pub_client_impl {
|
|||
since = "0.5",
|
||||
note = "choose between `Client::untracked()` and `Client::tracked()`"
|
||||
)]
|
||||
pub $($prefix)? fn new(rocket: Rocket) -> Result<Self, LaunchError> {
|
||||
pub $($prefix)? fn new(rocket: Rocket) -> Result<Self, Error> {
|
||||
Self::tracked(rocket) $(.$suffix)?
|
||||
}
|
||||
|
||||
|
@ -159,7 +159,7 @@ macro_rules! pub_client_impl {
|
|||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn cookies(&self) -> crate::http::CookieJar<'_> {
|
||||
let key = self.rocket().config.secret_key();
|
||||
let key = &self.rocket().config.secret_key;
|
||||
let jar = self._with_raw_cookies(|jar| jar.clone());
|
||||
crate::http::CookieJar::from(jar, key)
|
||||
}
|
||||
|
|
|
@ -1,49 +1,58 @@
|
|||
//! Rocket's logging infrastructure.
|
||||
|
||||
use std::{fmt, env};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use log;
|
||||
use yansi::Paint;
|
||||
use serde::{de, Serialize, Serializer, Deserialize, Deserializer};
|
||||
|
||||
pub(crate) const COLORS_ENV: &str = "ROCKET_CLI_COLORS";
|
||||
#[derive(Debug)]
|
||||
struct RocketLogger(LogLevel);
|
||||
|
||||
struct RocketLogger(LoggingLevel);
|
||||
|
||||
/// Defines the different levels for log messages.
|
||||
/// Defines the maximum level of log messages to show.
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
pub enum LoggingLevel {
|
||||
/// Only shows errors, warnings, and launch information.
|
||||
pub enum LogLevel {
|
||||
/// Only shows errors and warnings: `"critical"`.
|
||||
Critical,
|
||||
/// Shows everything except debug and trace information.
|
||||
/// Shows everything except debug and trace information: `"normal"`.
|
||||
Normal,
|
||||
/// Shows everything.
|
||||
/// Shows everything: `"debug"`.
|
||||
Debug,
|
||||
/// Shows nothing.
|
||||
/// Shows nothing: "`"off"`".
|
||||
Off,
|
||||
}
|
||||
|
||||
impl LoggingLevel {
|
||||
impl LogLevel {
|
||||
fn as_str(&self) -> &str {
|
||||
match self {
|
||||
LogLevel::Critical => "critical",
|
||||
LogLevel::Normal => "normal",
|
||||
LogLevel::Debug => "debug",
|
||||
LogLevel::Off => "off",
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn to_level_filter(self) -> log::LevelFilter {
|
||||
match self {
|
||||
LoggingLevel::Critical => log::LevelFilter::Warn,
|
||||
LoggingLevel::Normal => log::LevelFilter::Info,
|
||||
LoggingLevel::Debug => log::LevelFilter::Trace,
|
||||
LoggingLevel::Off => log::LevelFilter::Off
|
||||
LogLevel::Critical => log::LevelFilter::Warn,
|
||||
LogLevel::Normal => log::LevelFilter::Info,
|
||||
LogLevel::Debug => log::LevelFilter::Trace,
|
||||
LogLevel::Off => log::LevelFilter::Off
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for LoggingLevel {
|
||||
impl FromStr for LogLevel {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let level = match s {
|
||||
"critical" => LoggingLevel::Critical,
|
||||
"normal" => LoggingLevel::Normal,
|
||||
"debug" => LoggingLevel::Debug,
|
||||
"off" => LoggingLevel::Off,
|
||||
let level = match &*s.to_ascii_lowercase() {
|
||||
"critical" => LogLevel::Critical,
|
||||
"normal" => LogLevel::Normal,
|
||||
"debug" => LogLevel::Debug,
|
||||
"off" => LogLevel::Off,
|
||||
_ => return Err("a log level (off, debug, normal, critical)")
|
||||
};
|
||||
|
||||
|
@ -51,16 +60,25 @@ impl FromStr for LoggingLevel {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LoggingLevel {
|
||||
impl fmt::Display for LogLevel {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let string = match *self {
|
||||
LoggingLevel::Critical => "critical",
|
||||
LoggingLevel::Normal => "normal",
|
||||
LoggingLevel::Debug => "debug",
|
||||
LoggingLevel::Off => "off"
|
||||
};
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
write!(f, "{}", string)
|
||||
impl Serialize for LogLevel {
|
||||
fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
|
||||
ser.serialize_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for LogLevel {
|
||||
fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
|
||||
let string = String::deserialize(de)?;
|
||||
LogLevel::from_str(&string).map_err(|_| de::Error::invalid_value(
|
||||
de::Unexpected::Str(&string),
|
||||
&figment::error::OneOf( &["critical", "normal", "debug", "off"])
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,14 +118,14 @@ impl log::Log for RocketLogger {
|
|||
let configged_level = self.0;
|
||||
let from_hyper = record.module_path().map_or(false, |m| m.starts_with("hyper::"));
|
||||
let from_rustls = record.module_path().map_or(false, |m| m.starts_with("rustls::"));
|
||||
if configged_level != LoggingLevel::Debug && (from_hyper || from_rustls) {
|
||||
if configged_level != LogLevel::Debug && (from_hyper || from_rustls) {
|
||||
return;
|
||||
}
|
||||
|
||||
// In Rocket, we abuse targets with suffix "_" to indicate indentation.
|
||||
let is_launch = record.target().starts_with("launch");
|
||||
if record.target().ends_with('_') {
|
||||
if configged_level != LoggingLevel::Critical || is_launch {
|
||||
if configged_level != LogLevel::Critical || is_launch {
|
||||
print!(" {} ", Paint::default("=>").bold());
|
||||
}
|
||||
}
|
||||
|
@ -145,89 +163,45 @@ impl log::Log for RocketLogger {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_init(level: LoggingLevel, verbose: bool) -> bool {
|
||||
if level == LoggingLevel::Off {
|
||||
pub(crate) fn try_init(level: LogLevel, colors: bool, verbose: bool) -> bool {
|
||||
if level == LogLevel::Off {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !atty::is(atty::Stream::Stdout)
|
||||
|| (cfg!(windows) && !Paint::enable_windows_ascii())
|
||||
|| env::var_os(COLORS_ENV).map(|v| v == "0" || v == "off").unwrap_or(false)
|
||||
|| !colors
|
||||
{
|
||||
Paint::disable();
|
||||
}
|
||||
|
||||
push_max_level(level);
|
||||
if let Err(e) = log::set_boxed_logger(Box::new(RocketLogger(level))) {
|
||||
if verbose {
|
||||
eprintln!("Logger failed to initialize: {}", e);
|
||||
}
|
||||
|
||||
pop_max_level();
|
||||
return false;
|
||||
}
|
||||
|
||||
log::set_max_level(level.to_level_filter());
|
||||
true
|
||||
}
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering};
|
||||
|
||||
static PUSHED: AtomicBool = AtomicBool::new(false);
|
||||
static LAST_LOG_FILTER: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
fn filter_to_usize(filter: log::LevelFilter) -> usize {
|
||||
match filter {
|
||||
log::LevelFilter::Off => 0,
|
||||
log::LevelFilter::Error => 1,
|
||||
log::LevelFilter::Warn => 2,
|
||||
log::LevelFilter::Info => 3,
|
||||
log::LevelFilter::Debug => 4,
|
||||
log::LevelFilter::Trace => 5,
|
||||
}
|
||||
}
|
||||
|
||||
fn usize_to_filter(num: usize) -> log::LevelFilter {
|
||||
match num {
|
||||
0 => log::LevelFilter::Off,
|
||||
1 => log::LevelFilter::Error,
|
||||
2 => log::LevelFilter::Warn,
|
||||
3 => log::LevelFilter::Info,
|
||||
4 => log::LevelFilter::Debug,
|
||||
5 => log::LevelFilter::Trace,
|
||||
_ => unreachable!("max num is 5 in filter_to_usize")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn push_max_level(level: LoggingLevel) {
|
||||
LAST_LOG_FILTER.store(filter_to_usize(log::max_level()), Ordering::Release);
|
||||
PUSHED.store(true, Ordering::Release);
|
||||
log::set_max_level(level.to_level_filter());
|
||||
}
|
||||
|
||||
pub(crate) fn pop_max_level() {
|
||||
if PUSHED.load(Ordering::Acquire) {
|
||||
log::set_max_level(usize_to_filter(LAST_LOG_FILTER.load(Ordering::Acquire)));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait PaintExt {
|
||||
pub trait PaintExt {
|
||||
fn emoji(item: &str) -> Paint<&str>;
|
||||
}
|
||||
|
||||
impl PaintExt for Paint<&str> {
|
||||
/// Paint::masked(), but hidden on Windows due to broken output. See #1122.
|
||||
fn emoji(item: &str) -> Paint<&str> {
|
||||
if cfg!(windows) {
|
||||
Paint::masked("")
|
||||
} else {
|
||||
Paint::masked(item)
|
||||
}
|
||||
fn emoji(_item: &str) -> Paint<&str> {
|
||||
#[cfg(windows)] { Paint::masked("") }
|
||||
#[cfg(not(windows))] { Paint::masked(_item) }
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn init(level: LoggingLevel) -> bool {
|
||||
try_init(level, true)
|
||||
pub fn init(level: LogLevel) -> bool {
|
||||
try_init(level, true, true)
|
||||
}
|
||||
|
||||
// Expose logging macros as (hidden) funcions for use by core/contrib codegen.
|
||||
|
|
|
@ -2,13 +2,14 @@ use std::ops::Deref;
|
|||
|
||||
use crate::outcome::Outcome::*;
|
||||
use crate::request::{Request, form::{FromForm, FormItems, FormDataError}};
|
||||
use crate::data::{Outcome, Transform, Transformed, Data, FromTransformedData, TransformFuture, FromDataFuture};
|
||||
use crate::data::{Data, Outcome, Transform, Transformed, ToByteUnit};
|
||||
use crate::data::{TransformFuture, FromTransformedData, FromDataFuture};
|
||||
use crate::http::{Status, uri::{Query, FromUriParam}};
|
||||
|
||||
/// A data guard for parsing [`FromForm`] types strictly.
|
||||
///
|
||||
/// This type implements the [`FromTransformedData`] trait. It provides a generic means to
|
||||
/// parse arbitrary structures from incoming form data.
|
||||
/// This type implements the [`FromTransformedData`] trait. It provides a
|
||||
/// generic means to parse arbitrary structures from incoming form data.
|
||||
///
|
||||
/// # Strictness
|
||||
///
|
||||
|
@ -197,7 +198,8 @@ impl<'f, T: FromForm<'f> + Send + 'f> FromTransformedData<'f> for Form<T> {
|
|||
return Transform::Borrowed(Forward(data));
|
||||
}
|
||||
|
||||
match data.open(request.limits().forms).stream_to_string().await {
|
||||
let limit = request.limits().get("forms").unwrap_or(32.kibibytes());
|
||||
match data.open(limit).stream_to_string().await {
|
||||
Ok(form_string) => Transform::Borrowed(Success(form_string)),
|
||||
Err(e) => {
|
||||
let err = (Status::InternalServerError, FormDataError::Io(e));
|
||||
|
@ -207,10 +209,13 @@ impl<'f, T: FromForm<'f> + Send + 'f> FromTransformedData<'f> for Form<T> {
|
|||
})
|
||||
}
|
||||
|
||||
fn from_data(_: &'f Request<'_>, o: Transformed<'f, Self>) -> FromDataFuture<'f, Self, Self::Error> {
|
||||
Box::pin(futures::future::ready(o.borrowed().and_then(|data| {
|
||||
<Form<T>>::from_data(data, true).map(Form)
|
||||
})))
|
||||
fn from_data(
|
||||
_: &'f Request<'_>,
|
||||
o: Transformed<'f, Self>
|
||||
) -> FromDataFuture<'f, Self, Self::Error> {
|
||||
Box::pin(async move {
|
||||
o.borrowed().and_then(|data| <Form<T>>::from_data(data, true).map(Form))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ impl<'r> Request<'r> {
|
|||
managed: &rocket.managed_state,
|
||||
shutdown: &rocket.shutdown_handle,
|
||||
route: Atomic::new(None),
|
||||
cookies: CookieJar::new(rocket.config.secret_key()),
|
||||
cookies: CookieJar::new(&rocket.config.secret_key),
|
||||
accept: Storage::new(),
|
||||
content_type: Storage::new(),
|
||||
cache: Arc::new(Container::new()),
|
||||
|
@ -749,7 +749,7 @@ impl<'r> Request<'r> {
|
|||
impl<'r> Request<'r> {
|
||||
// Only used by doc-tests! Needs to be `pub` because doc-test are external.
|
||||
pub fn example<F: Fn(&mut Request<'_>)>(method: Method, uri: &str, f: F) {
|
||||
let rocket = Rocket::custom(Config::development());
|
||||
let rocket = Rocket::custom(Config::default());
|
||||
let uri = Origin::parse(uri).expect("invalid URI in example");
|
||||
let mut request = Request::new(&rocket, method, uri);
|
||||
f(&mut request);
|
||||
|
|
|
@ -20,8 +20,7 @@ macro_rules! assert_headers {
|
|||
$(expected.entry($key).or_insert(vec![]).append(&mut vec![$($value),+]);)+
|
||||
|
||||
// Dispatch the request and check that the headers are what we expect.
|
||||
let config = Config::development();
|
||||
let r = Rocket::custom(config);
|
||||
let r = Rocket::custom(Config::default());
|
||||
let req = Request::from_hyp(&r, h_method, h_headers, &h_uri, h_addr).unwrap();
|
||||
let actual_headers = req.headers();
|
||||
for (key, values) in expected.iter() {
|
||||
|
|
|
@ -11,16 +11,17 @@ use ref_cast::RefCast;
|
|||
|
||||
use yansi::Paint;
|
||||
use state::Container;
|
||||
use figment::Figment;
|
||||
|
||||
use crate::{logger, handler};
|
||||
use crate::config::{Config, FullConfig, ConfigError, LoggedValue};
|
||||
use crate::config::Config;
|
||||
use crate::request::{Request, FormItems};
|
||||
use crate::data::Data;
|
||||
use crate::catcher::Catcher;
|
||||
use crate::response::{Body, Response};
|
||||
use crate::router::{Router, Route};
|
||||
use crate::outcome::Outcome;
|
||||
use crate::error::{LaunchError, LaunchErrorKind};
|
||||
use crate::error::{Error, ErrorKind};
|
||||
use crate::fairing::{Fairing, Fairings};
|
||||
use crate::logger::PaintExt;
|
||||
use crate::ext::AsyncReadExt;
|
||||
|
@ -35,6 +36,7 @@ use crate::http::uri::Origin;
|
|||
/// application.
|
||||
pub struct Rocket {
|
||||
pub(crate) config: Config,
|
||||
pub(crate) figment: Figment,
|
||||
pub(crate) managed_state: Container,
|
||||
manifest: Vec<PreLaunchOp>,
|
||||
router: Router,
|
||||
|
@ -66,6 +68,9 @@ pub(crate) struct Token;
|
|||
impl Rocket {
|
||||
#[inline]
|
||||
fn _mount(&mut self, base: Origin<'static>, routes: Vec<Route>) {
|
||||
// info!("[$]🛰 [/m]mounting [b]{}[/]:", log::emoji(), base);
|
||||
// info!("[$]🛰 [/m]mounting [b]{}[/]:", log::emoji(), base);
|
||||
// info!("{}[m]Mounting [b]{}[/]:[/]", log::emoji("🛰 "), base);
|
||||
info!("{}{} {}{}",
|
||||
Paint::emoji("🛰 "),
|
||||
Paint::magenta("Mounting"),
|
||||
|
@ -107,6 +112,7 @@ impl Rocket {
|
|||
#[inline]
|
||||
async fn _attach(mut self, fairing: Box<dyn Fairing>) -> Self {
|
||||
// Attach (and run attach-) fairings, which requires us to move `self`.
|
||||
trace_!("Running attach fairing: {:?}", fairing.info());
|
||||
let mut fairings = mem::replace(&mut self.fairings, Fairings::new());
|
||||
self = fairings.attach(fairing, self).await;
|
||||
|
||||
|
@ -119,8 +125,9 @@ impl Rocket {
|
|||
// Create a "dummy" instance of `Rocket` to use while mem-swapping `self`.
|
||||
fn dummy() -> Rocket {
|
||||
Rocket {
|
||||
config: Config::debug_default(),
|
||||
figment: Figment::from(Config::debug_default()),
|
||||
manifest: vec![],
|
||||
config: Config::development(),
|
||||
router: Router::new(),
|
||||
default_catcher: None,
|
||||
catchers: HashMap::new(),
|
||||
|
@ -145,7 +152,7 @@ impl Rocket {
|
|||
// process them as a stack to maintain proper ordering.
|
||||
let mut manifest = mem::replace(&mut self.manifest, vec![]);
|
||||
while !manifest.is_empty() {
|
||||
trace_!("[MANIEST PROGRESS]: {:?}", manifest);
|
||||
trace_!("[MANIEST PROGRESS ({} left)]: {:?}", manifest.len(), manifest);
|
||||
match manifest.remove(0) {
|
||||
PreLaunchOp::Manage(_, callback) => callback(&mut self.managed_state),
|
||||
PreLaunchOp::Mount(base, routes) => self._mount(base, routes),
|
||||
|
@ -183,10 +190,9 @@ async fn hyper_service_fn(
|
|||
h_addr: std::net::SocketAddr,
|
||||
hyp_req: hyper::Request<hyper::Body>,
|
||||
) -> Result<hyper::Response<hyper::Body>, io::Error> {
|
||||
// This future must return a hyper::Response, but that's not easy
|
||||
// because the response body might borrow from the request. Instead,
|
||||
// we do the body writing in another future that will send us
|
||||
// the response metadata (and a body channel) beforehand.
|
||||
// This future must return a hyper::Response, but the response body might
|
||||
// borrow from the request. Instead, write the body in another future that
|
||||
// sends the response metadata (and a body channel) prior.
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
tokio::spawn(async move {
|
||||
|
@ -194,7 +200,10 @@ async fn hyper_service_fn(
|
|||
let (h_parts, h_body) = hyp_req.into_parts();
|
||||
|
||||
// Convert the Hyper request into a Rocket request.
|
||||
let req_res = Request::from_hyp(&rocket, h_parts.method, h_parts.headers, &h_parts.uri, h_addr);
|
||||
let req_res = Request::from_hyp(
|
||||
&rocket, h_parts.method, h_parts.headers, &h_parts.uri, h_addr
|
||||
);
|
||||
|
||||
let mut req = match req_res {
|
||||
Ok(req) => req,
|
||||
Err(e) => {
|
||||
|
@ -218,6 +227,7 @@ async fn hyper_service_fn(
|
|||
rocket.issue_response(r, tx).await;
|
||||
});
|
||||
|
||||
// Receive the response written to `tx` by the task above.
|
||||
rx.await.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||
}
|
||||
|
||||
|
@ -228,14 +238,9 @@ impl Rocket {
|
|||
response: Response<'_>,
|
||||
tx: oneshot::Sender<hyper::Response<hyper::Body>>,
|
||||
) {
|
||||
let result = self.write_response(response, tx);
|
||||
match result.await {
|
||||
Ok(()) => {
|
||||
info_!("{}", Paint::green("Response succeeded."));
|
||||
}
|
||||
Err(e) => {
|
||||
error_!("Failed to write response: {:?}.", e);
|
||||
}
|
||||
match self.write_response(response, tx).await {
|
||||
Ok(()) => info_!("{}", Paint::green("Response succeeded.")),
|
||||
Err(e) => error_!("Failed to write response: {:?}.", e),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -254,12 +259,12 @@ impl Rocket {
|
|||
hyp_res = hyp_res.header(name, value);
|
||||
}
|
||||
|
||||
let send_response = move |hyp_res: hyper::ResponseBuilder, body| -> io::Result<()> {
|
||||
let response = hyp_res.body(body)
|
||||
let send_response = move |res: hyper::ResponseBuilder, body| -> io::Result<()> {
|
||||
let response = res.body(body)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
|
||||
tx.send(response).map_err(|_| {
|
||||
let msg = "Client disconnected before the response was started";
|
||||
let msg = "client disconnected before the response was started";
|
||||
io::Error::new(io::ErrorKind::BrokenPipe, msg)
|
||||
})
|
||||
};
|
||||
|
@ -488,14 +493,13 @@ impl Rocket {
|
|||
}
|
||||
|
||||
// TODO.async: Solidify the Listener APIs and make this function public
|
||||
async fn listen_on<L>(mut self, listener: L) -> Result<(), crate::error::Error>
|
||||
async fn listen_on<L>(mut self, listener: L) -> Result<(), Error>
|
||||
where L: Listener + Send + Unpin + 'static,
|
||||
<L as Listener>::Connection: Send + Unpin + 'static,
|
||||
{
|
||||
// Determine the address and port we actually binded to.
|
||||
self.config.port = listener.local_addr().map(|a| a.port()).unwrap_or(0);
|
||||
let proto = self.config.tls.as_ref().map_or("http://", |_| "https://");
|
||||
let full_addr = format!("{}:{}", self.config.address, self.config.port);
|
||||
// We do this twice if `listen_on` was called through `launch()` but
|
||||
// only once if `listen_on()` gets called directly.
|
||||
self.prelaunch_check().await?;
|
||||
|
||||
// Freeze managed state for synchronization-free accesses later.
|
||||
self.managed_state.freeze();
|
||||
|
@ -504,31 +508,35 @@ impl Rocket {
|
|||
self.fairings.pretty_print_counts();
|
||||
self.fairings.handle_launch(self.cargo());
|
||||
|
||||
// Determine the address and port we actually bound to.
|
||||
self.config.port = listener.local_addr().map(|a| a.port()).unwrap_or(0);
|
||||
let proto = self.config.tls.as_ref().map_or("http://", |_| "https://");
|
||||
let full_addr = format!("{}:{}", self.config.address, self.config.port);
|
||||
|
||||
launch_info!("{}{} {}{}",
|
||||
Paint::emoji("🚀 "),
|
||||
Paint::default("Rocket has launched from").bold(),
|
||||
Paint::default(proto).bold().underline(),
|
||||
Paint::default(&full_addr).bold().underline());
|
||||
|
||||
// Restore the log level back to what it originally was.
|
||||
logger::pop_max_level();
|
||||
|
||||
// Set the keep-alive.
|
||||
// TODO.async: implement keep-alive in Listener
|
||||
// let timeout = self.config.keep_alive.map(|s| Duration::from_secs(s as u64));
|
||||
// listener.set_keepalive(timeout);
|
||||
// Determine keep-alives.
|
||||
let http1_keepalive = self.config.keep_alive != 0;
|
||||
let http2_keep_alive = match self.config.keep_alive {
|
||||
0 => None,
|
||||
n => Some(std::time::Duration::from_secs(n as u64))
|
||||
};
|
||||
|
||||
// We need to get this before moving `self` into an `Arc`.
|
||||
let mut shutdown_receiver = self.shutdown_receiver
|
||||
.take().expect("shutdown receiver has already been used");
|
||||
let mut shutdown_receiver = self.shutdown_receiver.take()
|
||||
.expect("shutdown receiver has already been used");
|
||||
|
||||
let rocket = Arc::new(self);
|
||||
let service = hyper::make_service_fn(move |connection: &<L as Listener>::Connection| {
|
||||
let service = hyper::make_service_fn(move |conn: &<L as Listener>::Connection| {
|
||||
let rocket = rocket.clone();
|
||||
let remote_addr = connection.remote_addr().unwrap_or_else(|| ([0, 0, 0, 0], 0).into());
|
||||
let remote = conn.remote_addr().unwrap_or_else(|| ([0, 0, 0, 0], 0).into());
|
||||
async move {
|
||||
Ok::<_, std::convert::Infallible>(hyper::service_fn(move |req| {
|
||||
hyper_service_fn(rocket.clone(), remote_addr, req)
|
||||
hyper_service_fn(rocket.clone(), remote, req)
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
@ -545,11 +553,13 @@ impl Rocket {
|
|||
}
|
||||
|
||||
hyper::Server::builder(Incoming::from_listener(listener))
|
||||
.http1_keepalive(http1_keepalive)
|
||||
.http2_keep_alive_interval(http2_keep_alive)
|
||||
.executor(TokioExecutor)
|
||||
.serve(service)
|
||||
.with_graceful_shutdown(async move { shutdown_receiver.recv().await; })
|
||||
.await
|
||||
.map_err(|e| crate::error::Error::Run(Box::new(e)))
|
||||
.map_err(|e| Error::new(ErrorKind::Runtime(Box::new(e))))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -576,97 +586,43 @@ impl Rocket {
|
|||
/// # };
|
||||
/// ```
|
||||
pub fn ignite() -> Rocket {
|
||||
Config::read()
|
||||
.or_else(|e| match e {
|
||||
ConfigError::IoError => {
|
||||
warn!("Failed to read 'Rocket.toml'. Using defaults.");
|
||||
Ok(FullConfig::env_default()?.take_active())
|
||||
}
|
||||
ConfigError::NotFound => Ok(FullConfig::env_default()?.take_active()),
|
||||
_ => Err(e)
|
||||
})
|
||||
.map(Rocket::configured)
|
||||
.unwrap_or_else(|e: ConfigError| {
|
||||
logger::init(logger::LoggingLevel::Debug);
|
||||
e.pretty_print();
|
||||
std::process::exit(1)
|
||||
})
|
||||
Rocket::custom(Config::figment())
|
||||
}
|
||||
|
||||
/// Creates a new `Rocket` application using the supplied custom
|
||||
/// configuration. The `Rocket.toml` file, if present, is ignored. Any
|
||||
/// environment variables setting config parameters are ignored.
|
||||
/// Creates a new `Rocket` application using the supplied configuration
|
||||
/// provider. This method is typically called through the
|
||||
/// [`rocket::custom()`] alias.
|
||||
///
|
||||
/// This method is typically called through the `rocket::custom` alias.
|
||||
/// # Panics
|
||||
///
|
||||
/// If there is an error reading configuration sources, this function prints
|
||||
/// a nice error message and then exits the process.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::{Config, Environment};
|
||||
/// # use rocket::config::ConfigError;
|
||||
/// ```rust,no_run
|
||||
/// use figment::{Figment, providers::{Toml, Env, Format}};
|
||||
///
|
||||
/// # #[allow(dead_code)]
|
||||
/// # fn try_config() -> Result<(), ConfigError> {
|
||||
/// let config = Config::build(Environment::Staging)
|
||||
/// .address("1.2.3.4")
|
||||
/// .port(9234)
|
||||
/// .finalize()?;
|
||||
/// #[rocket::launch]
|
||||
/// fn rocket() -> _ {
|
||||
/// let figment = Figment::from(rocket::Config::default())
|
||||
/// .merge(Toml::file("MyApp.toml").nested())
|
||||
/// .merge(Env::prefixed("MY_APP_"));
|
||||
///
|
||||
/// # #[allow(unused_variables)]
|
||||
/// let app = rocket::custom(config);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// rocket::custom(figment)
|
||||
/// }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn custom(config: Config) -> Rocket {
|
||||
Rocket::configured(config)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn configured(config: Config) -> Rocket {
|
||||
if logger::try_init(config.log_level, false) {
|
||||
// Temporary weaken log level for launch info.
|
||||
logger::push_max_level(logger::LoggingLevel::Normal);
|
||||
}
|
||||
|
||||
launch_info!("{}Configured for {}.", Paint::emoji("🔧 "), config.environment);
|
||||
launch_info_!("address: {}", Paint::default(&config.address).bold());
|
||||
launch_info_!("port: {}", Paint::default(&config.port).bold());
|
||||
launch_info_!("log: {}", Paint::default(config.log_level).bold());
|
||||
launch_info_!("workers: {}", Paint::default(config.workers).bold());
|
||||
launch_info_!("secret key: {}", Paint::default(&config.secret_key).bold());
|
||||
launch_info_!("limits: {}", Paint::default(&config.limits).bold());
|
||||
|
||||
match config.keep_alive {
|
||||
Some(v) => launch_info_!("keep-alive: {}", Paint::default(format!("{}s", v)).bold()),
|
||||
None => launch_info_!("keep-alive: {}", Paint::default("disabled").bold()),
|
||||
}
|
||||
|
||||
let tls_configured = config.tls.is_some();
|
||||
if tls_configured && cfg!(feature = "tls") {
|
||||
launch_info_!("tls: {}", Paint::default("enabled").bold());
|
||||
} else if tls_configured {
|
||||
error_!("tls: {}", Paint::default("disabled").bold());
|
||||
error_!("tls is configured, but the tls feature is disabled");
|
||||
} else {
|
||||
launch_info_!("tls: {}", Paint::default("disabled").bold());
|
||||
}
|
||||
|
||||
if config.secret_key.is_generated() && config.environment.is_prod() {
|
||||
warn!("environment is 'production' but no `secret_key` is configured");
|
||||
}
|
||||
|
||||
for (name, value) in config.extras() {
|
||||
launch_info_!("{} {}: {}",
|
||||
Paint::yellow("[extra]"), name,
|
||||
Paint::default(LoggedValue(value)).bold());
|
||||
}
|
||||
pub fn custom<T: figment::Provider>(provider: T) -> Rocket {
|
||||
let (config, figment) = (Config::from(&provider), Figment::from(provider));
|
||||
logger::try_init(config.log_level, config.cli_colors, false);
|
||||
config.pretty_print(figment.profile());
|
||||
|
||||
let managed_state = Container::new();
|
||||
let (shutdown_sender, shutdown_receiver) = mpsc::channel(1);
|
||||
|
||||
Rocket {
|
||||
config, managed_state,
|
||||
config, figment,
|
||||
managed_state,
|
||||
shutdown_handle: Shutdown(shutdown_sender),
|
||||
manifest: vec![],
|
||||
router: Router::new(),
|
||||
|
@ -887,7 +843,27 @@ impl Rocket {
|
|||
self.inspect().await.state()
|
||||
}
|
||||
|
||||
/// Returns the active configuration.
|
||||
/// Returns the figment.
|
||||
///
|
||||
/// This function is equivalent to `.inspect().await.figment()` and is
|
||||
/// provided as a convenience.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::Rocket;
|
||||
/// use rocket::fairing::AdHoc;
|
||||
///
|
||||
/// # rocket::async_test(async {
|
||||
/// let mut rocket = rocket::ignite();
|
||||
/// println!("Rocket config: {:?}", rocket.config().await);
|
||||
/// # });
|
||||
/// ```
|
||||
pub async fn figment(&mut self) -> &Figment {
|
||||
self.inspect().await.figment()
|
||||
}
|
||||
|
||||
/// Returns the config.
|
||||
///
|
||||
/// This function is equivalent to `.inspect().await.config()` and is
|
||||
/// provided as a convenience.
|
||||
|
@ -938,14 +914,14 @@ impl Rocket {
|
|||
|
||||
/// Perform "pre-launch" checks: verify that there are no routing colisions
|
||||
/// and that there were no fairing failures.
|
||||
pub(crate) async fn prelaunch_check(&mut self) -> Result<(), LaunchError> {
|
||||
pub(crate) async fn prelaunch_check(&mut self) -> Result<(), Error> {
|
||||
self.actualize_manifest().await;
|
||||
if let Err(e) = self.router.collisions() {
|
||||
return Err(LaunchError::new(LaunchErrorKind::Collision(e)));
|
||||
return Err(Error::new(ErrorKind::Collision(e)));
|
||||
}
|
||||
|
||||
if let Some(failures) = self.fairings.failures() {
|
||||
return Err(LaunchError::new(LaunchErrorKind::FailedFairings(failures.to_vec())))
|
||||
return Err(Error::new(ErrorKind::FailedFairings(failures.to_vec())))
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -963,8 +939,6 @@ impl Rocket {
|
|||
/// first being inspected. See the [`Error`] documentation for more
|
||||
/// information.
|
||||
///
|
||||
/// [`Error`]: crate::error::Error
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
|
@ -976,51 +950,46 @@ impl Rocket {
|
|||
/// # }
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn launch(mut self) -> Result<(), crate::error::Error> {
|
||||
pub async fn launch(mut self) -> Result<(), Error> {
|
||||
use std::net::ToSocketAddrs;
|
||||
use futures::future::Either;
|
||||
use crate::error::Error::Launch;
|
||||
use crate::http::private::bind_tcp;
|
||||
|
||||
self.prelaunch_check().await.map_err(crate::error::Error::Launch)?;
|
||||
self.prelaunch_check().await?;
|
||||
|
||||
let full_addr = format!("{}:{}", self.config.address, self.config.port);
|
||||
let addr = match full_addr.to_socket_addrs() {
|
||||
Ok(mut addrs) => addrs.next().expect(">= 1 socket addr"),
|
||||
Err(e) => return Err(Launch(e.into())),
|
||||
};
|
||||
let addr = full_addr.to_socket_addrs()
|
||||
.map(|mut addrs| addrs.next().expect(">= 1 socket addr"))
|
||||
.map_err(|e| Error::new(ErrorKind::Io(e)))?;
|
||||
|
||||
// FIXME: Make `ctrlc` a known `Rocket` config option.
|
||||
// If `ctrl-c` shutdown is enabled, we `select` on `the ctrl-c` signal
|
||||
// and server. Otherwise, we only wait on the `server`, hence `pending`.
|
||||
let shutdown_handle = self.shutdown_handle.clone();
|
||||
let shutdown_signal = match self.config.get_bool("ctrlc") {
|
||||
Ok(false) => futures::future::pending().boxed(),
|
||||
_ => tokio::signal::ctrl_c().boxed(),
|
||||
let shutdown_signal = match self.config.ctrlc {
|
||||
true => tokio::signal::ctrl_c().boxed(),
|
||||
false => futures::future::pending().boxed(),
|
||||
};
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
let server = {
|
||||
macro_rules! listen_on {
|
||||
($expr:expr) => {{
|
||||
let listener = match $expr {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return Err(Launch(LaunchError::new(LaunchErrorKind::Bind(err))))
|
||||
};
|
||||
self.listen_on(listener)
|
||||
}};
|
||||
}
|
||||
use crate::http::tls::bind_tls;
|
||||
|
||||
#[cfg(feature = "tls")] {
|
||||
if let Some(tls) = self.config.tls.clone() {
|
||||
listen_on!(crate::http::tls::bind_tls(addr, tls.certs, tls.key).await).boxed()
|
||||
} else {
|
||||
listen_on!(crate::http::private::bind_tcp(addr).await).boxed()
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "tls"))] {
|
||||
listen_on!(crate::http::private::bind_tcp(addr).await).boxed()
|
||||
if let Some(tls_config) = &self.config.tls {
|
||||
let (certs, key) = tls_config.to_readers().map_err(ErrorKind::Io)?;
|
||||
let l = bind_tls(addr, certs, key).await.map_err(ErrorKind::Bind)?;
|
||||
self.listen_on(l).boxed()
|
||||
} else {
|
||||
let l = bind_tcp(addr).await.map_err(ErrorKind::Bind)?;
|
||||
self.listen_on(l).boxed()
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "tls"))]
|
||||
let server = {
|
||||
let l = bind_tcp(addr).await.map_err(ErrorKind::Bind)?;
|
||||
self.listen_on(l).boxed()
|
||||
};
|
||||
|
||||
match futures::future::select(shutdown_signal, server).await {
|
||||
Either::Left((Ok(()), server)) => {
|
||||
// Ctrl-was pressed. Signal shutdown, wait for the server.
|
||||
|
@ -1029,7 +998,7 @@ impl Rocket {
|
|||
}
|
||||
Either::Left((Err(err), server)) => {
|
||||
// Error setting up ctrl-c signal. Let the user know.
|
||||
warn!("Failed to enable `ctrl+c` graceful signal shutdown.");
|
||||
warn!("Failed to enable `ctrl-c` graceful signal shutdown.");
|
||||
info_!("Error: {}", err);
|
||||
server.await
|
||||
}
|
||||
|
@ -1128,6 +1097,21 @@ impl Cargo {
|
|||
self.0.managed_state.try_get()
|
||||
}
|
||||
|
||||
/// Returns the figment for configured provider.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # rocket::async_test(async {
|
||||
/// let mut rocket = rocket::ignite();
|
||||
/// let figment = rocket.inspect().await.figment();
|
||||
/// # });
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn figment(&self) -> &Figment {
|
||||
&self.0.figment
|
||||
}
|
||||
|
||||
/// Returns the active configuration.
|
||||
///
|
||||
/// # Example
|
||||
|
|
|
@ -401,7 +401,7 @@ mod tests {
|
|||
fn req_route_mt_collide<S1, S2>(m: Method, mt1: S1, mt2: S2) -> bool
|
||||
where S1: Into<Option<&'static str>>, S2: Into<Option<&'static str>>
|
||||
{
|
||||
let rocket = Rocket::custom(Config::development());
|
||||
let rocket = Rocket::custom(Config::default());
|
||||
let mut req = Request::new(&rocket, m, Origin::dummy());
|
||||
if let Some(mt_str) = mt1.into() {
|
||||
if m.supports_payload() {
|
||||
|
@ -468,7 +468,7 @@ mod tests {
|
|||
}
|
||||
|
||||
fn req_route_path_match(a: &'static str, b: &'static str) -> bool {
|
||||
let rocket = Rocket::custom(Config::development());
|
||||
let rocket = Rocket::custom(Config::default());
|
||||
let req = Request::new(&rocket, Get, Origin::parse(a).expect("valid URI"));
|
||||
let route = Route::ranked(0, Get, b.to_string(), dummy);
|
||||
route.matches(&req)
|
||||
|
|
|
@ -224,7 +224,7 @@ mod test {
|
|||
}
|
||||
|
||||
fn route<'a>(router: &'a Router, method: Method, uri: &str) -> Option<&'a Route> {
|
||||
let rocket = Rocket::custom(Config::development());
|
||||
let rocket = Rocket::custom(Config::default());
|
||||
let request = Request::new(&rocket, method, Origin::parse(uri).unwrap());
|
||||
let matches = router.route(&request);
|
||||
if matches.len() > 0 {
|
||||
|
@ -235,7 +235,7 @@ mod test {
|
|||
}
|
||||
|
||||
fn matches<'a>(router: &'a Router, method: Method, uri: &str) -> Vec<&'a Route> {
|
||||
let rocket = Rocket::custom(Config::development());
|
||||
let rocket = Rocket::custom(Config::default());
|
||||
let request = Request::new(&rocket, method, Origin::parse(uri).unwrap());
|
||||
router.route(&request)
|
||||
}
|
||||
|
|
|
@ -14,16 +14,13 @@ fn index(form: Form<Simple>) -> String {
|
|||
|
||||
mod limits_tests {
|
||||
use rocket;
|
||||
use rocket::config::{Environment, Config};
|
||||
use rocket::local::blocking::Client;
|
||||
use rocket::http::{Status, ContentType};
|
||||
use rocket::data::Limits;
|
||||
|
||||
fn rocket_with_forms_limit(limit: u64) -> rocket::Rocket {
|
||||
let config = Config::build(Environment::Development)
|
||||
.limits(Limits::default().limit("forms", limit.into()))
|
||||
.unwrap();
|
||||
|
||||
let limits = Limits::default().limit("forms", limit.into());
|
||||
let config = rocket::Config::figment().merge(("limits", limits));
|
||||
rocket::custom(config).mount("/", routes![super::index])
|
||||
}
|
||||
|
||||
|
|
|
@ -2,33 +2,24 @@
|
|||
# defaults. We show all of them here explicitly for demonstrative purposes.
|
||||
|
||||
[global.limits]
|
||||
forms = 32768
|
||||
json = 1048576 # this is an extra used by the json contrib module
|
||||
msgpack = 1048576 # this is an extra used by the msgpack contrib module
|
||||
forms = "64 kB"
|
||||
json = "1 MiB"
|
||||
msgpack = "2 MiB"
|
||||
|
||||
[development]
|
||||
address = "localhost"
|
||||
[debug]
|
||||
address = "127.0.0.1"
|
||||
port = 8000
|
||||
workers = 1
|
||||
keep_alive = 5
|
||||
log = "normal"
|
||||
keep_alive = 0
|
||||
log_level = "normal"
|
||||
hi = "Hello!" # this is an unused extra; maybe application specific?
|
||||
is_extra = true # this is an unused extra; maybe application specific?
|
||||
|
||||
[staging]
|
||||
address = "0.0.0.0"
|
||||
port = 8000
|
||||
workers = 8
|
||||
keep_alive = 5
|
||||
log = "normal"
|
||||
# don't use this key! generate your own and keep it private!
|
||||
secret_key = "8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg="
|
||||
|
||||
[production]
|
||||
address = "0.0.0.0"
|
||||
[release]
|
||||
address = "127.0.0.1"
|
||||
port = 8000
|
||||
workers = 12
|
||||
keep_alive = 5
|
||||
log = "critical"
|
||||
log_level = "critical"
|
||||
# don't use this key! generate your own and keep it private!
|
||||
secret_key = "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk="
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#[cfg(test)] mod tests;
|
||||
|
||||
// This example's illustration is the Rocket.toml file.
|
||||
#[rocket::launch]
|
||||
fn rocket() -> rocket::Rocket { rocket::ignite() }
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
use rocket::config::{Config, LogLevel};
|
||||
|
||||
async fn test_config(profile: &str) {
|
||||
let mut rocket = rocket::custom(Config::figment().select(profile));
|
||||
let config = rocket.config().await;
|
||||
match &*profile {
|
||||
"debug" => {
|
||||
assert_eq!(config.address, std::net::Ipv4Addr::LOCALHOST);
|
||||
assert_eq!(config.port, 8000);
|
||||
assert_eq!(config.workers, 1);
|
||||
assert_eq!(config.keep_alive, 0);
|
||||
assert_eq!(config.log_level, LogLevel::Normal);
|
||||
}
|
||||
"release" => {
|
||||
assert_eq!(config.address, std::net::Ipv4Addr::LOCALHOST);
|
||||
assert_eq!(config.port, 8000);
|
||||
assert_eq!(config.workers, 12);
|
||||
assert_eq!(config.keep_alive, 5);
|
||||
assert_eq!(config.log_level, LogLevel::Critical);
|
||||
assert!(!config.secret_key.is_zero());
|
||||
}
|
||||
_ => {
|
||||
panic!("Unknown profile: {}", profile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rocket::async_test]
|
||||
async fn test_debug_config() {
|
||||
test_config("debug").await
|
||||
}
|
||||
|
||||
#[rocket::async_test]
|
||||
async fn test_release_config() {
|
||||
test_config("release").await
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
#[macro_use] extern crate rocket;
|
||||
|
||||
mod common;
|
||||
|
||||
#[test]
|
||||
fn test_development_config() {
|
||||
common::test_config(rocket::config::Environment::Development);
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
#[macro_use] extern crate rocket;
|
||||
|
||||
mod common;
|
||||
|
||||
#[test]
|
||||
fn test_production_config() {
|
||||
common::test_config(rocket::config::Environment::Production);
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
#[macro_use] extern crate rocket;
|
||||
|
||||
mod common;
|
||||
|
||||
#[test]
|
||||
fn test_staging_config() {
|
||||
common::test_config(rocket::config::Environment::Staging);
|
||||
}
|
|
@ -6,7 +6,6 @@ edition = "2018"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "0.2.0", features = ["io-util"] }
|
||||
rocket = { path = "../../core/lib" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
|
|
@ -68,8 +68,10 @@ fn rocket() -> rocket::Rocket {
|
|||
.attach(Counter::default())
|
||||
.attach(AdHoc::on_attach("Token State", |mut rocket| async {
|
||||
println!("Adding token managed state...");
|
||||
let token_val = rocket.config().await.get_int("token").unwrap_or(-1);
|
||||
Ok(rocket.manage(Token(token_val)))
|
||||
match rocket.figment().await.extract_inner("token") {
|
||||
Ok(value) => Ok(rocket.manage(Token(value))),
|
||||
Err(_) => Err(rocket)
|
||||
}
|
||||
}))
|
||||
.attach(AdHoc::on_launch("Launch Message", |_| {
|
||||
println!("Rocket is about to launch!");
|
||||
|
|
|
@ -6,7 +6,7 @@ edition = "2018"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
rocket = { path = "../../core/lib" }
|
||||
rocket = { path = "../../core/lib", features = ["secrets"] }
|
||||
|
||||
[dependencies.rocket_contrib]
|
||||
path = "../../contrib/lib"
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
[global]
|
||||
template_dir = "templates/"
|
|
@ -10,6 +10,9 @@ export PATH=${HOME}/.cargo/bin:${PATH}
|
|||
export CARGO_INCREMENTAL=0
|
||||
CARGO="cargo"
|
||||
|
||||
# We set a `cfg` so that a missing `secret_key` doesn't abort tests.
|
||||
export RUSTFLAGS="--cfg rocket_unsafe_secret_key"
|
||||
|
||||
# Checks that the versions for Cargo projects $@ all match
|
||||
function check_versions_match() {
|
||||
local last_version=""
|
||||
|
@ -59,6 +62,7 @@ fi
|
|||
echo ":: Preparing. Environment is..."
|
||||
print_environment
|
||||
echo " CARGO: $CARGO"
|
||||
echo " RUSTFLAGS: $RUSTFLAGS"
|
||||
|
||||
echo ":: Ensuring all crate versions match..."
|
||||
check_versions_match "${ALL_PROJECT_DIRS[@]}"
|
||||
|
|
|
@ -595,17 +595,25 @@ the [`CookieJar`] documentation contains complete usage information.
|
|||
|
||||
Cookies added via the [`CookieJar::add()`] method are set _in the clear._ In
|
||||
other words, the value set is visible to the client. For sensitive data, Rocket
|
||||
provides _private_ cookies.
|
||||
provides _private_ cookies. Private cookies are similar to regular cookies
|
||||
except that they are encrypted using authenticated encryption, a form of
|
||||
encryption which simultaneously provides confidentiality, integrity, and
|
||||
authenticity. Thus, private cookies cannot be inspected, tampered with, or
|
||||
manufactured by clients. If you prefer, you can think of private cookies as
|
||||
being signed and encrypted.
|
||||
|
||||
Private cookies are just like regular cookies except that they are encrypted
|
||||
using authenticated encryption, a form of encryption which simultaneously
|
||||
provides confidentiality, integrity, and authenticity. This means that private
|
||||
cookies cannot be inspected, tampered with, or manufactured by clients. If you
|
||||
prefer, you can think of private cookies as being signed and encrypted.
|
||||
Support for private cookies must be manually enabled via the `secrets` crate
|
||||
feature:
|
||||
|
||||
```toml
|
||||
## in Cargo.toml
|
||||
rocket = { version = "0.5.0-dev", features = ["secrets"] }
|
||||
```
|
||||
|
||||
The API for retrieving, adding, and removing private cookies is identical except
|
||||
methods are suffixed with `_private`. These methods are: [`get_private`],
|
||||
[`add_private`], and [`remove_private`]. An example of their usage is below:
|
||||
[`get_private_pending`], [`add_private`], and [`remove_private`]. An example of
|
||||
their usage is below:
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate rocket;
|
||||
|
@ -634,13 +642,11 @@ fn logout(cookies: &CookieJar<'_>) -> Flash<Redirect> {
|
|||
### Secret Key
|
||||
|
||||
To encrypt private cookies, Rocket uses the 256-bit key specified in the
|
||||
`secret_key` configuration parameter. If one is not specified, Rocket will
|
||||
automatically generate a fresh key. Note, however, that a private cookie can
|
||||
only be decrypted with the same key with which it was encrypted. As such, it is
|
||||
important to set a `secret_key` configuration parameter when using private
|
||||
cookies so that cookies decrypt properly after an application restart. Rocket
|
||||
emits a warning if an application is run in production without a configured
|
||||
`secret_key`.
|
||||
`secret_key` configuration parameter. When compiled in debug mode, a fresh key
|
||||
is generated automatically. In release mode, Rocket requires you to set a secret
|
||||
key if the `secrets` feature is enabled. Failure to do so results in a hard
|
||||
error at launch time. The value of the parameter may either be a 256-bit base64
|
||||
or hex string or a 32-byte slice.
|
||||
|
||||
Generating a string suitable for use as a `secret_key` configuration value is
|
||||
usually done through tools like `openssl`. Using `openssl`, a 256-bit base64 key
|
||||
|
@ -650,6 +656,7 @@ For more information on configuration, see the [Configuration](../configuration)
|
|||
section of the guide.
|
||||
|
||||
[`get_private`]: @api/rocket/http/struct.CookieJar.html#method.get_private
|
||||
[`get_private_pending`]: @api/rocket/http/struct.CookieJar.html#method.get_private_pending
|
||||
[`add_private`]: @api/rocket/http/struct.CookieJar.html#method.add_private
|
||||
[`remove_private`]: @api/rocket/http/struct.CookieJar.html#method.remove_private
|
||||
|
||||
|
|
|
@ -1,242 +1,180 @@
|
|||
# Configuration
|
||||
|
||||
Rocket aims to have a flexible and usable configuration system. Rocket
|
||||
applications can be configured via a configuration file, through environment
|
||||
variables, or both. Configurations are separated into three environments:
|
||||
development, staging, and production. The working environment is selected via an
|
||||
environment variable.
|
||||
Rocket's configuration system is flexible. Based on [Figment](@figment), it
|
||||
allows you to configure your application the way _you_ want while also providing
|
||||
with a sensible set of defaults.
|
||||
|
||||
## Environment
|
||||
## Overview
|
||||
|
||||
At any point in time, a Rocket application is operating in a given
|
||||
_configuration environment_. There are three such environments:
|
||||
Rocket's configuration system is based on Figment's [`Provider`]s, types which
|
||||
provide configuration data. Rocket's [`Config`] and [`Config::figment()`], as
|
||||
well as Figment's [`Toml`] and [`Json`], are some examples of providers.
|
||||
Providers can be combined into a single [`Figment`] provider from which any
|
||||
configuration structure that implements [`Deserialize`] can be extracted.
|
||||
|
||||
* `development` (short: `dev`)
|
||||
* `staging` (short: `stage`)
|
||||
* `production` (short: `prod`)
|
||||
Rocket expects to be able to extract a [`Config`] structure from the provider it
|
||||
is configured with. This means that no matter which configuration provider
|
||||
Rocket is asked to use, it must be able to read the following configuration
|
||||
values:
|
||||
|
||||
Without any action, Rocket applications run in the `development` environment for
|
||||
debug builds and the `production` environment for non-debug builds. The
|
||||
environment can be changed via the `ROCKET_ENV` environment variable. For
|
||||
example, to launch an application in the `staging` environment, we can run:
|
||||
| key | kind | description | debug/release default |
|
||||
|----------------|-----------------|-------------------------------------------------|-----------------------|
|
||||
| `address` | `IpAddr` | IP address to serve on | `127.0.0.1` |
|
||||
| `port` | `u16` | Port to serve on. | `8000` |
|
||||
| `workers` | `u16` | Number of threads to use for executing futures. | cpu core count * 2 |
|
||||
| `keep_alive` | `u32` | Keep-alive timeout seconds; disabled when `0`. | `5` |
|
||||
| `log_level` | `LogLevel` | Max level to log. (off/normal/debug/critical) | `normal`/`critical` |
|
||||
| `cli_colors` | `bool` | Whether to use colors and emoji when logging. | `true` |
|
||||
| `secret_key` | `SecretKey` | Secret key for signing and encrypting values. | `None` |
|
||||
| `tls` | `TlsConfig` | TLS configuration, if any. | `None` |
|
||||
| `tls.key` | `&[u8]`/`&Path` | Path/bytes to DER-encoded ASN.1 PKCS#1/#8 key. | |
|
||||
| `tls.certs` | `&[u8]`/`&Path` | Path/bytes to DER-encoded X.509 TLS cert chain. | |
|
||||
| `limits` | `Limits` | Streaming read size limits. | [`Limits::default()`] |
|
||||
| `limits.$name` | `&str`/`uint` | Read limit for `$name`. | forms = "32KiB" |
|
||||
| `ctrlc` | `bool` | Whether `ctrl-c` initiates a server shutdown. | `true` |
|
||||
|
||||
```sh
|
||||
ROCKET_ENV=stage cargo run
|
||||
```
|
||||
### Profiles
|
||||
|
||||
Note that you can use the short or long form of the environment name to specify
|
||||
the environment, `stage` _or_ `staging` here. Rocket tells us the environment we
|
||||
have chosen and its configuration when it launches:
|
||||
Configurations can be arbitrarily namespaced by [`Profile`]s. Rocket's
|
||||
[`Config`] and [`Config::figment()`] providers automatically set the
|
||||
configuration profile to "debug" when compiled in "debug" mode and "release"
|
||||
when compiled in release mode. With the exception of `log_level`, which changes
|
||||
from `normal` in debug to `critical` in release, all of the default
|
||||
configuration values are the same in all profiles. What's more, all
|
||||
configuration values _have_ defaults, so no configuration needs to be supplied
|
||||
to get an application going.
|
||||
|
||||
```sh
|
||||
$ sudo ROCKET_ENV=staging cargo run
|
||||
In addition to any profiles you declare, there are two meta-profiles, `default`
|
||||
and `global`, which can be used to provide values that apply to _all_ profiles.
|
||||
Values provided in a `default` profile are used as fall-back values when the
|
||||
selected profile doesn't contain a requested values, while values in the
|
||||
`global` profile supplant any values with the same name in any profile.
|
||||
|
||||
🔧 Configured for staging.
|
||||
=> address: 0.0.0.0
|
||||
=> port: 8000
|
||||
=> log: normal
|
||||
=> workers: [logical cores * 2]
|
||||
=> secret key: generated
|
||||
=> limits: forms = 32KiB
|
||||
=> keep-alive: 5s
|
||||
=> tls: disabled
|
||||
🛰 Mounting '/':
|
||||
=> GET / (hello)
|
||||
🚀 Rocket has launched from http://0.0.0.0:8000
|
||||
```
|
||||
[`Provider`]: @figment/trait.Provider.html
|
||||
[`Profile`]: @figment/struct.Profile.html
|
||||
[`Config`]: @api/rocket/struct.Config.html
|
||||
[`Config::figment()`]: @api/struct.Config.html#method.figment
|
||||
[`Toml`]: @figment/providers/struct.Toml.html
|
||||
[`Json`]: @figment/providers/struct.Json.html
|
||||
[`Figment`]: @api/rocket/struct.Figment.html
|
||||
[`Deserialize`]: @serde/trait.Deserialize.html
|
||||
[`Limits::default()`]: @api/rocket/data/struct.Limits.html#impl-Default
|
||||
|
||||
## Rocket.toml
|
||||
### Secret Key
|
||||
|
||||
An optional `Rocket.toml` file can be used to specify the configuration
|
||||
parameters for each environment. If it is not present, the default configuration
|
||||
parameters are used. Rocket searches for the file starting at the current
|
||||
working directory. If it is not found there, Rocket checks the parent directory.
|
||||
Rocket continues checking parent directories until the root is reached.
|
||||
The `secret_key` parameter configures a cryptographic key to use when encrypting
|
||||
application values. In particular, the key is used to encrypt [private cookies],
|
||||
which are available only when the `secrets` crate feature is enabled.
|
||||
|
||||
The file must be a series of TOML tables, at most one for each environment, and
|
||||
an optional "global" table. Each table contains key-value pairs corresponding to
|
||||
configuration parameters for that environment. If a configuration parameter is
|
||||
missing, the default value is used. The following is a complete `Rocket.toml`
|
||||
file, where every standard configuration parameter is specified with the default
|
||||
value:
|
||||
When compiled in debug mode, a fresh key is generated automatically. In release
|
||||
mode, Rocket requires you to set a secret key if the `secrets` feature is
|
||||
enabled. Failure to do so results in a hard error at launch time. The value of
|
||||
the parameter may either be a 256-bit base64 or hex string or a slice of 32
|
||||
bytes.
|
||||
|
||||
```toml
|
||||
[development]
|
||||
address = "localhost"
|
||||
port = 8000
|
||||
workers = [number of cpus * 2]
|
||||
keep_alive = 5
|
||||
log = "normal"
|
||||
secret_key = [randomly generated at launch]
|
||||
limits = { forms = 32768 }
|
||||
[private cookies]: ../requests/#private-cookies
|
||||
|
||||
[staging]
|
||||
address = "0.0.0.0"
|
||||
port = 8000
|
||||
workers = [number of cpus * 2]
|
||||
keep_alive = 5
|
||||
log = "normal"
|
||||
secret_key = [randomly generated at launch]
|
||||
limits = { forms = 32768 }
|
||||
|
||||
[production]
|
||||
address = "0.0.0.0"
|
||||
port = 8000
|
||||
workers = [number of cpus * 2]
|
||||
keep_alive = 5
|
||||
log = "critical"
|
||||
secret_key = [randomly generated at launch]
|
||||
limits = { forms = 32768 }
|
||||
```
|
||||
|
||||
The `workers` and `secret_key` default parameters are computed by Rocket
|
||||
automatically; the values above are not valid TOML syntax. When manually
|
||||
specifying the number of workers, the value should be an integer: `workers =
|
||||
10`. When manually specifying the secret key, the value should a random 256-bit
|
||||
value, encoded as a base64 or base16 string. Such a string can be generated
|
||||
using a tool like openssl: `openssl rand -base64 32`.
|
||||
|
||||
The "global" pseudo-environment can be used to set and/or override configuration
|
||||
parameters globally. A parameter defined in a `[global]` table sets, or
|
||||
overrides if already present, that parameter in every environment. For example,
|
||||
given the following `Rocket.toml` file, the value of `address` will be
|
||||
`"1.2.3.4"` in every environment:
|
||||
|
||||
```toml
|
||||
[global]
|
||||
address = "1.2.3.4"
|
||||
|
||||
[development]
|
||||
address = "localhost"
|
||||
|
||||
[production]
|
||||
address = "0.0.0.0"
|
||||
```
|
||||
|
||||
## Data Limits
|
||||
### Limits
|
||||
|
||||
The `limits` parameter configures the maximum amount of data Rocket will accept
|
||||
for a given data type. The parameter is a table where each key corresponds to a
|
||||
data type and each value corresponds to the maximum size in bytes Rocket
|
||||
should accept for that type.
|
||||
for a given data type. The value is expected to be a dictionary table where each
|
||||
key corresponds to a data type and each value corresponds to the maximum size in
|
||||
bytes Rocket should accept for that type. Rocket can parse both integers
|
||||
(`32768`) or SI unit based strings (`"32KiB"`) as limits.
|
||||
|
||||
By default, Rocket limits forms to 32KiB (32768 bytes). To increase the limit,
|
||||
simply set the `limits.forms` configuration parameter. For example, to increase
|
||||
the forms limit to 128KiB globally, we might write:
|
||||
By default, Rocket specifies a `32 KiB` limit for incoming forms. Since Rocket
|
||||
requires specifying a read limit whenever data is read, external data guards may
|
||||
also choose to have a configure limit via the `limits` parameter. The
|
||||
[`rocket_contrib::Json`] type, for instance, uses the `limits.json` parameter.
|
||||
|
||||
[`rocket_contrib::Json`]: @api/rocket_contrib/json/struct.Json.html
|
||||
|
||||
### TLS
|
||||
|
||||
Rocket includes built-in, native support for TLS >= 1.2 (Transport Layer
|
||||
Security). In order for TLS support to be enabled, Rocket must be compiled with
|
||||
the `"tls"` feature:
|
||||
|
||||
```toml
|
||||
[global.limits]
|
||||
forms = 131072
|
||||
[dependencies]
|
||||
rocket = { version = "0.5.0-dev", features = ["tls"] }
|
||||
```
|
||||
|
||||
The `limits` parameter can contain keys and values that are not endemic to
|
||||
Rocket. For instance, the [`Json`] type reads the `json` limit value to cap
|
||||
incoming JSON data. You should use the `limits` parameter for your application's
|
||||
data limits as well. Data limits can be retrieved at runtime via the
|
||||
[`Request::limits()`] method.
|
||||
TLS is configured through the `tls` configuration parameter. The value of `tls`
|
||||
is a dictionary with two keys: `certs` and `key`, described in the table above.
|
||||
Each key's value may be either a path to a file or raw bytes corresponding to
|
||||
the expected value. When a path is configured in a file source, such as
|
||||
`Rocket.toml`, relative paths are interpreted as being relative to the source
|
||||
file's directory.
|
||||
|
||||
[`Request::limits()`]: @api/rocket/struct.Request.html#method.limits
|
||||
[`Json`]: @api/rocket_contrib/json/struct.Json.html#incoming-data-limits
|
||||
! warning: Rocket's built-in TLS implements only TLS 1.2 and 1.3. As such, it
|
||||
may not be suitable for production use.
|
||||
|
||||
## Extras
|
||||
## Default Provider
|
||||
|
||||
In addition to overriding default configuration parameters, a configuration file
|
||||
can also define values for any number of _extra_ configuration parameters. While
|
||||
these parameters aren't used by Rocket directly, other libraries, or your own
|
||||
application, can use them as they wish. As an example, the
|
||||
[Template](@api/rocket_contrib/templates/struct.Template.html) type
|
||||
accepts a value for the `template_dir` configuration parameter. The parameter
|
||||
can be set in `Rocket.toml` as follows:
|
||||
Rocket's default configuration provider is [`Config::figment()`]; this is the
|
||||
provider that's used when calling [`rocket::ignite()`].
|
||||
|
||||
The default figment merges, at a per-key level, and reads from the following
|
||||
sources, in ascending priority order:
|
||||
|
||||
1. [`Config::default()`] - which provides default values for all parameters.
|
||||
2. `Rocket.toml` _or_ TOML file path in `ROCKET_CONFIG` environment variable.
|
||||
3. `ROCKET_` prefixed environment variables.
|
||||
|
||||
The selected profile is the value of the `ROCKET_PROFILE` environment variable,
|
||||
or if it is not set, "debug" when compiled in debug mode and "release" when
|
||||
compiled in release mode.
|
||||
|
||||
As a result, without any effort, Rocket's server can be configured via a
|
||||
`Rocket.toml` file and/or via environment variables, the latter of which take
|
||||
precedence over the former. Note that neither the file nor any environment
|
||||
variables need to be present as [`Config::default()`] is a complete
|
||||
configuration source.
|
||||
|
||||
[`Config::default()`]: @api/rocket/struct.Config.html#method.default
|
||||
|
||||
### Rocket.toml
|
||||
|
||||
Rocket searches for `Rocket.toml` or the filename in a `ROCKET_CONFIG`
|
||||
environment variable starting at the current working directory. If it is not
|
||||
found, the parent directory, its parent, and so on, are searched until the file
|
||||
is found or the root is reached. If the path set in `ROCKET_CONFIG` is absolute,
|
||||
no such search occurs, and the set path is used directly.
|
||||
|
||||
The file is assumed to be _nested_, so each top-level key declares a profile and
|
||||
its values the value for the profile. The following is an example of what such a
|
||||
file might look like:
|
||||
|
||||
```toml
|
||||
[development]
|
||||
template_dir = "dev_templates/"
|
||||
## defaults for _all_ profiles
|
||||
[default]
|
||||
address = "0.0.0.0"
|
||||
limits = { forms = "64 kB", json = "1 MiB" }
|
||||
|
||||
[production]
|
||||
template_dir = "prod_templates/"
|
||||
## set only when compiled in debug mode, i.e, `cargo build`
|
||||
[debug]
|
||||
port = 8000
|
||||
## only the `json` key from `default` will be overridden; `forms` will remain
|
||||
limits = { json = "10MiB" }
|
||||
|
||||
## set only when the `nyc` profile is selected
|
||||
[nyc]
|
||||
port = 9001
|
||||
|
||||
## set only when compiled in release mode, i.e, `cargo build --release`
|
||||
## don't use this secret_key! generate your own and keep it private!
|
||||
[release]
|
||||
port = 9999
|
||||
secret_key = "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk="
|
||||
```
|
||||
|
||||
This sets the `template_dir` extra configuration parameter to `"dev_templates/"`
|
||||
when operating in the `development` environment and `"prod_templates/"` when
|
||||
operating in the `production` environment. Rocket will prepend the `[extra]` tag
|
||||
to extra configuration parameters when launching:
|
||||
### Environment Variables
|
||||
|
||||
```sh
|
||||
🔧 Configured for development.
|
||||
=> ...
|
||||
=> [extra] template_dir: "dev_templates/"
|
||||
```
|
||||
|
||||
To retrieve a custom, extra configuration parameter in your application, we
|
||||
recommend using an [ad-hoc attach fairing] in combination with [managed state].
|
||||
For example, if your application makes use of a custom `assets_dir` parameter:
|
||||
|
||||
[ad-hoc attach fairing]: ../fairings/#ad-hoc-fairings
|
||||
[managed state]: ../state/#managed-state
|
||||
|
||||
```toml
|
||||
[development]
|
||||
assets_dir = "dev_assets/"
|
||||
|
||||
[production]
|
||||
assets_dir = "prod_assets/"
|
||||
```
|
||||
|
||||
The following code will:
|
||||
|
||||
1. Read the configuration parameter in an ad-hoc `attach` fairing.
|
||||
2. Store the parsed parameter in an `AssetsDir` structure in managed state.
|
||||
3. Retrieve the parameter in an `assets` route via the `State` guard.
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate rocket;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use rocket::State;
|
||||
use rocket::response::NamedFile;
|
||||
use rocket::fairing::AdHoc;
|
||||
|
||||
struct AssetsDir(String);
|
||||
|
||||
#[get("/<asset..>")]
|
||||
async fn assets(asset: PathBuf, assets_dir: State<'_, AssetsDir>) -> Option<NamedFile> {
|
||||
NamedFile::open(Path::new(&assets_dir.0).join(asset)).await.ok()
|
||||
}
|
||||
|
||||
#[launch]
|
||||
fn rocket() -> rocket::Rocket {
|
||||
rocket::ignite()
|
||||
.mount("/", routes![assets])
|
||||
.attach(AdHoc::on_attach("Assets Config", |mut rocket| async {
|
||||
let assets_dir = rocket.config().await
|
||||
.get_str("assets_dir")
|
||||
.unwrap_or("assets/")
|
||||
.to_string();
|
||||
|
||||
Ok(rocket.manage(AssetsDir(assets_dir)))
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
All configuration parameters, including extras, can be overridden through
|
||||
environment variables. To override the configuration parameter `{param}`, use an
|
||||
environment variable named `ROCKET_{PARAM}`. For instance, to override the
|
||||
"port" configuration parameter, you can run your application with:
|
||||
|
||||
```sh
|
||||
ROCKET_PORT=3721 ./your_application
|
||||
|
||||
🔧 Configured for development.
|
||||
=> ...
|
||||
=> port: 3721
|
||||
```
|
||||
|
||||
Environment variables take precedence over all other configuration methods: if
|
||||
the variable is set, it will be used as the value for the parameter. Variable
|
||||
values are parsed as if they were TOML syntax. As illustration, consider the
|
||||
Rocket reads all environment variable names prefixed with `ROCKET_` using the
|
||||
string after the `_` as the name of a configuration value as the value of the
|
||||
parameter as the value itself. Environment variables take precedence over values
|
||||
in `Rocket.toml`. Values are parsed as loose form of TOML syntax. Consider the
|
||||
following examples:
|
||||
|
||||
```sh
|
||||
|
@ -249,80 +187,161 @@ ROCKET_ARRAY=[1,"b",3.14]
|
|||
ROCKET_DICT={key="abc",val=123}
|
||||
```
|
||||
|
||||
## Programmatic
|
||||
## Extracting Values
|
||||
|
||||
In addition to using environment variables or a config file, Rocket can also be
|
||||
configured using the [`rocket::custom()`] method and [`ConfigBuilder`]:
|
||||
Your application can extract any configuration that implements [`Deserialize`]
|
||||
from the configured provider, which is exposed via [`Rocket::figment()`] and
|
||||
[`Cargo::figment()`]:
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate rocket;
|
||||
# extern crate serde;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
|
||||
#[launch]
|
||||
async fn rocket() -> _ {
|
||||
let mut rocket = rocket::ignite();
|
||||
let figment = rocket.figment().await;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Config {
|
||||
port: u16,
|
||||
custom: Vec<String>,
|
||||
}
|
||||
|
||||
// extract the entire config any `Deserialize` value
|
||||
let config: Config = figment.extract().expect("config");
|
||||
|
||||
// or a piece of it into any `Deserialize` value
|
||||
let custom: Vec<String> = figment.extract_inner("custom").expect("custom");
|
||||
|
||||
rocket
|
||||
}
|
||||
```
|
||||
|
||||
Both values recognized by Rocket and values _not_ recognized by Rocket can be
|
||||
extracted. This means you can configure values recognized by your application in
|
||||
Rocket's configuration sources directly. The next section describes how you can
|
||||
customize configuration sources by supplying your own `Provider`.
|
||||
|
||||
Because it is common to store configuration in managed state, Rocket provides an
|
||||
`AdHoc` fairing that 1) extracts a configuration from the configured provider,
|
||||
2) pretty prints any errors, and 3) stores the value in managed state:
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate rocket;
|
||||
# extern crate serde;
|
||||
# use serde::Deserialize;
|
||||
# #[derive(Deserialize)]
|
||||
# struct Config {
|
||||
# port: u16,
|
||||
# custom: Vec<String>,
|
||||
# }
|
||||
|
||||
use rocket::{State, fairing::AdHoc};
|
||||
|
||||
#[get("/custom")]
|
||||
fn custom(config: State<'_, Config>) -> String {
|
||||
config.custom.get(0).cloned().unwrap_or("default".into())
|
||||
}
|
||||
|
||||
#[launch]
|
||||
fn rocket() -> _ {
|
||||
rocket::ignite()
|
||||
.mount("/", routes![custom])
|
||||
.attach(AdHoc::config::<Config>())
|
||||
}
|
||||
```
|
||||
|
||||
[`Rocket::figment()`]: @api/rocket/struct.Rocket.html#method.figment
|
||||
[`Cargo::figment()`]: @api/rocket/struct.Cargo.html#method.figment
|
||||
|
||||
## Custom Providers
|
||||
|
||||
A custom provider can be set via [`rocket::custom()`], which replaces calls to
|
||||
[`rocket::ignite()`]. The configured provider can be built on top of
|
||||
[`Config::figment()`], [`Config::default()`], both, or neither. The
|
||||
[Figment](@figment) documentation has full details on instantiating existing
|
||||
providers like [`Toml`] and [`Json`] as well as creating custom providers for
|
||||
more complex cases.
|
||||
|
||||
! note: You may need to depend on `figment` and `serde` directly.
|
||||
|
||||
Rocket reexports `figment` from its crate root, so you can refer to `figment`
|
||||
types via `rocket::figment`. However, Rocket does not enable all features from
|
||||
the figment crate. As such, you may need to import `figment` directly:
|
||||
|
||||
`
|
||||
figment = { version = "0.9", features = ["env", "toml", "json"] }
|
||||
`
|
||||
|
||||
Furthermore, you should directly depend on `serde` when using its `derive`
|
||||
feature, which is also not enabled by Rocket:
|
||||
|
||||
`
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
`
|
||||
|
||||
As a first example, we override configuration values at runtime by merging
|
||||
figment's tuple providers with Rocket's default provider:
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate rocket;
|
||||
|
||||
use rocket::config::{Config, Environment};
|
||||
use rocket::data::{Limits, ToByteUnit};
|
||||
|
||||
# fn build_config() -> rocket::config::Result<Config> {
|
||||
let config = Config::build(Environment::Staging)
|
||||
.address("1.2.3.4")
|
||||
.port(9234)
|
||||
.finalize()?;
|
||||
# Ok(config)
|
||||
# }
|
||||
#[launch]
|
||||
fn rocket() -> _ {
|
||||
let figment = rocket::Config::figment()
|
||||
.merge(("port", 1111))
|
||||
.merge(("limits", Limits::new().limit("json", 2.mebibytes())));
|
||||
|
||||
# let config = build_config().expect("config okay");
|
||||
# /*
|
||||
rocket::custom(config)
|
||||
.mount("/", routes![/* .. */])
|
||||
.launch()
|
||||
.await;
|
||||
# */
|
||||
rocket::custom(figment).mount("/", routes![/* .. */])
|
||||
}
|
||||
```
|
||||
|
||||
Configuration via `rocket::custom()` replaces calls to `rocket::ignite()` and
|
||||
all configuration from `Rocket.toml` or environment variables. In other words,
|
||||
using `rocket::custom()` results in `Rocket.toml` and environment variables
|
||||
being ignored.
|
||||
More involved, consider an application that wants to use Rocket's defaults for
|
||||
[`Config`], but not its configuration sources, while allowing the application to
|
||||
be configured via an `App.toml` file and `APP_` environment variables:
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate rocket;
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
use figment::{Figment, providers::{Format, Toml, Serialized, Env}};
|
||||
use rocket::fairing::AdHoc;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct Config {
|
||||
app_value: usize,
|
||||
/* and so on.. */
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Config {
|
||||
Config { app_value: 3, }
|
||||
}
|
||||
}
|
||||
|
||||
#[launch]
|
||||
fn rocket() -> _ {
|
||||
let figment = Figment::from(rocket::Config::default())
|
||||
.merge(Serialized::defaults(Config::default()))
|
||||
.merge(Toml::file("App.toml"))
|
||||
.merge(Env::prefixed("APP_"));
|
||||
|
||||
rocket::custom(figment)
|
||||
.mount("/", routes![/* .. */])
|
||||
.attach(AdHoc::config::<Config>())
|
||||
}
|
||||
```
|
||||
|
||||
Rocket will extract it's configuration from the configured provider. This means
|
||||
that if values like `port` and `address` are configured in `Config`, `App.toml`
|
||||
or `APP_` environment variables, Rocket will make use of them. The application
|
||||
can also extract its configuration, done here via the `Adhoc::config()` fairing.
|
||||
|
||||
[`rocket::custom()`]: @api/rocket/fn.custom.html
|
||||
[`ConfigBuilder`]: @api/rocket/config/struct.ConfigBuilder.html
|
||||
|
||||
## Configuring TLS
|
||||
|
||||
! warning: Rocket's built-in TLS is **not** considered ready for production use.
|
||||
It is intended for development use _only_.
|
||||
|
||||
Rocket includes built-in, native support for TLS >= 1.2 (Transport Layer
|
||||
Security). In order for TLS support to be enabled, Rocket must be compiled with
|
||||
the `"tls"` feature. To do this, add the `"tls"` feature to the `rocket`
|
||||
dependency in your `Cargo.toml` file:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
rocket = { version = "0.5.0-dev", features = ["tls"] }
|
||||
```
|
||||
|
||||
TLS is configured through the `tls` configuration parameter. The value of `tls`
|
||||
must be a table with two keys:
|
||||
|
||||
* `certs`: _[string]_ a path to a certificate chain in PEM format
|
||||
* `key`: _[string]_ a path to a private key file in PEM format for the
|
||||
certificate in `certs`
|
||||
|
||||
The recommended way to specify these parameters is via the `global` environment:
|
||||
|
||||
```toml
|
||||
[global.tls]
|
||||
certs = "/path/to/certs.pem"
|
||||
key = "/path/to/key.pem"
|
||||
```
|
||||
|
||||
Of course, you can always specify the configuration values per environment:
|
||||
|
||||
```toml
|
||||
[development]
|
||||
tls = { certs = "/path/to/certs.pem", key = "/path/to/key.pem" }
|
||||
```
|
||||
|
||||
Or via environment variables:
|
||||
|
||||
```sh
|
||||
ROCKET_TLS={certs="/path/to/certs.pem",key="/path/to/key.pem"} cargo run
|
||||
```
|
||||
[`rocket::ignite()`]: @api/rocket/fn.custom.html
|
||||
|
|
|
@ -6,8 +6,9 @@ edition = "2018"
|
|||
publish = false
|
||||
|
||||
[dev-dependencies]
|
||||
rocket = { path = "../../core/lib" }
|
||||
rocket = { path = "../../core/lib", features = ["secrets"] }
|
||||
doc-comment = "0.3"
|
||||
rocket_contrib = { path = "../../contrib/lib", features = ["json", "tera_templates", "diesel_sqlite_pool"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
rand = "0.7"
|
||||
figment = { version = "0.9.2", features = ["toml", "env"] }
|
||||
|
|
Loading…
Reference in New Issue