From da2b0ed35a1caeef48a99052e541390b1272b26b Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Thu, 17 Mar 2016 01:57:04 -0700 Subject: [PATCH] Somewhat good infrastructure for params and responses. Can actually return strings and what-not from a route. Yay! --- examples/hello/src/main.rs | 33 +++++-- lib/src/lib.rs | 43 ++++----- lib/src/param.rs | 28 ++++++ lib/src/request.rs | 11 +-- lib/src/response.rs | 171 ++++++++++++++++++++++++---------- macros/src/route_decorator.rs | 17 +++- 6 files changed, 209 insertions(+), 94 deletions(-) create mode 100644 lib/src/param.rs diff --git a/examples/hello/src/main.rs b/examples/hello/src/main.rs index 27899282..20dea772 100644 --- a/examples/hello/src/main.rs +++ b/examples/hello/src/main.rs @@ -3,23 +3,36 @@ extern crate rocket; use rocket::Rocket; +use rocket::response::{HypResponse, HypFresh, Responder}; + +use std::fs::File; + +// #[route(GET, path = "/")] +// fn simple() -> &'static str { +// "Hello, simple example world! How is thou?" +// } #[route(GET, path = "/")] -fn simple() -> &'static str { - "Hello, simple example world! How is thou?" +fn simple() -> File { + File::open("/tmp/index.html").unwrap() } -#[route(GET, path = "/")] -fn hello(name: String) -> String { - format!("Hello, {}!", name) +// #[route(GET, path = "/")] +// fn simple2() -> &'static str { +// "Hello, world!" +// } + +#[route(GET, path = "/hello")] +fn simple3() -> String { + String::from("Hello, world!") } -#[route(PUT, path = "//")] -fn bye(x: usize, y: usize) -> String { - format!("{} + {} = {}", x, y, x + y) -} +// #[route(GET, path = "//")] +// fn simple4(name: &str, age: i8) -> &str { +// name +// } fn main() { let rocket = Rocket::new("localhost", 8000); - rocket.mount_and_launch("/", routes![simple, hello, bye]); + rocket.mount_and_launch("/", routes![simple, simple3]); } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 6071abf7..326979db 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,38 +1,39 @@ extern crate hyper; -mod method; -mod error; -mod response; -mod request; +pub mod method; +pub mod error; +pub mod response; +pub mod request; +pub mod param; use std::io::Write; pub use method::Method; pub use error::Error; -pub use response::{Body, Response}; +pub use response::{Response, HypResponse, HypFresh, Responder}; pub use request::Request; +pub use param::FromParam; use hyper::server::Handler as HypHandler; use hyper::server::Request as HypRequest; -use hyper::server::Response as HypResponse; -use hyper::net::Fresh as HypFresh; use hyper::Server; pub type Handler<'a> = fn(Request) -> Response<'a>; #[allow(dead_code)] #[derive(Clone)] -pub struct Route<'a, 'b> { +pub struct Route<'a> { pub method: Method, - pub path: &'a str, - pub handler: Handler<'b> + pub path: &'static str, + pub handler: Handler<'a> } #[allow(dead_code)] pub struct Rocket { address: &'static str, port: isize, - handler: Option> // just for testing + handler: Option>, // just for testing + paths: Vec<&'static str> // for now, to check for collisions // mounts: HashMap<&'static str, Route<'a>> } @@ -41,17 +42,9 @@ impl HypHandler for Rocket { mut res: HypResponse<'a, HypFresh>) { println!("Request: {:?}", req.uri); if self.handler.is_some() { - let response = (self.handler.as_ref().unwrap().handler)(Request::empty()); - *(res.headers_mut()) = response.headers; - *(res.status_mut()) = response.status; - match response.body { - Body::Str(string) => { - let mut stream = res.start().unwrap(); - stream.write_all(string.as_bytes()).unwrap(); - stream.end(); - } - _ => println!("UNIMPLEMENTED") - } + let handler = self.handler.as_ref(); + let mut response = (handler.unwrap().handler)(Request::empty()); + response.body.respond(res); } } } @@ -65,8 +58,8 @@ impl Rocket { } } - pub fn mount(&mut self, base: &str, routes: &[&Route<'static, 'static>]) -> &mut Self { - println!("🛰 Mounting '{}':", base); + pub fn mount(&mut self, base: &str, routes: &[&Route<'static>]) -> &mut Self { + println!("🛰 Mounting '{}':", base); for route in routes { if self.handler.is_none() { println!("\t* INSTALLED: {} '{}'", route.method, route.path); @@ -78,7 +71,7 @@ impl Rocket { self } - pub fn mount_and_launch(mut self, base: &str, routes: &[&Route<'static, 'static>]) { + pub fn mount_and_launch(mut self, base: &str, routes: &[&Route<'static>]) { self.mount(base, routes); self.launch(); } diff --git a/lib/src/param.rs b/lib/src/param.rs new file mode 100644 index 00000000..6428c0f6 --- /dev/null +++ b/lib/src/param.rs @@ -0,0 +1,28 @@ +use std::str::FromStr; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr}; + +use error::Error; + +pub trait FromParam<'a>: Sized { + fn from_param(param: &'a str) -> Result; +} + +impl<'a> FromParam<'a> for &'a str { + fn from_param(param: &'a str) -> Result<&'a str, Error> { + Ok(param) + } +} + +macro_rules! impl_with_fromstr { + ($($T:ident),+) => ( + $(impl<'a> FromParam<'a> for $T { + fn from_param(param: &'a str) -> Result<$T, Error> { + $T::from_str(param).map_err(|_| Error::BadParse) + } + } + )+) +} + +impl_with_fromstr!(f32, f64, isize, i8, i16, i32, i64, usize, u8, u16, u32, u64, + bool, String, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, + SocketAddr); diff --git a/lib/src/request.rs b/lib/src/request.rs index e88bbab1..97e122d2 100644 --- a/lib/src/request.rs +++ b/lib/src/request.rs @@ -1,5 +1,5 @@ -use std::str::FromStr; use error::Error; +use param::FromParam; pub struct Request; @@ -8,13 +8,12 @@ impl Request { Request } - pub fn get_param_str(&self, name: &str) -> Result<&str, Error> { + pub fn get_param_str<'a>(&self, name: &'a str) -> Result<&'a str, Error> { Err(Error::NoKey) } - pub fn get_param(&self, name: &str) -> Result { - self.get_param_str(name).and_then(|s| { - T::from_str(s).map_err(|_| Error::BadParse) - }) + pub fn get_param<'b, T: FromParam<'b>>(&self, name: &'b str) + -> Result { + self.get_param_str(name).and_then(T::from_param) } } diff --git a/lib/src/response.rs b/lib/src/response.rs index c74eddcd..009e6bd4 100644 --- a/lib/src/response.rs +++ b/lib/src/response.rs @@ -1,71 +1,146 @@ -pub use hyper::status::StatusCode; -pub use hyper::header::{self, Headers}; -use std::io::Read; +pub use hyper::server::Response as HypResponse; +pub use hyper::net::Fresh as HypFresh; -// Consider simply having Body be a trait, `RocketBody`, with one function that -// takes in a prepped up HypResponse and acts on it. Then, `Response` changes -// to be a Response { body: T } and the Rocket HypHandler -// simply sets up the status codes, headers, and calls body.fn(res). -pub enum Body<'a> { - Bytes(&'a [u8]), - Str(&'a str), - String(String), - Stream(Box), - Empty -} +use hyper::status::StatusCode; +use hyper::header::{self, Headers, Encoding}; +use std::io::{self, Read, Write}; +use std::fs::File; pub struct Response<'a> { - pub status: StatusCode, - pub headers: Headers, - pub body: Body<'a> + pub body: Box } +impl<'a> Response<'a> { + pub fn new(body: T) -> Response<'a> { + Response { + body: Box::new(body) + } + } +} + +struct Empty { + status: StatusCode +} + +impl Empty { + fn new(status: StatusCode) -> Empty { + Empty { + status: status + } + } +} + +pub trait Responder { + fn respond<'a>(&mut self, mut res: HypResponse<'a, HypFresh>); +} + +impl Responder for Empty { + fn respond<'a>(&mut self, mut res: HypResponse<'a, HypFresh>) { + res.send(b"").unwrap(); + } +} + +impl<'a> Responder for &'a str { + fn respond<'b>(&mut self, mut res: HypResponse<'b, HypFresh>) { + res.send(self.as_bytes()).unwrap(); + } +} + +impl Responder for String { + fn respond<'b>(&mut self, mut res: HypResponse<'b, HypFresh>) { + res.send(self.as_bytes()).unwrap(); + } +} + +impl Responder for File { + fn respond<'b>(&mut self, mut res: HypResponse<'b, HypFresh>) { + let size = self.metadata().unwrap().len(); + + res.headers_mut().set(header::ContentLength(size)); + *(res.status_mut()) = StatusCode::Ok; + + let mut s = String::new(); + self.read_to_string(&mut s).unwrap(); + + let mut stream = res.start().unwrap(); + stream.write_all(s.as_bytes()).unwrap(); + } +} + +// const CHUNK_SIZE: u32 = 4096; + +// pub struct Stream(T); + +// impl Responder for Stream { +// fn respond<'a>(&self, mut r: HypResponse<'a, HypFresh>) { +// r.headers_mut().set(header::TransferEncoding(vec![Encoding::Chunked])); +// *(r.status_mut()) = StatusCode::Ok; +// let mut stream = r.start(); + +// r.write() +// Response { +// status: StatusCode::Ok, +// headers: headers, +// body: Body::Stream(r) +// } +// } +// } + impl<'a> Response<'a> { pub fn empty() -> Response<'a> { Response { - status: StatusCode::Ok, - headers: Headers::new(), - body: Body::Empty + body: Box::new(Empty::new(StatusCode::Ok)) } } pub fn not_found() -> Response<'a> { Response { - status: StatusCode::NotFound, - headers: Headers::new(), - body: Body::Empty + body: Box::new(Empty::new(StatusCode::NotFound)) } } pub fn server_error() -> Response<'a> { Response { - status: StatusCode::InternalServerError, - headers: Headers::new(), - body: Body::Empty + body: Box::new(Empty::new(StatusCode::InternalServerError)) } } } -impl<'a> From<&'a str> for Response<'a> { - fn from(s: &'a str) -> Self { - let mut headers = Headers::new(); - headers.set(header::ContentLength(s.len() as u64)); - Response { - status: StatusCode::Ok, - headers: headers, - body: Body::Str(s) - } - } -} -impl<'a> From for Response<'a> { - fn from(s: String) -> Self { - let mut headers = Headers::new(); - headers.set(header::ContentLength(s.len() as u64)); - Response { - status: StatusCode::Ok, - headers: headers, - body: Body::String(s) - } - } -} +// macro_rules! impl_from_lengthed { +// ($name:ident, $T:ty) => ( +// impl<'a> From<$T> for Response<'a> { +// fn from(s: $T) -> Self { +// Response { +// status: StatusCode::Ok, +// headers: Headers::new(), +// body: Body::$name(s) +// } +// } +// } +// ) +// } + +// impl_from_lengthed!(Str, &'a str); +// impl_from_lengthed!(String, String); +// impl_from_lengthed!(Bytes, &'a [u8]); +// impl_from_lengthed!(File, File); + +// macro_rules! impl_from_reader { +// ($T:ty) => ( +// impl<'a> From<&'a $T> for Response<'a> { +// fn from(r: &'a $T) -> Self { +// let mut headers = Headers::new(); +// headers.set(header::TransferEncoding(vec![Encoding::Chunked])); +// Response { +// status: StatusCode::Ok, +// headers: headers, +// body: Body::Stream(r) +// } +// } +// } +// ) +// } + +// impl_from_reader!(File); +// impl_from_reader!(&'a [u8]); diff --git a/macros/src/route_decorator.rs b/macros/src/route_decorator.rs index 474296af..c29e4ffa 100644 --- a/macros/src/route_decorator.rs +++ b/macros/src/route_decorator.rs @@ -11,7 +11,7 @@ use syntax::ast::{Item, Expr, ItemKind, MetaItem, MetaItemKind, FnDecl}; use syntax::ext::base::{Annotatable, ExtCtxt}; use syntax::ptr::P; use syntax::ext::build::AstBuilder; -use syntax::print::pprust::item_to_string; +use syntax::print::pprust::{item_to_string, stmt_to_string}; use syntax::parse::token::{self, str_to_ident}; use rocket::Method; @@ -117,6 +117,9 @@ fn method_variant_to_expr(ecx: &ExtCtxt, method: Method) -> P { pub fn get_fn_params(ecx: &ExtCtxt, sp: Span, path: &str, fn_decl: &FnDecl) -> Vec { + + debug!("FUNCTION: {:?}", fn_decl); + let mut seen = HashSet::new(); let bad_match_err = "Path string is malformed."; let mut matching = false; @@ -188,12 +191,15 @@ pub fn route_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem, let mut fn_param_exprs = vec![]; for param in &fn_params { let param_ident = str_to_ident(param.as_str()); - fn_param_exprs.push(quote_stmt!(ecx, + let param_fn_item = quote_stmt!(ecx, let $param_ident = match _req.get_param($param) { Ok(v) => v, Err(_) => return rocket::Response::not_found() }; - ).unwrap()); + ).unwrap(); + + debug!("Param FN: {:?}", stmt_to_string(¶m_fn_item)); + fn_param_exprs.push(param_fn_item); } let mut fn_param_idents: Vec = vec![]; @@ -212,9 +218,10 @@ pub fn route_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem, fn $route_fn_name<'a>(_req: rocket::Request) -> rocket::Response<'a> { $fn_param_exprs let result = $fn_name($fn_param_idents); - rocket::Response::from(result) + rocket::Response::new(result) } ).unwrap(); + debug!("{}", item_to_string(&route_fn_item)); push(Annotatable::Item(route_fn_item)); @@ -223,7 +230,7 @@ pub fn route_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem, let method = method_variant_to_expr(ecx, route_params.method); push(Annotatable::Item(quote_item!(ecx, #[allow(non_upper_case_globals)] - pub static $struct_name: rocket::Route<'static, 'static> = rocket::Route { + pub static $struct_name: rocket::Route<'static> = rocket::Route { method: $method, path: $path, handler: $route_fn_name