Somewhat good infrastructure for params and responses.

Can actually return strings and what-not from a route. Yay!
This commit is contained in:
Sergio Benitez 2016-03-17 01:57:04 -07:00
parent c6e8cd47da
commit da2b0ed35a
6 changed files with 209 additions and 94 deletions

View File

@ -3,23 +3,36 @@
extern crate rocket; extern crate rocket;
use rocket::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 = "/")] #[route(GET, path = "/")]
fn simple() -> &'static str { fn simple() -> File {
"Hello, simple example world! How is thou?" File::open("/tmp/index.html").unwrap()
} }
#[route(GET, path = "/<name>")] // #[route(GET, path = "/")]
fn hello(name: String) -> String { // fn simple2() -> &'static str {
format!("Hello, {}!", name) // "Hello, world!"
// }
#[route(GET, path = "/hello")]
fn simple3() -> String {
String::from("Hello, world!")
} }
#[route(PUT, path = "/<x>/<y>")] // #[route(GET, path = "/<name>/<age>")]
fn bye(x: usize, y: usize) -> String { // fn simple4(name: &str, age: i8) -> &str {
format!("{} + {} = {}", x, y, x + y) // name
} // }
fn main() { fn main() {
let rocket = Rocket::new("localhost", 8000); let rocket = Rocket::new("localhost", 8000);
rocket.mount_and_launch("/", routes![simple, hello, bye]); rocket.mount_and_launch("/", routes![simple, simple3]);
} }

View File

