Remove config global state. Use Responder::respond_to.

This commit includes two major changes to core:

  1. Configuration state is no longer global. The `config::active()`
     function has been removed. The active configuration can be
     retrieved via the `config` method on a `Rocket` instance.

  2. The `Responder` trait has changed. `Responder::respond(self)` has
     been removed in favor of `Responder::respond_to(self, &Request)`.
     This allows responders to dynamically adjust their response based
     on the incoming request.

Additionally, it includes the following changes to core and codegen:

  * The `Request::guard` method was added to allow for simple
    retrivial of request guards.
  * The `Request::limits` method was added to retrieve configured
    limits.
  * The `File` `Responder` implementation now uses a fixed size body
    instead of a chunked body.
  * The `Outcome::of<R: Responder>(R)` method was removed while
    `Outcome::from<R: Responder(&Request, R)` was added.
  * The unmounted and unmanaged limits are more cautious: they will only
    emit warnings when the `Rocket` receiver is known.

This commit includes one major change to contrib:

  1. To use contrib's templating, the fairing returned by
     `Template::fairing()` must be attached to the running Rocket
     instance.

Additionally, the `Display` implementation of `Template` was removed. To
directly render a template to a `String`, the new `Template::show`
method can be used.
This commit is contained in:
Sergio Benitez 2017-05-19 03:29:08 -07:00
parent 28a1ef0916
commit 9b955747e4
57 changed files with 803 additions and 735 deletions

View File

@ -8,8 +8,8 @@ use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::tokenstream::TokenTree; use syntax::tokenstream::TokenTree;
use syntax::parse::token; use syntax::parse::token;
const ERR_PARAM: &'static str = "_error"; const ERR_PARAM: &'static str = "__err";
const REQ_PARAM: &'static str = "_request"; const REQ_PARAM: &'static str = "__req";
trait ErrorGenerateExt { trait ErrorGenerateExt {
fn generate_fn_arguments(&self, &ExtCtxt, Ident, Ident) -> Vec<TokenTree>; fn generate_fn_arguments(&self, &ExtCtxt, Ident, Ident) -> Vec<TokenTree>;
@ -71,7 +71,8 @@ pub fn error_decorator(ecx: &mut ExtCtxt,
$req_ident: &'_b ::rocket::Request) $req_ident: &'_b ::rocket::Request)
-> ::rocket::response::Result<'_b> { -> ::rocket::response::Result<'_b> {
let user_response = $user_fn_name($fn_arguments); let user_response = $user_fn_name($fn_arguments);
let response = ::rocket::response::Responder::respond(user_response)?; let response = ::rocket::response::Responder::respond_to(user_response,
$req_ident)?;
let status = ::rocket::http::Status::raw($code); let status = ::rocket::http::Status::raw($code);
::rocket::response::Response::build().status(status).merge(response).ok() ::rocket::response::Response::build().status(status).merge(response).ok()
} }

View File

@ -78,7 +78,7 @@ impl RouteGenerateExt for RouteParams {
let mut items = ::rocket::request::FormItems::from($form_string); let mut items = ::rocket::request::FormItems::from($form_string);
let obj = match ::rocket::request::FromForm::from_form_items(items.by_ref()) { let obj = match ::rocket::request::FromForm::from_form_items(items.by_ref()) {
Ok(v) => v, Ok(v) => v,
Err(_) => return ::rocket::Outcome::Forward(_data) Err(_) => return ::rocket::Outcome::Forward(__data)
}; };
if !items.exhaust() { if !items.exhaust() {
@ -106,7 +106,7 @@ impl RouteGenerateExt for RouteParams {
let ty = strip_ty_lifetimes(arg.ty.clone()); let ty = strip_ty_lifetimes(arg.ty.clone());
Some(quote_stmt!(ecx, Some(quote_stmt!(ecx,
let $name: $ty = let $name: $ty =
match ::rocket::data::FromData::from_data(_req, _data) { match ::rocket::data::FromData::from_data(__req, __data) {
::rocket::Outcome::Success(d) => d, ::rocket::Outcome::Success(d) => d,
::rocket::Outcome::Forward(d) => ::rocket::Outcome::Forward(d) =>
return ::rocket::Outcome::Forward(d), return ::rocket::Outcome::Forward(d),
@ -120,9 +120,9 @@ impl RouteGenerateExt for RouteParams {
fn generate_query_statement(&self, ecx: &ExtCtxt) -> Option<Stmt> { fn generate_query_statement(&self, ecx: &ExtCtxt) -> Option<Stmt> {
let param = self.query_param.as_ref(); let param = self.query_param.as_ref();
let expr = quote_expr!(ecx, let expr = quote_expr!(ecx,
match _req.uri().query() { match __req.uri().query() {
Some(query) => query, Some(query) => query,
None => return ::rocket::Outcome::Forward(_data) None => return ::rocket::Outcome::Forward(__data)
} }
); );
@ -149,13 +149,13 @@ impl RouteGenerateExt for RouteParams {
// Note: the `None` case shouldn't happen if a route is matched. // Note: the `None` case shouldn't happen if a route is matched.
let ident = param.ident().prepend(PARAM_PREFIX); let ident = param.ident().prepend(PARAM_PREFIX);
let expr = match param { let expr = match param {
Param::Single(_) => quote_expr!(ecx, match _req.get_param_str($i) { Param::Single(_) => quote_expr!(ecx, match __req.get_param_str($i) {
Some(s) => <$ty as ::rocket::request::FromParam>::from_param(s), Some(s) => <$ty as ::rocket::request::FromParam>::from_param(s),
None => return ::rocket::Outcome::Forward(_data) None => return ::rocket::Outcome::Forward(__data)
}), }),
Param::Many(_) => quote_expr!(ecx, match _req.get_raw_segments($i) { Param::Many(_) => quote_expr!(ecx, match __req.get_raw_segments($i) {
Some(s) => <$ty as ::rocket::request::FromSegments>::from_segments(s), Some(s) => <$ty as ::rocket::request::FromSegments>::from_segments(s),
None => return ::rocket::Outcome::Forward(_data) None => return ::rocket::Outcome::Forward(__data)
}), }),
}; };
@ -166,7 +166,7 @@ impl RouteGenerateExt for RouteParams {
Err(e) => { Err(e) => {
println!(" => Failed to parse '{}': {:?}", println!(" => Failed to parse '{}': {:?}",
stringify!($original_ident), e); stringify!($original_ident), e);
return ::rocket::Outcome::Forward(_data) return ::rocket::Outcome::Forward(__data)
} }
}; };
).expect("declared param parsing statement")); ).expect("declared param parsing statement"));
@ -195,10 +195,10 @@ impl RouteGenerateExt for RouteParams {
fn_param_statements.push(quote_stmt!(ecx, fn_param_statements.push(quote_stmt!(ecx,
#[allow(non_snake_case)] #[allow(non_snake_case)]
let $ident: $ty = match let $ident: $ty = match
::rocket::request::FromRequest::from_request(_req) { ::rocket::request::FromRequest::from_request(__req) {
::rocket::outcome::Outcome::Success(v) => v, ::rocket::outcome::Outcome::Success(v) => v,
::rocket::outcome::Outcome::Forward(_) => ::rocket::outcome::Outcome::Forward(_) =>
return ::rocket::Outcome::forward(_data), return ::rocket::Outcome::forward(__data),
::rocket::outcome::Outcome::Failure((code, _)) => { ::rocket::outcome::Outcome::Failure((code, _)) => {
return ::rocket::Outcome::Failure(code) return ::rocket::Outcome::Failure(code)
}, },
@ -254,13 +254,13 @@ fn generic_route_decorator(known_method: Option<Spanned<Method>>,
// Allow the `unreachable_code` lint for those FromParam impls that have // Allow the `unreachable_code` lint for those FromParam impls that have
// an `Error` associated type of !. // an `Error` associated type of !.
#[allow(unreachable_code)] #[allow(unreachable_code)]
fn $route_fn_name<'_b>(_req: &'_b ::rocket::Request, _data: ::rocket::Data) fn $route_fn_name<'_b>(__req: &'_b ::rocket::Request, __data: ::rocket::Data)
-> ::rocket::handler::Outcome<'_b> { -> ::rocket::handler::Outcome<'_b> {
$param_statements $param_statements
$query_statement $query_statement
$data_statement $data_statement
let responder = $user_fn_name($fn_arguments); let responder = $user_fn_name($fn_arguments);
::rocket::handler::Outcome::of(responder) ::rocket::handler::Outcome::from(__req, responder)
} }
).unwrap()); ).unwrap());

View File

@ -174,11 +174,11 @@ pub fn msg_and_help<'a, T: LintContext<'a>>(cx: &T,
note: &str, note: &str,
help_sp: Option<Span>, help_sp: Option<Span>,
help: &str) { help: &str) {
let mut b = cx.struct_span_lint(lint, msg_sp, msg); // Be conservative. If we don't know the receiver, don't emit the warning.
b.note(note);
if let Some(span) = help_sp { if let Some(span) = help_sp {
b.span_help(span, help); cx.struct_span_lint(lint, msg_sp, msg)
.note(note)
.span_help(span, help)
.emit()
} }
b.emit();
} }

View File

@ -18,8 +18,7 @@ tera_templates = ["tera", "templates"]
handlebars_templates = ["handlebars", "templates"] handlebars_templates = ["handlebars", "templates"]
# Internal use only. # Internal use only.
templates = ["serde", "serde_json", "lazy_static_macro", "glob"] templates = ["serde", "serde_json", "glob"]
lazy_static_macro = ["lazy_static"]
[dependencies] [dependencies]
rocket = { version = "0.2.6", path = "../lib/" } rocket = { version = "0.2.6", path = "../lib/" }
@ -36,5 +35,4 @@ rmp-serde = { version = "^0.13", optional = true }
# Templating dependencies only. # Templating dependencies only.
handlebars = { version = "^0.26.1", optional = true } handlebars = { version = "^0.26.1", optional = true }
glob = { version = "^0.2", optional = true } glob = { version = "^0.2", optional = true }
lazy_static = { version = "^0.2", optional = true }
tera = { version = "^0.10", optional = true } tera = { version = "^0.10", optional = true }

View File

@ -1,7 +1,6 @@
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::io::Read; use std::io::Read;
use rocket::config;
use rocket::outcome::{Outcome, IntoOutcome}; use rocket::outcome::{Outcome, IntoOutcome};
use rocket::request::Request; use rocket::request::Request;
use rocket::data::{self, Data, FromData}; use rocket::data::{self, Data, FromData};
@ -80,6 +79,7 @@ impl<T> JSON<T> {
/// let my_json = JSON(string); /// let my_json = JSON(string);
/// assert_eq!(my_json.into_inner(), "Hello".to_string()); /// assert_eq!(my_json.into_inner(), "Hello".to_string());
/// ``` /// ```
#[inline(always)]
pub fn into_inner(self) -> T { pub fn into_inner(self) -> T {
self.0 self.0
} }
@ -97,10 +97,7 @@ impl<T: DeserializeOwned> FromData for JSON<T> {
return Outcome::Forward(data); return Outcome::Forward(data);
} }
let size_limit = config::active() let size_limit = request.limits().get("json").unwrap_or(LIMIT);
.and_then(|c| c.limits.get("json"))
.unwrap_or(LIMIT);
serde_json::from_reader(data.open().take(size_limit)) serde_json::from_reader(data.open().take(size_limit))
.map(|val| JSON(val)) .map(|val| JSON(val))
.map_err(|e| { error_!("Couldn't parse JSON body: {:?}", e); e }) .map_err(|e| { error_!("Couldn't parse JSON body: {:?}", e); e })
@ -112,9 +109,9 @@ impl<T: DeserializeOwned> FromData for JSON<T> {
/// JSON and a fixed-size body with the serialized value. If serialization /// JSON and a fixed-size body with the serialized value. If serialization
/// fails, an `Err` of `Status::InternalServerError` is returned. /// fails, an `Err` of `Status::InternalServerError` is returned.
impl<T: Serialize> Responder<'static> for JSON<T> { impl<T: Serialize> Responder<'static> for JSON<T> {
fn respond(self) -> response::Result<'static> { fn respond_to(self, req: &Request) -> response::Result<'static> {
serde_json::to_string(&self.0).map(|string| { serde_json::to_string(&self.0).map(|string| {
content::JSON(string).respond().unwrap() content::JSON(string).respond_to(req).unwrap()
}).map_err(|e| { }).map_err(|e| {
error_!("JSON failed to serialize: {:?}", e); error_!("JSON failed to serialize: {:?}", e);
Status::InternalServerError Status::InternalServerError
@ -125,12 +122,14 @@ impl<T: Serialize> Responder<'static> for JSON<T> {
impl<T> Deref for JSON<T> { impl<T> Deref for JSON<T> {
type Target = T; type Target = T;
#[inline(always)]
fn deref<'a>(&'a self) -> &'a T { fn deref<'a>(&'a self) -> &'a T {
&self.0 &self.0
} }
} }
impl<T> DerefMut for JSON<T> { impl<T> DerefMut for JSON<T> {
#[inline(always)]
fn deref_mut<'a>(&'a mut self) -> &'a mut T { fn deref_mut<'a>(&'a mut self) -> &'a mut T {
&mut self.0 &mut self.0
} }

View File

@ -1,4 +1,7 @@
#![feature(drop_types_in_const, macro_reexport)] #![feature(drop_types_in_const, macro_reexport)]
#![cfg_attr(feature = "templates", feature(conservative_impl_trait))]
#![cfg_attr(feature = "templates", feature(associated_consts))]
#![cfg_attr(feature = "templates", feature(struct_field_attributes))]
//! 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.
@ -37,10 +40,6 @@
#[macro_use] extern crate log; #[macro_use] extern crate log;
#[macro_use] extern crate rocket; #[macro_use] extern crate rocket;
#[cfg_attr(feature = "lazy_static_macro", macro_use)]
#[cfg(feature = "lazy_static_macro")]
extern crate lazy_static;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
extern crate serde; extern crate serde;

View File

@ -3,7 +3,6 @@ extern crate rmp_serde;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::io::{Cursor, Read}; use std::io::{Cursor, Read};
use rocket::config;
use rocket::outcome::{Outcome, IntoOutcome}; use rocket::outcome::{Outcome, IntoOutcome};
use rocket::request::Request; use rocket::request::Request;
use rocket::data::{self, Data, FromData}; use rocket::data::{self, Data, FromData};
@ -111,11 +110,8 @@ impl<T: DeserializeOwned> FromData for MsgPack<T> {
return Outcome::Forward(data); return Outcome::Forward(data);
} }
let size_limit = config::active()
.and_then(|c| c.limits.get("msgpack"))
.unwrap_or(LIMIT);
let mut buf = Vec::new(); let mut buf = Vec::new();
let size_limit = request.limits().get("msgpack").unwrap_or(LIMIT);
if let Err(e) = data.open().take(size_limit).read_to_end(&mut buf) { if let Err(e) = data.open().take(size_limit).read_to_end(&mut buf) {
let e = MsgPackError::InvalidDataRead(e); let e = MsgPackError::InvalidDataRead(e);
error_!("Couldn't read request data: {:?}", e); error_!("Couldn't read request data: {:?}", e);
@ -132,7 +128,7 @@ impl<T: DeserializeOwned> FromData for MsgPack<T> {
/// Content-Type `MsgPack` and a fixed-size body with the serialization. If /// Content-Type `MsgPack` and a fixed-size body with the serialization. If
/// serialization fails, an `Err` of `Status::InternalServerError` is returned. /// serialization fails, an `Err` of `Status::InternalServerError` is returned.
impl<T: Serialize> Responder<'static> for MsgPack<T> { impl<T: Serialize> Responder<'static> for MsgPack<T> {
fn respond(self) -> response::Result<'static> { fn respond_to(self, _: &Request) -> response::Result<'static> {
rmp_serde::to_vec(&self.0).map_err(|e| { rmp_serde::to_vec(&self.0).map_err(|e| {
error_!("MsgPack failed to serialize: {:?}", e); error_!("MsgPack failed to serialize: {:?}", e);
Status::InternalServerError Status::InternalServerError

View File

@ -0,0 +1,131 @@
use std::path::{Path, PathBuf};
use std::collections::HashMap;
use super::{Engines, TemplateInfo};
use super::glob;
use rocket::http::ContentType;
pub struct Context {
/// The root of the template directory.
pub root: PathBuf,
/// Mapping from template name to its information.
pub templates: HashMap<String, TemplateInfo>,
/// Mapping from template name to its information.
pub engines: Engines
}
impl Context {
pub fn initialize(root: PathBuf) -> Option<Context> {
let mut templates: HashMap<String, TemplateInfo> = HashMap::new();
for ext in Engines::ENABLED_EXTENSIONS {
let mut glob_path = root.join("**").join("*");
glob_path.set_extension(ext);
let glob_path = glob_path.to_str().expect("valid glob path string");
for path in glob(glob_path).unwrap().filter_map(Result::ok) {
let (name, data_type_str) = split_path(&root, &path);
if let Some(info) = templates.get(&*name) {
warn_!("Template name '{}' does not have a unique path.", name);
info_!("Existing path: {:?}", info.path);
info_!("Additional path: {:?}", path);
warn_!("Using existing path for template '{}'.", name);
continue;
}
let data_type = data_type_str.as_ref()
.map(|ext| ContentType::from_extension(ext))
.unwrap_or(ContentType::HTML);
templates.insert(name, TemplateInfo {
path: path.to_path_buf(),
extension: ext.to_string(),
data_type: data_type,
});
}
}
Engines::init(&templates).map(|engines| {
Context { root, templates, engines }
})
}
}
/// Removes the file path's extension or does nothing if there is none.
fn remove_extension<P: AsRef<Path>>(path: P) -> PathBuf {
let path = path.as_ref();
let stem = match path.file_stem() {
Some(stem) => stem,
None => return path.to_path_buf()
};
match path.parent() {
Some(parent) => parent.join(stem),
None => PathBuf::from(stem)
}
}
/// Splits a path into a name that may be used to identify the template, and the
/// template's data type, if any.
fn split_path(root: &Path, path: &Path) -> (String, Option<String>) {
let rel_path = path.strip_prefix(root).unwrap().to_path_buf();
let path_no_ext = remove_extension(&rel_path);
let data_type = path_no_ext.extension();
let mut name = remove_extension(&path_no_ext).to_string_lossy().into_owned();
// Ensure template name consistency on Windows systems
if cfg!(windows) {
name = name.replace("\\", "/");
}
(name, data_type.map(|d| d.to_string_lossy().into_owned()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn template_path_index_html() {
for root in &["/", "/a/b/c/", "/a/b/c/d/", "/a/"] {
for filename in &["index.html.hbs", "index.html.tera"] {
let path = Path::new(root).join(filename);
let (name, data_type) = split_path(Path::new(root), &path);
assert_eq!(name, "index");
assert_eq!(data_type, Some("html".into()));
}
}
}
#[test]
fn template_path_subdir_index_html() {
for root in &["/", "/a/b/c/", "/a/b/c/d/", "/a/"] {
for sub in &["a/", "a/b/", "a/b/c/", "a/b/c/d/"] {
for filename in &["index.html.hbs", "index.html.tera"] {
let path = Path::new(root).join(sub).join(filename);
let (name, data_type) = split_path(Path::new(root), &path);
let expected_name = format!("{}index", sub);
assert_eq!(name, expected_name.as_str());
assert_eq!(data_type, Some("html".into()));
}
}
}
}
#[test]
fn template_path_doc_examples() {
fn name_for(path: &str) -> String {
split_path(Path::new("templates/"), &Path::new("templates/").join(path)).0
}
assert_eq!(name_for("index.html.hbs"), "index");
assert_eq!(name_for("index.tera"), "index");
assert_eq!(name_for("index.hbs"), "index");
assert_eq!(name_for("dir/index.hbs"), "dir/index");
assert_eq!(name_for("dir/index.html.tera"), "dir/index");
assert_eq!(name_for("index.template.html.hbs"), "index.template");
assert_eq!(name_for("subdir/index.template.html.hbs"), "subdir/index.template");
}
}

View File

@ -0,0 +1,72 @@
use std::collections::HashMap;
use super::serde::Serialize;
use super::TemplateInfo;
#[cfg(feature = "tera_templates")] use super::tera_templates::Tera;
#[cfg(feature = "handlebars_templates")] use super::handlebars_templates::Handlebars;
pub trait Engine: Send + Sync + 'static {
const EXT: &'static str;
fn init(templates: &[(&str, &TemplateInfo)]) -> Option<Self> where Self: Sized;
fn render<C: Serialize>(&self, name: &str, context: C) -> Option<String>;
}
pub struct Engines {
#[cfg(feature = "tera_templates")]
tera: Tera,
#[cfg(feature = "handlebars_templates")]
handlebars: Handlebars,
}
impl Engines {
pub const ENABLED_EXTENSIONS: &'static [&'static str] = &[
#[cfg(feature = "tera_templates")] Tera::EXT,
#[cfg(feature = "handlebars_templates")] Handlebars::EXT,
];
pub fn init(templates: &HashMap<String, TemplateInfo>) -> Option<Engines> {
fn inner<E: Engine>(templates: &HashMap<String, TemplateInfo>) -> Option<E> {
let named_templates = templates.iter()
.filter(|&(_, i)| i.extension == E::EXT)
.map(|(k, i)| (k.as_str(), i))
.collect::<Vec<_>>();
E::init(&*named_templates)
}
Some(Engines {
#[cfg(feature = "tera_templates")]
tera: match inner::<Tera>(templates) {
Some(tera) => tera,
None => return None
},
#[cfg(feature = "handlebars_templates")]
handlebars: match inner::<Handlebars>(templates) {
Some(hb) => hb,
None => return None
},
})
}
pub fn render<C>(&self, name: &str, info: &TemplateInfo, c: C) -> Option<String>
where C: Serialize
{
#[cfg(feature = "tera_templates")]
{
if info.extension == Tera::EXT {
return Engine::render(&self.tera, name, c);
}
}
#[cfg(feature = "handlebars_templates")]
{
if info.extension == Handlebars::EXT {
return Engine::render(&self.handlebars, name, c);
}
}
None
}
}

View File

@ -1,53 +1,35 @@
extern crate handlebars; extern crate handlebars;
use super::serde::Serialize; use super::serde::Serialize;
use super::TemplateInfo; use super::{Engine, TemplateInfo};
use self::handlebars::Handlebars; pub use self::handlebars::Handlebars;
static mut HANDLEBARS: Option<Handlebars> = None; impl Engine for Handlebars {
const EXT: &'static str = "hbs";
pub const EXT: &'static str = "hbs";
// 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 HANDLEBARS.is_some() {
error_!("Internal error: reregistering handlebars!");
return false;
}
fn init(templates: &[(&str, &TemplateInfo)]) -> Option<Handlebars> {
let mut hb = Handlebars::new(); let mut hb = Handlebars::new();
let mut success = true;
for &(name, info) in templates { for &(name, info) in templates {
let path = &info.full_path; let path = &info.path;
if let Err(e) = hb.register_template_file(name, path) { if let Err(e) = hb.register_template_file(name, path) {
error_!("Handlebars template '{}' failed registry: {:?}", name, e); error!("Error in Handlebars template '{}'.", name);
success = false; info_!("{}", e);
} info_!("Template path: '{}'.", path.to_string_lossy());
}
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; return None;
} }
}; }
if hb.get_template(name).is_none() { Some(hb)
}
fn render<C: Serialize>(&self, name: &str, context: C) -> Option<String> {
if self.get_template(name).is_none() {
error_!("Handlebars template '{}' does not exist.", name); error_!("Handlebars template '{}' does not exist.", name);
return None; return None;
} }
match hb.render(name, context) { match self.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);
@ -55,3 +37,4 @@ pub fn render<T>(name: &str, _info: &TemplateInfo, context: &T) -> Option<String
} }
} }
} }
}

View File

@ -1,53 +0,0 @@
/// Returns a hashset with the extensions of all of the enabled template
/// engines from the set of template engined passed in.
macro_rules! engine_set {
($($feature:expr => $engine:ident),+,) => ({
type RegisterFn = for<'a, 'b> unsafe fn(&'a [(&'b str, &TemplateInfo)]) -> bool;
let mut set = Vec::new();
$(
#[cfg(feature = $feature)]
fn $engine(set: &mut Vec<(&'static str, RegisterFn)>) {
set.push(($engine::EXT, $engine::register));
}
#[cfg(not(feature = $feature))]
fn $engine(_: &mut Vec<(&'static str, RegisterFn)>) { }
$engine(&mut set);
)+
set
});
}
/// Renders the template named `name` with the given template info `info` and
/// context `ctxt` using one of the templates in the template set passed in. It
/// does this by checking if the template's extension matches the engine's
/// 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.
macro_rules! render_set {
($name:expr, $info:expr, $ctxt:expr, $($feature:expr => $engine:ident),+,) => ({
$(
#[cfg(feature = $feature)]
fn $engine<T: Serialize>(name: &str, info: &TemplateInfo, c: &T)
-> Option<Template> {
if info.extension == $engine::EXT {
let rendered = $engine::render(name, info, c);
return Some(Template(rendered, info.data_type.clone()));
}
None
}
#[cfg(not(feature = $feature))]
fn $engine<T: Serialize>(_: &str, _: &TemplateInfo, _: &T)
-> Option<Template> { None }
if let Some(template) = $engine($name, &$info, $ctxt) {
return template
}
)+
});
}

View File

@ -2,25 +2,28 @@ extern crate serde;
extern crate serde_json; extern crate serde_json;
extern crate glob; extern crate glob;
#[cfg(feature = "tera_templates")] #[cfg(feature = "tera_templates")] mod tera_templates;
mod tera_templates; #[cfg(feature = "handlebars_templates")] mod handlebars_templates;
mod engine;
#[cfg(feature = "handlebars_templates")] mod context;
mod handlebars_templates;
#[macro_use] mod macros;
use self::engine::{Engine, Engines};
use self::context::Context;
use self::serde::Serialize; use self::serde::Serialize;
use self::serde_json::{Value, to_value};
use self::glob::glob; use self::glob::glob;
use std::borrow::Cow;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::collections::HashMap;
use std::fmt;
use rocket::config::{self, ConfigError}; use rocket::State;
use rocket::request::Request;
use rocket::fairing::{Fairing, AdHoc};
use rocket::response::{self, Content, Responder}; use rocket::response::{self, Content, Responder};
use rocket::http::{ContentType, Status}; use rocket::http::{ContentType, Status};
const DEFAULT_TEMPLATE_DIR: &'static str = "templates";
/// The Template type implements generic support for template rendering in /// The Template type implements generic support for template rendering in
/// Rocket. /// Rocket.
/// ///
@ -28,8 +31,9 @@ use rocket::http::{ContentType, Status};
/// the template directory. The template directory is configurable via the /// the template directory. The template directory is configurable via the
/// `template_dir` configuration parameter and defaults to `templates/`. The /// `template_dir` configuration parameter and defaults to `templates/`. The
/// path set in `template_dir` should be relative to the Rocket configuration /// path set in `template_dir` should be relative to the Rocket configuration
/// file. See the [configuration chapter](https://rocket.rs/guide/overview/#configuration) /// file. See the [configuration
/// of the guide for more information on configuration. /// chapter](https://rocket.rs/guide/overview/#configuration) of the guide for
/// more information on configuration.
/// ///
/// Templates are discovered according to their extension. At present, this /// Templates are discovered according to their extension. At present, this
/// library supports the following templates and extensions: /// library supports the following templates and extensions:
@ -55,6 +59,13 @@ use rocket::http::{ContentType, Status};
/// type, and one for the template extension. This means that template /// type, and one for the template extension. This means that template
/// extensions should look like: `.html.hbs`, `.html.tera`, `.xml.hbs`, etc. /// extensions should look like: `.html.hbs`, `.html.tera`, `.xml.hbs`, etc.
/// ///
/// Template discovery is actualized by the template fairing, which itself is
/// created via the
/// [`Template::fairing()`](/rocket_contrib/struct.Template.html#method.fairing)
/// method. In order for _any_ templates to be rendered, the template fairing
/// must be [attached](/rocket/struct.Rocket.html#method.attach) to the running
/// Rocket instance.
///
/// Templates are rendered with the `render` method. The method takes in the /// Templates are rendered with the `render` method. The method takes in the
/// name of a template and a context to render the template with. The context /// name of a template and a context to render the template with. The context
/// can be any type that implements `Serialize` from /// can be any type that implements `Serialize` from
@ -74,7 +85,25 @@ use rocket::http::{ContentType, Status};
/// features = ["handlebars_templates", "tera_templates"] /// features = ["handlebars_templates", "tera_templates"]
/// ``` /// ```
/// ///
/// The Template type implements Rocket's `Responder` trait, so it can be /// Then, ensure that the template [fairing](/rocket/fairing/) is attached to
/// your Rocket application:
///
/// ```rust
/// extern crate rocket;
/// extern crate rocket_contrib;
///
/// use rocket_contrib::Template;
///
/// fn main() {
/// rocket::ignite()
/// // ...
/// .attach(Template::fairing())
/// // ...
/// # ;
/// }
/// ```
///
/// The `Template` type implements Rocket's `Responder` trait, so it can be
/// returned from a request handler directly: /// returned from a request handler directly:
/// ///
/// ```rust,ignore /// ```rust,ignore
@ -84,49 +113,68 @@ use rocket::http::{ContentType, Status};
/// Template::render("index", &context) /// Template::render("index", &context)
/// } /// }
/// ``` /// ```
// Fields are: (optionally rendered template, template extension)
#[derive(Debug)] #[derive(Debug)]
pub struct Template(Option<String>, Option<String>); pub struct Template {
name: Cow<'static, str>,
value: Option<Value>
}
#[derive(Debug)] #[derive(Debug)]
pub struct TemplateInfo { pub struct TemplateInfo {
/// The complete path, including `template_dir`, to this template. /// The complete path, including `template_dir`, to this template.
full_path: PathBuf,
/// The complete path, without `template_dir`, to this template.
path: PathBuf, path: PathBuf,
/// The extension for the engine of this template. /// The extension for the engine of this template.
extension: String, extension: String,
/// The extension before the engine extension in the template, if any. /// The extension before the engine extension in the template, if any.
data_type: Option<String> data_type: ContentType
}
const DEFAULT_TEMPLATE_DIR: &'static str = "templates";
lazy_static! {
static ref TEMPLATES: HashMap<String, TemplateInfo> = discover_templates();
static ref TEMPLATE_DIR: PathBuf = {
let default_dir_path = config::active().ok_or(ConfigError::NotFound)
.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() {
e.pretty_print();
warn_!("Using default directory '{:?}'", default_dir_path);
}
})
.unwrap_or(default_dir_path)
};
} }
impl Template { impl Template {
/// Returns a fairing that intializes and maintains templating state.
///
/// This fairing _must_ be attached to any `Rocket` instance that wishes to
/// render templates. Failure to attach this fairing will result in a
/// "Uninitialized template context: missing fairing." error message when a
/// template is attempted to be rendered.
///
/// # Example
///
/// To attach this fairing, simple call `attach` on the application's
/// `Rocket` instance with `Template::fairing()`:
///
/// ```rust
/// extern crate rocket;
/// extern crate rocket_contrib;
///
/// use rocket_contrib::Template;
///
/// fn main() {
/// rocket::ignite()
/// // ...
/// .attach(Template::fairing())
/// // ...
/// # ;
/// }
/// ```
pub fn fairing() -> impl Fairing {
AdHoc::on_attach(|rocket| {
let mut template_root = rocket.config().root().join(DEFAULT_TEMPLATE_DIR);
match rocket.config().get_str("template_dir") {
Ok(dir) => template_root = rocket.config().root().join(dir),
Err(ref e) if !e.is_not_found() => {
e.pretty_print();
warn_!("Using default directory '{:?}'", template_root);
}
Err(_) => { }
};
match Context::initialize(template_root) {
Some(ctxt) => Ok(rocket.manage(ctxt)),
None => Err(rocket)
}
})
}
/// Render the template named `name` with the context `context`. The /// Render the template named `name` with the context `context`. The
/// `context` can be of any type that implements `Serialize`. This is /// `context` can be of any type that implements `Serialize`. This is
/// typically a `HashMap` or a custom `struct`. /// typically a `HashMap` or a custom `struct`.
@ -141,30 +189,73 @@ impl Template {
/// let mut context = HashMap::new(); /// let mut context = HashMap::new();
/// ///
/// # context.insert("test", "test"); /// # context.insert("test", "test");
/// let template = Template::render("index", &context); /// # #[allow(unused_variables)]
/// # assert_eq!(template.to_string(), ""); /// let template = Template::render("index", context);
pub fn render<S, T>(name: S, context: &T) -> Template #[inline]
where S: AsRef<str>, T: Serialize pub fn render<S, C>(name: S, context: C) -> Template
where S: Into<Cow<'static, str>>, C: Serialize
{ {
let name = name.as_ref(); Template { name: name.into(), value: to_value(context).ok() }
let template = TEMPLATES.get(name);
if template.is_none() {
let names: Vec<_> = TEMPLATES.keys().map(|s| s.as_str()).collect();
error_!("Template '{}' does not exist.", name);
info_!("Known templates: {}", names.join(","));
info_!("Searched in '{:?}'.", *TEMPLATE_DIR);
return Template(None, None);
} }
// Keep this set in-sync with the `engine_set` invocation. The macro /// Render the template named `name` located at the path `root` with the
// `return`s a `Template` if the extenion in `template` matches an /// context `context` into a `String`. This method is _very slow_ and should
// engine in the set. Otherwise, control will fall through. /// **not** be used in any running Rocket application. This method should
render_set!(name, template.unwrap(), context, /// only be used during testing to validate `Template` responses. For other
"tera_templates" => tera_templates, /// uses, use [`render`](#method.render) instead.
"handlebars_templates" => handlebars_templates, ///
); /// The `context` can be of any type that implements `Serialize`. This is
/// typically a `HashMap` or a custom `struct`. The path `root` can be
/// relative, in which case it is relative to the current working directory,
/// or absolute.
///
/// Returns `Some` if the template could be rendered. Otherwise, returns
/// `None`. If rendering fails, error output is printed to the console.
///
/// # Example
///
/// ```rust
/// use std::collections::HashMap;
/// use rocket_contrib::Template;
///
/// // Create a `context`. Here, just an empty `HashMap`.
/// let mut context = HashMap::new();
///
/// # context.insert("test", "test");
/// # #[allow(unused_variables)]
/// let template = Template::show("templates/", "index", context);
#[inline]
pub fn show<P, S, C>(root: P, name: S, context: C) -> Option<String>
where P: AsRef<Path>, S: Into<Cow<'static, str>>, C: Serialize
{
let root = root.as_ref().to_path_buf();
Context::initialize(root).and_then(|ctxt| {
Template::render(name, context).finalize(&ctxt).ok().map(|v| v.0)
})
}
unreachable!("A template extension was discovered but not rendered.") #[inline(always)]
fn finalize(self, ctxt: &Context) -> Result<(String, ContentType), Status> {
let name = &*self.name;
let info = ctxt.templates.get(name).ok_or_else(|| {
let ts: Vec<_> = ctxt.templates.keys().map(|s| s.as_str()).collect();
error_!("Template '{}' does not exist.", name);
info_!("Known templates: {}", ts.join(","));
info_!("Searched in '{:?}'.", ctxt.root);
Status::InternalServerError
})?;
let value = self.value.ok_or_else(|| {
error_!("The provided template context failed to serialize.");
Status::InternalServerError
})?;
let string = ctxt.engines.render(name, &info, value).ok_or_else(|| {
error_!("Template '{}' failed to render.", name);
Status::InternalServerError
})?;
Ok((string, info.data_type.clone()))
} }
} }
@ -172,155 +263,15 @@ impl Template {
/// extension and a fixed-size body containing the rendered template. If /// extension and a fixed-size body containing the rendered template. If
/// rendering fails, an `Err` of `Status::InternalServerError` is returned. /// rendering fails, an `Err` of `Status::InternalServerError` is returned.
impl Responder<'static> for Template { impl Responder<'static> for Template {
fn respond(self) -> response::Result<'static> { fn respond_to(self, req: &Request) -> response::Result<'static> {
let content_type = match self.1 { let ctxt = req.guard::<State<Context>>().succeeded().ok_or_else(|| {
Some(ref ext) => ContentType::from_extension(ext), error_!("Uninitialized template context: missing fairing.");
None => ContentType::HTML info_!("To use templates, you must attach `Template::fairing()`.");
}; info_!("See the `Template` documentation for more information.");
Status::InternalServerError
})?;
match self.0 { let (render, content_type) = self.finalize(&ctxt)?;
Some(render) => Content(content_type, render).respond(), Content(content_type, render).respond_to(req)
None => Err(Status::InternalServerError)
}
}
}
/// Renders `self`. If the template cannot be rendered, nothing is written.
impl fmt::Display for Template {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
Some(ref render) => render.fmt(f),
None => Ok(())
}
}
}
/// Removes the file path's extension or does nothing if there is none.
fn remove_extension<P: AsRef<Path>>(path: P) -> PathBuf {
let path = path.as_ref();
let stem = match path.file_stem() {
Some(stem) => stem,
None => return path.to_path_buf()
};
match path.parent() {
Some(parent) => parent.join(stem),
None => PathBuf::from(stem)
}
}
/// Splits a path into a relative path from TEMPLATE_DIR, a name that
/// may be used to identify the template, and the template's data type.
fn split_path(path: &Path) -> (PathBuf, String, Option<String>) {
let rel_path = path.strip_prefix(&*TEMPLATE_DIR).unwrap().to_path_buf();
let path_no_ext = remove_extension(&rel_path);
let data_type = path_no_ext.extension();
let mut name = remove_extension(&path_no_ext).to_string_lossy().into_owned();
// Ensure template name consistency on Windows systems
if cfg!(windows) {
name = name.replace("\\", "/");
}
(rel_path, name, data_type.map(|d| d.to_string_lossy().into_owned()))
}
/// 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
/// engine's in `engine_set`.
///
/// **WARNING:** This function should be called ONCE from a SINGLE THREAD.
fn discover_templates() -> HashMap<String, TemplateInfo> {
// Keep this set in-sync with the `render_set` invocation.
let engines = engine_set![
"tera_templates" => tera_templates,
"handlebars_templates" => handlebars_templates,
];
let mut templates: HashMap<String, TemplateInfo> = HashMap::new();
for &(ext, _) in &engines {
let mut glob_path: PathBuf = TEMPLATE_DIR.join("**").join("*");
glob_path.set_extension(ext);
for path in glob(glob_path.to_str().unwrap()).unwrap().filter_map(Result::ok) {
let (rel_path, name, data_type) = split_path(&path);
if let Some(info) = templates.get(&*name) {
warn_!("Template name '{}' does not have a unique path.", name);
info_!("Existing path: {:?}", info.full_path);
info_!("Additional path: {:?}", path);
warn_!("Using existing path for template '{}'.", name);
continue;
}
templates.insert(name, TemplateInfo {
full_path: path.to_path_buf(),
path: rel_path,
extension: ext.to_string(),
data_type: data_type,
});
}
}
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
}
#[cfg(test)]
mod tests {
use super::*;
/// Combines a `relative_path` and the `TEMPLATE_DIR` path into a full path.
fn template_path(relative_path: &str) -> PathBuf {
let mut path = PathBuf::from(&*TEMPLATE_DIR);
path.push(relative_path);
path
}
/// Returns the template system name, given a relative path to a file.
fn relative_path_to_name(relative_path: &str) -> String {
let path = template_path(relative_path);
let (_, name, _) = split_path(&path);
name
}
#[test]
fn template_path_index_html() {
let path = template_path("index.html.hbs");
let (rel_path, name, data_type) = split_path(&path);
assert_eq!(rel_path.to_string_lossy(), "index.html.hbs");
assert_eq!(name, "index");
assert_eq!(data_type, Some("html".to_owned()));
}
#[test]
fn template_path_subdir_index_html() {
let path = template_path("subdir/index.html.hbs");
let (rel_path, name, data_type) = split_path(&path);
assert_eq!(rel_path.to_string_lossy(), "subdir/index.html.hbs");
assert_eq!(name, "subdir/index");
assert_eq!(data_type, Some("html".to_owned()));
}
#[test]
fn template_path_doc_examples() {
assert_eq!(relative_path_to_name("index.html.hbs"), "index");
assert_eq!(relative_path_to_name("index.tera"), "index");
assert_eq!(relative_path_to_name("index.hbs"), "index");
assert_eq!(relative_path_to_name("dir/index.hbs"), "dir/index");
assert_eq!(relative_path_to_name("dir/index.html.tera"), "dir/index");
assert_eq!(relative_path_to_name("index.template.html.hbs"),
"index.template");
assert_eq!(relative_path_to_name("subdir/index.template.html.hbs"),
"subdir/index.template");
} }
} }

View File

@ -1,59 +1,43 @@
extern crate tera; extern crate tera;
use super::serde::Serialize; use super::serde::Serialize;
use super::TemplateInfo; use super::{Engine, TemplateInfo};
use self::tera::Tera; pub use self::tera::Tera;
static mut TERA: Option<Tera> = None; impl Engine for Tera {
const EXT: &'static str = "tera";
pub const EXT: &'static str = "tera";
// 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;
}
fn init(templates: &[(&str, &TemplateInfo)]) -> Option<Tera> {
// Create the Tera instance.
let mut tera = Tera::default(); let mut tera = Tera::default();
let ext = [".html.tera", ".htm.tera", ".xml.tera", ".html", ".htm", ".xml"]; let ext = [".html.tera", ".htm.tera", ".xml.tera", ".html", ".htm", ".xml"];
tera.autoescape_on(ext.to_vec()); tera.autoescape_on(ext.to_vec());
// Collect into a tuple of (name, path) for Tera. // Collect into a tuple of (name, path) for Tera.
let tera_templates = templates.iter() let tera_templates = templates.iter()
.map(|&(name, info)| (&info.full_path, Some(name))) .map(|&(name, info)| (&info.path, Some(name)))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// Finally try to tell Tera about all of the templates. // Finally try to tell Tera about all of the templates.
let mut success = true;
if let Err(e) = tera.add_template_files(tera_templates) { if let Err(e) = tera.add_template_files(tera_templates) {
error_!("Failed to initialize Tera templates: {:?}", e); error!("Failed to initialize Tera templating.");
success = false; for error in e.iter().skip(1) {
info_!("{}.", error);
}
return None
} }
TERA = Some(tera); Some(tera)
success
} }
pub fn render<T>(name: &str, _: &TemplateInfo, context: &T) -> Option<String> fn render<C: Serialize>(&self, name: &str, context: C) -> Option<String> {
where T: Serialize if self.get_template(name).is_err() {
{
let tera = match unsafe { TERA.as_ref() } {
Some(tera) => tera,
None => {
error_!("Internal error: `render` called before Tera init.");
return None;
}
};
if tera.get_template(name).is_err() {
error_!("Tera template '{}' does not exist.", name); error_!("Tera template '{}' does not exist.", name);
return None; return None;
}; };
match tera.render(name, context) { match self.render(name, &context) {
Ok(string) => Some(string), Ok(string) => Some(string),
Err(e) => { Err(e) => {
error_!("Error rendering Tera template '{}'.", name); error_!("Error rendering Tera template '{}'.", name);
@ -65,3 +49,4 @@ pub fn render<T>(name: &str, _: &TemplateInfo, context: &T) -> Option<String>
} }
} }
} }
}

View File

@ -2,18 +2,13 @@ extern crate rocket;
extern crate rocket_contrib; extern crate rocket_contrib;
use std::env; use std::env;
use rocket::config::Config; use std::path::PathBuf;
use rocket::config::Environment::*;
fn init() { fn template_root() -> PathBuf {
let cwd = env::current_dir().expect("current working directory"); let cwd = env::current_dir().expect("current working directory");
let tests_dir = cwd.join("tests"); cwd.join("tests").join("templates")
let config = Config::build(Development).root(tests_dir).unwrap();
rocket::custom(config, true);
} }
// FIXME: Do something about overlapping configs.
#[cfg(feature = "tera_templates")] #[cfg(feature = "tera_templates")]
mod tera_tests { mod tera_tests {
use super::*; use super::*;
@ -27,19 +22,17 @@ mod tera_tests {
#[test] #[test]
fn test_tera_templates() { fn test_tera_templates() {
init();
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert("title", "_test_"); map.insert("title", "_test_");
map.insert("content", "<script />"); map.insert("content", "<script />");
// Test with a txt file, which shouldn't escape. // Test with a txt file, which shouldn't escape.
let template = Template::render("tera/txt_test", &map); let template = Template::show(template_root(), "tera/txt_test", &map);
assert_eq!(&template.to_string(), UNESCAPED_EXPECTED); assert_eq!(template, Some(UNESCAPED_EXPECTED.into()));
// Now with an HTML file, which should. // Now with an HTML file, which should.
let template = Template::render("tera/html_test", &map); let template = Template::show(template_root(), "tera/html_test", &map);
assert_eq!(&template.to_string(), ESCAPED_EXPECTED); assert_eq!(template, Some(ESCAPED_EXPECTED.into()));
} }
} }
@ -54,15 +47,13 @@ mod handlebars_tests {
#[test] #[test]
fn test_handlebars_templates() { fn test_handlebars_templates() {
init();
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert("title", "_test_"); map.insert("title", "_test_");
map.insert("content", "<script /> hi"); map.insert("content", "<script /> hi");
// Test with a txt file, which shouldn't escape. // Test with a txt file, which shouldn't escape.
let template = Template::render("hbs/test", &map); let template = Template::show(template_root(), "hbs/test", &map);
assert_eq!(&template.to_string(), EXPECTED); assert_eq!(template, Some(EXPECTED.into()));
} }
} }

View File

@ -1,10 +0,0 @@
#![feature(plugin)]
#![plugin(rocket_codegen)]
extern crate rocket;
#[get("/")]
#[allow(unmounted_route)]
pub fn hello() -> &'static str {
"Hello, world!"
}

View File

@ -2,9 +2,8 @@
#![plugin(rocket_codegen)] #![plugin(rocket_codegen)]
extern crate rocket; extern crate rocket;
extern crate config;
// This example's illustration is the Rocket.toml file. // This example's illustration is the Rocket.toml file.
fn main() { fn main() {
rocket::ignite().mount("/hello", routes![config::hello]).launch(); rocket::ignite().launch();
} }

View File

@ -1,21 +1,22 @@
extern crate rocket; use rocket::{self, State};
extern crate config as lib; use rocket::fairing::AdHoc;
use rocket::config::{self, Config, Environment};
use rocket::config::{self, Environment}; use rocket::http::{Method, Status};
use rocket::http::Method;
use rocket::LoggingLevel; use rocket::LoggingLevel;
use rocket::testing::MockRequest; use rocket::testing::MockRequest;
pub fn test_config(environment: Environment) { struct LocalConfig(Config);
// Manually set the config environment variable. Rocket will initialize the
// environment in `ignite()`.
::std::env::set_var("ROCKET_ENV", environment.to_string());
rocket::ignite().mount("/hello", routes![lib::hello]);
// Get the active environment and ensure that it matches our expectations. #[get("/check_config")]
let config = config::active().unwrap(); fn check_config(config: State<LocalConfig>) -> Option<()> {
match environment { let environment = match ::std::env::var("ROCKET_ENV") {
Environment::Development => { Ok(name) => name,
Err(_) => return None
};
let config = &config.0;
match &*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.workers, 1);
@ -25,7 +26,7 @@ pub fn test_config(environment: Environment) {
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));
} }
Environment::Staging => { "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.workers, 8);
@ -33,7 +34,7 @@ pub fn test_config(environment: Environment) {
assert_eq!(config.environment, config::Environment::Staging); assert_eq!(config.environment, config::Environment::Staging);
assert_eq!(config.extras().count(), 0); assert_eq!(config.extras().count(), 0);
} }
Environment::Production => { "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.workers, 12);
@ -41,13 +42,30 @@ pub fn test_config(environment: Environment) {
assert_eq!(config.environment, config::Environment::Production); assert_eq!(config.environment, config::Environment::Production);
assert_eq!(config.extras().count(), 0); assert_eq!(config.extras().count(), 0);
} }
_ => {
panic!("Unknown environment in envvar: {}", environment);
} }
} }
pub fn test_hello() { Some(())
let rocket = rocket::ignite().mount("/hello", routes![lib::hello]); }
let mut request = MockRequest::new(Method::Get, "/hello");
let mut response = request.dispatch_with(&rocket); pub fn test_config(environment: Environment) {
// Manually set the config environment variable. Rocket will initialize the
assert_eq!(response.body_string(), Some("Hello, world!".into())); // environment in `ignite()`. We'll read this back in the handler to config.
::std::env::set_var("ROCKET_ENV", environment.to_string());
// FIXME: launch fairings aren't run during tests since...the Rocket isn't
// being launch
let rocket = rocket::ignite()
.attach(AdHoc::on_attach(|rocket| {
println!("Attaching local config.");
let config = rocket.config().clone();
Ok(rocket.manage(LocalConfig(config)))
}))
.mount("/", routes![check_config]);
let mut request = MockRequest::new(Method::Get, "/check_config");
let response = request.dispatch_with(&rocket);
assert_eq!(response.status(), Status::Ok);
} }

View File

@ -8,5 +8,4 @@ mod common;
#[test] #[test]
fn test_development_config() { fn test_development_config() {
common::test_config(rocket::config::Environment::Development); common::test_config(rocket::config::Environment::Development);
common::test_hello();
} }

View File

@ -8,5 +8,4 @@ mod common;
#[test] #[test]
fn test_production_config() { fn test_production_config() {
common::test_config(rocket::config::Environment::Production); common::test_config(rocket::config::Environment::Production);
common::test_hello();
} }

View File

@ -8,5 +8,4 @@ mod common;
#[test] #[test]
fn test_staging_config() { fn test_staging_config() {
common::test_config(rocket::config::Environment::Staging); common::test_config(rocket::config::Environment::Staging);
common::test_hello();
} }

View File

@ -36,6 +36,10 @@ fn index(cookies: Cookies) -> Template {
Template::render("index", &context) Template::render("index", &context)
} }
fn main() { fn rocket() -> rocket::Rocket {
rocket::ignite().mount("/", routes![submit, index]).launch(); rocket::ignite().mount("/", routes![submit, index]).attach(Template::fairing())
}
fn main() {
rocket().launch();
} }

View File

@ -5,9 +5,11 @@ use rocket::testing::MockRequest;
use rocket::http::*; use rocket::http::*;
use rocket_contrib::Template; use rocket_contrib::Template;
const TEMPLATE_ROOT: &'static str = "templates/";
#[test] #[test]
fn test_submit() { fn test_submit() {
let rocket = rocket::ignite().mount("/", routes![super::index, super::submit]); let rocket = rocket();
let mut request = MockRequest::new(Method::Post, "/submit") let mut request = MockRequest::new(Method::Post, "/submit")
.header(ContentType::Form) .header(ContentType::Form)
.body("message=Hello from Rocket!"); .body("message=Hello from Rocket!");
@ -21,7 +23,7 @@ fn test_submit() {
} }
fn test_body(optional_cookie: Option<Cookie<'static>>, expected_body: String) { fn test_body(optional_cookie: Option<Cookie<'static>>, expected_body: String) {
let rocket = rocket::ignite().mount("/", routes![super::index, super::submit]); let rocket = rocket();
let mut request = MockRequest::new(Method::Get, "/"); let mut request = MockRequest::new(Method::Get, "/");
// Attach a cookie if one is given. // Attach a cookie if one is given.
@ -38,16 +40,16 @@ fn test_body(optional_cookie: Option<Cookie<'static>>, expected_body: String) {
fn test_index() { fn test_index() {
// Render the template with an empty context to test against. // Render the template with an empty context to test against.
let mut context: HashMap<&str, &str> = HashMap::new(); let mut context: HashMap<&str, &str> = HashMap::new();
let template = Template::render("index", &context); let template = Template::show(TEMPLATE_ROOT, "index", &context).unwrap();
// Test the route without sending the "message" cookie. // Test the route without sending the "message" cookie.
test_body(None, template.to_string()); test_body(None, template);
// Render the template with a context that contains the message. // Render the template with a context that contains the message.
context.insert("message", "Hello from Rocket!"); context.insert("message", "Hello from Rocket!");
// Test the route with the "message" cookie. // Test the route with the "message" cookie.
let cookie = Cookie::new("message", "Hello from Rocket!"); let cookie = Cookie::new("message", "Hello from Rocket!");
let template = Template::render("index", &context); let template = Template::show(TEMPLATE_ROOT, "index", &context).unwrap();
test_body(Some(cookie), template.to_string()); test_body(Some(cookie), template);
} }

View File

@ -72,7 +72,7 @@ fn rocket() -> rocket::Rocket {
let token_val = rocket.config().get_int("token").unwrap_or(-1); let token_val = rocket.config().get_int("token").unwrap_or(-1);
Ok(rocket.manage(Token(token_val))) Ok(rocket.manage(Token(token_val)))
})) }))
.attach(AdHoc::on_launch(|rocket| { .attach(AdHoc::on_launch(|_| {
println!("Rocket is about to launch!"); println!("Rocket is about to launch!");
})) }))
.attach(AdHoc::on_request(|req, _| { .attach(AdHoc::on_request(|req, _| {

View File

@ -40,9 +40,13 @@ fn not_found(req: &Request) -> Template {
Template::render("error/404", &map) Template::render("error/404", &map)
} }
fn main() { fn rocket() -> rocket::Rocket {
rocket::ignite() rocket::ignite()
.mount("/", routes![index, get]) .mount("/", routes![index, get])
.attach(Template::fairing())
.catch(errors![not_found]) .catch(errors![not_found])
.launch(); }
fn main() {
rocket().launch();
} }

View File

@ -1,16 +1,15 @@
use rocket; use super::rocket;
use rocket::testing::MockRequest; use rocket::testing::MockRequest;
use rocket::http::Method::*; use rocket::http::Method::*;
use rocket::http::Status; use rocket::http::Status;
use rocket::Response; use rocket::Response;
use rocket_contrib::Template; use rocket_contrib::Template;
const TEMPLATE_ROOT: &'static str = "templates/";
macro_rules! run_test { macro_rules! run_test {
($req:expr, $test_fn:expr) => ({ ($req:expr, $test_fn:expr) => ({
let rocket = rocket::ignite() let rocket = rocket();
.mount("/", routes![super::index, super::get])
.catch(errors![super::not_found]);
let mut req = $req; let mut req = $req;
$test_fn(req.dispatch_with(&rocket)); $test_fn(req.dispatch_with(&rocket));
}) })
@ -25,8 +24,8 @@ fn test_root() {
assert_eq!(response.status(), Status::SeeOther); assert_eq!(response.status(), Status::SeeOther);
assert!(response.body().is_none()); assert!(response.body().is_none());
let location_headers: Vec<_> = response.headers().get("Location").collect(); let location: Vec<_> = response.headers().get("Location").collect();
assert_eq!(location_headers, vec!["/hello/Unknown"]); assert_eq!(location, vec!["/hello/Unknown"]);
}); });
} }
@ -36,10 +35,10 @@ fn test_root() {
run_test!(req, |mut response: Response| { run_test!(req, |mut response: Response| {
let mut map = ::std::collections::HashMap::new(); let mut map = ::std::collections::HashMap::new();
map.insert("path", "/"); map.insert("path", "/");
let expected_body = Template::render("error/404", &map).to_string(); let expected = Template::show(TEMPLATE_ROOT, "error/404", &map).unwrap();
assert_eq!(response.status(), Status::NotFound); assert_eq!(response.status(), Status::NotFound);
assert_eq!(response.body_string(), Some(expected_body)); assert_eq!(response.body_string(), Some(expected));
}); });
} }
} }
@ -56,7 +55,7 @@ fn test_name() {
items: vec!["One", "Two", "Three"].iter().map(|s| s.to_string()).collect() items: vec!["One", "Two", "Three"].iter().map(|s| s.to_string()).collect()
}; };
let expected = Template::render("index", &context).to_string(); let expected = Template::show(TEMPLATE_ROOT, "index", &context).unwrap();
assert_eq!(response.body_string(), Some(expected)); assert_eq!(response.body_string(), Some(expected));
}); });
} }
@ -70,7 +69,8 @@ fn test_404() {
let mut map = ::std::collections::HashMap::new(); let mut map = ::std::collections::HashMap::new();
map.insert("path", "/hello/"); map.insert("path", "/hello/");
let expected = Template::render("error/404", &map).to_string();
let expected = Template::show(TEMPLATE_ROOT, "error/404", &map).unwrap();
assert_eq!(response.body_string(), Some(expected)); assert_eq!(response.body_string(), Some(expected));
}); });
} }

