Merge branch 'master' into uuid_support_v2

This commit is contained in:
Lori Holden 2017-01-12 10:19:19 -05:00
commit 39e9234f5f
23 changed files with 833 additions and 307 deletions

View File

@ -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

View File

@ -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 }

View File

@ -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.
//! //!

View File

@ -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);

View File

@ -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,7 +27,8 @@ 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)] #[cfg(feature = $feature)]
fn $engine<T: Serialize>(name: &str, info: &TemplateInfo, c: &T) fn $engine<T: Serialize>(name: &str, info: &TemplateInfo, c: &T)
-> Option<Template> { -> Option<Template> {
@ -44,6 +47,7 @@ macro_rules! render_set {
if let Some(template) = $engine($name, &$info, $ctxt) { if let Some(template) = $engine($name, &$info, $ctxt) {
return template return template
} }
)+}); )+
});
} }

View File

@ -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
} }

View File

@ -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);

View File

@ -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&lt;script &#x2F;&gt;\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> &lt;script /&gt; 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);
}
}

View File

@ -0,0 +1 @@
Done.

View File

@ -0,0 +1 @@
Hello {{ title }}!

View File

@ -0,0 +1,3 @@
{{> hbs/common/header }}
<main> {{ content }} </main>
{{> hbs/common/footer }}

View File

@ -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 %}

View File

@ -0,0 +1,5 @@
{% extends "tera/base" %}
{% block title %}{{ title }}{% endblock title %}
{% block content %}
{{ content }}
{% endblock content %}

View File

@ -0,0 +1,5 @@
{% extends "tera/base" %}
{% block title %}{{ title }}{% endblock title %}
{% block content %}
{{ content }}
{% endblock content %}

View File

@ -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"

View File

@ -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);
} }
} }

View File

@ -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]

179
lib/src/config/builder.rs Normal file
View File

@ -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);

View File

@ -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
} }
} }

View File

@ -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));
} }
} }

View File

@ -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()
.map(|error| ParsingError {
byte_range: (error.lo, error.hi), byte_range: (error.lo, error.hi),
start: parser.to_linecol(error.lo), start: parser.to_linecol(error.lo),
end: parser.to_linecol(error.hi), end: parser.to_linecol(error.hi),
desc: error.desc.clone(), desc: error.desc.clone(),
}).collect() });
))?;
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)
.workers(21)
.log_level(LoggingLevel::Critical) .log_level(LoggingLevel::Critical)
.session_key("01234567890123456789012345678901".into()); .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
)
}); });
} }
} }

View File

@ -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)
} }

View File

@ -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();
} }
} }