Cleaned up response -> split into files. Started form example.

There's something going on with Hyper. When a 303 (see other) response is sent
in response to a POST, the browser does a GET to the location header. Hyper
somehow misreads the method parameter here, resulting in a route failer.

I need to MITM the connection to see exactly what the browser is sending and
what Hyper is receiving to see who's wrong.
This commit is contained in:
Sergio Benitez 2016-03-28 02:34:09 -07:00
parent cddc92f870
commit fb8fdc3bc2
18 changed files with 326 additions and 144 deletions

View File

@ -0,0 +1,8 @@
[package]
name = "forms"
version = "0.0.1"
authors = ["Sergio Benitez <sb@sergio.bz>"]
[dependencies]
rocket = { path = "../../lib" }
rocket_macros = { path = "../../macros" }

View File

@ -0,0 +1,39 @@
#![feature(plugin)]
#![plugin(rocket_macros)]
extern crate rocket;
use rocket::Rocket;
use std::fs::File;
use std::io::Error as IOError;
use rocket::response::Redirect;
#[route(GET, path = "/")]
fn index() -> File {
File::open("static/index.html").unwrap()
}
#[route(GET, path = "/<file>")]
fn files(file: &str) -> Result<File, IOError> {
File::open(format!("static/{}", file))
}
#[route(GET, path = "/user/<username>")]
fn user_page(username: &str) -> String {
format!("This is {}'s page.", username)
}
// TODO: Actually look at form parameters.
#[route(POST, path = "/login")]
fn login() -> Result<Redirect, &'static str> {
if true {
Ok(Redirect::other("/user/some_name"))
} else {
Err("Sorry, the username and password are invalid.")
}
}
fn main() {
let rocket = Rocket::new("localhost", 8000);
rocket.mount_and_launch("/", routes![index, files, user_page, login]);
}

View File

@ -0,0 +1,7 @@
<h1>Login</h1>
<form action="/login" method="post" accept-charset="utf-8">
Username:<input type="text" name="username">
Password:<input type="password" name="password">
<input type="submit" value="Login">
</form>

View File

@ -0,0 +1,8 @@
[package]
name = "optional_result"
version = "0.0.1"
authors = ["Sergio Benitez <sb@sergio.bz>"]
[dependencies]
rocket = { path = "../../lib" }
rocket_macros = { path = "../../macros" }

View File

@ -0,0 +1,23 @@
#![feature(plugin)]
#![plugin(rocket_macros)]
extern crate rocket;
use rocket::Rocket;
use rocket::response::Redirect;
#[route(GET, path = "/users/<name>")]
fn user(name: &str) -> Result<&'static str, Redirect> {
match name {
"Sergio" => Ok("Hello, Sergio!"),
_ => Err(Redirect::to("/users/login"))
}
}
#[route(GET, path = "/users/login")]
fn login() -> &'static str {
"Hi! That user doesn't exist. Maybe you need to log in?"
}
fn main() {
Rocket::new("localhost", 8000).mount_and_launch("/", routes![user, login]);
}

View File

@ -0,0 +1,8 @@
[package]
name = "optional_result"
version = "0.0.1"
authors = ["Sergio Benitez <sb@sergio.bz>"]
[dependencies]
rocket = { path = "../../lib" }
rocket_macros = { path = "../../macros" }

View File

@ -0,0 +1,18 @@
#![feature(plugin)]
#![plugin(rocket_macros)]
extern crate rocket;
use rocket::Rocket;
#[route(GET, path = "/users/<name>")]
fn user(name: &str) -> Option<&'static str> {
if name == "Sergio" {
Some("Hello, Sergio!")
} else {
None
}
}
fn main() {
Rocket::new("localhost", 8000).mount_and_launch("/", routes![user]);
}

View File