View File

@ -17,13 +17,13 @@ fn forward(_req: &Request, data: Data) -> Outcome<'static> {
Outcome::forward(data) Outcome::forward(data)
} }
fn hi(_req: &Request, _: Data) -> Outcome<'static> { fn hi(req: &Request, _: Data) -> Outcome<'static> {
Outcome::of("Hello!") Outcome::from(req, "Hello!")
} }
fn name<'a>(req: &'a Request, _: Data) -> Outcome<'a> { fn name<'a>(req: &'a Request, _: Data) -> Outcome<'a> {
let param = req.get_param::<&'a RawStr>(0); let param = req.get_param::<&'a RawStr>(0);
Outcome::of(param.map(|r| r.as_str()).unwrap_or("unnamed")) Outcome::from(req, param.map(|r| r.as_str()).unwrap_or("unnamed"))
} }
fn echo_url(req: &Request, _: Data) -> Outcome<'static> { fn echo_url(req: &Request, _: Data) -> Outcome<'static> {
@ -32,7 +32,7 @@ fn echo_url(req: &Request, _: Data) -> Outcome<'static> {
.split_at(6) .split_at(6)
.1; .1;
Outcome::of(RawStr::from_str(param).url_decode()) Outcome::from(req, RawStr::from_str(param).url_decode())
} }
fn upload<'r>(req: &'r Request, data: Data) -> Outcome<'r> { fn upload<'r>(req: &'r Request, data: Data) -> Outcome<'r> {
@ -44,7 +44,7 @@ fn upload<'r>(req: &'r Request, data: Data) -> Outcome<'r> {
let file = File::create("/tmp/upload.txt"); let file = File::create("/tmp/upload.txt");
if let Ok(mut file) = file { if let Ok(mut file) = file {
if let Ok(n) = io::copy(&mut data.open(), &mut file) { if let Ok(n) = io::copy(&mut data.open(), &mut file) {
return Outcome::of(format!("OK: {} bytes uploaded.", n)); return Outcome::from(req, format!("OK: {} bytes uploaded.", n));
} }
println!(" => Failed copying."); println!(" => Failed copying.");
@ -55,12 +55,13 @@ fn upload<'r>(req: &'r Request, data: Data) -> Outcome<'r> {
} }
} }
fn get_upload(_: &Request, _: Data) -> Outcome<'static> { fn get_upload(req: &Request, _: Data) -> Outcome<'static> {
Outcome::of(File::open("/tmp/upload.txt").ok()) Outcome::from(req, File::open("/tmp/upload.txt").ok())
} }
fn not_found_handler<'r>(_: Error, req: &'r Request) -> response::Result<'r> { fn not_found_handler<'r>(_: Error, req: &'r Request) -> response::Result<'r> {
Custom(Status::NotFound, format!("Couldn't find: {}", req.uri())).respond() let res = Custom(Status::NotFound, format!("Couldn't find: {}", req.uri()));
res.respond_to(req)
} }
fn rocket() -> rocket::Rocket { fn rocket() -> rocket::Rocket {

View File

@ -32,7 +32,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for Conn {
type Error = (); type Error = ();
fn from_request(request: &'a Request<'r>) -> request::Outcome<Conn, ()> { fn from_request(request: &'a Request<'r>) -> request::Outcome<Conn, ()> {
let pool = match <State<Pool> as FromRequest>::from_request(request) { let pool = match request.guard::<State<Pool>>() {
Outcome::Success(pool) => pool, Outcome::Success(pool) => pool,
Outcome::Failure(e) => return Outcome::Failure(e), Outcome::Failure(e) => return Outcome::Failure(e),
Outcome::Forward(_) => return Outcome::Forward(()), Outcome::Forward(_) => return Outcome::Forward(()),

View File

@ -76,5 +76,6 @@ fn main() {
.manage(db::init_pool()) .manage(db::init_pool())
.mount("/", routes![index, static_files::all]) .mount("/", routes![index, static_files::all])
.mount("/todo/", routes![new, toggle, delete]) .mount("/todo/", routes![new, toggle, delete])
.attach(Template::fairing())
.launch(); .launch();
} }

View File

@ -83,11 +83,12 @@ impl Catcher {
/// use rocket::http::Status; /// use rocket::http::Status;
/// ///
/// fn handle_404<'r>(_: Error, req: &'r Request) -> Result<'r> { /// fn handle_404<'r>(_: Error, req: &'r Request) -> Result<'r> {
/// Custom(Status::NotFound, format!("Couldn't find: {}", req.uri())).respond() /// let res = Custom(Status::NotFound, format!("404: {}", req.uri()));
/// res.respond_to(req)
/// } /// }
/// ///
/// fn handle_500<'r>(_: Error, _: &'r Request) -> Result<'r> { /// fn handle_500<'r>(_: Error, req: &'r Request) -> Result<'r> {
/// "Whoops, we messed up!".respond() /// "Whoops, we messed up!".respond_to(req)
/// } /// }
/// ///
/// let not_found_catcher = Catcher::new(404, handle_404); /// let not_found_catcher = Catcher::new(404, handle_404);
@ -156,10 +157,10 @@ macro_rules! default_errors {
let mut map = HashMap::new(); let mut map = HashMap::new();
$( $(
fn $fn_name<'r>(_: Error, _r: &'r Request) -> response::Result<'r> { fn $fn_name<'r>(_: Error, req: &'r Request) -> response::Result<'r> {
status::Custom(Status::from_code($code).unwrap(), status::Custom(Status::from_code($code).unwrap(),
content::HTML(error_page_template!($code, $name, $description)) content::HTML(error_page_template!($code, $name, $description))
).respond() ).respond_to(req)
} }
map.insert($code, Catcher::new_default($code, $fn_name)); map.insert($code, Catcher::new_default($code, $fn_name));

View File

@ -57,7 +57,7 @@ macro_rules! config_from_raw {
$($key:ident => ($type:ident, $set:ident, $map:expr)),+ | _ => $rest:expr) => ( $($key:ident => ($type:ident, $set:ident, $map:expr)),+ | _ => $rest:expr) => (
match $name { match $name {
$(stringify!($key) => { $(stringify!($key) => {
concat_idents!(value_as_, $type)($config, $name, $value) super::custom_values::$type($config, $name, $value)
.and_then(|parsed| $map($config.$set(parsed))) .and_then(|parsed| $map($config.$set(parsed)))
})+ })+
_ => $rest _ => $rest

View File

@ -50,7 +50,8 @@ pub struct Limits {
impl Default for Limits { impl Default for Limits {
fn default() -> Limits { fn default() -> Limits {
Limits { forms: 1024 * 32, extra: Vec::new() } /// Default limit for forms is 32KiB.
Limits { forms: 32 * 1024, extra: Vec::new() }
} }
} }
@ -103,33 +104,33 @@ impl fmt::Display for Limits {
} }
} }
pub fn value_as_str<'a>(conf: &Config, name: &str, v: &'a Value) -> Result<&'a str> { pub fn str<'a>(conf: &Config, name: &str, v: &'a Value) -> Result<&'a str> {
v.as_str().ok_or(conf.bad_type(name, v.type_str(), "a string")) v.as_str().ok_or(conf.bad_type(name, v.type_str(), "a string"))
} }
pub fn value_as_u64(conf: &Config, name: &str, value: &Value) -> Result<u64> { pub fn u64(conf: &Config, name: &str, value: &Value) -> Result<u64> {
match value.as_integer() { match value.as_integer() {
Some(x) if x >= 0 => Ok(x as u64), Some(x) if x >= 0 => Ok(x as u64),
_ => Err(conf.bad_type(name, value.type_str(), "an unsigned integer")) _ => Err(conf.bad_type(name, value.type_str(), "an unsigned integer"))
} }
} }
pub fn value_as_u16(conf: &Config, name: &str, value: &Value) -> Result<u16> { pub fn u16(conf: &Config, name: &str, value: &Value) -> Result<u16> {
match value.as_integer() { match value.as_integer() {
Some(x) if x >= 0 && x <= (u16::max_value() as i64) => Ok(x as u16), Some(x) if x >= 0 && x <= (u16::max_value() as i64) => Ok(x as u16),
_ => Err(conf.bad_type(name, value.type_str(), "a 16-bit unsigned integer")) _ => Err(conf.bad_type(name, value.type_str(), "a 16-bit unsigned integer"))
} }
} }
pub fn value_as_log_level(conf: &Config, pub fn log_level(conf: &Config,
name: &str, name: &str,
value: &Value value: &Value
) -> Result<LoggingLevel> { ) -> Result<LoggingLevel> {
value_as_str(conf, name, value) str(conf, name, value)
.and_then(|s| s.parse().map_err(|e| conf.bad_type(name, value.type_str(), e))) .and_then(|s| s.parse().map_err(|e| conf.bad_type(name, value.type_str(), e)))
} }
pub fn value_as_tls_config<'v>(conf: &Config, pub fn tls_config<'v>(conf: &Config,
name: &str, name: &str,
value: &'v Value, value: &'v Value,
) -> Result<(&'v str, &'v str)> { ) -> Result<(&'v str, &'v str)> {
@ -140,8 +141,8 @@ pub fn value_as_tls_config<'v>(conf: &Config,
let env = conf.environment; let env = conf.environment;
for (key, value) in table { for (key, value) in table {
match key.as_str() { match key.as_str() {
"certs" => certs_path = Some(value_as_str(conf, "tls.certs", value)?), "certs" => certs_path = Some(str(conf, "tls.certs", value)?),
"key" => key_path = Some(value_as_str(conf, "tls.key", value)?), "key" => key_path = Some(str(conf, "tls.key", value)?),
_ => return Err(ConfigError::UnknownKey(format!("{}.tls.{}", env, key))) _ => return Err(ConfigError::UnknownKey(format!("{}.tls.{}", env, key)))
} }
} }
@ -154,13 +155,13 @@ pub fn value_as_tls_config<'v>(conf: &Config,
} }
} }
pub fn value_as_limits(conf: &Config, name: &str, value: &Value) -> Result<Limits> { pub fn limits(conf: &Config, name: &str, value: &Value) -> Result<Limits> {
let table = value.as_table() let table = value.as_table()
.ok_or_else(|| conf.bad_type(name, value.type_str(), "a table"))?; .ok_or_else(|| conf.bad_type(name, value.type_str(), "a table"))?;
let mut limits = Limits::default(); let mut limits = Limits::default();
for (key, val) in table { for (key, val) in table {
let val = value_as_u64(conf, &format!("limits.{}", key), val)?; let val = u64(conf, &format!("limits.{}", key), val)?;
limits = limits.add(key.as_str(), val); limits = limits.add(key.as_str(), val);
} }

View File

@ -158,28 +158,33 @@
//! ## Retrieving Configuration Parameters //! ## Retrieving Configuration Parameters
//! //!
//! Configuration parameters for the currently active configuration environment //! Configuration parameters for the currently active configuration environment
//! can be retrieved via the [active](fn.active.html) function and methods on //! can be retrieved via the [config](/rocket/struct.Rocket.html#method.config)
//! the [Config](struct.Config.html) structure. The general structure is to call //! method on an instance of `Rocket` and `get_` methods on the
//! `active` and then one of the `get_` methods on the returned `Config` //! [Config](struct.Config.html) structure.
//! structure.
//! //!
//! As an example, consider the following code used by the `Template` type to //! The retrivial of configuration parameters usually occurs at launch time via
//! retrieve the value of the `template_dir` configuration parameter. If the //! a [launch fairing](/rocket/fairing/trait.Fairing.html). If information about
//! value isn't present or isn't a string, a default value is used. //! the configuraiton is needed later in the program, an attach fairing can be
//! used to store the information as managed state. As an example of the latter,
//! consider the following short program which reads the `token` configuration
//! parameter and stores the value or a default in a `Token` managed state
//! value:
//! //!
//! ```rust //! ```rust
//! use std::path::PathBuf; //! use rocket::fairing::AdHoc;
//! use rocket::config::{self, ConfigError};
//! //!
//! const DEFAULT_TEMPLATE_DIR: &'static str = "templates"; //! struct Token(i64);
//! //!
//! # #[allow(unused_variables)] //! fn main() {
//! let template_dir = config::active().ok_or(ConfigError::NotFound) //! rocket::ignite()
//! .map(|config| config.root().join(DEFAULT_TEMPLATE_DIR)) //! .attach(AdHoc::on_attach(|rocket| {
//! .unwrap_or_else(|_| PathBuf::from(DEFAULT_TEMPLATE_DIR)); //! println!("Adding token managed state from config...");
//! let token_val = rocket.config().get_int("token").unwrap_or(-1);
//! Ok(rocket.manage(Token(token_val)))
//! }))
//! # ;
//! }
//! ``` //! ```
//!
//! Libraries should always use a default if a parameter is not defined.
mod error; mod error;
mod environment; mod environment;
@ -188,7 +193,6 @@ mod builder;
mod toml_ext; mod toml_ext;
mod custom_values; mod custom_values;
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;
@ -213,9 +217,6 @@ use self::toml_ext::parse_simple_toml_value;
use logger::{self, LoggingLevel}; use logger::{self, LoggingLevel};
use http::uncased::uncased_eq; use http::uncased::uncased_eq;
static INIT: Once = ONCE_INIT;
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";
const ENV_VAR_PREFIX: &'static str = "ROCKET_"; const ENV_VAR_PREFIX: &'static str = "ROCKET_";
@ -466,32 +467,7 @@ impl RocketConfig {
/// # Panics /// # Panics
/// ///
/// If there is a problem, prints a nice error message and bails. /// If there is a problem, prints a nice error message and bails.
pub(crate) fn init() -> (&'static Config, bool) { pub(crate) fn init() -> Config {
let mut this_init = false;
unsafe {
INIT.call_once(|| {
private_init();
this_init = true;
});
(CONFIG.as_ref().unwrap().active(), this_init)
}
}
pub(crate) 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() {
let bail = |e: ConfigError| -> ! { let bail = |e: ConfigError| -> ! {
logger::init(LoggingLevel::Debug); logger::init(LoggingLevel::Debug);
e.pretty_print(); e.pretty_print();
@ -515,16 +491,8 @@ unsafe fn private_init() {
RocketConfig::active_default(&default_path).unwrap_or_else(|e| bail(e)) RocketConfig::active_default(&default_path).unwrap_or_else(|e| bail(e))
}); });
CONFIG = Some(config); // FIXME: Should probably store all of the config.
} config.active().clone()
/// Retrieve the active configuration, if there is one.
///
/// This function is guaranteed to return `Some` once a Rocket application has
/// started. Before a Rocket application has started, or when there is no active
/// Rocket application (such as during testing), this function will return None.
pub fn active() -> Option<&'static Config> {
unsafe { CONFIG.as_ref().map(|c| c.active()) }
} }
#[cfg(test)] #[cfg(test)]

View File

@ -1,3 +1,5 @@
//! Types and traits for handling incoming body data.
mod data; mod data;
mod data_stream; mod data_stream;
mod net_stream; mod net_stream;

View File

@ -24,8 +24,8 @@ pub enum Error {
/// ///
/// In almost every instance, a launch error occurs because of an I/O error; /// In almost every instance, a launch error occurs because of an I/O error;
/// this is represented by the `Io` variant. A launch error may also occur /// this is represented by the `Io` variant. A launch error may also occur
/// because of ill-defined routes that lead to collisions or because a launch /// because of ill-defined routes that lead to collisions or because a fairing
/// fairing encounted an error; these are represented by the `Collision` and /// encountered an error; these are represented by the `Collision` and
/// `FailedFairing` variants, respectively. The `Unknown` variant captures all /// `FailedFairing` variants, respectively. The `Unknown` variant captures all
/// other kinds of launch errors. /// other kinds of launch errors.
#[derive(Debug)] #[derive(Debug)]
@ -196,7 +196,7 @@ impl Drop for LaunchError {
panic!("route collisions detected"); panic!("route collisions detected");
} }
LaunchErrorKind::FailedFairing => { LaunchErrorKind::FailedFairing => {
error!("Rocket failed to launch due to a failing launch fairing."); error!("Rocket failed to launch due to a failing fairing.");
panic!("launch fairing failure"); panic!("launch fairing failure");
} }
LaunchErrorKind::Unknown(ref e) => { LaunchErrorKind::Unknown(ref e) => {

View File

@ -85,8 +85,8 @@ impl Fairings {
use term_painter::ToStyle; use term_painter::ToStyle;
use term_painter::Color::{White, Magenta}; use term_painter::Color::{White, Magenta};
if self.all_fairings.len() > 0 { if self.all_fairings.is_empty() {
info!("📡 {}:", Magenta.paint("Fairings")); return
} }
fn info_if_nonempty(kind: &str, fairings: &[&Fairing]) { fn info_if_nonempty(kind: &str, fairings: &[&Fairing]) {
@ -97,6 +97,7 @@ impl Fairings {
White.paint(names.join(", "))); White.paint(names.join(", ")));
} }
info!("📡 {}:", Magenta.paint("Fairings"));
info_if_nonempty("launch", &self.launch); info_if_nonempty("launch", &self.launch);
info_if_nonempty("request", &self.request); info_if_nonempty("request", &self.request);
info_if_nonempty("response", &self.response); info_if_nonempty("response", &self.response);

View File

@ -12,8 +12,8 @@ pub type Outcome<'r> = outcome::Outcome<Response<'r>, Status, Data>;
impl<'r> Outcome<'r> { impl<'r> Outcome<'r> {
#[inline] #[inline]
pub fn of<T: Responder<'r>>(responder: T) -> Outcome<'r> { pub fn from<T: Responder<'r>>(req: &Request, responder: T) -> Outcome<'r> {
match responder.respond() { match responder.respond_to(req) {
Ok(response) => outcome::Outcome::Success(response), Ok(response) => outcome::Outcome::Success(response),
Err(status) => outcome::Outcome::Failure(status) Err(status) => outcome::Outcome::Failure(status)
} }

View File

@ -7,7 +7,6 @@
#![feature(lookup_host)] #![feature(lookup_host)]
#![feature(plugin)] #![feature(plugin)]
#![feature(never_type)] #![feature(never_type)]
#![feature(concat_idents)]
#![feature(more_io_inner_methods)] #![feature(more_io_inner_methods)]
#![plugin(pear_codegen)] #![plugin(pear_codegen)]

View File

@ -141,13 +141,20 @@ impl Log for RocketLogger {
} }
#[doc(hidden)] #[doc(hidden)]
pub fn init(level: LoggingLevel) { pub fn try_init(level: LoggingLevel, verbose: bool) {
let result = log::set_logger(|max_log_level| { let result = log::set_logger(|max_log_level| {
max_log_level.set(level.max_log_level().to_log_level_filter()); max_log_level.set(level.max_log_level().to_log_level_filter());
Box::new(RocketLogger(level)) Box::new(RocketLogger(level))
}); });
if let Err(err) = result { if let Err(err) = result {
if verbose {
println!("Logger failed to initialize: {}", err); println!("Logger failed to initialize: {}", err);
} }
} }
}
#[doc(hidden)]
pub fn init(level: LoggingLevel) {
try_init(level, true)
}

View File

@ -28,7 +28,6 @@ use std::marker::PhantomData;
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};
use std::io::Read; use std::io::Read;
use config;
use http::Status; use http::Status;
use request::Request; use request::Request;
use data::{self, Data, FromData}; use data::{self, Data, FromData};
@ -270,9 +269,6 @@ impl<'f, T: FromForm<'f> + Debug + 'f> Debug for Form<'f, T> {
} }
} }
/// Default limit for forms is 32KiB.
const LIMIT: u64 = 32 * (1 << 10);
/// Parses a `Form` from incoming form data. /// Parses a `Form` from incoming form data.
/// ///
/// If the content type of the request data is not /// If the content type of the request data is not
@ -297,7 +293,7 @@ impl<'f, T: FromForm<'f>> FromData for Form<'f, T> where T::Error: Debug {
} }
let mut form_string = String::with_capacity(4096); let mut form_string = String::with_capacity(4096);
let limit = config::active().map(|c| c.limits.forms).unwrap_or(LIMIT); let limit = request.limits().forms;
let mut stream = data.open().take(limit); let mut stream = data.open().take(limit);
if let Err(e) = stream.read_to_string(&mut form_string) { if let Err(e) = stream.read_to_string(&mut form_string) {
error_!("IO Error: {:?}", e); error_!("IO Error: {:?}", e);

View File

@ -1,8 +1,8 @@
use std::fmt::Debug; use std::fmt::Debug;
use std::net::SocketAddr; use std::net::SocketAddr;
use outcome::{self, IntoOutcome};
use request::Request; use request::Request;
use outcome::{self, IntoOutcome};
use outcome::Outcome::*; use outcome::Outcome::*;
use http::{Status, ContentType, Accept, Method, Cookies, Session}; use http::{Status, ContentType, Accept, Method, Cookies, Session};

View File

@ -9,17 +9,20 @@ use term_painter::ToStyle;
use state::{Container, Storage}; use state::{Container, Storage};
use error::Error; use error::Error;
use super::{FromParam, FromSegments}; use super::{FromParam, FromSegments, FromRequest, Outcome};
use router::Route; use router::Route;
use config::{Config, Limits};
use http::uri::{URI, Segments}; use http::uri::{URI, Segments};
use http::{Method, Header, HeaderMap, Cookies, Session, CookieJar, Key}; use http::{Method, Header, HeaderMap, Cookies, Session, CookieJar};
use http::{RawStr, ContentType, Accept, MediaType}; use http::{RawStr, ContentType, Accept, MediaType};
use http::hyper; use http::hyper;
struct PresetState<'r> { struct PresetState<'r> {
key: &'r Key, // The running Rocket instances configuration.
managed_state: &'r Container, config: &'r Config,
// The managed state of the running Rocket instance.
state: &'r Container,
} }
struct RequestState<'r> { struct RequestState<'r> {
@ -286,30 +289,19 @@ impl<'r> Request<'r> {
#[inline] #[inline]
pub fn session(&self) -> Session { pub fn session(&self) -> Session {
let key = self.preset().config.secret_key();
match self.extra.session.try_borrow_mut() { match self.extra.session.try_borrow_mut() {
Ok(jar) => Session::new(jar, self.preset().key), Ok(jar) => Session::new(jar, key),
Err(_) => { Err(_) => {
error_!("Multiple `Session` instances are active at once."); error_!("Multiple `Session` instances are active at once.");
info_!("An instance of `Session` must be dropped before another \ info_!("An instance of `Session` must be dropped before another \
can be retrieved."); can be retrieved.");
warn_!("The retrieved `Session` instance will be empty."); warn_!("The retrieved `Session` instance will be empty.");
Session::empty(self.preset().key) Session::empty(key)
} }
} }
} }
/// Replace all of the cookies in `self` with those in `jar`.
#[inline]
pub(crate) fn set_cookies(&mut self, jar: CookieJar) {
self.extra.cookies = RefCell::new(jar);
}
/// Replace all of the session cookie in `self` with those in `jar`.
#[inline]
pub(crate) fn set_session(&mut self, jar: CookieJar) {
self.extra.session = RefCell::new(jar);
}
/// Returns the Content-Type header of `self`. If the header is not present, /// Returns the Content-Type header of `self`. If the header is not present,
/// returns `None`. The Content-Type header is cached after the first call /// returns `None`. The Content-Type header is cached after the first call
/// to this function. As a result, subsequent calls will always return the /// to this function. As a result, subsequent calls will always return the
@ -352,7 +344,6 @@ impl<'r> Request<'r> {
self.accept().and_then(|accept| accept.first()).map(|wmt| wmt.media_type()) self.accept().and_then(|accept| accept.first()).map(|wmt| wmt.media_type())
} }
#[inline(always)]
pub fn format(&self) -> Option<&MediaType> { pub fn format(&self) -> Option<&MediaType> {
static ANY: MediaType = MediaType::Any; static ANY: MediaType = MediaType::Any;
if self.method.supports_payload() { if self.method.supports_payload() {
@ -386,7 +377,7 @@ impl<'r> Request<'r> {
/// ///
/// # #[allow(dead_code)] /// # #[allow(dead_code)]
/// fn name<'a>(req: &'a Request, _: Data) -> Outcome<'a> { /// fn name<'a>(req: &'a Request, _: Data) -> Outcome<'a> {
/// Outcome::of(req.get_param::<String>(0).unwrap_or("unnamed".into())) /// Outcome::from(req, req.get_param::<String>(0).unwrap_or("unnamed".into()))
/// } /// }
/// ``` /// ```
pub fn get_param<'a, T: FromParam<'a>>(&'a self, n: usize) -> Result<T, Error> { pub fn get_param<'a, T: FromParam<'a>>(&'a self, n: usize) -> Result<T, Error> {
@ -394,15 +385,6 @@ impl<'r> Request<'r> {
T::from_param(param).map_err(|_| Error::BadParse) T::from_param(param).map_err(|_| Error::BadParse)
} }
/// Set `self`'s parameters given that the route used to reach this request
/// was `route`. This should only be used internally by `Rocket` as improper
/// use may result in out of bounds indexing.
/// TODO: Figure out the mount path from here.
#[inline]
pub(crate) fn set_params(&self, route: &Route) {
*self.extra.params.borrow_mut() = route.get_param_indexes(self.uri());
}
/// Get the `n`th path parameter as a string, if it exists. This is used by /// Get the `n`th path parameter as a string, if it exists. This is used by
/// codegen. /// codegen.
#[doc(hidden)] #[doc(hidden)]
@ -468,10 +450,9 @@ impl<'r> Request<'r> {
Some(Segments(&path[i..j])) Some(Segments(&path[i..j]))
} }
/// Get the managed state container, if it exists. For internal use only! /// Get the limits.
#[inline(always)] pub fn limits(&self) -> &'r Limits {
pub fn get_state<T: Send + Sync + 'static>(&self) -> Option<&'r T> { &self.preset().config.limits
self.preset().managed_state.try_get()
} }
#[inline(always)] #[inline(always)]
@ -485,10 +466,43 @@ impl<'r> Request<'r> {
} }
} }
/// Set `self`'s parameters given that the route used to reach this request
/// was `route`. This should only be used internally by `Rocket` as improper
/// use may result in out of bounds indexing.
/// TODO: Figure out the mount path from here.
#[inline]
pub(crate) fn set_params(&self, route: &Route) {
*self.extra.params.borrow_mut() = route.get_param_indexes(self.uri());
}
/// Replace all of the cookies in `self` with those in `jar`.
#[inline]
pub(crate) fn set_cookies(&mut self, jar: CookieJar) {
self.extra.cookies = RefCell::new(jar);
}
/// Replace all of the session cookie in `self` with those in `jar`.
#[inline]
pub(crate) fn set_session(&mut self, jar: CookieJar) {
self.extra.session = RefCell::new(jar);
}
/// Try to derive some guarded value from `self`.
#[inline(always)]
pub fn guard<'a, T: FromRequest<'a, 'r>>(&'a self) -> Outcome<T, T::Error> {
T::from_request(self)
}
/// Get the managed state T, if it exists. For internal use only!
#[inline(always)]
pub(crate) fn get_state<T: Send + Sync + 'static>(&self) -> Option<&'r T> {
self.preset().state.try_get()
}
/// Set the precomputed state. For internal use only! /// Set the precomputed state. For internal use only!
#[inline(always)] #[inline(always)]
pub(crate) fn set_preset_state(&mut self, key: &'r Key, state: &'r Container) { pub(crate) fn set_preset(&mut self, config: &'r Config, state: &'r Container) {
self.extra.preset = Some(PresetState { key, managed_state: state }); self.extra.preset = Some(PresetState { config, state });
} }
/// Convert from Hyper types into a Rocket Request. /// Convert from Hyper types into a Rocket Request.

View File

@ -22,6 +22,7 @@
//! let response = content::HTML("<h1>Hello, world!</h1>"); //! let response = content::HTML("<h1>Hello, world!</h1>");
//! ``` //! ```
use request::Request;
use response::{Response, Responder}; use response::{Response, Responder};
use http::{Status, ContentType}; use http::{Status, ContentType};
@ -47,9 +48,9 @@ pub struct Content<R>(pub ContentType, pub R);
/// delegates the remainder of the response to the wrapped responder. /// delegates the remainder of the response to the wrapped responder.
impl<'r, R: Responder<'r>> Responder<'r> for Content<R> { impl<'r, R: Responder<'r>> Responder<'r> for Content<R> {
#[inline(always)] #[inline(always)]
fn respond(self) -> Result<Response<'r>, Status> { fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
Response::build() Response::build()
.merge(self.1.respond()?) .merge(self.1.respond_to(req)?)
.header(self.0) .header(self.0)
.ok() .ok()
} }
@ -71,8 +72,8 @@ macro_rules! ctrs {
/// Sets the Content-Type of the response then delegates the /// Sets the Content-Type of the response then delegates the
/// remainder of the response to the wrapped responder. /// remainder of the response to the wrapped responder.
impl<'r, R: Responder<'r>> Responder<'r> for $name<R> { impl<'r, R: Responder<'r>> Responder<'r> for $name<R> {
fn respond(self) -> Result<Response<'r>, Status> { fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
Content(ContentType::$name, self.0).respond() Content(ContentType::$name, self.0).respond_to(req)
} }
} }
)+ )+

View File

@ -1,3 +1,4 @@
use request::Request;
use response::{Response, Responder}; use response::{Response, Responder};
use http::Status; use http::Status;
@ -6,8 +7,8 @@ use http::Status;
#[derive(Debug)] #[derive(Debug)]
pub struct Failure(pub Status); pub struct Failure(pub Status);
impl<'r> Responder<'r> for Failure { impl Responder<'static> for Failure {
fn respond(self) -> Result<Response<'r>, Status> { fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
Err(self.0) Err(self.0)
} }
} }

View File

@ -181,10 +181,10 @@ impl<'r, R: Responder<'r>> Flash<R> {
/// response handling to the wrapped responder. As a result, the `Outcome` of /// response handling to the wrapped responder. As a result, the `Outcome` of
/// the response is the `Outcome` of the wrapped `Responder`. /// the response is the `Outcome` of the wrapped `Responder`.
impl<'r, R: Responder<'r>> Responder<'r> for Flash<R> { impl<'r, R: Responder<'r>> Responder<'r> for Flash<R> {
fn respond(self) -> Result<Response<'r>, Status> { fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
trace_!("Flash: setting message: {}:{}", self.name, self.message); trace_!("Flash: setting message: {}:{}", self.name, self.message);
let cookie = self.cookie(); let cookie = self.cookie();
Response::build_from(self.responder.respond()?) Response::build_from(self.responder.respond_to(req)?)
.header_adjoin(&cookie) .header_adjoin(&cookie)
.ok() .ok()
} }

View File

@ -3,6 +3,7 @@ use std::path::{Path, PathBuf};
use std::io; use std::io;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use request::Request;
use response::{Response, Responder}; use response::{Response, Responder};
use http::{Status, ContentType}; use http::{Status, ContentType};
@ -78,8 +79,8 @@ impl NamedFile {
/// [ContentType::from_extension](/rocket/http/struct.ContentType.html#method.from_extension) /// [ContentType::from_extension](/rocket/http/struct.ContentType.html#method.from_extension)
/// for more information. If you would like to stream a file with a different /// for more information. If you would like to stream a file with a different
/// Content-Type than that implied by its extension, use a `File` directly. /// Content-Type than that implied by its extension, use a `File` directly.
impl<'r> Responder<'r> for NamedFile { impl Responder<'static> for NamedFile {
fn respond(self) -> Result<Response<'r>, Status> { fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
let mut response = Response::new(); let mut response = Response::new();
if let Some(ext) = self.path().extension() { if let Some(ext) = self.path().extension() {
// TODO: Use Cow for lowercase. // TODO: Use Cow for lowercase.

View File

@ -1,3 +1,4 @@
use request::Request;
use response::{Response, Responder}; use response::{Response, Responder};
use http::hyper::header; use http::hyper::header;
use http::Status; use http::Status;
@ -104,7 +105,7 @@ impl Redirect {
/// the `Location` header field. The body of the response is empty. This /// the `Location` header field. The body of the response is empty. This
/// responder does not fail. /// responder does not fail.
impl Responder<'static> for Redirect { impl Responder<'static> for Redirect {
fn respond(self) -> Result<Response<'static>, Status> { fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
Response::build() Response::build()
.status(self.0) .status(self.0)
.header(header::Location(self.1)) .header(header::Location(self.1))

View File

@ -3,7 +3,8 @@ use std::io::Cursor;
use std::fmt; use std::fmt;
use http::{Status, ContentType}; use http::{Status, ContentType};
use response::{Response, Stream}; use response::Response;
use request::Request;
/// Trait implemented by types that generate responses for clients. /// Trait implemented by types that generate responses for clients.
/// ///
@ -49,12 +50,14 @@ use response::{Response, Stream};
/// ///
/// * **File** /// * **File**
/// ///
/// Streams the `File` to the client. This is essentially an alias to /// Responds with a sized body containing the data in the `File`. No
/// `Stream::from(file)`. /// `Content-Type` is set. To automatically have a `Content-Type` set based
/// on the file's extension, use
/// [`NamedFile`](/rocket/response/struct.NamedFile.html).
/// ///
/// * **()** /// * **()**
/// ///
/// Responds with an empty body. No Content-Type is set. /// Responds with an empty body. No `Content-Type` is set.
/// ///
/// * **Option&lt;T>** /// * **Option&lt;T>**
/// ///
@ -95,6 +98,16 @@ use response::{Response, Stream};
/// or `ResponseBuilder` struct. Ensure that you document the merging or joining /// or `ResponseBuilder` struct. Ensure that you document the merging or joining
/// behavior appropriately. /// behavior appropriately.
/// ///
/// ## Inspecting Requests
///
/// A `Responder` has access to the request it is responding to. Even so, you
/// should avoid using the `Request` value as much as possible. This is because
/// using the `Request` object makes your responder _inpure_, and so the use of
/// the type as a `Responder` has less intrinsic meaning associated with it. If
/// the `Responder` were pure, however, it always respond in the same manner,
/// regardless of the incoming request. Thus, knowing the type is sufficient to
/// fully determine its functionality.
///
/// # Example /// # Example
/// ///
/// Say that you have a custom type, `Person`: /// Say that you have a custom type, `Person`:
@ -133,11 +146,12 @@ use response::{Response, Stream};
/// # /// #
/// use std::io::Cursor; /// use std::io::Cursor;
/// ///
/// use rocket::request::Request;
/// use rocket::response::{self, Response, Responder}; /// use rocket::response::{self, Response, Responder};
/// use rocket::http::ContentType; /// use rocket::http::ContentType;
/// ///
/// impl<'r> Responder<'r> for Person { /// impl Responder<'static> for Person {
/// fn respond(self) -> response::Result<'r> { /// fn respond_to(self, _: &Request) -> response::Result<'static> {
/// Response::build() /// Response::build()
/// .sized_body(Cursor::new(format!("{}:{}", self.name, self.age))) /// .sized_body(Cursor::new(format!("{}:{}", self.name, self.age)))
/// .raw_header("X-Person-Name", self.name) /// .raw_header("X-Person-Name", self.name)
@ -155,32 +169,21 @@ pub trait Responder<'r> {
/// Returns `Ok` if a `Response` could be generated successfully. Otherwise, /// Returns `Ok` if a `Response` could be generated successfully. Otherwise,
/// returns an `Err` with a failing `Status`. /// returns an `Err` with a failing `Status`.
/// ///
/// The `request` parameter is the `Request` that this `Responder` is
/// responding to.
///
/// When using Rocket's code generation, if an `Ok(Response)` is returned, /// When using Rocket's code generation, if an `Ok(Response)` is returned,
/// the response will be written out to the client. If an `Err(Status)` is /// the response will be written out to the client. If an `Err(Status)` is
/// returned, the error catcher for the given status is retrieved and called /// returned, the error catcher for the given status is retrieved and called
/// to generate a final error response, which is then written out to the /// to generate a final error response, which is then written out to the
/// client. /// client.
fn respond(self) -> Result<Response<'r>, Status>; fn respond_to(self, request: &Request) -> Result<Response<'r>, Status>;
} }
/// Returns a response with Content-Type `text/plain` and a fixed-size body /// Returns a response with Content-Type `text/plain` and a fixed-size body
/// containing the string `self`. Always returns `Ok`. /// containing the string `self`. Always returns `Ok`.
///
/// # Example
///
/// ```rust
/// use rocket::response::Responder;
/// use rocket::http::ContentType;
///
/// let mut response = "Hello".respond().unwrap();
/// assert_eq!(response.body_string(), Some("Hello".into()));
///
/// let content_type: Vec<_> = response.headers().get("Content-Type").collect();
/// assert_eq!(content_type.len(), 1);
/// assert_eq!(content_type[0], ContentType::Plain.to_string());
/// ```
impl<'r> Responder<'r> for &'r str { impl<'r> Responder<'r> for &'r str {
fn respond(self) -> Result<Response<'r>, Status> { fn respond_to(self, _: &Request) -> Result<Response<'r>, Status> {
Response::build() Response::build()
.header(ContentType::Plain) .header(ContentType::Plain)
.sized_body(Cursor::new(self)) .sized_body(Cursor::new(self))
@ -191,7 +194,7 @@ impl<'r> Responder<'r> for &'r str {
/// Returns a response with Content-Type `text/plain` and a fixed-size body /// Returns a response with Content-Type `text/plain` and a fixed-size body
/// containing the string `self`. Always returns `Ok`. /// containing the string `self`. Always returns `Ok`.
impl Responder<'static> for String { impl Responder<'static> for String {
fn respond(self) -> Result<Response<'static>, Status> { fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
Response::build() Response::build()
.header(ContentType::Plain) .header(ContentType::Plain)
.sized_body(Cursor::new(self)) .sized_body(Cursor::new(self))
@ -199,16 +202,16 @@ impl Responder<'static> for String {
} }
} }
/// Aliases Stream<File>: `Stream::from(self)`. /// Returns a response with a sized body for the file. Always returns `Ok`.
impl Responder<'static> for File { impl Responder<'static> for File {
fn respond(self) -> Result<Response<'static>, Status> { fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
Stream::from(self).respond() Response::build().sized_body(self).ok()
} }
} }
/// Returns an empty, default `Response`. Always returns `Ok`. /// Returns an empty, default `Response`. Always returns `Ok`.
impl Responder<'static> for () { impl Responder<'static> for () {
fn respond(self) -> Result<Response<'static>, Status> { fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
Ok(Response::new()) Ok(Response::new())
} }
} }
@ -216,11 +219,11 @@ impl Responder<'static> for () {
/// If `self` is `Some`, responds with the wrapped `Responder`. Otherwise prints /// If `self` is `Some`, responds with the wrapped `Responder`. Otherwise prints
/// a warning message and returns an `Err` of `Status::NotFound`. /// a warning message and returns an `Err` of `Status::NotFound`.
impl<'r, R: Responder<'r>> Responder<'r> for Option<R> { impl<'r, R: Responder<'r>> Responder<'r> for Option<R> {
fn respond(self) -> Result<Response<'r>, Status> { fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
self.map_or_else(|| { self.map_or_else(|| {
warn_!("Response was `None`."); warn_!("Response was `None`.");
Err(Status::NotFound) Err(Status::NotFound)
}, |r| r.respond()) }, |r| r.respond_to(req))
} }
} }
@ -228,8 +231,8 @@ impl<'r, R: Responder<'r>> Responder<'r> for Option<R> {
/// an error message with the `Err` value returns an `Err` of /// an error message with the `Err` value returns an `Err` of
/// `Status::InternalServerError`. /// `Status::InternalServerError`.
impl<'r, R: Responder<'r>, E: fmt::Debug> Responder<'r> for Result<R, E> { impl<'r, R: Responder<'r>, E: fmt::Debug> Responder<'r> for Result<R, E> {
default fn respond(self) -> Result<Response<'r>, Status> { default fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
self.map(|r| r.respond()).unwrap_or_else(|e| { self.map(|r| r.respond_to(req)).unwrap_or_else(|e| {
error_!("Response was `Err`: {:?}.", e); error_!("Response was `Err`: {:?}.", e);
Err(Status::InternalServerError) Err(Status::InternalServerError)
}) })
@ -239,10 +242,10 @@ impl<'r, R: Responder<'r>, E: fmt::Debug> Responder<'r> for Result<R, E> {
/// Responds with the wrapped `Responder` in `self`, whether it is `Ok` or /// Responds with the wrapped `Responder` in `self`, whether it is `Ok` or
/// `Err`. /// `Err`.
impl<'r, R: Responder<'r>, E: Responder<'r> + fmt::Debug> Responder<'r> for Result<R, E> { impl<'r, R: Responder<'r>, E: Responder<'r> + fmt::Debug> Responder<'r> for Result<R, E> {
fn respond(self) -> Result<Response<'r>, Status> { fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
match self { match self {
Ok(responder) => responder.respond(), Ok(responder) => responder.respond_to(req),
Err(responder) => responder.respond(), Err(responder) => responder.respond_to(req),
} }
} }
} }

View File

@ -1129,9 +1129,11 @@ impl<'r> fmt::Debug for Response<'r> {
} }
} }
use request::Request;
impl<'r> Responder<'r> for Response<'r> { impl<'r> Responder<'r> for Response<'r> {
/// This is the identity implementation. It simply returns `Ok(self)`. /// This is the identity implementation. It simply returns `Ok(self)`.
fn respond(self) -> Result<Response<'r>, Status> { fn respond_to(self, _: &Request) -> Result<Response<'r>, Status> {
Ok(self) Ok(self)
} }
} }

View File

@ -10,6 +10,7 @@
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;
use request::Request;
use response::{Responder, Response}; use response::{Responder, Response};
use http::hyper::header; use http::hyper::header;
use http::Status; use http::Status;
@ -40,10 +41,10 @@ pub struct Created<R>(pub String, pub Option<R>);
/// information about the created resource. If no responder is provided, the /// information about the created resource. If no responder is provided, the
/// response body will be empty. /// response body will be empty.
impl<'r, R: Responder<'r>> Responder<'r> for Created<R> { impl<'r, R: Responder<'r>> Responder<'r> for Created<R> {
default fn respond(self) -> Result<Response<'r>, Status> { default fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
let mut build = Response::build(); let mut build = Response::build();
if let Some(responder) = self.1 { if let Some(responder) = self.1 {
build.merge(responder.respond()?); build.merge(responder.respond_to(req)?);
} }
build.status(Status::Created).header(header::Location(self.0)).ok() build.status(Status::Created).header(header::Location(self.0)).ok()
@ -55,14 +56,14 @@ impl<'r, R: Responder<'r>> Responder<'r> for Created<R> {
/// a `Responder` is provided that implements `Hash`. The `ETag` header is set /// a `Responder` is provided that implements `Hash`. The `ETag` header is set
/// to a hash value of the responder. /// to a hash value of the responder.
impl<'r, R: Responder<'r> + Hash> Responder<'r> for Created<R> { impl<'r, R: Responder<'r> + Hash> Responder<'r> for Created<R> {
fn respond(self) -> Result<Response<'r>, Status> { fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
let mut hasher = DefaultHasher::default(); let mut hasher = DefaultHasher::default();
let mut build = Response::build(); let mut build = Response::build();
if let Some(responder) = self.1 { if let Some(responder) = self.1 {
responder.hash(&mut hasher); responder.hash(&mut hasher);
let hash = hasher.finish().to_string(); let hash = hasher.finish().to_string();
build.merge(responder.respond()?); build.merge(responder.respond_to(req)?);
build.header(header::ETag(header::EntityTag::strong(hash))); build.header(header::ETag(header::EntityTag::strong(hash)));
} }
@ -100,10 +101,10 @@ pub struct Accepted<R>(pub Option<R>);
/// Sets the status code of the response to 202 Accepted. If the responder is /// Sets the status code of the response to 202 Accepted. If the responder is
/// `Some`, it is used to finalize the response. /// `Some`, it is used to finalize the response.
impl<'r, R: Responder<'r>> Responder<'r> for Accepted<R> { impl<'r, R: Responder<'r>> Responder<'r> for Accepted<R> {
fn respond(self) -> Result<Response<'r>, Status> { fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
let mut build = Response::build(); let mut build = Response::build();
if let Some(responder) = self.0 { if let Some(responder) = self.0 {
build.merge(responder.respond()?); build.merge(responder.respond_to(req)?);
} }
build.status(Status::Accepted).ok() build.status(Status::Accepted).ok()
@ -127,7 +128,7 @@ pub struct NoContent;
/// Sets the status code of the response to 204 No Content. The body of the /// Sets the status code of the response to 204 No Content. The body of the
/// response will be empty. /// response will be empty.
impl<'r> Responder<'r> for NoContent { impl<'r> Responder<'r> for NoContent {
fn respond(self) -> Result<Response<'r>, Status> { fn respond_to(self, _: &Request) -> Result<Response<'r>, Status> {
Response::build().status(Status::NoContent).ok() Response::build().status(Status::NoContent).ok()
} }
} }
@ -148,8 +149,8 @@ pub struct Reset;
/// Sets the status code of the response to 205 Reset Content. The body of the /// Sets the status code of the response to 205 Reset Content. The body of the
/// response will be empty. /// response will be empty.
impl<'r> Responder<'r> for Reset { impl Responder<'static> for Reset {
fn respond(self) -> Result<Response<'r>, Status> { fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
Response::build().status(Status::ResetContent).ok() Response::build().status(Status::ResetContent).ok()
} }
} }
@ -171,8 +172,8 @@ pub struct Custom<R>(pub Status, pub R);
/// Sets the status code of the response and then delegates the remainder of the /// Sets the status code of the response and then delegates the remainder of the
/// response to the wrapped responder. /// response to the wrapped responder.
impl<'r, R: Responder<'r>> Responder<'r> for Custom<R> { impl<'r, R: Responder<'r>> Responder<'r> for Custom<R> {
fn respond(self) -> Result<Response<'r>, Status> { fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
Response::build_from(self.1.respond()?) Response::build_from(self.1.respond_to(req)?)
.status(self.0) .status(self.0)
.ok() .ok()
} }

View File

@ -1,6 +1,7 @@
use std::io::Read; use std::io::Read;
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};
use request::Request;
use response::{Response, Responder, DEFAULT_CHUNK_SIZE}; use response::{Response, Responder, DEFAULT_CHUNK_SIZE};
use http::Status; use http::Status;
@ -68,7 +69,7 @@ impl<T: Read> From<T> for Stream<T> {
/// response is abandoned, and the response ends abruptly. An error is printed /// response is abandoned, and the response ends abruptly. An error is printed
/// to the console with an indication of what went wrong. /// to the console with an indication of what went wrong.
impl<'r, T: Read + 'r> Responder<'r> for Stream<T> { impl<'r, T: Read + 'r> Responder<'r> for Stream<T> {
fn respond(self) -> Result<Response<'r>, Status> { fn respond_to(self, _: &Request) -> Result<Response<'r>, Status> {
Response::build().chunked_body(self.0, self.1).ok() Response::build().chunked_body(self.0, self.1).ok()
} }
} }

View File

@ -29,7 +29,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 {
config: &'static Config, config: Config,
router: Router, router: Router,
default_catchers: HashMap<u16, Catcher>, default_catchers: HashMap<u16, Catcher>,
catchers: HashMap<u16, Catcher>, catchers: HashMap<u16, Catcher>,
@ -96,8 +96,8 @@ macro_rules! serve {
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
macro_rules! serve { macro_rules! serve {
($rocket:expr, $addr:expr, |$server:ident, $proto:ident| $continue:expr) => ({ ($rocket:expr, $addr:expr, |$server:ident, $proto:ident| $continue:expr) => ({
if let Some(ref tls) = $rocket.config.tls { if let Some(tls) = $rocket.config.tls.clone() {
let tls = TlsServer::new(tls.certs.clone(), tls.key.clone()); let tls = TlsServer::new(tls.certs, tls.key);
let ($proto, $server) = ("https://", hyper::Server::https($addr, tls)); let ($proto, $server) = ("https://", hyper::Server::https($addr, tls));
$continue $continue
} else { } else {
@ -218,7 +218,7 @@ impl Rocket {
info!("{}:", request); info!("{}:", request);
// Inform the request about all of the precomputed state. // Inform the request about all of the precomputed state.
request.set_preset_state(&self.config.secret_key(), &self.state); request.set_preset(&self.config, &self.state);
// Do a bit of preprocessing before routing; run the attached fairings. // Do a bit of preprocessing before routing; run the attached fairings.
self.preprocess_request(request, &data); self.preprocess_request(request, &data);
@ -346,10 +346,10 @@ impl Rocket {
/// rocket::ignite() /// rocket::ignite()
/// # }; /// # };
/// ``` /// ```
#[inline]
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(); Rocket::configured(config::init(), true)
Rocket::configured(config, initted)
} }
/// Creates a new `Rocket` application using the supplied custom /// Creates a new `Rocket` application using the supplied custom
@ -377,14 +377,15 @@ impl Rocket {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[inline]
pub fn custom(config: Config, log: bool) -> Rocket { pub fn custom(config: Config, log: bool) -> Rocket {
let (config, initted) = config::custom_init(config); Rocket::configured(config, log)
Rocket::configured(config, log && initted)
} }
fn configured(config: &'static Config, log: bool) -> Rocket { #[inline]
fn configured(config: Config, log: bool) -> Rocket {
if log { if log {
logger::init(config.log_level); logger::try_init(config.log_level, false);
} }
info!("🔧 Configured for {}.", config.environment); info!("🔧 Configured for {}.", config.environment);
@ -426,17 +427,17 @@ impl Rocket {
/// path. Mounting a route with path `path` at path `base` makes the route /// path. Mounting a route with path `path` at path `base` makes the route
/// available at `base/path`. /// available at `base/path`.
/// ///
/// # Panics
///
/// The `base` mount point must be a static path. That is, the mount point
/// must _not_ contain dynamic path parameters: `<param>`.
///
/// # Examples /// # Examples
/// ///
/// Use the `routes!` macro to mount routes created using the code /// Use the `routes!` macro to mount routes created using the code
/// generation facilities. Requests to the `/hello/world` URI will be /// generation facilities. Requests to the `/hello/world` URI will be
/// dispatched to the `hi` route. /// dispatched to the `hi` route.
/// ///
/// # Panics
///
/// The `base` mount point must be a static path. That is, the mount point
/// must _not_ contain dynamic path parameters: `<param>`.
///
/// ```rust /// ```rust
/// # #![feature(plugin)] /// # #![feature(plugin)]
/// # #![plugin(rocket_codegen)] /// # #![plugin(rocket_codegen)]
@ -464,8 +465,8 @@ impl Rocket {
/// use rocket::handler::Outcome; /// use rocket::handler::Outcome;
/// use rocket::http::Method::*; /// use rocket::http::Method::*;
/// ///
/// fn hi(_: &Request, _: Data) -> Outcome<'static> { /// fn hi(req: &Request, _: Data) -> Outcome<'static> {
/// Outcome::of("Hello!") /// Outcome::from(req, "Hello!")
/// } /// }
/// ///
/// # if false { // We don't actually want to launch the server in an example. /// # if false { // We don't actually want to launch the server in an example.
@ -473,6 +474,7 @@ impl Rocket {
/// # .launch(); /// # .launch();
/// # } /// # }
/// ``` /// ```
#[inline]
pub fn mount(mut self, base: &str, routes: Vec<Route>) -> Self { pub fn mount(mut self, base: &str, routes: Vec<Route>) -> Self {
info!("🛰 {} '{}':", Magenta.paint("Mounting"), base); info!("🛰 {} '{}':", Magenta.paint("Mounting"), base);
@ -522,6 +524,7 @@ impl Rocket {
/// # } /// # }
/// } /// }
/// ``` /// ```
#[inline]
pub fn catch(mut self, catchers: Vec<Catcher>) -> Self { pub fn catch(mut self, catchers: Vec<Catcher>) -> Self {
info!("👾 {}:", Magenta.paint("Catchers")); info!("👾 {}:", Magenta.paint("Catchers"));
for c in catchers { for c in catchers {
@ -577,6 +580,7 @@ impl Rocket {
/// # } /// # }
/// } /// }
/// ``` /// ```
#[inline]
pub fn manage<T: Send + Sync + 'static>(self, state: T) -> Self { pub fn manage<T: Send + Sync + 'static>(self, state: T) -> Self {
if !self.state.set::<T>(state) { if !self.state.set::<T>(state) {
error!("State for this type is already being managed!"); error!("State for this type is already being managed!");
@ -648,7 +652,7 @@ impl Rocket {
/// rocket::ignite().launch(); /// rocket::ignite().launch();
/// # } /// # }
/// ``` /// ```
pub fn launch(self) -> LaunchError { pub fn launch(mut self) -> LaunchError {
if let Some(error) = self.prelaunch_check() { if let Some(error) = self.prelaunch_check() {
return error; return error;
} }
@ -662,19 +666,20 @@ impl Rocket {
Err(e) => return LaunchError::from(e) Err(e) => return LaunchError::from(e)
}; };
// Determine the port we actually binded to. // Determine the address and port we actually binded to.
let (addr, port) = match server.local_addr() { match server.local_addr() {
Ok(server_addr) => (&self.config.address, server_addr.port()), Ok(server_addr) => self.config.port = server_addr.port(),
Err(e) => return LaunchError::from(e) Err(e) => return LaunchError::from(e)
}; }
// Run the launch fairings. // Run the launch fairings.
self.fairings.handle_launch(&self); self.fairings.handle_launch(&self);
let full_addr = format!("{}:{}", self.config.address, self.config.port);
launch_info!("🚀 {} {}{}", launch_info!("🚀 {} {}{}",
White.paint("Rocket has launched from"), White.paint("Rocket has launched from"),
White.bold().paint(proto), White.bold().paint(proto),
White.bold().paint(&format!("{}:{}", addr, port))); White.bold().paint(&full_addr));
let threads = self.config.workers as usize; let threads = self.config.workers as usize;
if let Err(e) = server.handle_threads(self, threads) { if let Err(e) = server.handle_threads(self, threads) {
@ -691,9 +696,9 @@ impl Rocket {
self.router.routes() self.router.routes()
} }
/// Retrieve the configuration. /// Retrieve the active configuration.
#[inline(always)] #[inline(always)]
pub fn config(&self) -> &Config { pub fn config(&self) -> &Config {
self.config &self.config
} }
} }

View File

@ -141,8 +141,8 @@ mod tests {
type SimpleRoute = (Method, &'static str); type SimpleRoute = (Method, &'static str);
fn dummy_handler(_req: &Request, _: Data) -> Outcome<'static> { fn dummy_handler(req: &Request, _: Data) -> Outcome<'static> {
Outcome::of("hi") Outcome::from(req, "hi")
} }
fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool { fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool {

View File

@ -77,8 +77,8 @@ mod test {
use data::Data; use data::Data;
use handler::Outcome; use handler::Outcome;
fn dummy_handler(_req: &Request, _: Data) -> Outcome<'static> { fn dummy_handler(req: &Request, _: Data) -> Outcome<'static> {
Outcome::of("hi") Outcome::from(req, "hi")
} }
fn router_with_routes(routes: &[&'static str]) -> Router { fn router_with_routes(routes: &[&'static str]) -> Router {

View File

@ -15,7 +15,7 @@ fn index(form: Form<Simple>) -> String {
form.into_inner().value form.into_inner().value
} }
mod tests { mod limits_tests {
use rocket; use rocket;
use rocket::config::{Environment, Config, Limits}; use rocket::config::{Environment, Config, Limits};
use rocket::testing::MockRequest; use rocket::testing::MockRequest;
@ -30,44 +30,38 @@ mod tests {
rocket::custom(config, true).mount("/", routes![super::index]) rocket::custom(config, true).mount("/", routes![super::index])
} }
// FIXME: Config is global (it's the only global thing). Each of these tests #[test]
// will run in different threads in the same process, so the config used by fn large_enough() {
// all of the tests will be indentical: whichever of these gets executed let rocket = rocket_with_forms_limit(128);
// first. As such, only one test will pass; the rest will fail. Make config let mut req = MockRequest::new(Post, "/")
// _not_ global so we can actually do these tests. .body("value=Hello+world")
.header(ContentType::Form);
// #[test] let mut response = req.dispatch_with(&rocket);
// fn large_enough() { assert_eq!(response.body_string(), Some("Hello world".into()));
// let rocket = rocket_with_forms_limit(128); }
// let mut req = MockRequest::new(Post, "/")
// .body("value=Hello+world")
// .header(ContentType::Form);
// let mut response = req.dispatch_with(&rocket); #[test]
// assert_eq!(response.body_string(), Some("Hello world".into())); fn just_large_enough() {
// } let rocket = rocket_with_forms_limit(17);
let mut req = MockRequest::new(Post, "/")
.body("value=Hello+world")
.header(ContentType::Form);
// #[test] let mut response = req.dispatch_with(&rocket);
// fn just_large_enough() { assert_eq!(response.body_string(), Some("Hello world".into()));
// let rocket = rocket_with_forms_limit(17); }
// let mut req = MockRequest::new(Post, "/")
// .body("value=Hello+world")
// .header(ContentType::Form);
// let mut response = req.dispatch_with(&rocket); #[test]
// assert_eq!(response.body_string(), Some("Hello world".into())); fn much_too_small() {
// } let rocket = rocket_with_forms_limit(4);
let mut req = MockRequest::new(Post, "/")
.body("value=Hello+world")
.header(ContentType::Form);
// #[test] let response = req.dispatch_with(&rocket);
// fn much_too_small() { assert_eq!(response.status(), Status::BadRequest);
// let rocket = rocket_with_forms_limit(4); }
// let mut req = MockRequest::new(Post, "/")
// .body("value=Hello+world")
// .header(ContentType::Form);
// let response = req.dispatch_with(&rocket);
// assert_eq!(response.status(), Status::BadRequest);
// }
#[test] #[test]
fn contracted() { fn contracted() {

View File

@ -128,7 +128,7 @@ fn rocket_route_fn_hello<'_b>(_req: &'_b ::rocket::Request,
_data: ::rocket::Data) _data: ::rocket::Data)
-> ::rocket::handler::Outcome<'_b> { -> ::rocket::handler::Outcome<'_b> {
let responder = hello(); let responder = hello();
::rocket::handler::Outcome::of(responder) ::rocket::handler::Outcome::from(_req, responder)
} }
``` ```