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;
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 = "/<name>")]
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 = "/<x>/<y>")]
fn bye(x: usize, y: usize) -> String {
format!("{} + {} = {}", x, y, x + y)
}
// #[route(GET, path = "/<name>/<age>")]
// 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]);
}

View File

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

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 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<T: FromStr>(&self, name: &str) -> Result<T, Error> {
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<T, Error> {
self.get_param_str(name).and_then(T::from_param)
}
}

View File

@ -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<T: RocketBody> { 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<Read>),
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<Responder + '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> {
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<String> 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]);

View File

@ -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<Expr> {
pub fn get_fn_params(ecx: &ExtCtxt, sp: Span, path: &str,
fn_decl: &FnDecl) -> Vec<String> {
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(&param_fn_item));
fn_param_exprs.push(param_fn_item);
}
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_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