@ -1,38 +1,39 @@
extern crate hyper; extern crate hyper;
mod method; pub mod method;
mod error; pub mod error;
mod response; pub mod response;
mod request; pub mod request;
pub mod param;
use std::io::Write; use std::io::Write;
pub use method::Method; pub use method::Method;
pub use error::Error; pub use error::Error;
pub use response::{Body, Response}; pub use response::{Response, HypResponse, HypFresh, Responder};
pub use request::Request; pub use request::Request;
pub use param::FromParam;
use hyper::server::Handler as HypHandler; use hyper::server::Handler as HypHandler;
use hyper::server::Request as HypRequest; use hyper::server::Request as HypRequest;
use hyper::server::Response as HypResponse;
use hyper::net::Fresh as HypFresh;
use hyper::Server; use hyper::Server;
pub type Handler<'a> = fn(Request) -> Response<'a>; pub type Handler<'a> = fn(Request) -> Response<'a>;
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Clone)] #[derive(Clone)]
pub struct Route<'a, 'b> { pub struct Route<'a> {
pub method: Method, pub method: Method,
pub path: &'a str, pub path: &'static str,
pub handler: Handler<'b> pub handler: Handler<'a>
} }
#[allow(dead_code)] #[allow(dead_code)]
pub struct Rocket { pub struct Rocket {
address: &'static str, address: &'static str,
port: isize, port: isize,
handler: Option<Route<'static, 'static>> // just for testing handler: Option<Route<'static>>, // just for testing
paths: Vec<&'static str> // for now, to check for collisions
// mounts: HashMap<&'static str, Route<'a>> // mounts: HashMap<&'static str, Route<'a>>
} }
@ -41,17 +42,9 @@ impl HypHandler for Rocket {
mut res: HypResponse<'a, HypFresh>) { mut res: HypResponse<'a, HypFresh>) {
println!("Request: {:?}", req.uri); println!("Request: {:?}", req.uri);
if self.handler.is_some() { if self.handler.is_some() {
let response = (self.handler.as_ref().unwrap().handler)(Request::empty()); let handler = self.handler.as_ref();
*(res.headers_mut()) = response.headers; let mut response = (handler.unwrap().handler)(Request::empty());
*(res.status_mut()) = response.status; response.body.respond(res);
match response.body {
Body::Str(string) => {
let mut stream = res.start().unwrap();
stream.write_all(string.as_bytes()).unwrap();
stream.end();
}
_ => println!("UNIMPLEMENTED")
}
} }
} }
} }
@ -65,7 +58,7 @@ impl Rocket {
} }
} }
pub fn mount(&mut self, base: &str, routes: &[&Route<'static, 'static>]) -> &mut Self { pub fn mount(&mut self, base: &str, routes: &[&Route<'static>]) -> &mut Self {
println!("🛰 Mounting '{}':", base); println!("🛰 Mounting '{}':", base);
for route in routes { for route in routes {
if self.handler.is_none() { if self.handler.is_none() {
@ -78,7 +71,7 @@ impl Rocket {
self 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.mount(base, routes);
self.launch(); self.launch();
} }

28
lib/src/param.rs Normal file
View File

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

View File

@ -1,5 +1,5 @@
use std::str::FromStr;
use error::Error; use error::Error;
use param::FromParam;
pub struct Request; pub struct Request;
@ -8,13 +8,12 @@ impl Request {
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) Err(Error::NoKey)
} }
pub fn get_param<T: FromStr>(&self, name: &str) -> Result<T, Error> { pub fn get_param<'b, T: FromParam<'b>>(&self, name: &'b str)
self.get_param_str(name).and_then(|s| { -> Result<T, Error> {
T::from_str(s).map_err(|_| Error::BadParse) self.get_param_str(name).and_then(T::from_param)
})
} }
} }

View File

@ -1,71 +1,146 @@
pub use hyper::status::StatusCode; pub use hyper::server::Response as HypResponse;
pub use hyper::header::{self, Headers}; pub use hyper::net::Fresh as HypFresh;
use std::io::Read;
// Consider simply having Body be a trait, `RocketBody`, with one function that use hyper::status::StatusCode;
// takes in a prepped up HypResponse and acts on it. Then, `Response` changes use hyper::header::{self, Headers, Encoding};
// to be a Response<T: RocketBody> { body: T } and the Rocket HypHandler use std::io::{self, Read, Write};
// simply sets up the status codes, headers, and calls body.fn(res). use std::fs::File;
pub enum Body<'a> {
Bytes(&'a [u8]),
Str(&'a str),
String(String),
Stream(Box<Read>),
Empty
}
pub struct Response<'a> { pub struct Response<'a> {
pub status: StatusCode, pub body: Box<Responder + 'a>
pub headers: Headers,
pub body: Body<'a>
} }
impl<'a> Response<'a> {
pub fn new<T: Responder + 'a>(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: Read>(T);
// impl<T> Responder for Stream<T> {
// 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> { impl<'a> Response<'a> {
pub fn empty() -> Response<'a> { pub fn empty() -> Response<'a> {
Response { Response {
status: StatusCode::Ok, body: Box::new(Empty::new(StatusCode::Ok))
headers: Headers::new(),
body: Body::Empty
} }
} }
pub fn not_found() -> Response<'a> { pub fn not_found() -> Response<'a> {
Response { Response {
status: StatusCode::NotFound, body: Box::new(Empty::new(StatusCode::NotFound))
headers: Headers::new(),
body: Body::Empty
} }
} }
pub fn server_error() -> Response<'a> { pub fn server_error() -> Response<'a> {
Response { Response {
status: StatusCode::InternalServerError, body: Box::new(Empty::new(StatusCode::InternalServerError))
headers: Headers::new(),
body: Body::Empty
} }
} }
} }
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<String> for Response<'a> { // macro_rules! impl_from_lengthed {
fn from(s: String) -> Self { // ($name:ident, $T:ty) => (
let mut headers = Headers::new(); // impl<'a> From<$T> for Response<'a> {
headers.set(header::ContentLength(s.len() as u64)); // fn from(s: $T) -> Self {
Response { // Response {
status: StatusCode::Ok, // status: StatusCode::Ok,
headers: headers, // headers: Headers::new(),
body: Body::String(s) // 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]);

View File

@ -11,7 +11,7 @@ use syntax::ast::{Item, Expr, ItemKind, MetaItem, MetaItemKind, FnDecl};
use syntax::ext::base::{Annotatable, ExtCtxt}; use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::ptr::P; use syntax::ptr::P;
use syntax::ext::build::AstBuilder; 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 syntax::parse::token::{self, str_to_ident};
use rocket::Method; use rocket::Method;
@ -117,6 +117,9 @@ fn method_variant_to_expr(ecx: &ExtCtxt, method: Method) -> P<Expr> {
pub fn get_fn_params(ecx: &ExtCtxt, sp: Span, path: &str, pub fn get_fn_params(ecx: &ExtCtxt, sp: Span, path: &str,
fn_decl: &FnDecl) -> Vec<String> { fn_decl: &FnDecl) -> Vec<String> {
debug!("FUNCTION: {:?}", fn_decl);
let mut seen = HashSet::new(); let mut seen = HashSet::new();
let bad_match_err = "Path string is malformed."; let bad_match_err = "Path string is malformed.";
let mut matching = false; 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![]; let mut fn_param_exprs = vec![];
for param in &fn_params { for param in &fn_params {
let param_ident = str_to_ident(param.as_str()); 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) { let $param_ident = match _req.get_param($param) {
Ok(v) => v, Ok(v) => v,
Err(_) => return rocket::Response::not_found() Err(_) => return rocket::Response::not_found()
}; };
).unwrap()); ).unwrap();
debug!("Param FN: {:?}", stmt_to_string(&param_fn_item));
fn_param_exprs.push(param_fn_item);
} }
let mut fn_param_idents: Vec<TokenTree> = vec![]; let mut fn_param_idents: Vec<TokenTree> = 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 $route_fn_name<'a>(_req: rocket::Request) -> rocket::Response<'a> {
$fn_param_exprs $fn_param_exprs
let result = $fn_name($fn_param_idents); let result = $fn_name($fn_param_idents);
rocket::Response::from(result) rocket::Response::new(result)
} }
).unwrap(); ).unwrap();
debug!("{}", item_to_string(&route_fn_item)); debug!("{}", item_to_string(&route_fn_item));
push(Annotatable::Item(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); let method = method_variant_to_expr(ecx, route_params.method);
push(Annotatable::Item(quote_item!(ecx, push(Annotatable::Item(quote_item!(ecx,
#[allow(non_upper_case_globals)] #[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, method: $method,
path: $path, path: $path,
handler: $route_fn_name handler: $route_fn_name