@ -1,4 +1,5 @@
#![feature(str_char)] #![feature(str_char)]
#![feature(specialization)]
extern crate term_painter; extern crate term_painter;
extern crate hyper; extern crate hyper;
@ -12,10 +13,10 @@ pub mod router;
pub use method::Method; pub use method::Method;
pub use error::Error; pub use error::Error;
pub use response::{Response, HypResponse, HypFresh, Responder};
pub use request::Request; pub use request::Request;
pub use param::FromParam; pub use param::FromParam;
pub use router::Router; pub use router::Router;
pub use response::{Response, HypResponse, Responder, HypFresh};
use std::fmt; use std::fmt;
use term_painter::ToStyle; use term_painter::ToStyle;
@ -27,7 +28,8 @@ use hyper::Server;
pub type Handler<'a> = fn(Request) -> Response<'a>; pub type Handler<'a> = fn(Request) -> Response<'a>;
// TODO: Figure out if having Handler<'static> there is a good idea. // TODO: Figure out if using 'static for Handler is a good idea.
// TODO: Merge this `Route` and route::Route, somewhow.
pub struct Route { pub struct Route {
pub method: Method, pub method: Method,
pub path: &'static str, pub path: &'static str,
@ -35,7 +37,8 @@ pub struct Route {
} }
impl Route { impl Route {
pub fn new(method: Method, path: &'static str, handler: Handler<'static>) -> Route { pub fn new(method: Method, path: &'static str, handler: Handler<'static>)
-> Route {
Route { Route {
method: method, method: method,
path: path, path: path,
@ -50,7 +53,6 @@ impl<'a> fmt::Display for Route {
} }
} }
#[allow(dead_code)]
pub struct Rocket { pub struct Rocket {
address: &'static str, address: &'static str,
port: isize, port: isize,
@ -60,9 +62,10 @@ pub struct Rocket {
impl HypHandler for Rocket { impl HypHandler for Rocket {
fn handle<'a, 'k>(&'a self, req: HypRequest<'a, 'k>, fn handle<'a, 'k>(&'a self, req: HypRequest<'a, 'k>,
res: HypResponse<'a, HypFresh>) { res: HypResponse<'a, HypFresh>) {
println!("{} {:?} {:?}", White.paint("Incoming:"),
Green.paint(&req.method), Blue.paint(&req.uri));
if let RequestUri::AbsolutePath(uri_string) = req.uri { if let RequestUri::AbsolutePath(uri_string) = req.uri {
if let Some(method) = Method::from_hyp(req.method) { if let Some(method) = Method::from_hyp(req.method) {
println!("Request: {:?}", uri_string);
let uri_str = uri_string.as_str(); let uri_str = uri_string.as_str();
let route = self.router.route(method, uri_str); let route = self.router.route(method, uri_str);
let mut response = route.map_or(Response::not_found(), |route| { let mut response = route.map_or(Response::not_found(), |route| {
@ -71,11 +74,15 @@ impl HypHandler for Rocket {
(route.handler)(request) (route.handler)(request)
}); });
return response.body.respond(res); println!("{}", Green.paint("\t=> Dispatched request."));
} return response.respond(res);
} }
Response::not_found().body.respond(res); println!("{}", Yellow.paint("\t=> Debug: Method::from_hyp failed!"));
}
println!("{}", Red.paint("\t=> Dispatch failed. Returning 404."));
Response::not_found().respond(res);
} }
} }

View File

@ -21,6 +21,14 @@ impl Method {
pub fn from_hyp(method: HypMethod) -> Option<Method> { pub fn from_hyp(method: HypMethod) -> Option<Method> {
match method { match method {
HypMethod::Get => Some(Get), HypMethod::Get => Some(Get),
HypMethod::Put => Some(Put),
HypMethod::Post => Some(Post),
HypMethod::Delete => Some(Delete),
HypMethod::Options => Some(Options),
HypMethod::Head => Some(Head),
HypMethod::Trace => Some(Trace),
HypMethod::Connect => Some(Connect),
HypMethod::Patch => Some(Patch),
_ => None _ => None
} }
} }

View File

@ -1,133 +0,0 @@
pub use hyper::server::Response as HypResponse;
pub use hyper::net::Fresh as HypFresh;
use hyper::status::StatusCode;
use hyper::header;
use std::io::{Read, Write};
use std::fs::File;
use std::fmt;
pub struct Response<'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)
}
}
pub fn empty() -> Response<'a> {
Response {
body: Box::new(Empty::new(StatusCode::Ok))
}
}
pub fn not_found() -> Response<'a> {
Response {
body: Box::new(Empty::new(StatusCode::NotFound))
}
}
pub fn server_error() -> Response<'a> {
Response {
body: Box::new(Empty::new(StatusCode::InternalServerError))
}
}
}
pub trait Responder {
fn respond<'a>(&mut self, mut res: HypResponse<'a, HypFresh>);
}
pub struct Empty {
status: StatusCode
}
impl Empty {
fn new(status: StatusCode) -> Empty {
Empty {
status: status
}
}
}
impl Responder for Empty {
fn respond<'a>(&mut self, mut res: HypResponse<'a, HypFresh>) {
res.headers_mut().set(header::ContentLength(0));
*(res.status_mut()) = self.status;
let mut stream = res.start().unwrap();
stream.write_all(b"").unwrap();
}
}
impl<'a> Responder for &'a str {
fn respond<'b>(&mut self, res: HypResponse<'b, HypFresh>) {
res.send(self.as_bytes()).unwrap();
}
}
impl Responder for String {
fn respond<'b>(&mut self, 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 v = Vec::new();
self.read_to_end(&mut v).unwrap();
let mut stream = res.start().unwrap();
stream.write_all(&v).unwrap();
}
}
// Waiting for RFC #1210: impl specialization. It's not quite stable yet.
// impl<T: Responder, E: fmt::Display + fmt::Debug> Responder for Result<T, E> {
// fn respond<'b>(&mut self, res: HypResponse<'b, HypFresh>) {
// if self.is_err() {
// println!("Response error: {}", self.as_ref().err().unwrap());
// return;
// }
// self.as_mut().unwrap().respond(res);
// }
// }
impl<T: Responder, E: fmt::Debug> Responder for Result<T, E> {
// prepend with `default` when using impl specialization
fn respond<'b>(&mut self, res: HypResponse<'b, HypFresh>) {
if self.is_err() {
println!("Error: {:?}", self.as_ref().err().unwrap());
return;
}
self.as_mut().unwrap().respond(res);
}
}
// TODO: Allow streamed responses.
// 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)
// }
// }
// }

