mirror of https://github.com/rwf2/Rocket.git
Merge branch 'master' into uuid_support_v2
This commit is contained in:
commit
39e9234f5f
72
README.md
72
README.md
|
@ -1,4 +1,10 @@
|
||||||
# Rocket [![Build Status](https://travis-ci.org/SergioBenitez/Rocket.svg?branch=master)](https://travis-ci.org/SergioBenitez/Rocket) [![Rocket Homepage](https://img.shields.io/badge/web-rocket.rs-red.svg?style=flat&label=https&colorB=d33847)](https://rocket.rs) [![Current Crates.io Version](https://img.shields.io/crates/v/rocket.svg)](https://crates.io/crates/rocket)
|
# Rocket
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/SergioBenitez/Rocket.svg?branch=master)](https://travis-ci.org/SergioBenitez/Rocket)
|
||||||
|
[![Rocket Homepage](https://img.shields.io/badge/web-rocket.rs-red.svg?style=flat&label=https&colorB=d33847)](https://rocket.rs)
|
||||||
|
[![Current Crates.io Version](https://img.shields.io/crates/v/rocket.svg)](https://crates.io/crates/rocket)
|
||||||
|
[![Chat on Matrix](https://img.shields.io/badge/style-matrix-blue.svg?style=flat&label=chat)](https://riot.im/app/#/room/#mozilla_#rocket:matrix.org)
|
||||||
|
[![IRC: #rocket on irc.mozilla.org](https://img.shields.io/badge/style-%23rocket-blue.svg?style=flat&label=mozilla)](https://kiwiirc.com/client/irc.mozilla.org/#rocket)
|
||||||
|
|
||||||
Rocket is web framework for Rust (nightly) with a focus on ease-of-use,
|
Rocket is web framework for Rust (nightly) with a focus on ease-of-use,
|
||||||
expressability, and speed. Here's an example of a complete Rocket application:
|
expressability, and speed. Here's an example of a complete Rocket application:
|
||||||
|
@ -40,12 +46,16 @@ Rocket is extensively documented:
|
||||||
[Guide]: https://rocket.rs/guide
|
[Guide]: https://rocket.rs/guide
|
||||||
[API Documentation]: https://api.rocket.rs/rocket
|
[API Documentation]: https://api.rocket.rs/rocket
|
||||||
|
|
||||||
The official community support channel is via the [Mozilla IRC
|
The official community support channels are the `#rocket` IRC channel on the
|
||||||
Server](https://wiki.mozilla.org/IRC) at `irc.mozilla.org` in channel `#rocket`.
|
[Mozilla IRC Server](https://wiki.mozilla.org/IRC) at `irc.mozilla.org` and the
|
||||||
If you're not familiar with IRC, see Mozilla's [Getting Started with
|
bridged [Rocket room on
|
||||||
|
Matrix](https://riot.im/app/#/room/#mozilla_#rocket:matrix.org). If you're not
|
||||||
|
familiar with IRC, we recommend chatting through [Matrix via
|
||||||
|
Riot](https://riot.im/app/#/room/#mozilla_#rocket:matrix.org) or via the [Kiwi
|
||||||
|
web IRC client](https://kiwiirc.com/client/irc.mozilla.org/#rocket). You can
|
||||||
|
learn more about IRC via Mozilla's [Getting Started with
|
||||||
IRC](https://developer.mozilla.org/en-US/docs/Mozilla/QA/Getting_Started_with_IRC)
|
IRC](https://developer.mozilla.org/en-US/docs/Mozilla/QA/Getting_Started_with_IRC)
|
||||||
guide. You can find general Rust support in `#rust` or `#rust-beginners` on the
|
guide.
|
||||||
same network.
|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
|
@ -137,8 +147,9 @@ Apache License, Version 2.0, without any additional terms or conditions.
|
||||||
Rocket is designed to be performant. At this time, its performance is
|
Rocket is designed to be performant. At this time, its performance is
|
||||||
[bottlenecked by the Hyper HTTP
|
[bottlenecked by the Hyper HTTP
|
||||||
library](https://github.com/SergioBenitez/Rocket/issues/17). Even so, Rocket
|
library](https://github.com/SergioBenitez/Rocket/issues/17). Even so, Rocket
|
||||||
currently performs _better_ than the latest version of Hyper on a simple "Hello,
|
currently performs _significantly better_ than the latest version of
|
||||||
world!" benchmark:
|
asynchronous Hyper on a simple "Hello, world!" benchmark. Rocket also performs
|
||||||
|
_significantly better_ than the Iron web framework:
|
||||||
|
|
||||||
**Machine Specs:**
|
**Machine Specs:**
|
||||||
|
|
||||||
|
@ -147,32 +158,45 @@ world!" benchmark:
|
||||||
* **Processor:** Intel Xeon X5675 @ 3.07GHz
|
* **Processor:** Intel Xeon X5675 @ 3.07GHz
|
||||||
* **Operating System:** Mac OS X v10.11.6
|
* **Operating System:** Mac OS X v10.11.6
|
||||||
|
|
||||||
**Hyper v0.10.0-a.0** (46 LOC) results (best of 3, +/- 2000 req/s, +/- 10us latency):
|
**Rocket v0.2-rc** (8 LOC) results (best of 3, +/- 2000 req/s, +/- 5us latency):
|
||||||
|
|
||||||
Running 10s test @ http://localhost:80
|
Running 10s test @ http://localhost:80
|
||||||
2 threads and 10 connections
|
1 threads and 18 connections
|
||||||
Thread Stats Avg Stdev Max +/- Stdev
|
Thread Stats Avg Stdev Max +/- Stdev
|
||||||
Latency 175.12us 40.38us 429.00us 70.79%
|
Latency 153.01us 42.25us 449.00us 75.54%
|
||||||
Req/Sec 28.00k 2.41k 36.79k 72.28%
|
Req/Sec 75.58k 11.75k 90.22k 54.46%
|
||||||
562692 requests in 10.10s, 81.57MB read
|
758044 requests in 10.10s, 105.55MB read
|
||||||
Requests/sec: 55715.98
|
Requests/sec: 75051.28
|
||||||
Transfer/sec: 8.08MB
|
Transfer/sec: 10.45MB
|
||||||
|
|
||||||
**Rocket v0.1.0** (8 LOC) results (best of 3, +/- 1000 req/s, +/- 5us latency):
|
**Hyper v0.10.0-a.0 (1/12/2016)** (46 LOC) results (best of 3, +/- 5000 req/s, +/- 30us latency):
|
||||||
|
|
||||||
Running 10s test @ http://localhost:80
|
Running 10s test @ http://localhost:80
|
||||||
2 threads and 10 connections
|
1 threads and 18 connections
|
||||||
Thread Stats Avg Stdev Max +/- Stdev
|
Thread Stats Avg Stdev Max +/- Stdev
|
||||||
Latency 161.33us 37.40us 2.08ms 75.89%
|
Latency 287.81us 77.09us 606.00us 70.47%
|
||||||
Req/Sec 30.10k 1.13k 33.28k 72.77%
|
Req/Sec 59.94k 6.01k 79.72k 71.00%
|
||||||
604782 requests in 10.10s, 84.21MB read
|
596231 requests in 10.00s, 83.02MB read
|
||||||
Requests/sec: 59883.30
|
Requests/sec: 59621.32
|
||||||
Transfer/sec: 8.34MB
|
Transfer/sec: 8.30MB
|
||||||
|
|
||||||
|
**Iron v0.5.0** (11 LOC) results (best of 3, +/- 3000 req/s, +/- 500us latency):
|
||||||
|
|
||||||
|
Running 10s test @ http://localhost:80
|
||||||
|
1 threads and 18 connections
|
||||||
|
Thread Stats Avg Stdev Max +/- Stdev
|
||||||
|
Latency 512.36us 5.57ms 149.99ms 99.60%
|
||||||
|
Req/Sec 58.25k 11.61k 70.47k 46.00%
|
||||||
|
579227 requests in 10.00s, 80.65MB read
|
||||||
|
Requests/sec: 57920.73
|
||||||
|
Transfer/sec: 8.06MB
|
||||||
|
|
||||||
**Summary:**
|
**Summary:**
|
||||||
|
|
||||||
* Rocket throughput higher by 7.5% (higher is better).
|
* Rocket throughput higher by 25.9% (higher is better) compared to Hyper.
|
||||||
* Rocket latency lower by 7.8% (lower is better).
|
* Rocket throughput higher by 29.6% (higher is better) compared to Iron.
|
||||||
|
* Rocket latency lower by 46.8% (lower is better) compared to Hyper.
|
||||||
|
* Rocket latency lower by 70.1% (lower is better) compared to Iron.
|
||||||
|
|
||||||
### Future Improvements
|
### Future Improvements
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,8 @@ serde = { version = "^0.8", optional = true }
|
||||||
serde_json = { version = "^0.8", optional = true }
|
serde_json = { version = "^0.8", optional = true }
|
||||||
|
|
||||||
# Templating dependencies only.
|
# Templating dependencies only.
|
||||||
handlebars = { version = "^0.23", optional = true, features = ["serde_type"] }
|
handlebars = { version = "^0.24", optional = true, features = ["serde_type"] }
|
||||||
glob = { version = "^0.2", optional = true }
|
glob = { version = "^0.2", optional = true }
|
||||||
lazy_static = { version = "^0.2", optional = true }
|
lazy_static = { version = "^0.2", optional = true }
|
||||||
tera = { version = "^0.6", optional = true }
|
# tera = { version = "^0.6", optional = true }
|
||||||
|
tera = { git = "https://github.com/SergioBenitez/tera", optional = true }
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#![feature(drop_types_in_const)]
|
||||||
|
|
||||||
//! This crate contains officially sanctioned contributor libraries that provide
|
//! This crate contains officially sanctioned contributor libraries that provide
|
||||||
//! functionality commonly used by Rocket applications.
|
//! functionality commonly used by Rocket applications.
|
||||||
//!
|
//!
|
||||||
|
|
|
@ -1,31 +1,53 @@
|
||||||
extern crate handlebars;
|
extern crate handlebars;
|
||||||
|
|
||||||
use std::sync::RwLock;
|
|
||||||
|
|
||||||
use super::serde::Serialize;
|
use super::serde::Serialize;
|
||||||
use super::TemplateInfo;
|
use super::TemplateInfo;
|
||||||
|
|
||||||
use self::handlebars::Handlebars;
|
use self::handlebars::Handlebars;
|
||||||
|
|
||||||
lazy_static! {
|
static mut HANDLEBARS: Option<Handlebars> = None;
|
||||||
static ref HANDLEBARS: RwLock<Handlebars> = RwLock::new(Handlebars::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const EXT: &'static str = "hbs";
|
pub const EXT: &'static str = "hbs";
|
||||||
|
|
||||||
pub fn render<T>(name: &str, info: &TemplateInfo, context: &T) -> Option<String>
|
// This function must be called a SINGLE TIME from A SINGLE THREAD for safety to
|
||||||
where T: Serialize
|
// hold here and in `render`.
|
||||||
{
|
pub unsafe fn register(templates: &[(&str, &TemplateInfo)]) -> bool {
|
||||||
// FIXME: Expose a callback to register each template at launch => no lock.
|
if HANDLEBARS.is_some() {
|
||||||
if HANDLEBARS.read().unwrap().get_template(name).is_none() {
|
error_!("Internal error: reregistering handlebars!");
|
||||||
let p = &info.full_path;
|
return false;
|
||||||
if let Err(e) = HANDLEBARS.write().unwrap().register_template_file(name, p) {
|
}
|
||||||
|
|
||||||
|
let mut hb = Handlebars::new();
|
||||||
|
let mut success = true;
|
||||||
|
for &(name, info) in templates {
|
||||||
|
let path = &info.full_path;
|
||||||
|
if let Err(e) = hb.register_template_file(name, path) {
|
||||||
error_!("Handlebars template '{}' failed registry: {:?}", name, e);
|
error_!("Handlebars template '{}' failed registry: {:?}", name, e);
|
||||||
return None;
|
success = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match HANDLEBARS.read().unwrap().render(name, context) {
|
HANDLEBARS = Some(hb);
|
||||||
|
success
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render<T>(name: &str, _info: &TemplateInfo, context: &T) -> Option<String>
|
||||||
|
where T: Serialize
|
||||||
|
{
|
||||||
|
let hb = match unsafe { HANDLEBARS.as_ref() } {
|
||||||
|
Some(hb) => hb,
|
||||||
|
None => {
|
||||||
|
error_!("Internal error: `render` called before handlebars init.");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if hb.get_template(name).is_none() {
|
||||||
|
error_!("Handlebars template '{}' does not exist.", name);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match hb.render(name, context) {
|
||||||
Ok(string) => Some(string),
|
Ok(string) => Some(string),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error_!("Error rendering Handlebars template '{}': {}", name, e);
|
error_!("Error rendering Handlebars template '{}': {}", name, e);
|
||||||
|
|
|
@ -2,19 +2,21 @@
|
||||||
/// engines from the set of template engined passed in.
|
/// engines from the set of template engined passed in.
|
||||||
macro_rules! engine_set {
|
macro_rules! engine_set {
|
||||||
($($feature:expr => $engine:ident),+,) => ({
|
($($feature:expr => $engine:ident),+,) => ({
|
||||||
use std::collections::HashSet;
|
type RegisterFn = for<'a, 'b> unsafe fn(&'a [(&'b str, &TemplateInfo)]) -> bool;
|
||||||
let mut set = HashSet::new();
|
|
||||||
|
let mut set = Vec::new();
|
||||||
$(
|
$(
|
||||||
#[cfg(feature = $feature)]
|
#[cfg(feature = $feature)]
|
||||||
fn $engine(set: &mut HashSet<String>) {
|
fn $engine(set: &mut Vec<(&'static str, RegisterFn)>) {
|
||||||
set.insert($engine::EXT.to_string());
|
set.push(($engine::EXT, $engine::register));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = $feature))]
|
#[cfg(not(feature = $feature))]
|
||||||
fn $engine(_: &mut HashSet<String>) { }
|
fn $engine(_: &mut Vec<(&'static str, RegisterFn)>) { }
|
||||||
|
|
||||||
$engine(&mut set);
|
$engine(&mut set);
|
||||||
)+
|
)+
|
||||||
|
|
||||||
set
|
set
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -25,25 +27,27 @@ macro_rules! engine_set {
|
||||||
/// extension, and if so, calls the engine's `render` method. All of this only
|
/// extension, and if so, calls the engine's `render` method. All of this only
|
||||||
/// happens for engine's that have been enabled as features by the user.
|
/// happens for engine's that have been enabled as features by the user.
|
||||||
macro_rules! render_set {
|
macro_rules! render_set {
|
||||||
($name:expr, $info:expr, $ctxt:expr, $($feature:expr => $engine:ident),+,) => ({$(
|
($name:expr, $info:expr, $ctxt:expr, $($feature:expr => $engine:ident),+,) => ({
|
||||||
#[cfg(feature = $feature)]
|
$(
|
||||||
fn $engine<T: Serialize>(name: &str, info: &TemplateInfo, c: &T)
|
#[cfg(feature = $feature)]
|
||||||
-> Option<Template> {
|
fn $engine<T: Serialize>(name: &str, info: &TemplateInfo, c: &T)
|
||||||
if info.extension == $engine::EXT {
|
-> Option<Template> {
|
||||||
let rendered = $engine::render(name, info, c);
|
if info.extension == $engine::EXT {
|
||||||
return Some(Template(rendered, info.data_type.clone()));
|
let rendered = $engine::render(name, info, c);
|
||||||
|
return Some(Template(rendered, info.data_type.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
#[cfg(not(feature = $feature))]
|
||||||
}
|
fn $engine<T: Serialize>(_: &str, _: &TemplateInfo, _: &T)
|
||||||
|
-> Option<Template> { None }
|
||||||
|
|
||||||
#[cfg(not(feature = $feature))]
|
if let Some(template) = $engine($name, &$info, $ctxt) {
|
||||||
fn $engine<T: Serialize>(_: &str, _: &TemplateInfo, _: &T)
|
return template
|
||||||
-> Option<Template> { None }
|
}
|
||||||
|
)+
|
||||||
if let Some(template) = $engine($name, &$info, $ctxt) {
|
});
|
||||||
return template
|
|
||||||
}
|
|
||||||
)+});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ use std::path::{Path, PathBuf};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use rocket::config;
|
use rocket::config::{self, ConfigError};
|
||||||
use rocket::response::{self, Content, Responder};
|
use rocket::response::{self, Content, Responder};
|
||||||
use rocket::http::{ContentType, Status};
|
use rocket::http::{ContentType, Status};
|
||||||
|
|
||||||
|
@ -104,17 +104,25 @@ const DEFAULT_TEMPLATE_DIR: &'static str = "templates";
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref TEMPLATES: HashMap<String, TemplateInfo> = discover_templates();
|
static ref TEMPLATES: HashMap<String, TemplateInfo> = discover_templates();
|
||||||
static ref TEMPLATE_DIR: String = {
|
static ref TEMPLATE_DIR: PathBuf = {
|
||||||
config::active().map(|config| {
|
let default_dir_path = config::active().ok_or(ConfigError::NotFound)
|
||||||
let dir = config.get_str("template_dir").map_err(|e| {
|
.map(|config| config.root().join(DEFAULT_TEMPLATE_DIR))
|
||||||
|
.map_err(|_| {
|
||||||
|
warn_!("No configuration is active!");
|
||||||
|
warn_!("Using default template directory: {:?}", DEFAULT_TEMPLATE_DIR);
|
||||||
|
})
|
||||||
|
.unwrap_or(PathBuf::from(DEFAULT_TEMPLATE_DIR));
|
||||||
|
|
||||||
|
config::active().ok_or(ConfigError::NotFound)
|
||||||
|
.and_then(|config| config.get_str("template_dir"))
|
||||||
|
.map(|user_dir| PathBuf::from(user_dir))
|
||||||
|
.map_err(|e| {
|
||||||
if !e.is_not_found() {
|
if !e.is_not_found() {
|
||||||
e.pretty_print();
|
e.pretty_print();
|
||||||
warn_!("Using default directory '{}'", DEFAULT_TEMPLATE_DIR);
|
warn_!("Using default directory '{:?}'", default_dir_path);
|
||||||
}
|
}
|
||||||
}).unwrap_or(DEFAULT_TEMPLATE_DIR);
|
})
|
||||||
|
.unwrap_or(default_dir_path)
|
||||||
config.root().join(dir).to_string_lossy().into_owned()
|
|
||||||
}).unwrap_or(DEFAULT_TEMPLATE_DIR.to_string())
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,11 +152,13 @@ impl Template {
|
||||||
let names: Vec<_> = TEMPLATES.keys().map(|s| s.as_str()).collect();
|
let names: Vec<_> = TEMPLATES.keys().map(|s| s.as_str()).collect();
|
||||||
error_!("Template '{}' does not exist.", name);
|
error_!("Template '{}' does not exist.", name);
|
||||||
info_!("Known templates: {}", names.join(","));
|
info_!("Known templates: {}", names.join(","));
|
||||||
info_!("Searched in '{}'.", *TEMPLATE_DIR);
|
info_!("Searched in '{:?}'.", *TEMPLATE_DIR);
|
||||||
return Template(None, None);
|
return Template(None, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep this set in-sync with the `engine_set` invocation.
|
// Keep this set in-sync with the `engine_set` invocation. The macro
|
||||||
|
// `return`s a `Template` if the extenion in `template` matches an
|
||||||
|
// engine in the set. Otherwise, control will fall through.
|
||||||
render_set!(name, template.unwrap(), context,
|
render_set!(name, template.unwrap(), context,
|
||||||
"tera_templates" => tera_templates,
|
"tera_templates" => tera_templates,
|
||||||
"handlebars_templates" => handlebars_templates,
|
"handlebars_templates" => handlebars_templates,
|
||||||
|
@ -219,6 +229,8 @@ fn split_path(path: &Path) -> (PathBuf, String, Option<String>) {
|
||||||
/// Returns a HashMap of `TemplateInfo`'s for all of the templates in
|
/// Returns a HashMap of `TemplateInfo`'s for all of the templates in
|
||||||
/// `TEMPLATE_DIR`. Templates are all files that match one of the extensions for
|
/// `TEMPLATE_DIR`. Templates are all files that match one of the extensions for
|
||||||
/// engine's in `engine_set`.
|
/// engine's in `engine_set`.
|
||||||
|
///
|
||||||
|
/// **WARNING:** This function should be called ONCE from a SINGLE THREAD.
|
||||||
fn discover_templates() -> HashMap<String, TemplateInfo> {
|
fn discover_templates() -> HashMap<String, TemplateInfo> {
|
||||||
// Keep this set in-sync with the `render_set` invocation.
|
// Keep this set in-sync with the `render_set` invocation.
|
||||||
let engines = engine_set![
|
let engines = engine_set![
|
||||||
|
@ -227,20 +239,31 @@ fn discover_templates() -> HashMap<String, TemplateInfo> {
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut templates = HashMap::new();
|
let mut templates = HashMap::new();
|
||||||
for ext in engines {
|
for &(ext, _) in &engines {
|
||||||
let mut glob_path: PathBuf = [&*TEMPLATE_DIR, "**", "*"].iter().collect();
|
let mut glob_path: PathBuf = TEMPLATE_DIR.join("**").join("*");
|
||||||
glob_path.set_extension(ext);
|
glob_path.set_extension(ext);
|
||||||
for path in glob(glob_path.to_str().unwrap()).unwrap().filter_map(Result::ok) {
|
for path in glob(glob_path.to_str().unwrap()).unwrap().filter_map(Result::ok) {
|
||||||
let (rel_path, name, data_type) = split_path(&path);
|
let (rel_path, name, data_type) = split_path(&path);
|
||||||
templates.insert(name, TemplateInfo {
|
let info = TemplateInfo {
|
||||||
full_path: path.to_path_buf(),
|
full_path: path.to_path_buf(),
|
||||||
path: rel_path,
|
path: rel_path,
|
||||||
extension: path.extension().unwrap().to_string_lossy().into_owned(),
|
extension: ext.to_string(),
|
||||||
data_type: data_type,
|
data_type: data_type,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
templates.insert(name, info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for &(ext, register_fn) in &engines {
|
||||||
|
let named_templates = templates.iter()
|
||||||
|
.filter(|&(_, i)| i.extension == ext)
|
||||||
|
.map(|(k, i)| (k.as_str(), i))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
unsafe { register_fn(&*named_templates); }
|
||||||
|
};
|
||||||
|
|
||||||
templates
|
templates
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,43 +1,59 @@
|
||||||
extern crate tera;
|
extern crate tera;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use super::serde::Serialize;
|
use super::serde::Serialize;
|
||||||
use super::{TemplateInfo, TEMPLATE_DIR};
|
use super::TemplateInfo;
|
||||||
|
|
||||||
lazy_static! {
|
use self::tera::Tera;
|
||||||
static ref TERA: Result<tera::Tera, String> = {
|
|
||||||
let path: PathBuf = [&*TEMPLATE_DIR, "**", "*.tera"].iter().collect();
|
static mut TERA: Option<Tera> = None;
|
||||||
let ext = [".html.tera", ".htm.tera", ".xml.tera", ".html", ".htm", ".xml"];
|
|
||||||
tera::Tera::new(path.to_str().unwrap())
|
|
||||||
.map(|mut tera| {
|
|
||||||
tera.autoescape_on(ext.to_vec());
|
|
||||||
tera
|
|
||||||
})
|
|
||||||
.map_err(|e| format!("{:?}", e))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const EXT: &'static str = "tera";
|
pub const EXT: &'static str = "tera";
|
||||||
|
|
||||||
pub fn render<T>(name: &str, info: &TemplateInfo, context: &T) -> Option<String>
|
// This function must be called a SINGLE TIME from A SINGLE THREAD for safety to
|
||||||
|
// hold here and in `render`.
|
||||||
|
pub unsafe fn register(templates: &[(&str, &TemplateInfo)]) -> bool {
|
||||||
|
if TERA.is_some() {
|
||||||
|
error_!("Internal error: reregistering Tera!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut tera = Tera::default();
|
||||||
|
let ext = [".html.tera", ".htm.tera", ".xml.tera", ".html", ".htm", ".xml"];
|
||||||
|
tera.autoescape_on(ext.to_vec());
|
||||||
|
|
||||||
|
// Collect into a tuple of (name, path) for Tera.
|
||||||
|
let tera_templates = templates.iter()
|
||||||
|
.map(|&(name, info)| (name, &info.full_path))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Finally try to tell Tera about all of the templates.
|
||||||
|
let mut success = true;
|
||||||
|
if let Err(e) = tera.add_template_files(tera_templates) {
|
||||||
|
error_!("Failed to initialize Tera templates: {:?}", e);
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TERA = Some(tera);
|
||||||
|
success
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render<T>(name: &str, _: &TemplateInfo, context: &T) -> Option<String>
|
||||||
where T: Serialize
|
where T: Serialize
|
||||||
{
|
{
|
||||||
let tera = match *TERA {
|
let tera = match unsafe { TERA.as_ref() } {
|
||||||
Ok(ref tera) => tera,
|
Some(tera) => tera,
|
||||||
Err(ref e) => {
|
None => {
|
||||||
error_!("Tera failed to initialize: {}.", e);
|
error_!("Internal error: `render` called before Tera init.");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let template_name = &info.path.to_string_lossy();
|
if tera.get_template(name).is_err() {
|
||||||
if tera.get_template(template_name).is_err() {
|
error_!("Tera template '{}' does not exist.", name);
|
||||||
error_!("Tera template '{}' does not exist.", template_name);
|
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
match tera.value_render(template_name, &context) {
|
match tera.value_render(name, context) {
|
||||||
Ok(string) => Some(string),
|
Ok(string) => Some(string),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error_!("Error rendering Tera template '{}': {}", name, e);
|
error_!("Error rendering Tera template '{}': {}", name, e);
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
extern crate rocket;
|
||||||
|
extern crate rocket_contrib;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use rocket::config::Config;
|
||||||
|
use rocket::config::Environment::*;
|
||||||
|
|
||||||
|
fn init() {
|
||||||
|
let cwd = env::current_dir().expect("current working directory");
|
||||||
|
let tests_dir = cwd.join("tests");
|
||||||
|
let config_dir = tests_dir.join("Rocket.toml");
|
||||||
|
|
||||||
|
let config = Config::default_for(Development, &config_dir).unwrap();
|
||||||
|
rocket::custom(config, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Do something about overlapping configs.
|
||||||
|
#[cfg(feature = "tera_templates")]
|
||||||
|
mod tera_tests {
|
||||||
|
use super::*;
|
||||||
|
use rocket_contrib::Template;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
const UNESCAPED_EXPECTED: &'static str
|
||||||
|
= "\nh_start\ntitle: _test_\nh_end\n\n\n<script />\n\nfoot\n";
|
||||||
|
const ESCAPED_EXPECTED: &'static str
|
||||||
|
= "\nh_start\ntitle: _test_\nh_end\n\n\n<script />\n\nfoot\n";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tera_templates() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
map.insert("title", "_test_");
|
||||||
|
map.insert("content", "<script />");
|
||||||
|
|
||||||
|
// Test with a txt file, which shouldn't escape.
|
||||||
|
let template = Template::render("tera/txt_test", &map);
|
||||||
|
assert_eq!(&template.to_string(), UNESCAPED_EXPECTED);
|
||||||
|
|
||||||
|
// Now with an HTML file, which should.
|
||||||
|
let template = Template::render("tera/html_test", &map);
|
||||||
|
assert_eq!(&template.to_string(), ESCAPED_EXPECTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "handlebars_templates")]
|
||||||
|
mod handlebars_tests {
|
||||||
|
use super::*;
|
||||||
|
use rocket_contrib::Template;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
const EXPECTED: &'static str
|
||||||
|
= "Hello _test_!\n\n<main> <script /> hi </main>\nDone.\n\n";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_handlebars_templates() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
map.insert("title", "_test_");
|
||||||
|
map.insert("content", "<script /> hi");
|
||||||
|
|
||||||
|
// Test with a txt file, which shouldn't escape.
|
||||||
|
let template = Template::render("hbs/test", &map);
|
||||||
|
assert_eq!(&template.to_string(), EXPECTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Done.
|
|
@ -0,0 +1 @@
|
||||||
|
Hello {{ title }}!
|
|
@ -0,0 +1,3 @@
|
||||||
|
{{> hbs/common/header }}
|
||||||
|
<main> {{ content }} </main>
|
||||||
|
{{> hbs/common/footer }}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% block head %}
|
||||||
|
h_start
|
||||||
|
title: {% block title %}{% endblock title %}
|
||||||
|
h_end
|
||||||
|
{% endblock head %}
|
||||||
|
{% block content %}{% endblock content %}
|
||||||
|
{% block footer %}foot{% endblock footer %}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{% extends "tera/base" %}
|
||||||
|
{% block title %}{{ title }}{% endblock title %}
|
||||||
|
{% block content %}
|
||||||
|
{{ content }}
|
||||||
|
{% endblock content %}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{% extends "tera/base" %}
|
||||||
|
{% block title %}{{ title }}{% endblock title %}
|
||||||
|
{% block content %}
|
||||||
|
{{ content }}
|
||||||
|
{% endblock content %}
|
|
@ -4,6 +4,7 @@
|
||||||
[development]
|
[development]
|
||||||
address = "localhost"
|
address = "localhost"
|
||||||
port = 8000
|
port = 8000
|
||||||
|
workers = 1
|
||||||
log = "normal"
|
log = "normal"
|
||||||
hi = "Hello!"
|
hi = "Hello!"
|
||||||
is_extra = true
|
is_extra = true
|
||||||
|
@ -12,12 +13,14 @@ is_extra = true
|
||||||
address = "0.0.0.0"
|
address = "0.0.0.0"
|
||||||
port = 80
|
port = 80
|
||||||
log = "normal"
|
log = "normal"
|
||||||
|
workers = 8
|
||||||
# don't use this key! generate your own and keep it private!
|
# don't use this key! generate your own and keep it private!
|
||||||
session_key = "VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5"
|
session_key = "VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5"
|
||||||
|
|
||||||
[production]
|
[production]
|
||||||
address = "0.0.0.0"
|
address = "0.0.0.0"
|
||||||
port = 80
|
port = 80
|
||||||
|
workers = 12
|
||||||
log = "critical"
|
log = "critical"
|
||||||
# don't use this key! generate your own and keep it private!
|
# don't use this key! generate your own and keep it private!
|
||||||
session_key = "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz"
|
session_key = "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz"
|
||||||
|
|
|
@ -18,8 +18,9 @@ pub fn test_config(environment: Environment) {
|
||||||
Environment::Development => {
|
Environment::Development => {
|
||||||
assert_eq!(config.address, "localhost".to_string());
|
assert_eq!(config.address, "localhost".to_string());
|
||||||
assert_eq!(config.port, 8000);
|
assert_eq!(config.port, 8000);
|
||||||
|
assert_eq!(config.workers, 1);
|
||||||
assert_eq!(config.log_level, LoggingLevel::Normal);
|
assert_eq!(config.log_level, LoggingLevel::Normal);
|
||||||
assert_eq!(config.env, config::Environment::Development);
|
assert_eq!(config.environment, config::Environment::Development);
|
||||||
assert_eq!(config.extras().count(), 2);
|
assert_eq!(config.extras().count(), 2);
|
||||||
assert_eq!(config.get_str("hi"), Ok("Hello!"));
|
assert_eq!(config.get_str("hi"), Ok("Hello!"));
|
||||||
assert_eq!(config.get_bool("is_extra"), Ok(true));
|
assert_eq!(config.get_bool("is_extra"), Ok(true));
|
||||||
|
@ -27,15 +28,17 @@ pub fn test_config(environment: Environment) {
|
||||||
Environment::Staging => {
|
Environment::Staging => {
|
||||||
assert_eq!(config.address, "0.0.0.0".to_string());
|
assert_eq!(config.address, "0.0.0.0".to_string());
|
||||||
assert_eq!(config.port, 80);
|
assert_eq!(config.port, 80);
|
||||||
|
assert_eq!(config.workers, 8);
|
||||||
assert_eq!(config.log_level, LoggingLevel::Normal);
|
assert_eq!(config.log_level, LoggingLevel::Normal);
|
||||||
assert_eq!(config.env, config::Environment::Staging);
|
assert_eq!(config.environment, config::Environment::Staging);
|
||||||
assert_eq!(config.extras().count(), 0);
|
assert_eq!(config.extras().count(), 0);
|
||||||
}
|
}
|
||||||
Environment::Production => {
|
Environment::Production => {
|
||||||
assert_eq!(config.address, "0.0.0.0".to_string());
|
assert_eq!(config.address, "0.0.0.0".to_string());
|
||||||
assert_eq!(config.port, 80);
|
assert_eq!(config.port, 80);
|
||||||
|
assert_eq!(config.workers, 12);
|
||||||
assert_eq!(config.log_level, LoggingLevel::Critical);
|
assert_eq!(config.log_level, LoggingLevel::Critical);
|
||||||
assert_eq!(config.env, config::Environment::Production);
|
assert_eq!(config.environment, config::Environment::Production);
|
||||||
assert_eq!(config.extras().count(), 0);
|
assert_eq!(config.extras().count(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ term-painter = "^0.2"
|
||||||
log = "^0.3"
|
log = "^0.3"
|
||||||
url = "^1"
|
url = "^1"
|
||||||
toml = "^0.2"
|
toml = "^0.2"
|
||||||
|
num_cpus = "1"
|
||||||
# cookie = "^0.3"
|
# cookie = "^0.3"
|
||||||
|
|
||||||
[dependencies.hyper]
|
[dependencies.hyper]
|
||||||
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
use std::collections::{HashMap, BTreeMap};
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
use config::{Result, Config, Value, Environment};
|
||||||
|
use logger::LoggingLevel;
|
||||||
|
|
||||||
|
/// The core configuration structure.
|
||||||
|
#[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,
|
||||||
|
/// How much information to log.
|
||||||
|
pub log_level: LoggingLevel,
|
||||||
|
/// The session key.
|
||||||
|
pub session_key: Option<String>,
|
||||||
|
/// Any extra parameters that aren't part of Rocket's config.
|
||||||
|
pub extras: HashMap<String, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigBuilder {
|
||||||
|
pub fn new(environment: Environment) -> ConfigBuilder {
|
||||||
|
let config = Config::new(environment)
|
||||||
|
.expect("ConfigBuilder::new(): couldn't get current directory.");
|
||||||
|
|
||||||
|
ConfigBuilder {
|
||||||
|
environment: config.environment,
|
||||||
|
address: config.address,
|
||||||
|
port: config.port,
|
||||||
|
workers: config.workers,
|
||||||
|
log_level: config.log_level,
|
||||||
|
session_key: None,
|
||||||
|
extras: config.extras,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the `address` in `self` to `address` and returns the structure.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn address<A: Into<String>>(mut self, address: A) -> Self {
|
||||||
|
self.address = address.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the `port` in `self` to `port` and returns the structure.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn port(mut self, port: u16) -> Self {
|
||||||
|
self.port = port;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the `workers` in `self` to `workers` and returns the structure.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn workers(mut self, workers: u16) -> Self {
|
||||||
|
self.workers = workers;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the `log_level` in `self` to `log_level` and returns the structure.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn log_level(mut self, log_level: LoggingLevel) -> Self {
|
||||||
|
self.log_level = log_level;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the `session_key` in `self` to `key` and returns the structure.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn session_key<K: Into<String>>(mut self, key: K) -> Self {
|
||||||
|
self.session_key = Some(key.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the environment in `self` to `env` and returns the structure.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn environment(mut self, env: Environment) -> Self {
|
||||||
|
self.environment = env;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds an extra configuration parameter with `name` and `value` to `value`
|
||||||
|
/// and returns the structure. The value can be any type that implements the
|
||||||
|
/// `IntoValue` trait defined in this module.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn extra<V: IntoValue>(mut self, name: &str, value: V) -> Self {
|
||||||
|
self.extras.insert(name.into(), value.into_value());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Collect all errors into one `Several` `ConfigError` variant.
|
||||||
|
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_log_level(self.log_level);
|
||||||
|
config.set_extras(self.extras);
|
||||||
|
|
||||||
|
if let Some(key) = self.session_key {
|
||||||
|
config.set_session_key(key)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn unwrap(self) -> Config {
|
||||||
|
self.finalize().expect("ConfigBuilder::unwrap() failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IntoValue {
|
||||||
|
fn into_value(self) -> Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoValue for &'a str {
|
||||||
|
fn into_value(self) -> Value {
|
||||||
|
Value::String(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoValue for Value {
|
||||||
|
fn into_value(self) -> Value {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: IntoValue> IntoValue for Vec<V> {
|
||||||
|
fn into_value(self) -> Value {
|
||||||
|
Value::Array(self.into_iter().map(|v| v.into_value()).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Into<String>, V: IntoValue> IntoValue for BTreeMap<S, V> {
|
||||||
|
fn into_value(self) -> Value {
|
||||||
|
let table = self.into_iter()
|
||||||
|
.map(|(s, v)| (s.into(), v.into_value()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Value::Table(table)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Into<String> + Hash + Eq, V: IntoValue> IntoValue for HashMap<S, V> {
|
||||||
|
fn into_value(self) -> Value {
|
||||||
|
let table = self.into_iter()
|
||||||
|
.map(|(s, v)| (s.into(), v.into_value()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Value::Table(table)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_into_value {
|
||||||
|
($variant:ident : $t:ty) => ( impl_into_value!($variant: $t,); );
|
||||||
|
|
||||||
|
($variant:ident : $t:ty, $($extra:tt)*) => (
|
||||||
|
impl IntoValue for $t {
|
||||||
|
fn into_value(self) -> Value {
|
||||||
|
Value::$variant(self $($extra)*)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_into_value!(String: String);
|
||||||
|
impl_into_value!(Integer: i64);
|
||||||
|
impl_into_value!(Integer: isize, as i64);
|
||||||
|
impl_into_value!(Integer: i32, as i64);
|
||||||
|
impl_into_value!(Integer: i8, as i64);
|
||||||
|
impl_into_value!(Integer: u8, as i64);
|
||||||
|
impl_into_value!(Integer: u32, as i64);
|
||||||
|
impl_into_value!(Boolean: bool);
|
||||||
|
impl_into_value!(Float: f64);
|
||||||
|
impl_into_value!(Float: f32, as f64);
|
||||||
|
|
|
@ -1,81 +1,121 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::net::ToSocketAddrs;
|
use std::net::ToSocketAddrs;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
use std::convert::AsRef;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
use config::Environment::*;
|
use config::Environment::*;
|
||||||
use config::{self, Environment, ConfigError};
|
use config::{self, Value, ConfigBuilder, Environment, ConfigError};
|
||||||
|
|
||||||
|
use num_cpus;
|
||||||
use logger::LoggingLevel;
|
use logger::LoggingLevel;
|
||||||
use toml::Value;
|
|
||||||
|
|
||||||
/// The core configuration structure.
|
/// The core configuration structure.
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
/// The environment that this configuration corresponds to.
|
||||||
|
pub environment: Environment,
|
||||||
/// The address to serve on.
|
/// The address to serve on.
|
||||||
pub address: String,
|
pub address: String,
|
||||||
/// The port to serve on.
|
/// The port to serve on.
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
|
/// The number of workers to run concurrently.
|
||||||
|
pub workers: u16,
|
||||||
/// How much information to log.
|
/// How much information to log.
|
||||||
pub log_level: LoggingLevel,
|
pub log_level: LoggingLevel,
|
||||||
/// The environment that this configuration corresponds to.
|
/// Extra parameters that aren't part of Rocket's core config.
|
||||||
pub env: Environment,
|
pub extras: HashMap<String, Value>,
|
||||||
|
/// The path to the configuration file this config belongs to.
|
||||||
|
pub config_path: PathBuf,
|
||||||
|
/// The session key.
|
||||||
session_key: RwLock<Option<String>>,
|
session_key: RwLock<Option<String>>,
|
||||||
extras: HashMap<String, Value>,
|
|
||||||
filepath: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! parse {
|
macro_rules! parse {
|
||||||
($conf:expr, $name:expr, $val:expr, $method:ident, $expect: expr) => (
|
($conf:expr, $name:expr, $val:expr, $method:ident, $expect: expr) => (
|
||||||
$val.$method().ok_or_else(|| {
|
$val.$method().ok_or_else(|| {
|
||||||
$conf.bad_type($name, $val, $expect)
|
$conf.bad_type($name, $val.type_str(), $expect)
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
/// Creates a new configuration using the default parameters for the
|
||||||
|
/// environment `env`. The root configuration directory is set to the
|
||||||
|
/// current working directory.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// If the current directory cannot be retrieved, a `BadCWD` error is
|
||||||
|
/// returned.
|
||||||
|
pub fn build(env: Environment) -> ConfigBuilder {
|
||||||
|
ConfigBuilder::new(env)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new configuration using the default parameters for the
|
||||||
|
/// environment `env`. The root configuration directory is set to the
|
||||||
|
/// current working directory.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// If the current directory cannot be retrieved, a `BadCWD` error is
|
||||||
|
/// returned.
|
||||||
|
pub fn new(env: Environment) -> config::Result<Config> {
|
||||||
|
let cwd = env::current_dir().map_err(|_| ConfigError::BadCWD)?;
|
||||||
|
Config::default_for(env, cwd.as_path().join("Rocket.custom.toml"))
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the default configuration for the environment `env` given that
|
/// Returns the default configuration for the environment `env` given that
|
||||||
/// the configuration was stored at `filepath`. If `filepath` is not an
|
/// the configuration was stored at `config_path`. If `config_path` is not
|
||||||
/// absolute path, an `Err` of `ConfigError::BadFilePath` is returned.
|
/// an absolute path, an `Err` of `ConfigError::BadFilePath` is returned.
|
||||||
pub fn default_for(env: Environment, filepath: &str) -> config::Result<Config> {
|
pub fn default_for<P>(env: Environment, config_path: P) -> config::Result<Config>
|
||||||
let file_path = Path::new(filepath);
|
where P: AsRef<Path>
|
||||||
if file_path.parent().is_none() {
|
{
|
||||||
return Err(ConfigError::BadFilePath(filepath.to_string(),
|
let config_path = config_path.as_ref().to_path_buf();
|
||||||
|
if config_path.parent().is_none() {
|
||||||
|
return Err(ConfigError::BadFilePath(config_path,
|
||||||
"Configuration files must be rooted in a directory."));
|
"Configuration files must be rooted in a directory."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: This may truncate if num_cpus::get() > u16::max. That's okay.
|
||||||
|
let default_workers = ::std::cmp::max(num_cpus::get(), 2) as u16;
|
||||||
|
|
||||||
Ok(match env {
|
Ok(match env {
|
||||||
Development => {
|
Development => {
|
||||||
Config {
|
Config {
|
||||||
|
environment: Development,
|
||||||
address: "localhost".to_string(),
|
address: "localhost".to_string(),
|
||||||
port: 8000,
|
port: 8000,
|
||||||
|
workers: default_workers,
|
||||||
log_level: LoggingLevel::Normal,
|
log_level: LoggingLevel::Normal,
|
||||||
session_key: RwLock::new(None),
|
session_key: RwLock::new(None),
|
||||||
extras: HashMap::new(),
|
extras: HashMap::new(),
|
||||||
env: env,
|
config_path: config_path,
|
||||||
filepath: filepath.to_string(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Staging => {
|
Staging => {
|
||||||
Config {
|
Config {
|
||||||
|
environment: Staging,
|
||||||
address: "0.0.0.0".to_string(),
|
address: "0.0.0.0".to_string(),
|
||||||
port: 80,
|
port: 80,
|
||||||
|
workers: default_workers,
|
||||||
log_level: LoggingLevel::Normal,
|
log_level: LoggingLevel::Normal,
|
||||||
session_key: RwLock::new(None),
|
session_key: RwLock::new(None),
|
||||||
extras: HashMap::new(),
|
extras: HashMap::new(),
|
||||||
env: env,
|
config_path: config_path,
|
||||||
filepath: filepath.to_string(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Production => {
|
Production => {
|
||||||
Config {
|
Config {
|
||||||
|
environment: Production,
|
||||||
address: "0.0.0.0".to_string(),
|
address: "0.0.0.0".to_string(),
|
||||||
port: 80,
|
port: 80,
|
||||||
|
workers: default_workers,
|
||||||
log_level: LoggingLevel::Critical,
|
log_level: LoggingLevel::Critical,
|
||||||
session_key: RwLock::new(None),
|
session_key: RwLock::new(None),
|
||||||
extras: HashMap::new(),
|
extras: HashMap::new(),
|
||||||
env: env,
|
config_path: config_path,
|
||||||
filepath: filepath.to_string(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -84,9 +124,10 @@ impl Config {
|
||||||
/// Constructs a `BadType` error given the entry `name`, the invalid `val`
|
/// Constructs a `BadType` error given the entry `name`, the invalid `val`
|
||||||
/// at that entry, and the `expect`ed type name.
|
/// at that entry, and the `expect`ed type name.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn bad_type(&self, name: &str, val: &Value, expect: &'static str) -> ConfigError {
|
fn bad_type(&self, name: &str, actual: &'static str, expect: &'static str)
|
||||||
let id = format!("{}.{}", self.env, name);
|
-> ConfigError {
|
||||||
ConfigError::BadType(id, expect, val.type_str(), self.filepath.clone())
|
let id = format!("{}.{}", self.environment, name);
|
||||||
|
ConfigError::BadType(id, expect, actual, self.config_path.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the configuration `val` for the `name` entry. If the `name` is one
|
/// Sets the configuration `val` for the `name` entry. If the `name` is one
|
||||||
|
@ -100,44 +141,38 @@ impl Config {
|
||||||
///
|
///
|
||||||
/// * **address**: String
|
/// * **address**: String
|
||||||
/// * **port**: Integer (16-bit unsigned)
|
/// * **port**: Integer (16-bit unsigned)
|
||||||
/// * **session_key**: String (192-bit base64)
|
/// * **workers**: Integer (16-bit unsigned)
|
||||||
/// * **log**: String
|
/// * **log**: String
|
||||||
|
/// * **session_key**: String (192-bit base64)
|
||||||
///
|
///
|
||||||
pub fn set(&mut self, name: &str, val: &Value) -> config::Result<()> {
|
pub fn set(&mut self, name: &str, val: &Value) -> config::Result<()> {
|
||||||
if name == "address" {
|
if name == "address" {
|
||||||
let address_str = parse!(self, name, val, as_str, "a string")?;
|
let address_str = parse!(self, name, val, as_str, "a string")?;
|
||||||
if address_str.contains(':') {
|
self.set_address(address_str)?;
|
||||||
return Err(self.bad_type(name, val, "an IP address with no port"));
|
|
||||||
} else if format!("{}:{}", address_str, 80).to_socket_addrs().is_err() {
|
|
||||||
return Err(self.bad_type(name, val, "a valid IP address"));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.address = address_str.to_string();
|
|
||||||
} else if name == "port" {
|
} else if name == "port" {
|
||||||
let port = parse!(self, name, val, as_integer, "an integer")?;
|
let port = parse!(self, name, val, as_integer, "an integer")?;
|
||||||
if port < 0 {
|
if port < 0 || port > (u16::max_value() as i64) {
|
||||||
return Err(self.bad_type(name, val, "an unsigned integer"));
|
return Err(self.bad_type(name, val.type_str(), "a 16-bit unsigned integer"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if port > (u16::max_value() as i64) {
|
self.set_port(port as u16);
|
||||||
return Err(self.bad_type(name, val, "a 16-bit unsigned integer"))
|
} else if name == "workers" {
|
||||||
|
let workers = parse!(self, name, val, as_integer, "an integer")?;
|
||||||
|
if workers < 0 || workers > (u16::max_value() as i64) {
|
||||||
|
return Err(self.bad_type(name, val.type_str(), "a 16-bit unsigned integer"));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.port = port as u16;
|
self.set_workers(workers as u16);
|
||||||
} else if name == "session_key" {
|
} else if name == "session_key" {
|
||||||
let key = parse!(self, name, val, as_str, "a string")?;
|
let key = parse!(self, name, val, as_str, "a string")?;
|
||||||
if key.len() != 32 {
|
self.set_session_key(key)?;
|
||||||
return Err(self.bad_type(name, val, "a 192-bit base64 string"));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.session_key = RwLock::new(Some(key.to_string()));
|
|
||||||
} else if name == "log" {
|
} else if name == "log" {
|
||||||
let level_str = parse!(self, name, val, as_str, "a string")?;
|
let level_str = parse!(self, name, val, as_str, "a string")?;
|
||||||
self.log_level = match level_str.parse() {
|
let expect = "log level ('normal', 'critical', 'debug')";
|
||||||
Ok(level) => level,
|
match level_str.parse() {
|
||||||
Err(_) => return Err(self.bad_type(name, val,
|
Ok(level) => self.set_log_level(level),
|
||||||
"log level ('normal', 'critical', 'debug')"))
|
Err(_) => return Err(self.bad_type(name, val.type_str(), expect))
|
||||||
};
|
}
|
||||||
} else {
|
} else {
|
||||||
self.extras.insert(name.into(), val.clone());
|
self.extras.insert(name.into(), val.clone());
|
||||||
}
|
}
|
||||||
|
@ -145,6 +180,44 @@ impl Config {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_address<A: Into<String>>(&mut self, address: A) -> config::Result<()> {
|
||||||
|
let address = address.into();
|
||||||
|
if address.contains(':') {
|
||||||
|
return Err(self.bad_type("address", "string", "a hostname or IP with no port"));
|
||||||
|
} else if format!("{}:{}", address, 80).to_socket_addrs().is_err() {
|
||||||
|
return Err(self.bad_type("address", "string", "a valid hostname or IP"));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.address = address.into();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_port(&mut self, port: u16) {
|
||||||
|
self.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_workers(&mut self, workers: u16) {
|
||||||
|
self.workers = workers;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_session_key<K: Into<String>>(&mut self, key: K) -> config::Result<()> {
|
||||||
|
let key = key.into();
|
||||||
|
if key.len() != 32 {
|
||||||
|
return Err(self.bad_type("session_key", "string", "a 192-bit base64 string"));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.session_key = RwLock::new(Some(key));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_log_level(&mut self, log_level: LoggingLevel) {
|
||||||
|
self.log_level = log_level;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_extras(&mut self, extras: HashMap<String, Value>) {
|
||||||
|
self.extras = extras;
|
||||||
|
}
|
||||||
|
|
||||||
/// Moves the session key string out of the `self` Config, if there is one.
|
/// Moves the session key string out of the `self` Config, if there is one.
|
||||||
/// Because the value is moved out, subsequent calls will result in a return
|
/// Because the value is moved out, subsequent calls will result in a return
|
||||||
/// value of `None`.
|
/// value of `None`.
|
||||||
|
@ -155,13 +228,14 @@ impl Config {
|
||||||
/// use rocket::config::{Config, Environment, Value};
|
/// use rocket::config::{Config, Environment, Value};
|
||||||
///
|
///
|
||||||
/// // Create a new config with a session key.
|
/// // Create a new config with a session key.
|
||||||
/// let key = "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz".to_string();
|
/// let key = "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz";
|
||||||
/// let config = Config::default_for(Environment::Staging, "/custom").unwrap()
|
/// let config = Config::build(Environment::Staging)
|
||||||
/// .session_key(key.clone());
|
/// .session_key(key)
|
||||||
|
/// .unwrap();
|
||||||
///
|
///
|
||||||
/// // Get the key for the first time.
|
/// // Get the key for the first time.
|
||||||
/// let session_key = config.take_session_key();
|
/// let session_key = config.take_session_key();
|
||||||
/// assert_eq!(session_key, Some(key.clone()));
|
/// assert_eq!(session_key, Some(key.to_string()));
|
||||||
///
|
///
|
||||||
/// // Try to get the key again.
|
/// // Try to get the key again.
|
||||||
/// let session_key_again = config.take_session_key();
|
/// let session_key_again = config.take_session_key();
|
||||||
|
@ -220,71 +294,34 @@ impl Config {
|
||||||
/// For instance, if the configuration file is at `/tmp/Rocket.toml`, the
|
/// For instance, if the configuration file is at `/tmp/Rocket.toml`, the
|
||||||
/// path `/tmp` is returned.
|
/// path `/tmp` is returned.
|
||||||
pub fn root(&self) -> &Path {
|
pub fn root(&self) -> &Path {
|
||||||
match Path::new(self.filepath.as_str()).parent() {
|
match self.config_path.parent() {
|
||||||
Some(parent) => parent,
|
Some(parent) => parent,
|
||||||
None => panic!("root(): filepath {} has no parent", self.filepath)
|
None => panic!("root(): path {:?} has no parent", self.config_path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the `address` in `self` to `var` and returns the structure.
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn address(mut self, var: String) -> Self {
|
|
||||||
self.address = var;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the `port` in `self` to `var` and returns the structure.
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn port(mut self, var: u16) -> Self {
|
|
||||||
self.port = var;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the `log_level` in `self` to `var` and returns the structure.
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn log_level(mut self, var: LoggingLevel) -> Self {
|
|
||||||
self.log_level = var;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the `session_key` in `self` to `var` and returns the structure.
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn session_key(mut self, var: String) -> Self {
|
|
||||||
self.session_key = RwLock::new(Some(var));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the `env` in `self` to `var` and returns the structure.
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn env(mut self, var: Environment) -> Self {
|
|
||||||
self.env = var;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds an extra configuration parameter with `name` and `value` to `self`
|
|
||||||
/// and returns the structure.
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn extra(mut self, name: &str, value: &Value) -> Self {
|
|
||||||
self.extras.insert(name.into(), value.clone());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Config {
|
impl fmt::Debug for Config {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "Config[{}] {{ address: {}, port: {}, log_level: {:?} }}",
|
write!(f, "Config[{}] {{ address: {}, port: {}, workers: {}, log: {:?}",
|
||||||
self.env, self.address, self.port, self.log_level)
|
self.environment, self.address, self.port, self.workers, self.log_level)?;
|
||||||
|
|
||||||
|
for (key, value) in self.extras() {
|
||||||
|
write!(f, ", {}: {}", key, value)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(f, " }}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Doesn't consider the session key or config path.
|
||||||
impl PartialEq for Config {
|
impl PartialEq for Config {
|
||||||
fn eq(&self, other: &Config) -> bool {
|
fn eq(&self, other: &Config) -> bool {
|
||||||
&*self.session_key.read().unwrap() == &*other.session_key.read().unwrap()
|
self.address == other.address
|
||||||
&& self.address == other.address
|
|
||||||
&& self.port == other.port
|
&& self.port == other.port
|
||||||
|
&& self.workers == other.workers
|
||||||
&& self.log_level == other.log_level
|
&& self.log_level == other.log_level
|
||||||
&& self.env == other.env
|
&& self.environment == other.environment
|
||||||
&& self.extras == other.extras
|
&& self.extras == other.extras
|
||||||
&& self.filepath == other.filepath
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use super::Environment;
|
use super::Environment;
|
||||||
|
|
||||||
use term_painter::Color::White;
|
use term_painter::Color::White;
|
||||||
|
@ -28,7 +30,7 @@ pub enum ConfigError {
|
||||||
/// The path at which the configuration file was found was invalid.
|
/// The path at which the configuration file was found was invalid.
|
||||||
///
|
///
|
||||||
/// Parameters: (path, reason)
|
/// Parameters: (path, reason)
|
||||||
BadFilePath(String, &'static str),
|
BadFilePath(PathBuf, &'static str),
|
||||||
/// An environment specified in `ROCKET_ENV` is invalid.
|
/// An environment specified in `ROCKET_ENV` is invalid.
|
||||||
///
|
///
|
||||||
/// Parameters: (environment_name)
|
/// Parameters: (environment_name)
|
||||||
|
@ -36,15 +38,15 @@ pub enum ConfigError {
|
||||||
/// An environment specified as a table `[environment]` is invalid.
|
/// An environment specified as a table `[environment]` is invalid.
|
||||||
///
|
///
|
||||||
/// Parameters: (environment_name, filename)
|
/// Parameters: (environment_name, filename)
|
||||||
BadEntry(String, String),
|
BadEntry(String, PathBuf),
|
||||||
/// A key was specified with a value of the wrong type.
|
/// A config key was specified with a value of the wrong type.
|
||||||
///
|
///
|
||||||
/// Parameters: (entry_name, expected_type, actual_type, filename)
|
/// Parameters: (entry_name, expected_type, actual_type, filename)
|
||||||
BadType(String, &'static str, &'static str, String),
|
BadType(String, &'static str, &'static str, PathBuf),
|
||||||
/// There was a TOML parsing error.
|
/// There was a TOML parsing error.
|
||||||
///
|
///
|
||||||
/// Parameters: (toml_source_string, filename, error_list)
|
/// Parameters: (toml_source_string, filename, error_list)
|
||||||
ParseError(String, String, Vec<ParsingError>),
|
ParseError(String, PathBuf, Vec<ParsingError>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigError {
|
impl ConfigError {
|
||||||
|
@ -58,13 +60,13 @@ impl ConfigError {
|
||||||
NotFound => error!("config file was not found"),
|
NotFound => error!("config file was not found"),
|
||||||
IOError => error!("failed reading the config file: IO error"),
|
IOError => error!("failed reading the config file: IO error"),
|
||||||
BadFilePath(ref path, reason) => {
|
BadFilePath(ref path, reason) => {
|
||||||
error!("configuration file path '{}' is invalid", path);
|
error!("configuration file path '{:?}' is invalid", path);
|
||||||
info_!("{}", reason);
|
info_!("{}", reason);
|
||||||
}
|
}
|
||||||
BadEntry(ref name, ref filename) => {
|
BadEntry(ref name, ref filename) => {
|
||||||
let valid_entries = format!("{}, and global", valid_envs);
|
let valid_entries = format!("{}, and global", valid_envs);
|
||||||
error!("[{}] is not a known configuration environment", name);
|
error!("[{}] is not a known configuration environment", name);
|
||||||
info_!("in {}", White.paint(filename));
|
info_!("in {:?}", White.paint(filename));
|
||||||
info_!("valid environments are: {}", White.paint(valid_entries));
|
info_!("valid environments are: {}", White.paint(valid_entries));
|
||||||
}
|
}
|
||||||
BadEnv(ref name) => {
|
BadEnv(ref name) => {
|
||||||
|
@ -73,7 +75,7 @@ impl ConfigError {
|
||||||
}
|
}
|
||||||
BadType(ref name, expected, actual, ref filename) => {
|
BadType(ref name, expected, actual, ref filename) => {
|
||||||
error!("'{}' key could not be parsed", name);
|
error!("'{}' key could not be parsed", name);
|
||||||
info_!("in {}", White.paint(filename));
|
info_!("in {:?}", White.paint(filename));
|
||||||
info_!("expected value to be {}, but found {}",
|
info_!("expected value to be {}, but found {}",
|
||||||
White.paint(expected), White.paint(actual));
|
White.paint(expected), White.paint(actual));
|
||||||
}
|
}
|
||||||
|
@ -84,7 +86,7 @@ impl ConfigError {
|
||||||
let error_source = &source[lo..hi];
|
let error_source = &source[lo..hi];
|
||||||
|
|
||||||
error!("config file could not be parsed as TOML");
|
error!("config file could not be parsed as TOML");
|
||||||
info_!("at {}:{}:{}", White.paint(filename), line + 1, col + 1);
|
info_!("at {:?}:{}:{}", White.paint(filename), line + 1, col + 1);
|
||||||
trace_!("'{}' - {}", error_source, White.paint(&error.desc));
|
trace_!("'{}' - {}", error_source, White.paint(&error.desc));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,8 @@
|
||||||
//! * examples: `"localhost"`, `"0.0.0.0"`, `"1.2.3.4"`
|
//! * examples: `"localhost"`, `"0.0.0.0"`, `"1.2.3.4"`
|
||||||
//! * **port**: _[integer]_ a port number to listen on
|
//! * **port**: _[integer]_ a port number to listen on
|
||||||
//! * examples: `"8000"`, `"80"`, `"4242"`
|
//! * examples: `"8000"`, `"80"`, `"4242"`
|
||||||
|
//! * **workers**: _[integer]_ the number of concurrent workers to use
|
||||||
|
//! * examples: `"12"`, `"1"`, `"4"`
|
||||||
//! * **log**: _[string]_ how much information to log; one of `"normal"`,
|
//! * **log**: _[string]_ how much information to log; one of `"normal"`,
|
||||||
//! `"debug"`, or `"critical"`
|
//! `"debug"`, or `"critical"`
|
||||||
//! * **session_key**: _[string]_ a 192-bit base64 encoded string (32
|
//! * **session_key**: _[string]_ a 192-bit base64 encoded string (32
|
||||||
|
@ -58,11 +60,13 @@
|
||||||
//! [development]
|
//! [development]
|
||||||
//! address = "localhost"
|
//! address = "localhost"
|
||||||
//! port = 8000
|
//! port = 8000
|
||||||
|
//! workers = max(number_of_cpus, 2)
|
||||||
//! log = "normal"
|
//! log = "normal"
|
||||||
//!
|
//!
|
||||||
//! [staging]
|
//! [staging]
|
||||||
//! address = "0.0.0.0"
|
//! address = "0.0.0.0"
|
||||||
//! port = 80
|
//! port = 80
|
||||||
|
//! workers = max(number_of_cpus, 2)
|
||||||
//! log = "normal"
|
//! log = "normal"
|
||||||
//! # don't use this key! generate your own and keep it private!
|
//! # don't use this key! generate your own and keep it private!
|
||||||
//! session_key = "VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5"
|
//! session_key = "VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5"
|
||||||
|
@ -70,11 +74,16 @@
|
||||||
//! [production]
|
//! [production]
|
||||||
//! address = "0.0.0.0"
|
//! address = "0.0.0.0"
|
||||||
//! port = 80
|
//! port = 80
|
||||||
|
//! workers = max(number_of_cpus, 2)
|
||||||
//! log = "critical"
|
//! log = "critical"
|
||||||
//! # don't use this key! generate your own and keep it private!
|
//! # don't use this key! generate your own and keep it private!
|
||||||
//! session_key = "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz"
|
//! session_key = "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz"
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
//! The `workers` parameter is computed by Rocket automatically; the value above
|
||||||
|
//! is not valid TOML syntax. When manually specifying the number of workers,
|
||||||
|
//! the value should be an integer: `workers = 10`.
|
||||||
|
//!
|
||||||
//! The "global" pseudo-environment can be used to set and/or override
|
//! The "global" pseudo-environment can be used to set and/or override
|
||||||
//! configuration parameters globally. A parameter defined in a `[global]` table
|
//! configuration parameters globally. A parameter defined in a `[global]` table
|
||||||
//! sets, or overrides if already present, that parameter in every environment.
|
//! sets, or overrides if already present, that parameter in every environment.
|
||||||
|
@ -123,30 +132,31 @@
|
||||||
mod error;
|
mod error;
|
||||||
mod environment;
|
mod environment;
|
||||||
mod config;
|
mod config;
|
||||||
|
mod builder;
|
||||||
|
|
||||||
use std::sync::{Once, ONCE_INIT};
|
use std::sync::{Once, ONCE_INIT};
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
|
use toml;
|
||||||
|
|
||||||
|
pub use toml::{Array, Table, Value};
|
||||||
pub use self::error::{ConfigError, ParsingError};
|
pub use self::error::{ConfigError, ParsingError};
|
||||||
pub use self::environment::Environment;
|
pub use self::environment::Environment;
|
||||||
pub use self::config::Config;
|
pub use self::config::Config;
|
||||||
|
pub use self::builder::ConfigBuilder;
|
||||||
|
|
||||||
use self::Environment::*;
|
use self::Environment::*;
|
||||||
|
|
||||||
use toml;
|
|
||||||
pub use toml::{Array, Table, Value};
|
|
||||||
|
|
||||||
use logger::{self, LoggingLevel};
|
use logger::{self, LoggingLevel};
|
||||||
|
|
||||||
static INIT: Once = ONCE_INIT;
|
static INIT: Once = ONCE_INIT;
|
||||||
static mut CONFIG: Option<RocketConfig> = None;
|
static mut CONFIG: Option<RocketConfig> = None;
|
||||||
|
|
||||||
const CONFIG_FILENAME: &'static str = "Rocket.toml";
|
const CONFIG_FILENAME: &'static str = "Rocket.toml";
|
||||||
|
|
||||||
const GLOBAL_ENV_NAME: &'static str = "global";
|
const GLOBAL_ENV_NAME: &'static str = "global";
|
||||||
|
|
||||||
/// Wraps `std::result` with the error type of
|
/// Wraps `std::result` with the error type of
|
||||||
|
@ -161,8 +171,29 @@ pub struct RocketConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RocketConfig {
|
impl RocketConfig {
|
||||||
/// Iteratively search for `file` in `pwd` and its parents, returning the path
|
/// TODO: Doc.
|
||||||
/// to the file or an Error::NoKey if the file couldn't be found.
|
fn new(config: Config) -> RocketConfig {
|
||||||
|
let f = config.config_path.clone();
|
||||||
|
let active_env = config.environment;
|
||||||
|
|
||||||
|
// None of these unwraps should fail since the filename is coming from
|
||||||
|
// an existing connfig.
|
||||||
|
let mut configs = HashMap::new();
|
||||||
|
configs.insert(Development, Config::default_for(Development, &f).unwrap());
|
||||||
|
configs.insert(Staging, Config::default_for(Staging, &f).unwrap());
|
||||||
|
configs.insert(Production, Config::default_for(Production, &f).unwrap());
|
||||||
|
configs.insert(active_env, config);
|
||||||
|
|
||||||
|
RocketConfig {
|
||||||
|
active_env: active_env,
|
||||||
|
config: configs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iteratively search for `CONFIG_FILENAME` starting at the current working
|
||||||
|
/// directory and working up through its parents. Returns the path to the
|
||||||
|
/// file or an Error::NoKey if the file couldn't be found. If the current
|
||||||
|
/// working directory can't be determined, return `BadCWD`.
|
||||||
fn find() -> Result<PathBuf> {
|
fn find() -> Result<PathBuf> {
|
||||||
let cwd = env::current_dir().map_err(|_| ConfigError::BadCWD)?;
|
let cwd = env::current_dir().map_err(|_| ConfigError::BadCWD)?;
|
||||||
let mut current = cwd.as_path();
|
let mut current = cwd.as_path();
|
||||||
|
@ -182,8 +213,11 @@ impl RocketConfig {
|
||||||
Err(ConfigError::NotFound)
|
Err(ConfigError::NotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set(&mut self, env: Environment, kvs: &Table)
|
/// Set the configuration for the environment `env` to be the configuration
|
||||||
-> Result<()> {
|
/// derived from the TOML table `kvs`. The environment must already exist in
|
||||||
|
/// `self`, otherwise this function panics. Any existing values are
|
||||||
|
/// overriden by those in `kvs`.
|
||||||
|
fn set(&mut self, env: Environment, kvs: &Table) -> Result<()> {
|
||||||
let config = match self.config.get_mut(&env) {
|
let config = match self.config.get_mut(&env) {
|
||||||
Some(config) => config,
|
Some(config) => config,
|
||||||
None => panic!("set(): {} config is missing.", env),
|
None => panic!("set(): {} config is missing.", env),
|
||||||
|
@ -196,6 +230,7 @@ impl RocketConfig {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieves the `Config` for the environment `env`.
|
||||||
pub fn get(&self, env: Environment) -> &Config {
|
pub fn get(&self, env: Environment) -> &Config {
|
||||||
match self.config.get(&env) {
|
match self.config.get(&env) {
|
||||||
Some(config) => config,
|
Some(config) => config,
|
||||||
|
@ -203,22 +238,29 @@ impl RocketConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieves the `Config` for the active environment.
|
||||||
pub fn active(&self) -> &Config {
|
pub fn active(&self) -> &Config {
|
||||||
self.get(self.active_env)
|
self.get(self.active_env)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(src: String, filename: &str) -> Result<RocketConfig> {
|
fn parse<P: AsRef<Path>>(src: String, filename: P) -> Result<RocketConfig> {
|
||||||
|
// Get a PathBuf version of the filename.
|
||||||
|
let path = filename.as_ref().to_path_buf();
|
||||||
|
|
||||||
// Parse the source as TOML, if possible.
|
// Parse the source as TOML, if possible.
|
||||||
let mut parser = toml::Parser::new(&src);
|
let mut parser = toml::Parser::new(&src);
|
||||||
let toml = parser.parse().ok_or_else(|| ConfigError::ParseError(
|
let toml = parser.parse().ok_or_else(|| {
|
||||||
src.clone(), filename.into(),
|
let source = src.clone();
|
||||||
parser.errors.iter().map(|error| ParsingError {
|
let errors = parser.errors.iter()
|
||||||
byte_range: (error.lo, error.hi),
|
.map(|error| ParsingError {
|
||||||
start: parser.to_linecol(error.lo),
|
byte_range: (error.lo, error.hi),
|
||||||
end: parser.to_linecol(error.hi),
|
start: parser.to_linecol(error.lo),
|
||||||
desc: error.desc.clone(),
|
end: parser.to_linecol(error.hi),
|
||||||
}).collect()
|
desc: error.desc.clone(),
|
||||||
))?;
|
});
|
||||||
|
|
||||||
|
ConfigError::ParseError(source, path.clone(), errors.collect())
|
||||||
|
})?;
|
||||||
|
|
||||||
// Create a config with the defaults; set the env to the active one.
|
// Create a config with the defaults; set the env to the active one.
|
||||||
let mut config = RocketConfig::active_default(filename)?;
|
let mut config = RocketConfig::active_default(filename)?;
|
||||||
|
@ -232,20 +274,21 @@ impl RocketConfig {
|
||||||
let kv_pairs = match value.as_table() {
|
let kv_pairs = match value.as_table() {
|
||||||
Some(table) => table,
|
Some(table) => table,
|
||||||
None => return Err(ConfigError::BadType(
|
None => return Err(ConfigError::BadType(
|
||||||
entry, "a table", value.type_str(), filename.into()
|
entry, "a table", value.type_str(), path.clone()
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Store the global table for later use and move on.
|
||||||
if entry.as_str() == GLOBAL_ENV_NAME {
|
if entry.as_str() == GLOBAL_ENV_NAME {
|
||||||
global = Some(kv_pairs.clone());
|
global = Some(kv_pairs.clone());
|
||||||
} else {
|
continue;
|
||||||
// Parse the environment from the table entry name.
|
}
|
||||||
let env = entry.as_str().parse().map_err(|_| {
|
|
||||||
ConfigError::BadEntry(entry.clone(), filename.into())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Set the environment configuration from the kv pairs.
|
// This is not the global table. Parse the environment name from the
|
||||||
config.set(env, kv_pairs)?;
|
// table entry name and then set all of the key/values.
|
||||||
|
match entry.as_str().parse() {
|
||||||
|
Ok(env) => config.set(env, kv_pairs)?,
|
||||||
|
Err(_) => Err(ConfigError::BadEntry(entry.clone(), path.clone()))?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,14 +314,14 @@ impl RocketConfig {
|
||||||
handle.read_to_string(&mut contents).map_err(|_| ConfigError::IOError)?;
|
handle.read_to_string(&mut contents).map_err(|_| ConfigError::IOError)?;
|
||||||
|
|
||||||
// Parse the contents from the file.
|
// Parse the contents from the file.
|
||||||
RocketConfig::parse(contents, &file.to_string_lossy())
|
RocketConfig::parse(contents, &file)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn active_default(filename: &str) -> Result<RocketConfig> {
|
pub fn active_default<P: AsRef<Path>>(filename: P) -> Result<RocketConfig> {
|
||||||
let mut defaults = HashMap::new();
|
let mut defaults = HashMap::new();
|
||||||
defaults.insert(Development, Config::default_for(Development, filename)?);
|
defaults.insert(Development, Config::default_for(Development, &filename)?);
|
||||||
defaults.insert(Staging, Config::default_for(Staging, filename)?);
|
defaults.insert(Staging, Config::default_for(Staging, &filename)?);
|
||||||
defaults.insert(Production, Config::default_for(Production, filename)?);
|
defaults.insert(Production, Config::default_for(Production, &filename)?);
|
||||||
|
|
||||||
Ok(RocketConfig {
|
Ok(RocketConfig {
|
||||||
active_env: Environment::active()?,
|
active_env: Environment::active()?,
|
||||||
|
@ -314,6 +357,20 @@ pub fn init() -> (&'static Config, bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn custom_init(config: Config) -> (&'static Config, bool) {
|
||||||
|
let mut this_init = false;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
INIT.call_once(|| {
|
||||||
|
CONFIG = Some(RocketConfig::new(config));
|
||||||
|
this_init = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
(CONFIG.as_ref().unwrap().active(), this_init)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsafe fn private_init() {
|
unsafe fn private_init() {
|
||||||
let bail = |e: ConfigError| -> ! {
|
let bail = |e: ConfigError| -> ! {
|
||||||
logger::init(LoggingLevel::Debug);
|
logger::init(LoggingLevel::Debug);
|
||||||
|
@ -335,8 +392,7 @@ unsafe fn private_init() {
|
||||||
Err(_) => bail(ConfigError::BadCWD)
|
Err(_) => bail(ConfigError::BadCWD)
|
||||||
};
|
};
|
||||||
|
|
||||||
let filename = default_path.to_string_lossy();
|
RocketConfig::active_default(&default_path).unwrap_or_else(|e| bail(e))
|
||||||
RocketConfig::active_default(&filename).unwrap_or_else(|e| bail(e))
|
|
||||||
});
|
});
|
||||||
|
|
||||||
CONFIG = Some(config);
|
CONFIG = Some(config);
|
||||||
|
@ -356,13 +412,12 @@ mod test {
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use super::{RocketConfig, ConfigError, GLOBAL_ENV_NAME};
|
use super::{RocketConfig, ConfigError, ConfigBuilder};
|
||||||
use super::environment::{Environment, CONFIG_ENV};
|
use super::{Environment, GLOBAL_ENV_NAME};
|
||||||
|
use super::environment::CONFIG_ENV;
|
||||||
use super::Environment::*;
|
use super::Environment::*;
|
||||||
use super::config::Config;
|
|
||||||
use super::Result;
|
use super::Result;
|
||||||
|
|
||||||
use ::toml::Value;
|
|
||||||
use ::logger::LoggingLevel;
|
use ::logger::LoggingLevel;
|
||||||
|
|
||||||
const TEST_CONFIG_FILENAME: &'static str = "/tmp/testing/Rocket.toml";
|
const TEST_CONFIG_FILENAME: &'static str = "/tmp/testing/Rocket.toml";
|
||||||
|
@ -373,15 +428,17 @@ mod test {
|
||||||
|
|
||||||
macro_rules! check_config {
|
macro_rules! check_config {
|
||||||
($rconfig:expr, $econfig:expr) => (
|
($rconfig:expr, $econfig:expr) => (
|
||||||
|
let expected = $econfig.finalize().unwrap();
|
||||||
match $rconfig {
|
match $rconfig {
|
||||||
Ok(config) => assert_eq!(config.active(), &$econfig),
|
Ok(config) => assert_eq!(config.active(), &expected),
|
||||||
Err(e) => panic!("Config {} failed: {:?}", stringify!($rconfig), e)
|
Err(e) => panic!("Config {} failed: {:?}", stringify!($rconfig), e)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
($env:expr, $rconfig:expr, $econfig:expr) => (
|
($env:expr, $rconfig:expr, $econfig:expr) => (
|
||||||
|
let expected = $econfig.finalize().unwrap();
|
||||||
match $rconfig {
|
match $rconfig {
|
||||||
Ok(ref config) => assert_eq!(config.get($env), &$econfig),
|
Ok(ref config) => assert_eq!(config.get($env), &expected),
|
||||||
Err(ref e) => panic!("Config {} failed: {:?}", stringify!($rconfig), e)
|
Err(ref e) => panic!("Config {} failed: {:?}", stringify!($rconfig), e)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -391,8 +448,8 @@ mod test {
|
||||||
RocketConfig::active_default(TEST_CONFIG_FILENAME)
|
RocketConfig::active_default(TEST_CONFIG_FILENAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_config(env: Environment) -> Config {
|
fn default_config(env: Environment) -> ConfigBuilder {
|
||||||
Config::default_for(env, TEST_CONFIG_FILENAME).expect("config")
|
ConfigBuilder::new(env)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -454,6 +511,7 @@ mod test {
|
||||||
let config_str = r#"
|
let config_str = r#"
|
||||||
address = "1.2.3.4"
|
address = "1.2.3.4"
|
||||||
port = 7810
|
port = 7810
|
||||||
|
workers = 21
|
||||||
log = "critical"
|
log = "critical"
|
||||||
session_key = "01234567890123456789012345678901"
|
session_key = "01234567890123456789012345678901"
|
||||||
template_dir = "mine"
|
template_dir = "mine"
|
||||||
|
@ -462,30 +520,30 @@ mod test {
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let mut expected = default_config(Development)
|
let mut expected = default_config(Development)
|
||||||
.address("1.2.3.4".to_string())
|
.address("1.2.3.4")
|
||||||
.port(7810)
|
.port(7810)
|
||||||
.log_level(LoggingLevel::Critical)
|
.workers(21)
|
||||||
.session_key("01234567890123456789012345678901".into());
|
.log_level(LoggingLevel::Critical)
|
||||||
|
.session_key("01234567890123456789012345678901")
|
||||||
|
.extra("template_dir", "mine")
|
||||||
|
.extra("json", true)
|
||||||
|
.extra("pi", 3.14);
|
||||||
|
|
||||||
expected.set("template_dir", &Value::String("mine".into())).unwrap();
|
expected.environment = Development;
|
||||||
expected.set("json", &Value::Boolean(true)).unwrap();
|
|
||||||
expected.set("pi", &Value::Float(3.14)).unwrap();
|
|
||||||
|
|
||||||
expected.env = Development;
|
|
||||||
let dev_config = ["[dev]", config_str].join("\n");
|
let dev_config = ["[dev]", config_str].join("\n");
|
||||||
let parsed = RocketConfig::parse(dev_config, TEST_CONFIG_FILENAME);
|
let parsed = RocketConfig::parse(dev_config, TEST_CONFIG_FILENAME);
|
||||||
check_config!(Development, parsed, expected);
|
check_config!(Development, parsed, expected.clone());
|
||||||
check_config!(Staging, parsed, default_config(Staging));
|
check_config!(Staging, parsed, default_config(Staging));
|
||||||
check_config!(Production, parsed, default_config(Production));
|
check_config!(Production, parsed, default_config(Production));
|
||||||
|
|
||||||
expected.env = Staging;
|
expected.environment = Staging;
|
||||||
let stage_config = ["[stage]", config_str].join("\n");
|
let stage_config = ["[stage]", config_str].join("\n");
|
||||||
let parsed = RocketConfig::parse(stage_config, TEST_CONFIG_FILENAME);
|
let parsed = RocketConfig::parse(stage_config, TEST_CONFIG_FILENAME);
|
||||||
check_config!(Staging, parsed, expected);
|
check_config!(Staging, parsed, expected.clone());
|
||||||
check_config!(Development, parsed, default_config(Development));
|
check_config!(Development, parsed, default_config(Development));
|
||||||
check_config!(Production, parsed, default_config(Production));
|
check_config!(Production, parsed, default_config(Production));
|
||||||
|
|
||||||
expected.env = Production;
|
expected.environment = Production;
|
||||||
let prod_config = ["[prod]", config_str].join("\n");
|
let prod_config = ["[prod]", config_str].join("\n");
|
||||||
let parsed = RocketConfig::parse(prod_config, TEST_CONFIG_FILENAME);
|
let parsed = RocketConfig::parse(prod_config, TEST_CONFIG_FILENAME);
|
||||||
check_config!(Production, parsed, expected);
|
check_config!(Production, parsed, expected);
|
||||||
|
@ -503,21 +561,21 @@ mod test {
|
||||||
[development]
|
[development]
|
||||||
address = "localhost"
|
address = "localhost"
|
||||||
"#.to_string(), TEST_CONFIG_FILENAME), {
|
"#.to_string(), TEST_CONFIG_FILENAME), {
|
||||||
default_config(Development).address("localhost".into())
|
default_config(Development).address("localhost")
|
||||||
});
|
});
|
||||||
|
|
||||||
check_config!(RocketConfig::parse(r#"
|
check_config!(RocketConfig::parse(r#"
|
||||||
[dev]
|
[dev]
|
||||||
address = "127.0.0.1"
|
address = "127.0.0.1"
|
||||||
"#.to_string(), TEST_CONFIG_FILENAME), {
|
"#.to_string(), TEST_CONFIG_FILENAME), {
|
||||||
default_config(Development).address("127.0.0.1".into())
|
default_config(Development).address("127.0.0.1")
|
||||||
});
|
});
|
||||||
|
|
||||||
check_config!(RocketConfig::parse(r#"
|
check_config!(RocketConfig::parse(r#"
|
||||||
[dev]
|
[dev]
|
||||||
address = "0.0.0.0"
|
address = "0.0.0.0"
|
||||||
"#.to_string(), TEST_CONFIG_FILENAME), {
|
"#.to_string(), TEST_CONFIG_FILENAME), {
|
||||||
default_config(Development).address("0.0.0.0".into())
|
default_config(Development).address("0.0.0.0")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -608,6 +666,66 @@ mod test {
|
||||||
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
|
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_good_workers_values() {
|
||||||
|
// Take the lock so changing the environment doesn't cause races.
|
||||||
|
let _env_lock = ENV_LOCK.lock().unwrap();
|
||||||
|
env::set_var(CONFIG_ENV, "stage");
|
||||||
|
|
||||||
|
check_config!(RocketConfig::parse(r#"
|
||||||
|
[stage]
|
||||||
|
workers = 1
|
||||||
|
"#.to_string(), TEST_CONFIG_FILENAME), {
|
||||||
|
default_config(Staging).workers(1)
|
||||||
|
});
|
||||||
|
|
||||||
|
check_config!(RocketConfig::parse(r#"
|
||||||
|
[stage]
|
||||||
|
workers = 300
|
||||||
|
"#.to_string(), TEST_CONFIG_FILENAME), {
|
||||||
|
default_config(Staging).workers(300)
|
||||||
|
});
|
||||||
|
|
||||||
|
check_config!(RocketConfig::parse(r#"
|
||||||
|
[stage]
|
||||||
|
workers = 65535
|
||||||
|
"#.to_string(), TEST_CONFIG_FILENAME), {
|
||||||
|
default_config(Staging).workers(65535)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bad_workers_values() {
|
||||||
|
// Take the lock so changing the environment doesn't cause races.
|
||||||
|
let _env_lock = ENV_LOCK.lock().unwrap();
|
||||||
|
env::remove_var(CONFIG_ENV);
|
||||||
|
|
||||||
|
assert!(RocketConfig::parse(r#"
|
||||||
|
[development]
|
||||||
|
workers = true
|
||||||
|
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
|
||||||
|
|
||||||
|
assert!(RocketConfig::parse(r#"
|
||||||
|
[production]
|
||||||
|
workers = "hello"
|
||||||
|
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
|
||||||
|
|
||||||
|
assert!(RocketConfig::parse(r#"
|
||||||
|
[staging]
|
||||||
|
workers = -1
|
||||||
|
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
|
||||||
|
|
||||||
|
assert!(RocketConfig::parse(r#"
|
||||||
|
[staging]
|
||||||
|
workers = 65536
|
||||||
|
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
|
||||||
|
|
||||||
|
assert!(RocketConfig::parse(r#"
|
||||||
|
[staging]
|
||||||
|
workers = 105836
|
||||||
|
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_good_log_levels() {
|
fn test_good_log_levels() {
|
||||||
// Take the lock so changing the environment doesn't cause races.
|
// Take the lock so changing the environment doesn't cause races.
|
||||||
|
@ -670,7 +788,7 @@ mod test {
|
||||||
session_key = "VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5"
|
session_key = "VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5"
|
||||||
"#.to_string(), TEST_CONFIG_FILENAME), {
|
"#.to_string(), TEST_CONFIG_FILENAME), {
|
||||||
default_config(Staging).session_key(
|
default_config(Staging).session_key(
|
||||||
"VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5".into()
|
"VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5"
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -679,7 +797,7 @@ mod test {
|
||||||
session_key = "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz"
|
session_key = "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz"
|
||||||
"#.to_string(), TEST_CONFIG_FILENAME), {
|
"#.to_string(), TEST_CONFIG_FILENAME), {
|
||||||
default_config(Staging).session_key(
|
default_config(Staging).session_key(
|
||||||
"adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz".into()
|
"adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz"
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -740,27 +858,21 @@ mod test {
|
||||||
[{}]
|
[{}]
|
||||||
address = "7.6.5.4"
|
address = "7.6.5.4"
|
||||||
"#, GLOBAL_ENV_NAME), TEST_CONFIG_FILENAME), {
|
"#, GLOBAL_ENV_NAME), TEST_CONFIG_FILENAME), {
|
||||||
default_config(*env).address(
|
default_config(*env).address("7.6.5.4")
|
||||||
"7.6.5.4".into()
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
check_config!(RocketConfig::parse(format!(r#"
|
check_config!(RocketConfig::parse(format!(r#"
|
||||||
[{}]
|
[{}]
|
||||||
database = "mysql"
|
database = "mysql"
|
||||||
"#, GLOBAL_ENV_NAME), TEST_CONFIG_FILENAME), {
|
"#, GLOBAL_ENV_NAME), TEST_CONFIG_FILENAME), {
|
||||||
default_config(*env).extra("database",
|
default_config(*env).extra("database", "mysql")
|
||||||
&Value::String("mysql".into())
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
check_config!(RocketConfig::parse(format!(r#"
|
check_config!(RocketConfig::parse(format!(r#"
|
||||||
[{}]
|
[{}]
|
||||||
port = 3980
|
port = 3980
|
||||||
"#, GLOBAL_ENV_NAME), TEST_CONFIG_FILENAME), {
|
"#, GLOBAL_ENV_NAME), TEST_CONFIG_FILENAME), {
|
||||||
default_config(*env).port(
|
default_config(*env).port(3980)
|
||||||
3980
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,7 @@ extern crate term_painter;
|
||||||
extern crate hyper;
|
extern crate hyper;
|
||||||
extern crate url;
|
extern crate url;
|
||||||
extern crate toml;
|
extern crate toml;
|
||||||
|
extern crate num_cpus;
|
||||||
|
|
||||||
#[cfg(test)] #[macro_use] extern crate lazy_static;
|
#[cfg(test)] #[macro_use] extern crate lazy_static;
|
||||||
|
|
||||||
|
@ -135,6 +136,6 @@ pub fn ignite() -> Rocket {
|
||||||
|
|
||||||
/// Alias to [Rocket::custom()](/rocket/struct.Rocket.html#method.custom).
|
/// Alias to [Rocket::custom()](/rocket/struct.Rocket.html#method.custom).
|
||||||
/// Creates a new instance of `Rocket` with a custom configuration.
|
/// Creates a new instance of `Rocket` with a custom configuration.
|
||||||
pub fn custom(config: &config::Config) -> Rocket {
|
pub fn custom(config: config::Config, log: bool) -> Rocket {
|
||||||
Rocket::custom(config)
|
Rocket::custom(config, log)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,7 @@ use http::uri::URI;
|
||||||
/// The main `Rocket` type: used to mount routes and catchers and launch the
|
/// The main `Rocket` type: used to mount routes and catchers and launch the
|
||||||
/// application.
|
/// application.
|
||||||
pub struct Rocket {
|
pub struct Rocket {
|
||||||
address: String,
|
config: &'static Config,
|
||||||
port: u16,
|
|
||||||
router: Router,
|
router: Router,
|
||||||
default_catchers: HashMap<u16, Catcher>,
|
default_catchers: HashMap<u16, Catcher>,
|
||||||
catchers: HashMap<u16, Catcher>,
|
catchers: HashMap<u16, Catcher>,
|
||||||
|
@ -276,17 +275,12 @@ impl Rocket {
|
||||||
pub fn ignite() -> Rocket {
|
pub fn ignite() -> Rocket {
|
||||||
// Note: init() will exit the process under config errors.
|
// Note: init() will exit the process under config errors.
|
||||||
let (config, initted) = config::init();
|
let (config, initted) = config::init();
|
||||||
if initted {
|
Rocket::configured(config, initted)
|
||||||
logger::init(config.log_level);
|
|
||||||
}
|
|
||||||
|
|
||||||
Rocket::custom(config)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new `Rocket` application using the supplied custom
|
/// Creates a new `Rocket` application using the supplied custom
|
||||||
/// configuration information. Ignores the `Rocket.toml` file. Does not
|
/// configuration information. The `Rocket.toml` file, if present, is
|
||||||
/// enable logging. To enable logging, use the hidden
|
/// ignored. If `log` is `true`, logging is enabled.
|
||||||
/// `logger::init(LoggingLevel)` method.
|
|
||||||
///
|
///
|
||||||
/// This method is typically called through the `rocket::custom` alias.
|
/// This method is typically called through the `rocket::custom` alias.
|
||||||
///
|
///
|
||||||
|
@ -297,20 +291,31 @@ impl Rocket {
|
||||||
/// # use rocket::config::ConfigError;
|
/// # use rocket::config::ConfigError;
|
||||||
///
|
///
|
||||||
/// # fn try_config() -> Result<(), ConfigError> {
|
/// # fn try_config() -> Result<(), ConfigError> {
|
||||||
/// let config = Config::default_for(Environment::active()?, "/custom")?
|
/// let config = Config::build(Environment::Staging)
|
||||||
/// .address("1.2.3.4".into())
|
/// .address("1.2.3.4")
|
||||||
/// .port(9234);
|
/// .port(9234)
|
||||||
|
/// .finalize()?;
|
||||||
///
|
///
|
||||||
/// let app = rocket::custom(&config);
|
/// let app = rocket::custom(config, false);
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn custom(config: &Config) -> Rocket {
|
pub fn custom(config: Config, log: bool) -> Rocket {
|
||||||
info!("🔧 Configured for {}.", config.env);
|
let (config, initted) = config::custom_init(config);
|
||||||
|
Rocket::configured(config, log && initted)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configured(config: &'static Config, log: bool) -> Rocket {
|
||||||
|
if log {
|
||||||
|
logger::init(config.log_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("🔧 Configured for {}.", config.environment);
|
||||||
info_!("listening: {}:{}",
|
info_!("listening: {}:{}",
|
||||||
White.paint(&config.address),
|
White.paint(&config.address),
|
||||||
White.paint(&config.port));
|
White.paint(&config.port));
|
||||||
info_!("logging: {:?}", White.paint(config.log_level));
|
info_!("logging: {:?}", White.paint(config.log_level));
|
||||||
|
info_!("workers: {}", White.paint(config.workers));
|
||||||
|
|
||||||
let session_key = config.take_session_key();
|
let session_key = config.take_session_key();
|
||||||
if session_key.is_some() {
|
if session_key.is_some() {
|
||||||
|
@ -324,8 +329,7 @@ impl Rocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
Rocket {
|
Rocket {
|
||||||
address: config.address.clone(),
|
config: config,
|
||||||
port: config.port,
|
|
||||||
router: Router::new(),
|
router: Router::new(),
|
||||||
default_catchers: catcher::defaults::get(),
|
default_catchers: catcher::defaults::get(),
|
||||||
catchers: catcher::defaults::get(),
|
catchers: catcher::defaults::get(),
|
||||||
|
@ -468,7 +472,7 @@ impl Rocket {
|
||||||
warn!("Route collisions detected!");
|
warn!("Route collisions detected!");
|
||||||
}
|
}
|
||||||
|
|
||||||
let full_addr = format!("{}:{}", self.address, self.port);
|
let full_addr = format!("{}:{}", self.config.address, self.config.port);
|
||||||
let server = match hyper::Server::http(full_addr.as_str()) {
|
let server = match hyper::Server::http(full_addr.as_str()) {
|
||||||
Ok(hyper_server) => hyper_server,
|
Ok(hyper_server) => hyper_server,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -482,6 +486,7 @@ impl Rocket {
|
||||||
White.bold().paint("http://"),
|
White.bold().paint("http://"),
|
||||||
White.bold().paint(&full_addr));
|
White.bold().paint(&full_addr));
|
||||||
|
|
||||||
server.handle(self).unwrap();
|
let threads = self.config.workers as usize;
|
||||||
|
server.handle_threads(self, threads).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue