mirror of https://github.com/rwf2/Rocket.git
This commit is a squash of the following commits:
* Add content-type responsers for JSON, HTML, and plain text. * Use content-type responders in content_type example. * Conditionally create Request `from` HypRequest. * Clean-up dispatching and handling in main rocket. * Change Level enum to Logging Level and reexport. * Allow users to set logging level before launch. * Fix content_type example error handling. * Percent decode params when user requests `String`.
This commit is contained in:
parent
90d8621adf
commit
a1ad05e879
|
@ -4,7 +4,8 @@
|
|||
extern crate rocket;
|
||||
extern crate serde_json;
|
||||
|
||||
use rocket::{Rocket, Request, ContentType, Error};
|
||||
use rocket::{Rocket, Request, Error};
|
||||
use rocket::response::JSON;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Person {
|
||||
|
@ -13,24 +14,24 @@ struct Person {
|
|||
}
|
||||
|
||||
#[GET(path = "/<name>/<age>", content = "application/json")]
|
||||
fn hello(name: String, age: i8) -> String {
|
||||
fn hello(name: String, age: i8) -> JSON<String> {
|
||||
let person = Person {
|
||||
name: name,
|
||||
age: age,
|
||||
};
|
||||
|
||||
serde_json::to_string(&person).unwrap()
|
||||
JSON(serde_json::to_string(&person).unwrap())
|
||||
}
|
||||
|
||||
#[error(code = "404")]
|
||||
fn not_found<'r>(error: Error, request: &'r Request<'r>) -> String {
|
||||
match error {
|
||||
Error::NoRoute if !request.content_type().is_json() => {
|
||||
Error::BadMethod if !request.content_type().is_json() => {
|
||||
format!("<p>This server only supports JSON requests, not '{}'.</p>",
|
||||
request.content_type())
|
||||
}
|
||||
Error::NoRoute => {
|
||||
format!("<p>Sorry, this server but '{}' is not a valid path!</p>
|
||||
format!("<p>Sorry, '{}' is not a valid path!</p>
|
||||
<p>Try visiting /hello/<name>/<age> instead.</p>",
|
||||
request.uri())
|
||||
}
|
||||
|
@ -40,7 +41,6 @@ fn not_found<'r>(error: Error, request: &'r Request<'r>) -> String {
|
|||
|
||||
fn main() {
|
||||
let mut rocket = Rocket::new("0.0.0.0", 8000);
|
||||
rocket.mount("/hello", routes![hello]);
|
||||
rocket.catch(errors![not_found]);
|
||||
rocket.mount("/hello", routes![hello]).catch(errors![not_found]);
|
||||
rocket.launch();
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
pub use hyper::mime::{Mime, TopLevel, SubLevel};
|
||||
pub use response::mime::{Mime, TopLevel, SubLevel};
|
||||
use response::mime::{Param};
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::borrow::Borrow;
|
||||
use std::fmt;
|
||||
use hyper::mime::{Param};
|
||||
use self::TopLevel::{Text, Application};
|
||||
use self::SubLevel::{Json, Html};
|
||||
|
||||
|
|
|
@ -3,5 +3,6 @@ pub enum Error {
|
|||
BadMethod,
|
||||
BadParse,
|
||||
NoRoute, // FIXME: Add a chain of routes attempted.
|
||||
Internal,
|
||||
NoKey
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::str::FromStr;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr};
|
||||
use url::{self};
|
||||
use url;
|
||||
|
||||
use error::Error;
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ pub mod handler {
|
|||
pub type ErrorHandler = for<'r> fn(error: Error, &'r Request<'r>) -> Response<'r>;
|
||||
}
|
||||
|
||||
pub use logger::RocketLogger;
|
||||
pub use logger::{RocketLogger, LoggingLevel};
|
||||
pub use content_type::ContentType;
|
||||
pub use codegen::{StaticRouteInfo, StaticCatchInfo};
|
||||
pub use request::Request;
|
||||
|
|
|
@ -2,9 +2,10 @@ use log::{self, Log, LogLevel, LogRecord, LogMetadata};
|
|||
use term_painter::Color::*;
|
||||
use term_painter::ToStyle;
|
||||
|
||||
pub struct RocketLogger(Level);
|
||||
pub struct RocketLogger(LoggingLevel);
|
||||
|
||||
pub enum Level {
|
||||
#[derive(PartialEq)]
|
||||
pub enum LoggingLevel {
|
||||
/// Only shows errors and warning.
|
||||
Critical,
|
||||
/// Shows everything except debug and trace information.
|
||||
|
@ -13,13 +14,13 @@ pub enum Level {
|
|||
Debug,
|
||||
}
|
||||
|
||||
impl Level {
|
||||
impl LoggingLevel {
|
||||
#[inline(always)]
|
||||
fn max_log_level(&self) -> LogLevel {
|
||||
match *self {
|
||||
Level::Critical => LogLevel::Warn,
|
||||
Level::Normal => LogLevel::Info,
|
||||
Level::Debug => LogLevel::Trace
|
||||
LoggingLevel::Critical => LogLevel::Warn,
|
||||
LoggingLevel::Normal => LogLevel::Info,
|
||||
LoggingLevel::Debug => LogLevel::Trace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +56,7 @@ impl Log for RocketLogger {
|
|||
}
|
||||
|
||||
// In Rocket, we abuse target with value "_" to indicate indentation.
|
||||
if record.target() == "_" {
|
||||
if record.target() == "_" && self.0 != LoggingLevel::Critical {
|
||||
print!(" {} ", White.paint("=>"));
|
||||
}
|
||||
|
||||
|
@ -83,7 +84,7 @@ impl Log for RocketLogger {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn init(level: Level) {
|
||||
pub fn init(level: LoggingLevel) {
|
||||
let result = log::set_logger(|max_log_level| {
|
||||
max_log_level.set(level.max_log_level().to_log_level_filter());
|
||||
Box::new(RocketLogger(level))
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use std::str::FromStr;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr};
|
||||
use url;
|
||||
|
||||
use error::Error;
|
||||
|
||||
|
@ -13,6 +14,13 @@ impl<'a> FromParam<'a> for &'a str {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> FromParam<'a> for String {
|
||||
fn from_param(p: &'a str) -> Result<String, Error> {
|
||||
let decoder = url::percent_encoding::percent_decode(p.as_bytes());
|
||||
decoder.decode_utf8().map_err(|_| Error::BadParse).map(|s| s.into_owned())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_with_fromstr {
|
||||
($($T:ident),+) => ($(
|
||||
impl<'a> FromParam<'a> for $T {
|
||||
|
@ -24,5 +32,5 @@ macro_rules! impl_with_fromstr {
|
|||
}
|
||||
|
||||
impl_with_fromstr!(f32, f64, isize, i8, i16, i32, i64, usize, u8, u16, u32, u64,
|
||||
bool, String, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6,
|
||||
bool, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6,
|
||||
SocketAddr);
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
use std::io::{Read};
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
|
||||
use term_painter::Color::*;
|
||||
use term_painter::ToStyle;
|
||||
|
||||
use error::Error;
|
||||
use param::FromParam;
|
||||
|
@ -16,10 +20,10 @@ use router::Route;
|
|||
use request::{HyperHeaders, HyperRequest};
|
||||
|
||||
pub struct Request<'a> {
|
||||
pub params: RefCell<Option<Vec<&'a str>>>, // This also sucks.
|
||||
pub method: Method,
|
||||
pub uri: URIBuf, // FIXME: Should be URI (without Hyper).
|
||||
pub data: Vec<u8>, // FIXME: Don't read this! (bad Hyper.)
|
||||
params: RefCell<Option<Vec<&'a str>>>, // This also sucks.
|
||||
headers: HyperHeaders, // This sucks.
|
||||
}
|
||||
|
||||
|
@ -33,7 +37,6 @@ impl<'a> Request<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn mock(method: Method, uri: &str) -> Request {
|
||||
Request {
|
||||
params: RefCell::new(None),
|
||||
|
@ -44,7 +47,6 @@ impl<'a> Request<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// FIXME: Get rid of Hyper.
|
||||
#[inline(always)]
|
||||
pub fn headers(&self) -> &HyperHeaders {
|
||||
|
@ -70,27 +72,38 @@ impl<'a> Request<'a> {
|
|||
self.headers.set::<header::ContentType>(hyper_ct)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl<'a, 'h, 'k> From<HyperRequest<'h, 'k>> for Request<'a> {
|
||||
fn from(hyper_req: HyperRequest<'h, 'k>) -> Request<'a> {
|
||||
pub fn from_hyp<'h, 'k>(hyper_req: HyperRequest<'h, 'k>)
|
||||
-> Result<Request<'a>, String> {
|
||||
let (_, h_method, h_headers, h_uri, _, mut h_body) = hyper_req.deconstruct();
|
||||
|
||||
let uri = match h_uri {
|
||||
HyperRequestUri::AbsolutePath(s) => URIBuf::from(s),
|
||||
_ => panic!("Can only accept absolute paths!")
|
||||
_ => return Err(format!("Bad URI: {}", h_uri))
|
||||
};
|
||||
|
||||
let method = match Method::from_hyp(&h_method) {
|
||||
Some(m) => m,
|
||||
_ => return Err(format!("Bad method: {}", h_method))
|
||||
};
|
||||
|
||||
// FIXME: GRRR.
|
||||
let mut data = vec![];
|
||||
h_body.read_to_end(&mut data).unwrap();
|
||||
|
||||
Request {
|
||||
let request = Request {
|
||||
params: RefCell::new(None),
|
||||
method: Method::from_hyp(&h_method).unwrap(),
|
||||
method: method,
|
||||
uri: uri,
|
||||
data: data,
|
||||
headers: h_headers,
|
||||
}
|
||||
};
|
||||
|
||||
Ok(request)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> fmt::Display for Request<'r> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{} {}", Green.paint(&self.method), Blue.paint(&self.uri))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
use response::{header, Responder, FreshHyperResponse, Outcome};
|
||||
use response::mime::{Mime, TopLevel, SubLevel};
|
||||
|
||||
macro_rules! impl_data_type_responder {
|
||||
($name:ident: $top:ident/$sub:ident) => (
|
||||
pub struct $name<T: Responder>(pub T);
|
||||
|
||||
impl<T: Responder> Responder for $name<T> {
|
||||
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> {
|
||||
let mime = Mime(TopLevel::$top, SubLevel::$sub, vec![]);
|
||||
res.headers_mut().set(header::ContentType(mime));
|
||||
self.0.respond(res)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
impl_data_type_responder!(JSON: Application/Json);
|
||||
impl_data_type_responder!(HTML: Text/Html);
|
||||
impl_data_type_responder!(Plain: Text/Plain);
|
|
@ -4,6 +4,7 @@ mod redirect;
|
|||
mod with_status;
|
||||
mod outcome;
|
||||
mod cookied;
|
||||
mod data_type;
|
||||
|
||||
pub use hyper::server::Response as HyperResponse;
|
||||
pub use hyper::net::Fresh as HyperFresh;
|
||||
|
@ -11,6 +12,7 @@ pub use hyper::status::StatusCode;
|
|||
pub use hyper::header;
|
||||
pub use hyper::mime;
|
||||
|
||||
pub use self::data_type::*;
|
||||
pub use self::responder::Responder;
|
||||
pub use self::empty::{Empty, Forward};
|
||||
pub use self::redirect::Redirect;
|
||||
|
|
|
@ -14,8 +14,11 @@ pub trait Responder {
|
|||
|
||||
impl<'a> Responder for &'a str {
|
||||
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> {
|
||||
let mime = Mime(TopLevel::Text, SubLevel::Html, vec![]);
|
||||
if res.headers().get::<header::ContentType>().is_none() {
|
||||
let mime = Mime(TopLevel::Text, SubLevel::Plain, vec![]);
|
||||
res.headers_mut().set(header::ContentType(mime));
|
||||
}
|
||||
|
||||
res.send(self.as_bytes()).unwrap();
|
||||
Outcome::Complete
|
||||
}
|
||||
|
@ -23,8 +26,10 @@ impl<'a> Responder for &'a str {
|
|||
|
||||
impl Responder for String {
|
||||
fn respond<'a>(&mut self, mut res: FreshHyperResponse<'a>) -> Outcome<'a> {
|
||||
if res.headers().get::<header::ContentType>().is_none() {
|
||||
let mime = Mime(TopLevel::Text, SubLevel::Html, vec![]);
|
||||
res.headers_mut().set(header::ContentType(mime));
|
||||
}
|
||||
res.send(self.as_bytes()).unwrap();
|
||||
Outcome::Complete
|
||||
}
|
||||
|
|
|
@ -3,14 +3,11 @@ use response::FreshHyperResponse;
|
|||
use request::HyperRequest;
|
||||
use catcher;
|
||||
|
||||
use std::io::Read;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use term_painter::Color::*;
|
||||
use term_painter::ToStyle;
|
||||
|
||||
use hyper::uri::RequestUri as HyperRequestUri;
|
||||
use hyper::method::Method as HyperMethod;
|
||||
use hyper::server::Server as HyperServer;
|
||||
use hyper::server::Handler as HyperHandler;
|
||||
|
||||
|
@ -19,91 +16,80 @@ pub struct Rocket {
|
|||
port: isize,
|
||||
router: Router,
|
||||
catchers: HashMap<u16, Catcher>,
|
||||
}
|
||||
|
||||
fn uri_is_absolute(uri: &HyperRequestUri) -> bool {
|
||||
match *uri {
|
||||
HyperRequestUri::AbsolutePath(_) => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
fn method_is_valid(method: &HyperMethod) -> bool {
|
||||
Method::from_hyp(method).is_some()
|
||||
log_set: bool,
|
||||
}
|
||||
|
||||
impl HyperHandler for Rocket {
|
||||
fn handle<'h, 'k>(&self, req: HyperRequest<'h, 'k>,
|
||||
mut res: FreshHyperResponse<'h>) {
|
||||
info!("{:?} '{}':", Green.paint(&req.method), Blue.paint(&req.uri));
|
||||
|
||||
let finalize = |mut req: HyperRequest, _res: FreshHyperResponse| {
|
||||
let mut buf = vec![];
|
||||
// FIXME: Simple DOS attack here. Working around Hyper bug.
|
||||
let _ = req.read_to_end(&mut buf);
|
||||
};
|
||||
|
||||
if !uri_is_absolute(&req.uri) {
|
||||
error_!("Internal failure. Bad URI.");
|
||||
debug_!("Debug: {}", req.uri);
|
||||
return finalize(req, res);
|
||||
}
|
||||
|
||||
if !method_is_valid(&req.method) {
|
||||
error_!("Internal failure. Bad method.");
|
||||
debug_!("Method: {}", req.method);
|
||||
return finalize(req, res);
|
||||
}
|
||||
|
||||
res.headers_mut().set(response::header::Server("rocket".to_string()));
|
||||
self.dispatch(req, res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Rocket {
|
||||
fn dispatch<'h, 'k>(&self, hyper_req: HyperRequest<'h, 'k>,
|
||||
fn dispatch<'h, 'k>(&self, hyp_req: HyperRequest<'h, 'k>,
|
||||
res: FreshHyperResponse<'h>) {
|
||||
let req = Request::from(hyper_req);
|
||||
let route = self.router.route(&req);
|
||||
if let Some(route) = route {
|
||||
// Get a copy of the URI for later use.
|
||||
let uri = hyp_req.uri.to_string();
|
||||
|
||||
// Try to create a Rocket request from the hyper request.
|
||||
let request = match Request::from_hyp(hyp_req) {
|
||||
Ok(req) => req,
|
||||
Err(reason) => {
|
||||
let mock_request = Request::mock(Method::Get, uri.as_str());
|
||||
return self.handle_internal_error(reason, &mock_request, res);
|
||||
}
|
||||
};
|
||||
|
||||
info!("{}:", request);
|
||||
let route = self.router.route(&request);
|
||||
if let Some(ref route) = route {
|
||||
// Retrieve and set the requests parameters.
|
||||
req.set_params(&route);
|
||||
request.set_params(route);
|
||||
|
||||
// Here's the magic: dispatch the request to the handler.
|
||||
let outcome = (route.handler)(&req).respond(res);
|
||||
let outcome = (route.handler)(&request).respond(res);
|
||||
info_!("{} {}", White.paint("Outcome:"), outcome);
|
||||
|
||||
// // TODO: keep trying lower ranked routes before dispatching a not
|
||||
// // found error.
|
||||
// outcome.map_forward(|res| {
|
||||
// error_!("No further matching routes.");
|
||||
// // TODO: Have some way to know why this was failed forward. Use that
|
||||
// // instead of always using an unchained error.
|
||||
// self.handle_not_found(req, res);
|
||||
// });
|
||||
// TODO: keep trying lower ranked routes before dispatching a not
|
||||
// found error.
|
||||
outcome.map_forward(|res| {
|
||||
error_!("No further matching routes.");
|
||||
// TODO: Have some way to know why this was failed forward. Use that
|
||||
// instead of always using an unchained error.
|
||||
self.handle_not_found(&request, res);
|
||||
});
|
||||
} else {
|
||||
error_!("No matching routes.");
|
||||
return self.handle_not_found(&req, res);
|
||||
self.handle_not_found(&request, res);
|
||||
}
|
||||
}
|
||||
|
||||
// A closure which we call when we know there is no route.
|
||||
// Call on internal server error.
|
||||
fn handle_internal_error<'r>(&self, reason: String, request: &'r Request<'r>,
|
||||
response: FreshHyperResponse) {
|
||||
error!("Internal server error.");
|
||||
debug!("{}", reason);
|
||||
let catcher = self.catchers.get(&500).unwrap();
|
||||
catcher.handle(Error::Internal, request).respond(response);
|
||||
}
|
||||
|
||||
// Call when no route was found.
|
||||
fn handle_not_found<'r>(&self, request: &'r Request<'r>,
|
||||
response: FreshHyperResponse) {
|
||||
error_!("Dispatch failed. Returning 404.");
|
||||
error_!("{} dispatch failed: 404.", request);
|
||||
let catcher = self.catchers.get(&404).unwrap();
|
||||
catcher.handle(Error::NoRoute, request).respond(response);
|
||||
}
|
||||
|
||||
pub fn new(address: &'static str, port: isize) -> Rocket {
|
||||
// FIXME: Allow user to override level/disable logging.
|
||||
logger::init(logger::Level::Normal);
|
||||
|
||||
Rocket {
|
||||
address: address,
|
||||
port: port,
|
||||
router: Router::new(),
|
||||
catchers: catcher::defaults::get(),
|
||||
log_set: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,11 +124,24 @@ impl Rocket {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn launch(self) {
|
||||
pub fn log(&mut self, level: LoggingLevel) {
|
||||
if self.log_set {
|
||||
warn!("Log level already set! Not overriding.");
|
||||
} else {
|
||||
logger::init(level);
|
||||
self.log_set = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn launch(mut self) {
|
||||
if self.router.has_collisions() {
|
||||
warn!("Route collisions detected!");
|
||||
}
|
||||
|
||||
if !self.log_set {
|
||||
self.log(LoggingLevel::Normal)
|
||||
}
|
||||
|
||||
let full_addr = format!("{}:{}", self.address, self.port);
|
||||
info!("🚀 {} {}...", White.paint("Rocket has launched from"),
|
||||
White.bold().paint(&full_addr));
|
||||
|
|
|
@ -181,7 +181,7 @@ fn content_type_to_expr(ecx: &ExtCtxt, content_type: &ContentType) -> P<Expr> {
|
|||
pub fn route_decorator(known_method: Option<Spanned<Method>>, ecx: &mut ExtCtxt,
|
||||
sp: Span, meta_item: &MetaItem, annotated: &Annotatable,
|
||||
push: &mut FnMut(Annotatable)) {
|
||||
::rocket::logger::init(::rocket::logger::Level::Debug);
|
||||
::rocket::logger::init(::rocket::LoggingLevel::Debug);
|
||||
|
||||
// Get the encompassing item and function declaration for the annotated func.
|
||||
let parser = MetaItemParser::new(ecx, meta_item, annotated, &sp);
|
||||
|
|
Loading…
Reference in New Issue