20
lib/src/response/empty.rs Normal file
View File

@ -0,0 +1,20 @@
use response::*;
use std::io::Write;
pub struct Empty(StatusCode);
impl Empty {
pub fn new(status: StatusCode) -> Empty {
Empty(status)
}
}
impl Responder for Empty {
fn respond<'a>(&mut self, mut res: HypResponse<'a, HypFresh>) {
res.headers_mut().set(header::ContentLength(0));
*(res.status_mut()) = self.0;
let mut stream = res.start().unwrap();
stream.write_all(b"").unwrap();
}
}

48
lib/src/response/mod.rs Normal file
View File

@ -0,0 +1,48 @@
mod empty;
mod responder;
mod redirect;
pub use self::responder::Responder;
pub use self::empty::Empty;
pub use self::redirect::Redirect;
pub use hyper::server::Response as HypResponse;
pub use hyper::net::Fresh as HypFresh;
pub use hyper::status::StatusCode;
pub use hyper::header;
use std::ops::{Deref, DerefMut};
pub struct Response<'a>(Box<Responder + 'a>);
impl<'a> Response<'a> {
pub fn new<T: Responder + 'a>(body: T) -> Response<'a> {
Response(Box::new(body))
}
pub fn empty() -> Response<'a> {
Response(Box::new(Empty::new(StatusCode::Ok)))
}
pub fn not_found() -> Response<'a> {
Response(Box::new(Empty::new(StatusCode::NotFound)))
}
pub fn server_error() -> Response<'a> {
Response(Box::new(Empty::new(StatusCode::InternalServerError)))
}
}
impl<'a> Deref for Response<'a> {
type Target = Box<Responder + 'a>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> DerefMut for Response<'a> {
fn deref_mut(&mut self) -> &mut Box<Responder + 'a> {
&mut self.0
}
}

View File

@ -0,0 +1,32 @@
use response::*;
#[derive(Debug)]
pub struct Redirect(StatusCode, String);
impl Redirect {
pub fn to(uri: &str) -> Redirect {
Redirect(StatusCode::TemporaryRedirect, String::from(uri))
}
pub fn created(uri: &str) -> Redirect {
Redirect(StatusCode::Created, String::from(uri))
}
pub fn other(uri: &str) -> Redirect {
Redirect(StatusCode::SeeOther, String::from(uri))
}
pub fn permanent(uri: &str) -> Redirect {
Redirect(StatusCode::PermanentRedirect, String::from(uri))
}
}
impl<'a> Responder for Redirect {
fn respond<'b>(&mut self, mut res: HypResponse<'b, HypFresh>) {
res.headers_mut().set(header::ContentLength(0));
res.headers_mut().set(header::Location(self.1.clone()));
*(res.status_mut()) = self.0;
res.send(b"").unwrap();
}
}

View File

@ -0,0 +1,70 @@
use response::*;
use std::io::{Read, Write};
use std::fs::File;
use std::fmt;
pub trait Responder {
fn respond<'a>(&mut self, mut res: HypResponse<'a, HypFresh>);
}
impl<'a> Responder for &'a str {
fn respond<'b>(&mut self, res: HypResponse<'b, HypFresh>) {
res.send(self.as_bytes()).unwrap();
}
}
impl Responder for String {
fn respond<'b>(&mut self, res: HypResponse<'b, HypFresh>) {
res.send(self.as_bytes()).unwrap();
}
}
// FIXME: Should we set a content-type here? Safari needs text/html to render.
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 v = Vec::new();
self.read_to_end(&mut v).unwrap();
let mut stream = res.start().unwrap();
stream.write_all(&v).unwrap();
}
}
impl<T: Responder> Responder for Option<T> {
fn respond<'b>(&mut self, res: HypResponse<'b, HypFresh>) {
if self.is_none() {
println!("Option is none.");
// TODO: Should this be a 404 or 500?
return Empty::new(StatusCode::NotFound).respond(res)
}
self.as_mut().unwrap().respond(res);
}
}
impl<T: Responder, E: fmt::Debug> Responder for Result<T, E> {
// prepend with `default` when using impl specialization
default fn respond<'b>(&mut self, res: HypResponse<'b, HypFresh>) {
if self.is_err() {
println!("Error: {:?}", self.as_ref().err().unwrap());
// TODO: Should this be a 404 or 500?
return Empty::new(StatusCode::NotFound).respond(res)
}
self.as_mut().unwrap().respond(res);
}
}
impl<T: Responder, E: Responder + fmt::Debug> Responder for Result<T, E> {
fn respond<'b>(&mut self, res: HypResponse<'b, HypFresh>) {
match self {
&mut Ok(ref mut responder) => responder.respond(res),
&mut Err(ref mut responder) => responder.respond(res)
}
}
}

View File

@ -0,0 +1,17 @@
// TODO: Allow streamed responses.
// 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)
// }
// }
// }

View File

@ -42,7 +42,7 @@ impl Router {
let num_components = path.components().count(); let num_components = path.components().count();
if let Some(routes) = self.routes.get(&(method, num_components)) { if let Some(routes) = self.routes.get(&(method, num_components)) {
for route in routes.iter().filter(|r| r.collides_with(uri)) { for route in routes.iter().filter(|r| r.collides_with(uri)) {
println!("Matched {} to: {}", uri, route); println!("\t=> Matched {} to: {}", uri, route);
if let None = matched_route { if let None = matched_route {
matched_route = Some(route); matched_route = Some(route);
} }

View File

@ -7,6 +7,7 @@ use super::Collider; // :D
use std::path::Component; use std::path::Component;
use Handler; use Handler;
// TODO: Add ranking to routes. Give static routes higher rank by default.
// FIXME: Take in the handler! Or maybe keep that in `Router`? // FIXME: Take in the handler! Or maybe keep that in `Router`?
pub struct Route { pub struct Route {
method: Method, method: Method,
@ -50,7 +51,7 @@ impl Route {
// FIXME: This is dirty (the comp_to_str and the RootDir thing). Might need // FIXME: This is dirty (the comp_to_str and the RootDir thing). Might need
// to have my own wrapper arround path strings. // to have my own wrapper arround path strings.
// FIXME: Decide whether a component has to be fully variable or not. That // FIXME: Decide whether a component has to be fully variable or not. That
// is, whether you can have: /a<a>b/ // is, whether you can have: /a<a>b/ or even /<a>:<b>/
// TODO: Don't return a Vec...take in an &mut [&'a str] (no alloc!) // TODO: Don't return a Vec...take in an &mut [&'a str] (no alloc!)
pub fn get_params<'a>(&self, uri: &'a str) -> Vec<&'a str> { pub fn get_params<'a>(&self, uri: &'a str) -> Vec<&'a str> {
let mut result = Vec::with_capacity(self.component_count()); let mut result = Vec::with_capacity(self.component_count());

View File

@ -58,7 +58,7 @@ fn get_route_params(ecx: &mut ExtCtxt, meta_item: &MetaItem) -> Params {
let params: &Vec<P<MetaItem>> = match meta_item.node { let params: &Vec<P<MetaItem>> = match meta_item.node {
MetaItemKind::List(_, ref params) => params, MetaItemKind::List(_, ref params) => params,
_ => ecx.span_fatal(meta_item.span, _ => ecx.span_fatal(meta_item.span,
"incorrect use of macro. correct form is: #[demo(...)]"), "Incorrect use of macro. correct form is: #[route(...)]"),
}; };
// Ensure we can unwrap the k = v params. // Ensure we can unwrap the k = v params.
@ -117,7 +117,6 @@ 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); debug!("FUNCTION: {:?}", fn_decl);
let mut seen = HashSet::new(); let mut seen = HashSet::new();
@ -125,6 +124,7 @@ pub fn get_fn_params(ecx: &ExtCtxt, sp: Span, path: &str,
let mut matching = false; let mut matching = false;
// Collect all of the params in the path and insert into HashSet. // Collect all of the params in the path and insert into HashSet.
// TODO: Move this logic into main library.
let mut start = 0; let mut start = 0;
for (i, c) in path.char_indices() { for (i, c) in path.char_indices() {
match c { match c {
@ -147,6 +147,7 @@ pub fn get_fn_params(ecx: &ExtCtxt, sp: Span, path: &str,
} }
} }
// TODO: This should stay here, though.
// Ensure every param in the function declaration is in `path`. Also add // Ensure every param in the function declaration is in `path`. Also add
// each param name in the declaration to the result vector. // each param name in the declaration to the result vector.
let mut result = vec![]; let mut result = vec![];