mirror of https://github.com/rwf2/Rocket.git
Add support for flash cookie. Revamp cookie support.
This commit is contained in:
parent
9a9d07f044
commit
e8e85f09cd
|
@ -7,8 +7,8 @@ extern crate rocket;
|
|||
extern crate tera;
|
||||
|
||||
use rocket::Rocket;
|
||||
use rocket::response::{Cookied, Redirect};
|
||||
use rocket::request::Cookies;
|
||||
use rocket::response::Redirect;
|
||||
use rocket::request::{Cookie, Cookies};
|
||||
|
||||
lazy_static!(static ref TERA: tera::Tera = tera::Tera::new("templates/**/*"););
|
||||
|
||||
|
@ -25,12 +25,13 @@ struct Message {
|
|||
}
|
||||
|
||||
#[post("/submit", form = "<message>")]
|
||||
fn submit(message: Message) -> Cookied<Redirect> {
|
||||
Cookied::new(Redirect::to("/")).add("message", &message.message)
|
||||
fn submit(cookies: &Cookies, message: Message) -> Redirect {
|
||||
cookies.add(Cookie::new("message".into(), message.message));
|
||||
Redirect::to("/")
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
fn index(cookies: Cookies) -> tera::TeraResult<String> {
|
||||
fn index(cookies: &Cookies) -> tera::TeraResult<String> {
|
||||
let message = cookies.find("message").map(|msg| msg.value);
|
||||
TERA.render("index.html", ctxt(message))
|
||||
}
|
||||
|
|
|
@ -11,28 +11,30 @@ mod static_files;
|
|||
mod task;
|
||||
|
||||
use rocket::Rocket;
|
||||
use rocket::response::Redirect;
|
||||
use rocket::response::{Flash, Redirect};
|
||||
use task::Task;
|
||||
|
||||
lazy_static!(static ref TERA: tera::Tera = tera::Tera::new("templates/**/*"););
|
||||
lazy_static!(static ref TERA: tera::Tera = tera::Tera::new("static/*.html"););
|
||||
|
||||
fn ctxt(error: Option<&str>) -> tera::Context {
|
||||
fn ctxt(msg: Option<(&str, &str)>) -> tera::Context {
|
||||
let unwrapped_msg = msg.unwrap_or(("", ""));
|
||||
let mut context = tera::Context::new();
|
||||
context.add("error", &error.is_some());
|
||||
context.add("msg", &error.unwrap_or("").to_string());
|
||||
context.add("has_msg", &msg.is_some());
|
||||
context.add("msg_type", &unwrapped_msg.0.to_string());
|
||||
context.add("msg", &unwrapped_msg.1.to_string());
|
||||
context.add("tasks", &Task::all());
|
||||
context
|
||||
}
|
||||
|
||||
#[post("/", form = "<todo>")]
|
||||
fn new(todo: Task) -> Result<Redirect, tera::TeraResult<String>> {
|
||||
fn new(todo: Task) -> Result<Flash<Redirect>, tera::TeraResult<String>> {
|
||||
if todo.description.is_empty() {
|
||||
let context = ctxt(Some("Description cannot be empty."));
|
||||
let context = ctxt(Some(("error", "Description cannot be empty.")));
|
||||
Err(TERA.render("index.html", context))
|
||||
} else if todo.insert() {
|
||||
Ok(Redirect::to("/")) // Say that it was added...somehow.
|
||||
Ok(Flash::success(Redirect::to("/"), "Todo successfully added."))
|
||||
} else {
|
||||
let context = ctxt(Some("Whoops! The server failed."));
|
||||
let context = ctxt(Some(("error", "Whoops! The server failed.")));
|
||||
Err(TERA.render("index.html", context))
|
||||
}
|
||||
}
|
||||
|
@ -41,32 +43,32 @@ fn new(todo: Task) -> Result<Redirect, tera::TeraResult<String>> {
|
|||
#[get("/<id>/toggle")]
|
||||
fn toggle(id: i32) -> Result<Redirect, tera::TeraResult<String>> {
|
||||
if Task::toggle_with_id(id) {
|
||||
Ok(Redirect::to("/")) // Say that it was added...somehow.
|
||||
Ok(Redirect::to("/"))
|
||||
} else {
|
||||
let context = ctxt(Some("Could not toggle that task."));
|
||||
let context = ctxt(Some(("error", "Could not toggle that task.")));
|
||||
Err(TERA.render("index.html", context))
|
||||
}
|
||||
}
|
||||
|
||||
// Should likely do something to simulate DELETE.
|
||||
#[get("/<id>/delete")]
|
||||
fn delete(id: i32) -> Result<Redirect, tera::TeraResult<String>> {
|
||||
fn delete(id: i32) -> Result<Flash<Redirect>, tera::TeraResult<String>> {
|
||||
if Task::delete_with_id(id) {
|
||||
Ok(Redirect::to("/")) // Say that it was added...somehow.
|
||||
Ok(Flash::success(Redirect::to("/"), "Todo was deleted."))
|
||||
} else {
|
||||
let context = ctxt(Some("Could not delete that task."));
|
||||
let context = ctxt(Some(("error", "Could not delete that task.")));
|
||||
Err(TERA.render("index.html", context))
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
fn index() -> tera::TeraResult<String> {
|
||||
TERA.render("index.html", ctxt(None))
|
||||
fn index(msg: Option<Flash<()>>) -> tera::TeraResult<String> {
|
||||
TERA.render("index.html", ctxt(msg.as_ref().map(|m| (m.name(), m.msg()))))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut rocket = Rocket::new("127.0.0.1", 8000);
|
||||
rocket.mount("/", routes![index, static_files::all, static_files::all_level_one]);
|
||||
rocket.mount("/todo/", routes![new, delete, toggle]);
|
||||
rocket.mount("/", routes![index, static_files::all])
|
||||
.mount("/todo/", routes![new, delete, toggle]);
|
||||
rocket.launch();
|
||||
}
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[get("/<top>/<file>")]
|
||||
fn all_level_one(top: &str, file: &str) -> io::Result<File> {
|
||||
let file = format!("static/{}/{}", top, file);
|
||||
File::open(file)
|
||||
}
|
||||
|
||||
#[get("/<file>")]
|
||||
fn all(file: &str) -> io::Result<File> {
|
||||
let file = format!("static/{}", file);
|
||||
File::open(file)
|
||||
#[get("/<path..>", rank = 5)]
|
||||
fn all(path: PathBuf) -> io::Result<File> {
|
||||
File::open(Path::new("static/").join(path))
|
||||
}
|
||||
|
|
|
@ -35,12 +35,9 @@ impl Task {
|
|||
return false;
|
||||
}
|
||||
|
||||
Task::update_with_id(id, !task.unwrap().completed.unwrap())
|
||||
}
|
||||
|
||||
pub fn update_with_id(id: i32, completed: bool) -> bool {
|
||||
let task = diesel::update(all_tasks.find(id));
|
||||
task.set(task_completed.eq(completed)).execute(&db()).is_ok()
|
||||
let new_status = !task.unwrap().completed.unwrap();
|
||||
let updated_task = diesel::update(all_tasks.find(id));
|
||||
updated_task.set(task_completed.eq(new_status)).execute(&db()).is_ok()
|
||||
}
|
||||
|
||||
pub fn delete_with_id(id: i32) -> bool {
|
||||
|
|
|
@ -8,6 +8,16 @@
|
|||
margin: -10px 0 10px 0;
|
||||
}
|
||||
|
||||
.field-success {
|
||||
border: 1px solid #5AB953 !important;
|
||||
}
|
||||
|
||||
.field-success-msg {
|
||||
color: #5AB953;
|
||||
display: block;
|
||||
margin: -10px 0 10px 0;
|
||||
}
|
||||
|
||||
span.completed {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
|
|
@ -23,9 +23,9 @@
|
|||
<div class="ten columns">
|
||||
<input type="text" placeholder="enter a task description..."
|
||||
name="description" id="description" value="" autofocus
|
||||
class="u-full-width {% if error %}field-error{% endif %}" />
|
||||
{% if error %}
|
||||
<small class="field-error-msg">
|
||||
class="u-full-width {% if has_msg %}field-{{msg_type}}{% endif %}" />
|
||||
{% if has_msg %}
|
||||
<small class="field-{{msg_type}}-msg">
|
||||
{{ msg }}
|
||||
</small>
|
||||
{% endif %}
|
|
@ -9,7 +9,3 @@ log = "*"
|
|||
hyper = "*"
|
||||
url = "*"
|
||||
mime = "*"
|
||||
|
||||
# [dependencies.hyper]
|
||||
# git = "https://github.com/hyperium/hyper.git"
|
||||
# branch = "mio"
|
||||
|
|
|
@ -25,15 +25,10 @@ impl<'r, 'c> FromRequest<'r, 'c> for Method {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'r, 'c> FromRequest<'r, 'c> for Cookies {
|
||||
impl<'r, 'c> FromRequest<'r, 'c> for &'r Cookies {
|
||||
type Error = ();
|
||||
|
||||
fn from_request(request: &'r Request<'c>) -> Result<Self, Self::Error> {
|
||||
match request.headers().get::<HyperCookie>() {
|
||||
// TODO: What to do about key?
|
||||
Some(cookie) => Ok(cookie.to_cookie_jar(&[])),
|
||||
None => Ok(Cookies::new(&[]))
|
||||
}
|
||||
Ok(request.cookies())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,4 +9,5 @@ pub use hyper::header::Headers as HyperHeaders;
|
|||
pub use hyper::header::Cookie as HyperCookie;
|
||||
use hyper::header::CookieJar;
|
||||
|
||||
pub use hyper::header::CookiePair as Cookie;
|
||||
pub type Cookies = CookieJar<'static>;
|
||||
|
|
|
@ -17,18 +17,19 @@ use router::URI;
|
|||
use router::Route;
|
||||
|
||||
// Hyper stuff.
|
||||
use request::{HyperHeaders, HyperRequest};
|
||||
use request::{Cookies, HyperCookie, HyperHeaders, HyperRequest};
|
||||
|
||||
pub struct Request<'a> {
|
||||
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.
|
||||
cookies: Cookies,
|
||||
headers: HyperHeaders, // This sucks.
|
||||
params: RefCell<Option<Vec<&'a str>>>, // This also sucks.
|
||||
}
|
||||
|
||||
impl<'a> Request<'a> {
|
||||
// FIXME: Don't do the parsing here. I think. Not sure. Decide.
|
||||
// FIXME: Don't do the from_param parsing here. I think. Not sure. Decide.
|
||||
pub fn get_param<T: FromParam<'a>>(&self, n: usize) -> Result<T, Error> {
|
||||
let params = self.params.borrow();
|
||||
if params.is_none() || n >= params.as_ref().unwrap().len() {
|
||||
|
@ -39,6 +40,10 @@ impl<'a> Request<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn cookies<'r>(&'r self) -> &'r Cookies {
|
||||
&self.cookies
|
||||
}
|
||||
|
||||
/// i is the index of the first segment to consider
|
||||
pub fn get_segments<'r: 'a, T: FromSegments<'a>>(&'r self, i: usize)
|
||||
-> Result<T, Error> {
|
||||
|
@ -58,6 +63,7 @@ impl<'a> Request<'a> {
|
|||
Request {
|
||||
params: RefCell::new(None),
|
||||
method: method,
|
||||
cookies: Cookies::new(&[]),
|
||||
uri: URIBuf::from(uri),
|
||||
data: vec![],
|
||||
headers: HyperHeaders::new()
|
||||
|
@ -118,6 +124,12 @@ impl<'a> Request<'a> {
|
|||
_ => return Err(format!("Bad method: {}", h_method))
|
||||
};
|
||||
|
||||
let cookies = match h_headers.get::<HyperCookie>() {
|
||||
// TODO: What to do about key?
|
||||
Some(cookie) => cookie.to_cookie_jar(&[]),
|
||||
None => Cookies::new(&[])
|
||||
};
|
||||
|
||||
// FIXME: GRRR.
|
||||
let mut data = vec![];
|
||||
h_body.read_to_end(&mut data).unwrap();
|
||||
|
@ -125,6 +137,7 @@ impl<'a> Request<'a> {
|
|||
let request = Request {
|
||||
params: RefCell::new(None),
|
||||
method: method,
|
||||
cookies: cookies,
|
||||
uri: uri,
|
||||
data: data,
|
||||
headers: h_headers,
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
use response::*;
|
||||
use std::string::ToString;
|
||||
use hyper::header::{SetCookie, CookiePair};
|
||||
|
||||
pub struct Cookied<R: Responder> {
|
||||
cookies: Option<Vec<CookiePair>>,
|
||||
responder: R
|
||||
}
|
||||
|
||||
impl<R: Responder> Cookied<R> {
|
||||
pub fn new(responder: R) -> Cookied<R> {
|
||||
Cookied {
|
||||
cookies: None,
|
||||
responder: responder
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pairs(responder: R, pairs: &[(&ToString, &ToString)]) -> Cookied<R> {
|
||||
Cookied {
|
||||
cookies: Some(
|
||||
pairs.iter()
|
||||
.map(|p| CookiePair::new(p.0.to_string(), p.1.to_string()))
|
||||
.collect()
|
||||
),
|
||||
responder: responder
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn add<A: ToString, B: ToString>(mut self, a: A, b: B) -> Self {
|
||||
let new_pair = CookiePair::new(a.to_string(), b.to_string());
|
||||
match self.cookies {
|
||||
Some(ref mut pairs) => pairs.push(new_pair),
|
||||
None => self.cookies = Some(vec![new_pair])
|
||||
};
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Responder> Responder for Cookied<R> {
|
||||
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> {
|
||||
if let Some(pairs) = self.cookies.take() {
|
||||
res.headers_mut().set(SetCookie(pairs));
|
||||
}
|
||||
|
||||
self.responder.respond(res)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
use response::*;
|
||||
use std::convert::AsRef;
|
||||
use hyper::header::{SetCookie, CookiePair};
|
||||
use request::{Request, FromRequest};
|
||||
|
||||
pub struct Flash<R> {
|
||||
name: String,
|
||||
message: String,
|
||||
responder: R
|
||||
}
|
||||
|
||||
impl<R: Responder> Flash<R> {
|
||||
pub fn new<N: AsRef<str>, M: AsRef<str>>(res: R, name: N, msg: M) -> Flash<R> {
|
||||
Flash {
|
||||
name: name.as_ref().to_string(),
|
||||
message: msg.as_ref().to_string(),
|
||||
responder: res,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn warning<S: AsRef<str>>(responder: R, msg: S) -> Flash<R> {
|
||||
Flash::new(responder, "warning", msg)
|
||||
}
|
||||
|
||||
pub fn success<S: AsRef<str>>(responder: R, msg: S) -> Flash<R> {
|
||||
Flash::new(responder, "success", msg)
|
||||
}
|
||||
|
||||
pub fn error<S: AsRef<str>>(responder: R, msg: S) -> Flash<R> {
|
||||
Flash::new(responder, "error", msg)
|
||||
}
|
||||
|
||||
pub fn cookie_pair(&self) -> CookiePair {
|
||||
let content = format!("{}{}{}", self.name.len(), self.name, self.message);
|
||||
let mut pair = CookiePair::new("flash".to_string(), content);
|
||||
pair.path = Some("/".to_string());
|
||||
pair.max_age = Some(300);
|
||||
pair
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Responder> Responder for Flash<R> {
|
||||
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> {
|
||||
trace_!("Flash: setting message: {}:{}", self.name, self.message);
|
||||
res.headers_mut().set(SetCookie(vec![self.cookie_pair()]));
|
||||
self.responder.respond(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Flash<()> {
|
||||
fn named(name: &str, msg: &str) -> Flash<()> {
|
||||
Flash {
|
||||
name: name.to_string(),
|
||||
message: msg.to_string(),
|
||||
responder: (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
pub fn msg(&self) -> &str {
|
||||
self.message.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Using Flash<()> is ugly. Either create a type FlashMessage = Flash<()>
|
||||
// or create a Flash under request that does this.
|
||||
// TODO: Consider not removing the 'flash' cookie until after this thing is
|
||||
// dropped. This is because, at the moment, if Flash is including as a
|
||||
// from_request param, and some other param fails, then the flash message will
|
||||
// be dropped needlessly. This may or may not be the intended behavior.
|
||||
// Alternatively, provide a guarantee about the order that from_request params
|
||||
// will be evaluated and recommend that Flash is last.
|
||||
impl<'r, 'c> FromRequest<'r, 'c> for Flash<()> {
|
||||
type Error = ();
|
||||
|
||||
fn from_request(request: &'r Request<'c>) -> Result<Self, Self::Error> {
|
||||
trace_!("Flash: attemping to retrieve message.");
|
||||
request.cookies().find("flash").ok_or(()).and_then(|cookie| {
|
||||
// Clear the flash message.
|
||||
trace_!("Flash: retrieving message: {:?}", cookie);
|
||||
request.cookies().remove("flash");
|
||||
|
||||
// Parse the flash.
|
||||
let content = cookie.pair().1;
|
||||
let (len_str, rest) = match content.find(|c: char| !c.is_digit(10)) {
|
||||
Some(i) => (&content[..i], &content[i..]),
|
||||
None => (content, "")
|
||||
};
|
||||
|
||||
let name_len: usize = len_str.parse().map_err(|_| ())?;
|
||||
let (name, msg) = (&rest[..name_len], &rest[name_len..]);
|
||||
Ok(Flash::named(name, msg))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ mod responder;
|
|||
mod redirect;
|
||||
mod with_status;
|
||||
mod outcome;
|
||||
mod cookied;
|
||||
mod flash;
|
||||
mod data_type;
|
||||
|
||||
pub use hyper::server::Response as HyperResponse;
|
||||
|
@ -18,7 +18,7 @@ pub use self::empty::{Empty, Forward};
|
|||
pub use self::redirect::Redirect;
|
||||
pub use self::with_status::StatusResponse;
|
||||
pub use self::outcome::Outcome;
|
||||
pub use self::cookied::Cookied;
|
||||
pub use self::flash::Flash;
|
||||
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ use term_painter::ToStyle;
|
|||
|
||||
use hyper::server::Server as HyperServer;
|
||||
use hyper::server::Handler as HyperHandler;
|
||||
use hyper::header::SetCookie;
|
||||
|
||||
pub struct Rocket {
|
||||
address: String,
|
||||
|
@ -49,8 +50,12 @@ impl Rocket {
|
|||
info_!("Matched: {}", route);
|
||||
request.set_params(route);
|
||||
|
||||
// Here's the magic: dispatch the request to the handler.
|
||||
let outcome = (route.handler)(&request).respond(res);
|
||||
// Dispatch the request to the handler and update the cookies.
|
||||
let mut responder = (route.handler)(&request);
|
||||
res.headers_mut().set(SetCookie(request.cookies().delta()));
|
||||
|
||||
// Get the response.
|
||||
let outcome = responder.respond(res);
|
||||
info_!("{} {}", White.paint("Outcome:"), outcome);
|
||||
|
||||
// Get the result if we failed so we can try again.
|
||||
|
|
|
@ -39,12 +39,12 @@ impl Router {
|
|||
// let num_segments = req.uri.segment_count();
|
||||
// self.routes.get(&(req.method, num_segments)).map_or(vec![], |routes| {
|
||||
self.routes.get(&req.method).map_or(vec![], |routes| {
|
||||
trace_!("All possible matches: {:?}", routes);
|
||||
let mut matches: Vec<_> = routes.iter().filter(|r| {
|
||||
r.collides_with(req)
|
||||
}).collect();
|
||||
|
||||
matches.sort_by(|a, b| a.rank.cmp(&b.rank));
|
||||
trace_!("All matches: {:?}", matches);
|
||||
matches
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue