From 8e462b38631c8ca535302722b1c3ac972bb7224b Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Fri, 3 Jul 2015 14:23:23 +0100 Subject: [PATCH 01/18] feat(server): add some shared data across all requests This can be used to add some compile time dependencies for Middleware and Plugins. To fix code broken by this, you will need to introduce a type parameter for Request, Response, MiddlewareResult and NickelError. ``` // e.g. This fn foo<'a>(&mut Request, Response<'a>) -> MiddlewareResult<'a> // Should become: fn foo<'a, D>(&mut Request, Response<'a, D>) -> MiddlewareResult<'a, D> ``` You can add bounds to `D` in the above to place compile-time restrictions on the server data (can be used for configuration). BREAKING CHANGE --- examples/example.rs | 4 +-- examples/macro_example.rs | 6 ++-- src/default_error_handler.rs | 4 +-- src/favicon_handler.rs | 13 ++++--- src/json_body_parser.rs | 7 ++-- src/macros/middleware.rs | 10 +++--- src/middleware.rs | 38 ++++++++++---------- src/mount.rs | 22 ++++++------ src/nickel.rs | 37 +++++++++++++------- src/nickel_error.rs | 26 +++++++------- src/query_string.rs | 6 ++-- src/request.rs | 20 ++++++----- src/responder.rs | 34 +++++++++--------- src/response.rs | 67 ++++++++++++++++++++---------------- src/router/http_router.rs | 16 ++++----- src/router/router.rs | 42 +++++++++++----------- src/server.rs | 30 +++++++++------- src/static_files_handler.rs | 13 +++---- 18 files changed, 213 insertions(+), 182 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index 7548f84262..b28684268f 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -113,7 +113,7 @@ fn main() { server.utilize(StaticFilesHandler::new("examples/assets/")); //this is how to overwrite the default error handler to handle 404 cases with a custom view - fn custom_404<'a>(err: &mut NickelError, _req: &mut Request) -> Action { + fn custom_404<'a, D>(err: &mut NickelError, _req: &mut Request) -> Action { if let Some(ref mut res) = err.stream { if res.status() == NotFound { let _ = res.write_all(b"

Call the police!

"); @@ -126,7 +126,7 @@ fn main() { // issue #20178 - let custom_handler: fn(&mut NickelError, &mut Request) -> Action = custom_404; + let custom_handler: fn(&mut NickelError<()>, &mut Request<()>) -> Action = custom_404; server.handle_error(custom_handler); diff --git a/examples/macro_example.rs b/examples/macro_example.rs index d0a8431658..1449afc2a7 100644 --- a/examples/macro_example.rs +++ b/examples/macro_example.rs @@ -19,13 +19,13 @@ struct Person { } //this is an example middleware function that just logs each request -fn logger<'a>(request: &mut Request, response: Response<'a>) -> MiddlewareResult<'a> { +fn logger<'a, D>(request: &mut Request, response: Response<'a, D>) -> MiddlewareResult<'a, D> { println!("logging request: {:?}", request.origin.uri); Ok(Continue(response)) } //this is how to overwrite the default error handler to handle 404 cases with a custom view -fn custom_404<'a>(err: &mut NickelError, _req: &mut Request) -> Action { +fn custom_404<'a, D>(err: &mut NickelError, _req: &mut Request) -> Action { if let Some(ref mut res) = err.stream { if res.status() == NotFound { let _ = res.write_all(b"

Call the police!

"); @@ -122,7 +122,7 @@ fn main() { )); // issue #20178 - let custom_handler: fn(&mut NickelError, &mut Request) -> Action = custom_404; + let custom_handler: fn(&mut NickelError<()>, &mut Request<()>) -> Action = custom_404; server.handle_error(custom_handler); diff --git a/src/default_error_handler.rs b/src/default_error_handler.rs index 33cec2786e..75fb4b88f3 100644 --- a/src/default_error_handler.rs +++ b/src/default_error_handler.rs @@ -7,8 +7,8 @@ use std::io::Write; #[derive(Clone, Copy)] pub struct DefaultErrorHandler; -impl ErrorHandler for DefaultErrorHandler { - fn handle_error(&self, err: &mut NickelError, _req: &mut Request) -> Action { +impl ErrorHandler for DefaultErrorHandler { + fn handle_error(&self, err: &mut NickelError, _req: &mut Request) -> Action { if let Some(ref mut res) = err.stream { let msg : &[u8] = match res.status() { NotFound => b"Not Found", diff --git a/src/favicon_handler.rs b/src/favicon_handler.rs index 5d3d191eb8..eaa13314f1 100644 --- a/src/favicon_handler.rs +++ b/src/favicon_handler.rs @@ -6,7 +6,6 @@ use hyper::uri::RequestUri::AbsolutePath; use hyper::method::Method::{Get, Head, Options}; use hyper::status::StatusCode; use hyper::header; -use hyper::net; use request::Request; use response::Response; @@ -18,9 +17,9 @@ pub struct FaviconHandler { icon_path: PathBuf, // Is it useful to log where in-memory favicon came from every request? } -impl Middleware for FaviconHandler { - fn invoke<'a, 'server>(&'a self, req: &mut Request<'a, 'server>, res: Response<'a, net::Fresh>) - -> MiddlewareResult<'a> { +impl Middleware for FaviconHandler { + fn invoke<'a, 'server>(&'a self, req: &mut Request<'a, 'server, D>, res: Response<'a, D>) + -> MiddlewareResult<'a, D> { if FaviconHandler::is_favicon_request(req) { self.handle_request(req, res) } else { @@ -53,14 +52,14 @@ impl FaviconHandler { } #[inline] - pub fn is_favicon_request(req: &Request) -> bool { + pub fn is_favicon_request(req: &Request) -> bool { match req.origin.uri { AbsolutePath(ref path) => &**path == "/favicon.ico", _ => false } } - pub fn handle_request<'a>(&self, req: &Request, mut res: Response<'a>) -> MiddlewareResult<'a> { + pub fn handle_request<'a, D>(&self, req: &Request, mut res: Response<'a, D>) -> MiddlewareResult<'a, D> { match req.origin.method { Get | Head => { self.send_favicon(req, res) @@ -78,7 +77,7 @@ impl FaviconHandler { } } - pub fn send_favicon<'a, 'server>(&self, req: &Request, mut res: Response<'a>) -> MiddlewareResult<'a> { + pub fn send_favicon<'a, D>(&self, req: &Request, mut res: Response<'a, D>) -> MiddlewareResult<'a, D> { debug!("{:?} {:?}", req.origin.method, self.icon_path.display()); res.set(MediaType::Ico); res.send(&*self.icon) diff --git a/src/json_body_parser.rs b/src/json_body_parser.rs index 3f8b0c179e..29009fe9d7 100644 --- a/src/json_body_parser.rs +++ b/src/json_body_parser.rs @@ -8,10 +8,11 @@ use std::io::{Read, ErrorKind}; // Plugin boilerplate struct JsonBodyParser; impl Key for JsonBodyParser { type Value = String; } -impl<'mw, 'conn> Plugin> for JsonBodyParser { + +impl<'mw, 'conn, D> Plugin> for JsonBodyParser { type Error = io::Error; - fn eval(req: &mut Request) -> Result { + fn eval(req: &mut Request) -> Result { let mut s = String::new(); try!(req.origin.read_to_string(&mut s)); Ok(s) @@ -22,7 +23,7 @@ pub trait JsonBody { fn json_as(&mut self) -> Result; } -impl<'mw, 'conn> JsonBody for Request<'mw, 'conn> { +impl<'mw, 'conn, D> JsonBody for Request<'mw, 'conn, D> { fn json_as(&mut self) -> Result { self.get_ref::().and_then(|parsed| json::decode::(&*parsed).map_err(|err| diff --git a/src/macros/middleware.rs b/src/macros/middleware.rs index c13b16cc40..b8977b9170 100644 --- a/src/macros/middleware.rs +++ b/src/macros/middleware.rs @@ -38,18 +38,18 @@ macro_rules! _middleware_inner { use $crate::{MiddlewareResult,Responder, Response, Request}; #[inline(always)] - fn restrict<'mw, R: Responder>(r: R, res: Response<'mw>) - -> MiddlewareResult<'mw> { + fn restrict<'mw, D, R: Responder>(r: R, res: Response<'mw, D>) + -> MiddlewareResult<'mw, D> { res.send(r) } // Inference fails due to thinking it's a (&Request, Response) with // different mutability requirements #[inline(always)] - fn restrict_closure(f: F) -> F + fn restrict_closure(f: F) -> F where F: for<'r, 'mw, 'conn> - Fn(&'r mut Request<'mw, 'conn>, Response<'mw>) - -> MiddlewareResult<'mw> + Send + Sync { f } + Fn(&'r mut Request<'mw, 'conn, D>, Response<'mw, D>) + -> MiddlewareResult<'mw, D> + Send + Sync { f } restrict_closure(move |as_pat!($req), $res_binding| { restrict(as_block!({$($b)+}), $res) diff --git a/src/middleware.rs b/src/middleware.rs index 6f86bacdda..36552d536e 100644 --- a/src/middleware.rs +++ b/src/middleware.rs @@ -5,9 +5,9 @@ use hyper::net; pub use self::Action::{Continue, Halt}; -pub type MiddlewareResult<'mw> = Result, - Response<'mw, net::Streaming>>, - NickelError<'mw>>; +pub type MiddlewareResult<'mw, D> = Result, + Response<'mw, D, net::Streaming>>, + NickelError<'mw, D>>; pub enum Action { Continue(T), @@ -16,43 +16,43 @@ pub enum Action { // the usage of + Send is weird here because what we really want is + Static // but that's not possible as of today. We have to use + Send for now. -pub trait Middleware: Send + 'static + Sync { - fn invoke<'mw, 'conn>(&'mw self, _req: &mut Request<'mw, 'conn>, res: Response<'mw, net::Fresh>) -> MiddlewareResult<'mw> { +pub trait Middleware: Send + 'static + Sync { + fn invoke<'mw, 'conn>(&'mw self, _req: &mut Request<'mw, 'conn, D>, res: Response<'mw, D, net::Fresh>) -> MiddlewareResult<'mw, D> { Ok(Continue(res)) } } -impl Middleware for T where T: for<'r, 'mw, 'conn> Fn(&'r mut Request<'mw, 'conn>, Response<'mw>) -> MiddlewareResult<'mw> + Send + Sync + 'static { - fn invoke<'mw, 'conn>(&'mw self, req: &mut Request<'mw, 'conn>, res: Response<'mw>) -> MiddlewareResult<'mw> { +impl Middleware for T where T: for<'r, 'mw, 'conn> Fn(&'r mut Request<'mw, 'conn, D>, Response<'mw, D>) -> MiddlewareResult<'mw, D> + Send + Sync + 'static { + fn invoke<'mw, 'conn>(&'mw self, req: &mut Request<'mw, 'conn, D>, res: Response<'mw, D>) -> MiddlewareResult<'mw, D> { (*self)(req, res) } } -pub trait ErrorHandler: Send + 'static + Sync { - fn handle_error(&self, &mut NickelError, &mut Request) -> Action; +pub trait ErrorHandler: Send + 'static + Sync { + fn handle_error(&self, &mut NickelError, &mut Request) -> Action; } -impl ErrorHandler for fn(&mut NickelError, &mut Request) -> Action { - fn handle_error(&self, err: &mut NickelError, req: &mut Request) -> Action { +impl ErrorHandler for fn(&mut NickelError, &mut Request) -> Action { + fn handle_error(&self, err: &mut NickelError, req: &mut Request) -> Action { (*self)(err, req) } } -pub struct MiddlewareStack { - handlers: Vec>, - error_handlers: Vec> +pub struct MiddlewareStack { + handlers: Vec + Send + Sync>>, + error_handlers: Vec + Send + Sync>> } -impl MiddlewareStack { - pub fn add_middleware (&mut self, handler: T) { +impl MiddlewareStack { + pub fn add_middleware> (&mut self, handler: T) { self.handlers.push(Box::new(handler)); } - pub fn add_error_handler (&mut self, handler: T) { + pub fn add_error_handler> (&mut self, handler: T) { self.error_handlers.push(Box::new(handler)); } - pub fn invoke<'mw, 'conn>(&'mw self, mut req: Request<'mw, 'conn>, mut res: Response<'mw>) { + pub fn invoke<'mw, 'conn>(&'mw self, mut req: Request<'mw, 'conn, D>, mut res: Response<'mw, D>) { for handler in self.handlers.iter() { match handler.invoke(&mut req, res) { Ok(Halt(res)) => { @@ -92,7 +92,7 @@ impl MiddlewareStack { } } - pub fn new () -> MiddlewareStack { + pub fn new () -> MiddlewareStack { MiddlewareStack{ handlers: Vec::new(), error_handlers: Vec::new() diff --git a/src/mount.rs b/src/mount.rs index c376dd098a..e898e49d2c 100644 --- a/src/mount.rs +++ b/src/mount.rs @@ -7,12 +7,12 @@ use hyper::uri::RequestUri::AbsolutePath; use std::mem; -pub trait Mountable { - fn mount, M: Middleware>(&mut self, mount_point: S, middleware: M); +pub trait Mountable { + fn mount, M: Middleware>(&mut self, mount_point: S, middleware: M); } -impl Mountable for Nickel { - /// +impl Mountable for Nickel +where D: Send + Sync + 'static { /// A trait that makes mounting more convenient. Works the same as /// manually adding a `Mount` middleware. /// @@ -27,17 +27,17 @@ impl Mountable for Nickel { /// /// # Panics /// Panics if mount_point does not have a leading and trailing slash. - fn mount, M: Middleware>(&mut self, mount_point: S, middleware: M) { + fn mount, M: Middleware>(&mut self, mount_point: S, middleware: M) { self.utilize(Mount::new(mount_point, middleware)); } } -pub struct Mount { +pub struct Mount { mount_point: String, middleware: M } -impl Mount { +impl Mount { /// /// Creates a new middleware that mounts a middleware at a mount point. /// An incoming request that matches the mount point will be forwareded to @@ -63,7 +63,7 @@ impl Mount { pub fn new>(mount_point: S, middleware: M) -> Mount { let mount_point: String = mount_point.into(); match (mount_point.chars().last(), mount_point.chars().nth(0)) { - (Some('/'), Some('/')) => + (Some('/'), Some('/')) => Mount { mount_point: mount_point, middleware: middleware @@ -73,9 +73,9 @@ impl Mount { } } -impl Middleware for Mount { - fn invoke<'mw, 'conn>(&'mw self, req: &mut Request<'mw, 'conn>, res: Response<'mw>) - -> MiddlewareResult<'mw> { +impl> Middleware for Mount { + fn invoke<'mw, 'conn>(&'mw self, req: &mut Request<'mw, 'conn, D>, res: Response<'mw, D>) + -> MiddlewareResult<'mw, D> { let subpath = match req.origin.uri { AbsolutePath(ref path) if path.starts_with(&*self.mount_point) => { AbsolutePath(format!("/{}", &path[self.mount_point.len()..])) diff --git a/src/nickel.rs b/src/nickel.rs index 82d045fb89..ae479a3301 100644 --- a/src/nickel.rs +++ b/src/nickel.rs @@ -10,12 +10,13 @@ use default_error_handler::DefaultErrorHandler; /// Nickel is the application object. It's the surface that /// holds all public APIs. -pub struct Nickel{ - middleware_stack: MiddlewareStack, +pub struct Nickel { + middleware_stack: MiddlewareStack, + data: D } -impl HttpRouter for Nickel { - fn add_route, H: Middleware>(&mut self, method: Method, matcher: M, handler: H) -> &mut Self { +impl HttpRouter for Nickel { + fn add_route, H: Middleware>(&mut self, method: Method, matcher: M, handler: H) -> &mut Self { let mut router = Router::new(); router.add_route(method, matcher, handler); self.utilize(router); @@ -23,9 +24,16 @@ impl HttpRouter for Nickel { } } -impl Nickel { +impl Nickel<()> { /// Creates an instance of Nickel with default error handling. - pub fn new() -> Nickel { + pub fn new() -> Nickel<()> { + Nickel::with_data(()) + } +} + +impl Nickel { + /// Creates an instance of Nickel with default error handling and custom data. + pub fn with_data(data: D) -> Nickel { let mut middleware_stack = MiddlewareStack::new(); // Hook up the default error handler by default. Users are @@ -33,7 +41,10 @@ impl Nickel { // they don't like the default behaviour. middleware_stack.add_error_handler(DefaultErrorHandler); - Nickel { middleware_stack: middleware_stack } + Nickel { + middleware_stack: middleware_stack, + data: data + } } /// Registers a middleware handler which will be invoked among other middleware @@ -57,7 +68,7 @@ impl Nickel { /// }); /// # } /// ``` - pub fn utilize(&mut self, handler: T){ + pub fn utilize>(&mut self, handler: T){ self.middleware_stack.add_middleware(handler); } @@ -77,7 +88,7 @@ impl Nickel { /// use nickel::{NickelError, Action}; /// use nickel::status::StatusCode::NotFound; /// - /// fn error_handler(err: &mut NickelError, _req: &mut Request) -> Action { + /// fn error_handler(err: &mut NickelError, _req: &mut Request) -> Action { /// if let Some(ref mut res) = err.stream { /// if res.status() == NotFound { /// let _ = res.write_all(b"

Call the police!

"); @@ -90,12 +101,12 @@ impl Nickel { /// /// let mut server = Nickel::new(); /// - /// let ehandler: fn(&mut NickelError, &mut Request) -> Action = error_handler; + /// let ehandler: fn(&mut NickelError<()>, &mut Request<()>) -> Action = error_handler; /// /// server.handle_error(ehandler) /// # } /// ``` - pub fn handle_error(&mut self, handler: T){ + pub fn handle_error>(&mut self, handler: T){ self.middleware_stack.add_error_handler(handler); } @@ -118,7 +129,7 @@ impl Nickel { /// server.utilize(router); /// } /// ``` - pub fn router() -> Router { + pub fn router() -> Router { Router::new() } @@ -140,7 +151,7 @@ impl Nickel { (StatusCode::NotFound, "File Not Found") }); - let server = Server::new(self.middleware_stack); + let server = Server::new(self.middleware_stack, self.data); let listener = server.serve(addr).unwrap(); println!("Listening on http://{}", listener.socket); diff --git a/src/nickel_error.rs b/src/nickel_error.rs index 2362c3e1f0..32c3c75432 100644 --- a/src/nickel_error.rs +++ b/src/nickel_error.rs @@ -7,12 +7,12 @@ use hyper::net::{Fresh, Streaming}; /// NickelError is the basic error type for HTTP errors as well as user defined errors. /// One can pattern match against the `kind` property to handle the different cases. -pub struct NickelError<'a> { - pub stream: Option>, +pub struct NickelError<'a, D: 'a> { + pub stream: Option>, pub message: Cow<'static, str> } -impl<'a> NickelError<'a> { +impl<'a, D> NickelError<'a, D> { /// Creates a new `NickelError` instance. /// /// You should probably use `Response#error` in favor of this. @@ -26,14 +26,14 @@ impl<'a> NickelError<'a> { /// use nickel::status::StatusCode; /// /// # #[allow(dead_code)] - /// fn handler<'a>(_: &mut Request, res: Response<'a>) -> MiddlewareResult<'a> { + /// fn handler<'a, D>(_: &mut Request, res: Response<'a, D>) -> MiddlewareResult<'a, D> { /// Err(NickelError::new(res, "Error Parsing JSON", StatusCode::BadRequest)) /// } /// # } /// ``` - pub fn new(mut stream: Response<'a, Fresh>, + pub fn new(mut stream: Response<'a, D, Fresh>, message: T, - status_code: StatusCode) -> NickelError<'a> + status_code: StatusCode) -> NickelError<'a, D> where T: Into> { stream.set(status_code); @@ -56,7 +56,7 @@ impl<'a> NickelError<'a> { /// /// This is considered `unsafe` as deadlock can occur if the `Response` /// does not have the underlying stream flushed when processing is finished. - pub unsafe fn without_response(message: T) -> NickelError<'a> + pub unsafe fn without_response(message: T) -> NickelError<'a, D> where T: Into> { NickelError { stream: None, @@ -69,22 +69,22 @@ impl<'a> NickelError<'a> { } } -impl<'a, T> From<(Response<'a>, (StatusCode, T))> for NickelError<'a> +impl<'a, T, D> From<(Response<'a, D>, (StatusCode, T))> for NickelError<'a, D> where T: Into> { - fn from((res, (errorcode, err)): (Response<'a>, (StatusCode, T))) -> NickelError<'a> { + fn from((res, (errorcode, err)): (Response<'a, D>, (StatusCode, T))) -> NickelError<'a, D> { let err = err.into(); NickelError::new(res, err.description().to_string(), errorcode) } } -impl<'a> From<(Response<'a>, String)> for NickelError<'a> { - fn from((res, msg): (Response<'a>, String)) -> NickelError<'a> { +impl<'a, D> From<(Response<'a, D>, String)> for NickelError<'a, D> { + fn from((res, msg): (Response<'a, D>, String)) -> NickelError<'a, D> { NickelError::new(res, msg, StatusCode::InternalServerError) } } -impl<'a> From<(Response<'a>, StatusCode)> for NickelError<'a> { - fn from((res, code): (Response<'a>, StatusCode)) -> NickelError<'a> { +impl<'a, D> From<(Response<'a, D>, StatusCode)> for NickelError<'a, D> { + fn from((res, code): (Response<'a, D>, StatusCode)) -> NickelError<'a, D> { NickelError::new(res, "", code) } } diff --git a/src/query_string.rs b/src/query_string.rs index 19eab05761..bef54ffc57 100644 --- a/src/query_string.rs +++ b/src/query_string.rs @@ -33,10 +33,10 @@ impl Query { struct QueryStringParser; impl Key for QueryStringParser { type Value = Query; } -impl<'mw, 'conn> Plugin> for QueryStringParser { +impl<'mw, 'conn, D> Plugin> for QueryStringParser { type Error = (); - fn eval(req: &mut Request) -> Result { + fn eval(req: &mut Request) -> Result { Ok(parse(&req.origin.uri)) } } @@ -46,7 +46,7 @@ pub trait QueryString { fn query(&mut self) -> &Query; } -impl<'mw, 'conn> QueryString for Request<'mw, 'conn> { +impl<'mw, 'conn, D> QueryString for Request<'mw, 'conn, D> { fn query(&mut self) -> &Query { self.get_ref::() .ok() diff --git a/src/request.rs b/src/request.rs index 2de6dda19b..f591713378 100644 --- a/src/request.rs +++ b/src/request.rs @@ -11,21 +11,25 @@ use hyper::uri::RequestUri::AbsolutePath; /// /// The lifetime `'server` represents the lifetime of data internal to /// the server. It is fixed and longer than `'mw`. -pub struct Request<'mw, 'server: 'mw> { +pub struct Request<'mw, 'server: 'mw, D: 'mw> { ///the original `hyper::server::Request` pub origin: HyperRequest<'mw, 'server>, ///a `HashMap` holding all params with names and values - pub route_result: Option>, + pub route_result: Option>, - map: TypeMap + map: TypeMap, + + data: &'mw D, } -impl<'mw, 'server> Request<'mw, 'server> { - pub fn from_internal(req: HyperRequest<'mw, 'server>) -> Request<'mw, 'server> { +impl<'mw, 'server, D> Request<'mw, 'server, D> { + pub fn from_internal(req: HyperRequest<'mw, 'server>, + data: &'mw D) -> Request<'mw, 'server, D> { Request { origin: req, route_result: None, - map: TypeMap::new() + map: TypeMap::new(), + data: data } } @@ -41,7 +45,7 @@ impl<'mw, 'server> Request<'mw, 'server> { } } -impl<'mw, 'server> Extensible for Request<'mw, 'server> { +impl<'mw, 'server, D> Extensible for Request<'mw, 'server, D> { fn extensions(&self) -> &TypeMap { &self.map } @@ -51,4 +55,4 @@ impl<'mw, 'server> Extensible for Request<'mw, 'server> { } } -impl<'mw, 'server> Pluggable for Request<'mw, 'server> {} +impl<'mw, 'server, D> Pluggable for Request<'mw, 'server, D> {} diff --git a/src/responder.rs b/src/responder.rs index a4a9b13bd4..91098aaeae 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -21,28 +21,28 @@ use std::io::Write; /// also modifying the `Response` as required. /// /// Please see the examples for some uses. -pub trait Responder { - fn respond<'a>(self, Response<'a>) -> MiddlewareResult<'a>; +pub trait Responder { + fn respond<'a>(self, Response<'a, D>) -> MiddlewareResult<'a, D>; } -impl Responder for () { - fn respond<'a>(self, res: Response<'a>) -> MiddlewareResult<'a> { +impl Responder for () { + fn respond<'a>(self, res: Response<'a, D>) -> MiddlewareResult<'a, D> { Ok(Continue(res)) } } -impl Responder for json::Json { - fn respond<'a>(self, mut res: Response<'a>) -> MiddlewareResult<'a> { +impl Responder for json::Json { + fn respond<'a>(self, mut res: Response<'a, D>) -> MiddlewareResult<'a, D> { maybe_set_type(&mut res, MediaType::Json); res.send(json::encode(&self) .map_err(|e| format!("Failed to parse JSON: {}", e))) } } -impl Responder for Result - where T: Responder, - for<'e> NickelError<'e>: From<(Response<'e>, E)> { - fn respond<'a>(self, res: Response<'a>) -> MiddlewareResult<'a> { +impl Responder for Result + where T: Responder, + for<'e> NickelError<'e, D>: From<(Response<'e, D>, E)> { + fn respond<'a>(self, res: Response<'a, D>) -> MiddlewareResult<'a, D> { let data = try_with!(res, self); res.send(data) } @@ -50,16 +50,16 @@ impl Responder for Result macro_rules! dual_impl { ($view:ty, $alloc:ty, |$s:ident, $res:ident| $b:block) => ( - impl<'a> Responder for $view { + impl<'a, D> Responder for $view { #[allow(unused_mut)] #[inline] - fn respond<'c>($s, mut $res: Response<'c>) -> MiddlewareResult<'c> $b + fn respond<'c>($s, mut $res: Response<'c, D>) -> MiddlewareResult<'c, D> $b } - impl<'a> Responder for $alloc { + impl<'a, D> Responder for $alloc { #[allow(unused_mut)] #[inline] - fn respond<'c>($s, mut $res: Response<'c>) -> MiddlewareResult<'c> $b + fn respond<'c>($s, mut $res: Response<'c, D>) -> MiddlewareResult<'c, D> $b } ) } @@ -99,9 +99,9 @@ dual_impl!((StatusCode, &'static str), } }); -impl<'a> Responder for StatusCode { +impl<'a, D> Responder for StatusCode { #[inline] - fn respond<'c>(self, res: Response<'c>) -> MiddlewareResult<'c> { + fn respond<'c>(self, res: Response<'c, D>) -> MiddlewareResult<'c, D> { res.send((self, "")) } } @@ -147,6 +147,6 @@ dual_impl!((u16, &'static str), // Ok(Halt) // }) -fn maybe_set_type(res: &mut Response, mime: MediaType) { +fn maybe_set_type(res: &mut Response, mime: MediaType) { res.set_header_fallback(|| header::ContentType(mime.into())); } diff --git a/src/response.rs b/src/response.rs index b791d954ad..99bfd0d4fb 100644 --- a/src/response.rs +++ b/src/response.rs @@ -23,19 +23,22 @@ use modifier::Modifier; pub type TemplateCache = RwLock>; ///A container for the response -pub struct Response<'a, T: 'static + Any = Fresh> { +pub struct Response<'a, D: 'a, T: 'static + Any = Fresh> { ///the original `hyper::server::Response` origin: HyperResponse<'a, T>, - templates: &'a TemplateCache + templates: &'a TemplateCache, + data: &'a D, } -impl<'a> Response<'a, Fresh> { +impl<'a, D> Response<'a, D, Fresh> { pub fn from_internal<'c, 'd>(response: HyperResponse<'c, Fresh>, - templates: &'c TemplateCache) - -> Response<'c, Fresh> { + templates: &'c TemplateCache, + data: &'c D) + -> Response<'c, D, Fresh> { Response { origin: response, - templates: templates + templates: templates, + data: data } } @@ -81,7 +84,7 @@ impl<'a> Response<'a, Fresh> { /// // ... /// } /// ``` - pub fn set>>(&mut self, attribute: T) -> &mut Response<'a> { + pub fn set>>(&mut self, attribute: T) -> &mut Response<'a, D> { attribute.modify(self); self } @@ -93,12 +96,12 @@ impl<'a> Response<'a, Fresh> { /// use nickel::{Request, Response, MiddlewareResult}; /// /// # #[allow(dead_code)] - /// fn handler<'a>(_: &mut Request, res: Response<'a>) -> MiddlewareResult<'a> { + /// fn handler<'a, D>(_: &mut Request, res: Response<'a, D>) -> MiddlewareResult<'a, D> { /// res.send("hello world") /// } /// ``` #[inline] - pub fn send(self, data: T) -> MiddlewareResult<'a> { + pub fn send>(self, data: T) -> MiddlewareResult<'a, D> { data.respond(self) } @@ -110,12 +113,12 @@ impl<'a> Response<'a, Fresh> { /// use std::path::Path; /// /// # #[allow(dead_code)] - /// fn handler<'a>(_: &mut Request, res: Response<'a>) -> MiddlewareResult<'a> { + /// fn handler<'a, D>(_: &mut Request, res: Response<'a, D>) -> MiddlewareResult<'a, D> { /// let favicon = Path::new("/assets/favicon.ico"); /// res.send_file(favicon) /// } /// ``` - pub fn send_file>(mut self, path: P) -> MiddlewareResult<'a> { + pub fn send_file>(mut self, path: P) -> MiddlewareResult<'a, D> { let path = path.as_ref(); // Chunk the response self.origin.headers_mut().remove::(); @@ -147,7 +150,7 @@ impl<'a> Response<'a, Fresh> { /// Return an error with the appropriate status code for error handlers to /// provide output for. - pub fn error(self, status: StatusCode, message: T) -> MiddlewareResult<'a> + pub fn error(self, status: StatusCode, message: T) -> MiddlewareResult<'a, D> where T: Into> { Err(NickelError::new(self, message, status)) } @@ -195,16 +198,16 @@ impl<'a> Response<'a, Fresh> { /// use nickel::{Request, Response, MiddlewareResult}; /// /// # #[allow(dead_code)] - /// fn handler<'a>(_: &mut Request, res: Response<'a>) -> MiddlewareResult<'a> { + /// fn handler<'a, D>(_: &mut Request, res: Response<'a, D>) -> MiddlewareResult<'a, D> { /// let mut data = HashMap::new(); /// data.insert("name", "user"); /// res.render("examples/assets/template.tpl", &data) /// } /// ``` - pub fn render(self, path: P, data: &T) -> MiddlewareResult<'a> + pub fn render(self, path: P, data: &T) -> MiddlewareResult<'a, D> where T: Encodable, P: AsRef + Into { - fn render<'a, T>(res: Response<'a>, template: &Template, data: &T) - -> MiddlewareResult<'a> where T: Encodable { + fn render<'a, D, T>(res: Response<'a, D>, template: &Template, data: &T) + -> MiddlewareResult<'a, D> where T: Encodable { let mut stream = try!(res.start()); match template.render(&mut stream, data) { Ok(()) => Ok(Halt(stream)), @@ -239,12 +242,18 @@ impl<'a> Response<'a, Fresh> { render(self, template, data) } - pub fn start(mut self) -> Result, NickelError<'a>> { + pub fn start(mut self) -> Result, NickelError<'a, D>> { self.set_fallback_headers(); - let Response { origin, templates } = self; + let Response { origin, templates, data } = self; match origin.start() { - Ok(origin) => Ok(Response { origin: origin, templates: templates }), + Ok(origin) => { + Ok(Response { + origin: origin, + templates: templates, + data: data + }) + }, Err(e) => unsafe { Err(NickelError::without_response(format!("Failed to start response: {}", e))) @@ -253,7 +262,7 @@ impl<'a> Response<'a, Fresh> { } } -impl<'a, 'b> Write for Response<'a, Streaming> { +impl<'a, 'b, D> Write for Response<'a, D, Streaming> { #[inline(always)] fn write(&mut self, buf: &[u8]) -> io::Result { self.origin.write(buf) @@ -265,12 +274,12 @@ impl<'a, 'b> Write for Response<'a, Streaming> { } } -impl<'a, 'b> Response<'a, Streaming> { +impl<'a, 'b, D> Response<'a, D, Streaming> { /// In the case of an unrecoverable error while a stream is already in /// progress, there is no standard way to signal to the client that an /// error has occurred. `bail` will drop the connection and log an error /// message. - pub fn bail(self, message: T) -> MiddlewareResult<'a> + pub fn bail(self, message: T) -> MiddlewareResult<'a, D> where T: Into> { let _ = self.end(); unsafe { Err(NickelError::without_response(message)) } @@ -282,7 +291,7 @@ impl<'a, 'b> Response<'a, Streaming> { } } -impl <'a, T: 'static + Any> Response<'a, T> { +impl <'a, D, T: 'static + Any> Response<'a, D, T> { /// The status of this response. pub fn status(&self) -> StatusCode { self.origin.status() @@ -315,14 +324,14 @@ mod modifier_impls { use modifier::Modifier; use {Response, MediaType}; - impl<'a> Modifier> for StatusCode { - fn modify(self, res: &mut Response<'a>) { + impl<'a, D> Modifier> for StatusCode { + fn modify(self, res: &mut Response<'a, D>) { *res.status_mut() = self } } - impl<'a> Modifier> for MediaType { - fn modify(self, res: &mut Response<'a>) { + impl<'a, D> Modifier> for MediaType { + fn modify(self, res: &mut Response<'a, D>) { ContentType(self.into()).modify(res) } } @@ -330,8 +339,8 @@ mod modifier_impls { macro_rules! header_modifiers { ($($t:ty),+) => ( $( - impl<'a> Modifier> for $t { - fn modify(self, res: &mut Response<'a>) { + impl<'a, D> Modifier> for $t { + fn modify(self, res: &mut Response<'a, D>) { res.headers_mut().set(self) } } diff --git a/src/router/http_router.rs b/src/router/http_router.rs index bf62eab6be..5df7502d95 100644 --- a/src/router/http_router.rs +++ b/src/router/http_router.rs @@ -2,7 +2,7 @@ use hyper::method::Method; use middleware::Middleware; use router::Matcher; -pub trait HttpRouter { +pub trait HttpRouter { /// Registers a handler to be used for a specified method. /// A handler can be anything implementing the `RequestHandler` trait. /// @@ -36,7 +36,7 @@ pub trait HttpRouter { /// server.add_route(Get, regex, middleware! { "Regex Get request! "}); /// } /// ``` - fn add_route, H: Middleware>(&mut self, Method, M, H) -> &mut Self; + fn add_route, H: Middleware>(&mut self, Method, M, H) -> &mut Self; /// Registers a handler to be used for a specific GET request. /// Handlers are assigned to paths and paths are allowed to contain @@ -121,42 +121,42 @@ pub trait HttpRouter { /// server.utilize(router); /// } /// ``` - fn get, H: Middleware>(&mut self, matcher: M, handler: H) -> &mut Self { + fn get, H: Middleware>(&mut self, matcher: M, handler: H) -> &mut Self { self.add_route(Method::Get, matcher, handler) } /// Registers a handler to be used for a specific POST request. /// /// Take a look at `get(...)` for a more detailed description. - fn post, H: Middleware>(&mut self, matcher: M, handler: H) -> &mut Self { + fn post, H: Middleware>(&mut self, matcher: M, handler: H) -> &mut Self { self.add_route(Method::Post, matcher, handler) } /// Registers a handler to be used for a specific PUT request. /// /// Take a look at `get(...)` for a more detailed description. - fn put, H: Middleware>(&mut self, matcher: M, handler: H) -> &mut Self { + fn put, H: Middleware>(&mut self, matcher: M, handler: H) -> &mut Self { self.add_route(Method::Put, matcher, handler) } /// Registers a handler to be used for a specific DELETE request. /// /// Take a look at `get(...)` for a more detailed description. - fn delete, H: Middleware>(&mut self, matcher: M, handler: H) -> &mut Self { + fn delete, H: Middleware>(&mut self, matcher: M, handler: H) -> &mut Self { self.add_route(Method::Delete, matcher, handler) } /// Registers a handler to be used for a specific OPTIONS request. /// /// Take a look at `get(...)` for a more detailed description. - fn options, H: Middleware>(&mut self, matcher: M, handler: H) -> &mut Self { + fn options, H: Middleware>(&mut self, matcher: M, handler: H) -> &mut Self { self.add_route(Method::Options, matcher, handler) } /// Registers a handler to be used for a specific PATCH request. /// /// Take a look at `get(...)` for a more detailed description. - fn patch, H: Middleware>(&mut self, matcher: M, handler: H) -> &mut Self { + fn patch, H: Middleware>(&mut self, matcher: M, handler: H) -> &mut Self { self.add_route(Method::Patch, matcher, handler) } } diff --git a/src/router/router.rs b/src/router/router.rs index 261a5184e2..7a969037ef 100644 --- a/src/router/router.rs +++ b/src/router/router.rs @@ -10,9 +10,9 @@ use router::{Matcher, FORMAT_PARAM}; /// A Route is the basic data structure that stores both the path /// and the handler that gets executed for the route. /// The path can contain variable pattern such as `user/:userid/invoices` -pub struct Route { +pub struct Route { pub method: Method, - pub handler: Box, + pub handler: Box + Send + Sync + 'static>, matcher: Matcher } @@ -20,12 +20,12 @@ pub struct Route { /// It contains the matched `route` and also a `params` property holding /// a HashMap with the keys being the variable names and the value being the /// evaluated string -pub struct RouteResult<'mw> { - pub route: &'mw Route, +pub struct RouteResult<'mw, D: 'mw> { + pub route: &'mw Route, params: Vec<(String, String)> } -impl<'mw> RouteResult<'mw> { +impl<'mw, D> RouteResult<'mw, D> { pub fn param(&self, key: &str) -> Option<&str> { for &(ref k, ref v) in &self.params { if k == &key { @@ -45,18 +45,18 @@ impl<'mw> RouteResult<'mw> { /// The Router's job is it to hold routes and to resolve them later against /// concrete URLs. The router is also a regular middleware and needs to be /// added to the middleware stack with `server.utilize(router)`. -pub struct Router { - routes: Vec, +pub struct Router { + routes: Vec>, } -impl Router { - pub fn new () -> Router { +impl Router { + pub fn new() -> Router { Router { routes: Vec::new() } } - pub fn match_route<'mw>(&'mw self, method: &Method, path: &str) -> Option> { + pub fn match_route<'mw>(&'mw self, method: &Method, path: &str) -> Option> { self.routes .iter() .find(|item| item.method == *method && item.matcher.is_match(path)) @@ -69,7 +69,7 @@ impl Router { } } -fn extract_params(route: &Route, path: &str) -> Vec<(String, String)> { +fn extract_params(route: &Route, path: &str) -> Vec<(String, String)> { match route.matcher.captures(path) { Some(captures) => { captures.iter_named() @@ -82,8 +82,8 @@ fn extract_params(route: &Route, path: &str) -> Vec<(String, String)> { } } -impl HttpRouter for Router { - fn add_route, H: Middleware>(&mut self, method: Method, matcher: M, handler: H) -> &mut Self { +impl HttpRouter for Router { + fn add_route, H: Middleware>(&mut self, method: Method, matcher: M, handler: H) -> &mut Self { let route = Route { matcher: matcher.into(), method: method, @@ -95,9 +95,9 @@ impl HttpRouter for Router { } } -impl Middleware for Router { - fn invoke<'mw, 'conn>(&'mw self, req: &mut Request<'mw, 'conn>, mut res: Response<'mw>) - -> MiddlewareResult<'mw> { +impl Middleware for Router { + fn invoke<'mw, 'conn>(&'mw self, req: &mut Request<'mw, 'conn, D>, mut res: Response<'mw, D>) + -> MiddlewareResult<'mw, D> { debug!("Router::invoke for '{:?}'", req.origin.uri); // Strip off the querystring when matching a route @@ -191,7 +191,7 @@ fn creates_valid_regex_for_routes () { #[test] fn can_match_var_routes () { - let route_store = &mut Router::new(); + let route_store = &mut Router::<()>::new(); route_store.add_route(Method::Get, "/foo/:userid", middleware! { "hello from foo" }); route_store.add_route(Method::Get, "/bar", middleware! { "hello from foo" }); @@ -256,7 +256,7 @@ fn can_match_var_routes () { #[test] fn params_lifetime() { - let route_store = &mut Router::new(); + let route_store = &mut Router::<()>::new(); let handler = middleware! { "hello from foo" }; route_store.add_route(Method::Get, "/file/:format/:file", handler); @@ -276,7 +276,7 @@ fn params_lifetime() { fn regex_path() { use regex::Regex; - let route_store = &mut Router::new(); + let route_store = &mut Router::<()>::new(); let regex = Regex::new("/(foo|bar)").unwrap(); route_store.add_route(Method::Get, regex, middleware! { "hello from foo" }); @@ -298,7 +298,7 @@ fn regex_path() { fn regex_path_named() { use regex::Regex; - let route_store = &mut Router::new(); + let route_store = &mut Router::<()>::new(); let regex = Regex::new("/(?Pfoo|bar)/b").unwrap(); route_store.add_route(Method::Get, regex, middleware! { "hello from foo" }); @@ -323,7 +323,7 @@ fn regex_path_named() { fn ignores_querystring() { use regex::Regex; - let route_store = &mut Router::new(); + let route_store = &mut Router::<()>::new(); let regex = Regex::new("/(?Pfoo|bar)/b").unwrap(); route_store.add_route(Method::Get, regex, middleware! { "hello from foo" }); diff --git a/src/server.rs b/src/server.rs index b00990954a..f0c16575db 100644 --- a/src/server.rs +++ b/src/server.rs @@ -9,32 +9,38 @@ use middleware::MiddlewareStack; use request; use response; -pub struct Server { - middleware_stack: MiddlewareStack, - templates: response::TemplateCache +pub struct Server { + middleware_stack: MiddlewareStack, + templates: response::TemplateCache, + shared_data: D, } // FIXME: Any better coherence solutions? -struct ArcServer(Arc); +struct ArcServer(Arc>); -impl Handler for ArcServer { +impl Handler for ArcServer { fn handle<'a, 'k>(&'a self, req: Request<'a, 'k>, res: Response<'a>) { - let req: Request<'a, 'k> = req; - let nickel_req = request::Request::from_internal(req); - let nickel_res = response::Response::from_internal(res, &self.0.templates); + let nickel_req = request::Request::from_internal(req, + &self.0.shared_data); + + let nickel_res = response::Response::from_internal(res, + &self.0.templates, + &self.0.shared_data); + self.0.middleware_stack.invoke(nickel_req, nickel_res); } } -impl Server { - pub fn new(middleware_stack: MiddlewareStack) -> Server { +impl Server { + pub fn new(middleware_stack: MiddlewareStack, data: D) -> Server { Server { middleware_stack: middleware_stack, - templates: RwLock::new(HashMap::new()) + templates: RwLock::new(HashMap::new()), + shared_data: data } } - pub fn serve(self, addr: T) -> HttpResult { + pub fn serve(self, addr: A) -> HttpResult { let arc = ArcServer(Arc::new(self)); let server = try!(HyperServer::http(addr)); server.handle(arc) diff --git a/src/static_files_handler.rs b/src/static_files_handler.rs index 51ad14e300..b726e1398a 100644 --- a/src/static_files_handler.rs +++ b/src/static_files_handler.rs @@ -15,8 +15,9 @@ pub struct StaticFilesHandler { root_path: PathBuf } -impl Middleware for StaticFilesHandler { - fn invoke<'a>(&self, req: &mut Request, res: Response<'a>) -> MiddlewareResult<'a> { +impl Middleware for StaticFilesHandler { + fn invoke<'a>(&self, req: &mut Request, res: Response<'a, D>) + -> MiddlewareResult<'a, D> { match req.origin.method { Get | Head => self.with_file(self.extract_path(req), res), _ => Ok(Continue(res)) @@ -43,7 +44,7 @@ impl StaticFilesHandler { } } - fn extract_path<'a>(&self, req: &'a mut Request) -> Option<&'a str> { + fn extract_path<'a, D>(&self, req: &'a mut Request) -> Option<&'a str> { req.path_without_query().map(|path| { debug!("{:?} {:?}{:?}", req.origin.method, self.root_path.display(), path); @@ -54,10 +55,10 @@ impl StaticFilesHandler { }) } - fn with_file<'a, 'b, P>(&self, + fn with_file<'a, 'b, D, P>(&self, relative_path: Option

, - res: Response<'a>) - -> MiddlewareResult<'a> where P: AsRef { + res: Response<'a, D>) + -> MiddlewareResult<'a, D> where P: AsRef { if let Some(path) = relative_path { let path = self.root_path.join(path); match fs::metadata(&path) { From 0a0e43b91b917b9d9ad923cbc981562a89a61445 Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Fri, 3 Jul 2015 12:47:12 +0100 Subject: [PATCH 02/18] feat(cookies): allow reading the cookies from a Request --- Cargo.toml | 6 ++++++ examples/cookies_example.rs | 28 +++++++++++++++++++++++++++ src/cookies.rs | 38 +++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 +++ src/request.rs | 4 ++++ 5 files changed, 79 insertions(+) create mode 100644 examples/cookies_example.rs create mode 100644 src/cookies.rs diff --git a/Cargo.toml b/Cargo.toml index 6c5010aebf..5dd05c7282 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ groupable = "*" mustache = "*" lazy_static = "*" modifier = "*" +cookie = "^0.1" [dependencies.hyper] version = "=0.6" @@ -44,6 +45,11 @@ path = "examples/example.rs" [[example]] +name = "cookies_example" +path = "examples/cookies_example.rs" + +[[example]] + name = "example_with_default_router" path = "examples/example_with_default_router.rs" diff --git a/examples/cookies_example.rs b/examples/cookies_example.rs new file mode 100644 index 0000000000..8d66ae1de5 --- /dev/null +++ b/examples/cookies_example.rs @@ -0,0 +1,28 @@ +#[macro_use] extern crate nickel; +extern crate cookie; + +use nickel::{Nickel, HttpRouter, Cookies}; +use nickel::cookies; + +struct Data { + secret_key: cookies::SecretKey +} + +impl AsRef for Data { + fn as_ref(&self) -> &cookies::SecretKey { + &self.secret_key + } +} + +fn main() { + let data = Data { secret_key: cookies::SecretKey([0; 32]) }; + let mut server = Nickel::with_data(data); + + // Try curl -b MyCookie=bar localhost:6767 + server.get("/", middleware! { |req| + let cookie = req.cookies().find("MyCookie"); + format!("MyCookie={:?}", cookie.map(|c| c.value)) + }); + + server.listen("127.0.0.1:6767"); +} diff --git a/src/cookies.rs b/src/cookies.rs new file mode 100644 index 0000000000..c4ec6094ce --- /dev/null +++ b/src/cookies.rs @@ -0,0 +1,38 @@ +use request::Request; +use plugin::{Plugin, Pluggable}; +use typemap::Key; +use hyper::header; + +use cookie::CookieJar; + +pub struct SecretKey(pub [u8; 32]); + +// Plugin boilerplate +struct CookiePlugin; +impl Key for CookiePlugin { type Value = CookieJar<'static>; } + +impl<'mw, 'conn, D> Plugin> for CookiePlugin + where D: AsRef { + type Error = (); + + fn eval(req: &mut Request) -> Result, ()> { + let key = req.data().as_ref(); + let jar = match req.origin.headers.get::() { + Some(c) => c.to_cookie_jar(&key.0), + None => CookieJar::new(&key.0) + }; + + Ok(jar) + } +} + +pub trait Cookies { + fn cookies(&mut self) -> &CookieJar; +} + +impl<'mw, 'conn, D> Cookies for Request<'mw, 'conn, D> + where D: AsRef { + fn cookies(&mut self) -> &CookieJar { + self.get_ref::().unwrap() + } +} diff --git a/src/lib.rs b/src/lib.rs index 79326e7027..f55d7172b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ extern crate url; extern crate mustache; extern crate groupable; extern crate modifier; +extern crate cookie; #[macro_use] extern crate log; #[macro_use] extern crate lazy_static; @@ -28,6 +29,7 @@ pub use router::{Router, Route, RouteResult, HttpRouter}; pub use nickel_error::NickelError; pub use mimes::MediaType; pub use responder::Responder; +pub use cookies::Cookies; #[macro_use] pub mod macros; @@ -47,6 +49,7 @@ mod query_string; mod urlencoded; mod nickel_error; mod default_error_handler; +pub mod cookies; pub mod status { pub use hyper::status::StatusCode; diff --git a/src/request.rs b/src/request.rs index f591713378..04cd0aaa0e 100644 --- a/src/request.rs +++ b/src/request.rs @@ -43,6 +43,10 @@ impl<'mw, 'server, D> Request<'mw, 'server, D> { _ => None } } + + pub fn data(&self) -> &D { + &self.data + } } impl<'mw, 'server, D> Extensible for Request<'mw, 'server, D> { From 060e9cc46e1fc9fb870b421fd5bb018bac47ff10 Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Sat, 4 Jul 2015 02:02:16 +0100 Subject: [PATCH 03/18] feat(response): allow Plugins for Response Also adds `on_send` which can be used to execute code when the Response headers are being sent. --- src/response.rs | 45 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/response.rs b/src/response.rs index 99bfd0d4fb..03f34da52f 100644 --- a/src/response.rs +++ b/src/response.rs @@ -1,3 +1,4 @@ +use std::mem; use std::borrow::Cow; use std::sync::RwLock; use std::collections::HashMap; @@ -19,6 +20,8 @@ use std::fs::File; use std::any::Any; use {NickelError, Halt, MiddlewareResult, Responder}; use modifier::Modifier; +use plugin::{Extensible, Pluggable}; +use typemap::TypeMap; pub type TemplateCache = RwLock>; @@ -28,6 +31,9 @@ pub struct Response<'a, D: 'a, T: 'static + Any = Fresh> { origin: HyperResponse<'a, T>, templates: &'a TemplateCache, data: &'a D, + map: TypeMap, + // This should be FnBox, but that's currently unstable + on_send: Vec)>> } impl<'a, D> Response<'a, D, Fresh> { @@ -38,7 +44,9 @@ impl<'a, D> Response<'a, D, Fresh> { Response { origin: response, templates: templates, - data: data + data: data, + map: TypeMap::new(), + on_send: vec![] } } @@ -243,15 +251,25 @@ impl<'a, D> Response<'a, D, Fresh> { } pub fn start(mut self) -> Result, NickelError<'a, D>> { + let on_send = mem::replace(&mut self.on_send, vec![]); + for mut f in on_send.into_iter() { + // TODO: Ensure `f` doesn't call on_send again + f(&mut self) + } + + // Set fallback headers last after everything runs, if we did this before as an + // on_send then it would possibly set redundant things. self.set_fallback_headers(); - let Response { origin, templates, data } = self; + let Response { origin, templates, data, map, on_send } = self; match origin.start() { Ok(origin) => { Ok(Response { origin: origin, templates: templates, - data: data + data: data, + map: map, + on_send: on_send }) }, Err(e) => @@ -260,6 +278,11 @@ impl<'a, D> Response<'a, D, Fresh> { } } } + + pub fn on_send(&mut self, f: F) + where F: FnMut(&mut Response<'a, D, Fresh>) + 'static { + self.on_send.push(Box::new(f)) + } } impl<'a, 'b, D> Write for Response<'a, D, Streaming> { @@ -301,8 +324,24 @@ impl <'a, D, T: 'static + Any> Response<'a, D, T> { pub fn headers(&self) -> &Headers { self.origin.headers() } + + pub fn data(&self) -> &D { + &self.data + } } +impl<'a, D, T: 'static + Any> Extensible for Response<'a, D, T> { + fn extensions(&self) -> &TypeMap { + &self.map + } + + fn extensions_mut(&mut self) -> &mut TypeMap { + &mut self.map + } +} + +impl<'a, D, T: 'static + Any> Pluggable for Response<'a, D, T> {} + fn mime_from_filename>(path: P) -> Option { path.as_ref() .extension() From 7ace3fac6e5aa0a4f231082b7a898894b2381924 Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Sat, 4 Jul 2015 02:26:48 +0100 Subject: [PATCH 04/18] feat(cookies): allow setting cookies on Response --- examples/cookies_example.rs | 19 ++++++++++++++++++- src/cookies.rs | 32 +++++++++++++++++++++++++++++++- src/lib.rs | 2 +- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/examples/cookies_example.rs b/examples/cookies_example.rs index 8d66ae1de5..fd4fe6e620 100644 --- a/examples/cookies_example.rs +++ b/examples/cookies_example.rs @@ -1,8 +1,9 @@ #[macro_use] extern crate nickel; extern crate cookie; -use nickel::{Nickel, HttpRouter, Cookies}; +use nickel::{Nickel, HttpRouter, Cookies, CookiesMut, QueryString}; use nickel::cookies; +use cookie::Cookie; struct Data { secret_key: cookies::SecretKey @@ -24,5 +25,21 @@ fn main() { format!("MyCookie={:?}", cookie.map(|c| c.value)) }); + // Note: Don't use get for login in real applications ;) + // Try http://localhost:6767/login?name=foo + server.get("/login", middleware! { |req, mut res| + let jar = res.cookies_mut() + // long life cookies! + .permanent(); + + let name = req.query().get("name") + .unwrap_or("default_name"); + let cookie = Cookie::new("MyCookie".to_owned(), + name.to_owned()); + jar.add(cookie); + + "Cookie set!" + }); + server.listen("127.0.0.1:6767"); } diff --git a/src/cookies.rs b/src/cookies.rs index c4ec6094ce..3e7cf6d136 100644 --- a/src/cookies.rs +++ b/src/cookies.rs @@ -1,4 +1,4 @@ -use request::Request; +use {Request, Response}; use plugin::{Plugin, Pluggable}; use typemap::Key; use hyper::header; @@ -36,3 +36,33 @@ impl<'mw, 'conn, D> Cookies for Request<'mw, 'conn, D> self.get_ref::().unwrap() } } + +impl<'a, 'b, 'k, D> Plugin> for CookiePlugin + where D: AsRef { + type Error = (); + + fn eval(res: &mut Response<'a, D>) -> Result, ()> { + // Schedule the cookie to be written when headers are being sent + res.on_send(|res| { + let header = { + let jar = res.get_ref::().unwrap(); + header::SetCookie::from_cookie_jar(jar) + }; + res.set(header); + }); + + let key = res.data().as_ref(); + Ok(CookieJar::new(&key.0)) + } +} + +pub trait CookiesMut { + fn cookies_mut(&mut self) -> &mut CookieJar<'static>; +} + +impl<'a, D> CookiesMut for Response<'a, D> + where D: AsRef { + fn cookies_mut(&mut self) -> &mut CookieJar<'static> { + self.get_mut::().unwrap() + } +} diff --git a/src/lib.rs b/src/lib.rs index f55d7172b2..6950020039 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,7 @@ pub use router::{Router, Route, RouteResult, HttpRouter}; pub use nickel_error::NickelError; pub use mimes::MediaType; pub use responder::Responder; -pub use cookies::Cookies; +pub use cookies::{Cookies, CookiesMut}; #[macro_use] pub mod macros; From fc83ec146d19409f30d43340a3f08ac82bf41e46 Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Sat, 4 Jul 2015 17:38:14 +0100 Subject: [PATCH 05/18] refactor(cookies): collapse to a single trait and add some docs --- examples/cookies_example.rs | 2 +- src/cookies.rs | 48 +++++++++++++++++++++++-------------- src/lib.rs | 2 +- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/examples/cookies_example.rs b/examples/cookies_example.rs index fd4fe6e620..93c3b84ec4 100644 --- a/examples/cookies_example.rs +++ b/examples/cookies_example.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate nickel; extern crate cookie; -use nickel::{Nickel, HttpRouter, Cookies, CookiesMut, QueryString}; +use nickel::{Nickel, HttpRouter, Cookies, QueryString}; use nickel::cookies; use cookie::Cookie; diff --git a/src/cookies.rs b/src/cookies.rs index 3e7cf6d136..be7240e8c3 100644 --- a/src/cookies.rs +++ b/src/cookies.rs @@ -1,5 +1,5 @@ use {Request, Response}; -use plugin::{Plugin, Pluggable}; +use plugin::{Plugin, Pluggable, Extensible}; use typemap::Key; use hyper::header; @@ -26,17 +26,6 @@ impl<'mw, 'conn, D> Plugin> for CookiePlugin } } -pub trait Cookies { - fn cookies(&mut self) -> &CookieJar; -} - -impl<'mw, 'conn, D> Cookies for Request<'mw, 'conn, D> - where D: AsRef { - fn cookies(&mut self) -> &CookieJar { - self.get_ref::().unwrap() - } -} - impl<'a, 'b, 'k, D> Plugin> for CookiePlugin where D: AsRef { type Error = (); @@ -56,13 +45,36 @@ impl<'a, 'b, 'k, D> Plugin> for CookiePlugin } } -pub trait CookiesMut { - fn cookies_mut(&mut self) -> &mut CookieJar<'static>; -} +/// Trait to whitelist access to `&'mut CookieJar` via the `Cookies` trait. +pub trait AllowMutCookies {} +impl<'a, D> AllowMutCookies for Response<'a, D> {} -impl<'a, D> CookiesMut for Response<'a, D> - where D: AsRef { - fn cookies_mut(&mut self) -> &mut CookieJar<'static> { +/// Provides access to a `CookieJar`. +/// +/// Access to cookies for a `Request` is read-only and represents the cookies +/// sent from the client. +/// +/// The `Response` has access to a mutable `CookieJar` when first accessed. +/// Any cookies added to this jar will be sent as `Set-Cookie` response headers +/// when the `Response` sends it's `Headers` to the client. +/// +/// #Examples +/// See `examples/cookies_example.rs`. +pub trait Cookies : Pluggable + Extensible + where CookiePlugin: Plugin, Error=()> { + /// Provides access to an immutable CookieJar. + /// + /// Currently requires a mutable reciever, hopefully this can change in future. + fn cookies(&mut self) -> &CookieJar { + self.get_ref::().unwrap() + } + + /// Provides access to a mutable CookieJar. + fn cookies_mut(&mut self) -> &mut CookieJar<'static> where Self: AllowMutCookies { self.get_mut::().unwrap() } } + +impl<'mw, 'conn, D: AsRef> Cookies for Request<'mw, 'conn, D> {} + +impl<'a, D: AsRef> Cookies for Response<'a, D> {} diff --git a/src/lib.rs b/src/lib.rs index 6950020039..f55d7172b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,7 @@ pub use router::{Router, Route, RouteResult, HttpRouter}; pub use nickel_error::NickelError; pub use mimes::MediaType; pub use responder::Responder; -pub use cookies::{Cookies, CookiesMut}; +pub use cookies::Cookies; #[macro_use] pub mod macros; From a3e3c26b39e2a4cdcb46e7bcccb4ef3ea1a8f2c3 Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Sun, 5 Jul 2015 23:45:11 +0100 Subject: [PATCH 06/18] fix(cookies): add explicit implementations for Cookies trait The 1.2 beta has a regression for this kind of code, so being explicit should allow us to compile on all targets. --- src/cookies.rs | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/cookies.rs b/src/cookies.rs index be7240e8c3..2b551b3df9 100644 --- a/src/cookies.rs +++ b/src/cookies.rs @@ -1,5 +1,5 @@ use {Request, Response}; -use plugin::{Plugin, Pluggable, Extensible}; +use plugin::{Plugin, Pluggable}; use typemap::Key; use hyper::header; @@ -8,7 +8,7 @@ use cookie::CookieJar; pub struct SecretKey(pub [u8; 32]); // Plugin boilerplate -struct CookiePlugin; +pub struct CookiePlugin; impl Key for CookiePlugin { type Value = CookieJar<'static>; } impl<'mw, 'conn, D> Plugin> for CookiePlugin @@ -60,21 +60,32 @@ impl<'a, D> AllowMutCookies for Response<'a, D> {} /// /// #Examples /// See `examples/cookies_example.rs`. -pub trait Cookies : Pluggable + Extensible - where CookiePlugin: Plugin, Error=()> { +pub trait Cookies { /// Provides access to an immutable CookieJar. /// /// Currently requires a mutable reciever, hopefully this can change in future. - fn cookies(&mut self) -> &CookieJar { + fn cookies(&mut self) -> &CookieJar<'static>; + + /// Provides access to a mutable CookieJar. + fn cookies_mut(&mut self) -> &mut CookieJar<'static> where Self: AllowMutCookies; +} + +impl<'mw, 'conn, D: AsRef> Cookies for Request<'mw, 'conn, D> { + fn cookies(&mut self) -> &::Value { self.get_ref::().unwrap() } - /// Provides access to a mutable CookieJar. - fn cookies_mut(&mut self) -> &mut CookieJar<'static> where Self: AllowMutCookies { - self.get_mut::().unwrap() + fn cookies_mut(&mut self) -> &mut ::Value where Self: AllowMutCookies { + unreachable!() } } -impl<'mw, 'conn, D: AsRef> Cookies for Request<'mw, 'conn, D> {} +impl<'mw, D: AsRef> Cookies for Response<'mw, D> { + fn cookies(&mut self) -> &::Value { + self.get_ref::().unwrap() + } -impl<'a, D: AsRef> Cookies for Response<'a, D> {} + fn cookies_mut(&mut self) -> &mut ::Value where Self: AllowMutCookies { + self.get_mut::().unwrap() + } +} From b5b1f6fc16e2d7d8cbd7fd11bdc507e394ecaa52 Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Mon, 6 Jul 2015 01:13:33 +0100 Subject: [PATCH 07/18] chore(tests): update compile-fail tests --- tests/compile-fail/inference_fully_hinted.rs | 8 ++++---- tests/compile-fail/inference_no_hints.rs | 8 ++++---- tests/compile-fail/inference_request_hinted.rs | 8 ++++---- tests/compile-fail/inference_response_hinted.rs | 12 ++++++------ 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/compile-fail/inference_fully_hinted.rs b/tests/compile-fail/inference_fully_hinted.rs index bee4328b85..645d1572a2 100644 --- a/tests/compile-fail/inference_fully_hinted.rs +++ b/tests/compile-fail/inference_fully_hinted.rs @@ -8,11 +8,11 @@ use nickel::{Nickel, HttpRouter, Request, Response}; fn main() { let mut server = Nickel::new(); - server.utilize(|_: &mut Request, res: Response| res.send("Hello World!")); - //~^ ERROR type mismatch resolving `for<'r,'b,'a> <[closure tests/com + server.utilize(|_: &mut Request<()>, res: Response<()>| res.send("Hello World!")); + //~^ ERROR type mismatch resolving `for<' - server.get("**", |_: &mut Request, res: Response| res.send("Hello World!")); - //~^ ERROR type mismatch resolving `for<'r,'b,'a> <[closure tests/com + server.get("**", |_: &mut Request<()>, res: Response<()>| res.send("Hello World!")); + //~^ ERROR type mismatch resolving `for<' server.listen("127.0.0.1:6767"); } diff --git a/tests/compile-fail/inference_no_hints.rs b/tests/compile-fail/inference_no_hints.rs index 5b4780d7c9..f524af5a23 100644 --- a/tests/compile-fail/inference_no_hints.rs +++ b/tests/compile-fail/inference_no_hints.rs @@ -9,14 +9,14 @@ fn main() { let mut server = Nickel::new(); server.utilize(|_, res| res.send("Hello World!")); - //~^ ERROR type mismatch resolving `for<'r,'b,'a> <[closure tests/compile-fail + //~^ ERROR type mismatch resolving `for<' //~^^ ERROR the type of this value must be known in this context - //~^^^ ERROR type mismatch: the type `[closure tests/compile + //~^^^ ERROR type mismatch: the type `[closure@tests/compile server.get("**", |_, res| res.send("Hello World!")); - //~^ ERROR type mismatch resolving `for<'r,'b,'a> <[closure tests/compile-fail + //~^ ERROR type mismatch resolving `for<' //~^^ ERROR the type of this value must be known in this context - //~^^^ ERROR type mismatch: the type `[closure tests/compile + //~^^^ ERROR type mismatch: the type `[closure@tests/compile server.listen("127.0.0.1:6767"); } diff --git a/tests/compile-fail/inference_request_hinted.rs b/tests/compile-fail/inference_request_hinted.rs index 1b46b5e098..b6c6d96cc3 100644 --- a/tests/compile-fail/inference_request_hinted.rs +++ b/tests/compile-fail/inference_request_hinted.rs @@ -9,13 +9,13 @@ fn main() { let mut server = Nickel::new(); // Request hinted - server.utilize(|_: &mut Request, res| res.send("Hello World!")); + server.utilize(|_: &mut Request<()>, res| res.send("Hello World!")); //~^ ERROR the type of this value must be known in this context - //~^^ ERROR type mismatch resolving `for<'r,'b,'a> + //~^^ ERROR type mismatch resolving `for<' - server.get("**", |_: &mut Request, res| res.send("Hello World!")); + server.get("**", |_: &mut Request<()>, res| res.send("Hello World!")); //~^ ERROR the type of this value must be known in this context - //~^^ ERROR type mismatch resolving `for<'r,'b,'a> + //~^^ ERROR type mismatch resolving `for<' server.listen("127.0.0.1:6767"); } diff --git a/tests/compile-fail/inference_response_hinted.rs b/tests/compile-fail/inference_response_hinted.rs index 6b8f07e36c..a7c9431afb 100644 --- a/tests/compile-fail/inference_response_hinted.rs +++ b/tests/compile-fail/inference_response_hinted.rs @@ -8,13 +8,13 @@ use nickel::{Nickel, HttpRouter, Request, Response}; fn main() { let mut server = Nickel::new(); - server.utilize(|_, res: Response| res.send("Hello World!")); - //~^ ERROR type mismatch resolving `for<'r,'b,'a> <[closure tests/compile-fail - //~^^ ERROR type mismatch: the type `[closure tests/compile-fail + server.utilize(|_, res: Response<()>| res.send("Hello World!")); + //~^ ERROR type mismatch resolving `for<' + //~^^ ERROR type mismatch: the type `[closure@tests/compile-fail - server.get("**", |_, res: Response| res.send("Hello World!")); - //~^ ERROR type mismatch resolving `for<'r,'b,'a> <[closure tests/compile-fail - //~^^ ERROR type mismatch: the type `[closure tests/compile-fail + server.get("**", |_, res: Response<()>| res.send("Hello World!")); + //~^ ERROR type mismatch resolving `for<' + //~^^ ERROR type mismatch: the type `[closure@tests/compile-fail server.listen("127.0.0.1:6767"); } From 07a697d7b9dffd311baa34dd506fd154e5629808 Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Mon, 6 Jul 2015 16:47:27 +0100 Subject: [PATCH 08/18] test(server): add a compile-fail test to ensure reasonable error message --- tests/compile-fail/unmet_data_requirement.rs | 41 ++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 tests/compile-fail/unmet_data_requirement.rs diff --git a/tests/compile-fail/unmet_data_requirement.rs b/tests/compile-fail/unmet_data_requirement.rs new file mode 100644 index 0000000000..6a60f90b26 --- /dev/null +++ b/tests/compile-fail/unmet_data_requirement.rs @@ -0,0 +1,41 @@ +#[macro_use] extern crate nickel; +extern crate cookie; + +use nickel::{Nickel, HttpRouter, Cookies, QueryString}; +use nickel::cookies; +use cookie::Cookie; + +struct Data { + secret_key: cookies::SecretKey +} + +fn main() { + let data = Data { secret_key: cookies::SecretKey([0; 32]) }; + let mut server = Nickel::with_data(data); + + // Try curl -b MyCookie=bar localhost:6767 + server.get("/", middleware! { |req| + let cookie = req.cookies().find("MyCookie"); + //~^ ERROR: the trait `core::convert::AsRef` is not implemented for the type `Data` + format!("MyCookie={:?}", cookie.map(|c| c.value)) + }); + + // Note: Don't use get for login in real applications ;) + // Try http://localhost:6767/login?name=foo + server.get("/login", middleware! { |req, mut res| + let jar = res.cookies_mut() + //~^ ERROR: the trait `core::convert::AsRef` is not implemented for the type `Data` + // long life cookies! + .permanent(); + + let name = req.query().get("name") + .unwrap_or("default_name"); + let cookie = Cookie::new("MyCookie".to_owned(), + name.to_owned()); + jar.add(cookie); + + "Cookie set!" + }); + + server.listen("127.0.0.1:6767"); +} From 696b4a107a1bf093e8847522c5a3c1e8cec90782 Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Fri, 17 Jul 2015 21:21:58 +0100 Subject: [PATCH 09/18] feat(macros): allow hinting the server data type in middleware macro This can sometimes be required when using some middleware which are predicated on the datatype of the Server. An alternative would be to litter the handler with extra type annotations (which is awkward without type ascription), or to use explicit type-annotated functions as middleware. Example usage: ``` middleware! { |res| // res is of type Response } ``` --- src/macros/middleware.rs | 44 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/macros/middleware.rs b/src/macros/middleware.rs index b8977b9170..db01377626 100644 --- a/src/macros/middleware.rs +++ b/src/macros/middleware.rs @@ -23,8 +23,29 @@ /// server.listen("127.0.0.1:6767"); /// # } /// ``` +/// +/// # Type hinting +/// Sometimes type inference is unable to determine the datatype for the server, +/// which can lead to a lot of extra type annotations. The `middleware!` macro +/// supports annotating the macro so as to drive the inference allowing the handler +/// code to remain with minimal annotations. +/// +/// ``` +/// # #[macro_use] extern crate nickel; +/// # fn main() { +/// # struct MyServerData; +/// middleware! { |_request, _response| +/// // _response is of type Response +/// "Hello World" +/// } +/// # ; // This semicolon is required to satisfy returning `()` +/// # } +/// ``` #[macro_export] macro_rules! middleware { + (|$req:tt, mut $res:ident| <$data:path> $($b:tt)+) => { _middleware_inner!($req, $res, mut $res, <$data> $($b)+) }; + (|$req:tt, $res:ident| <$data:path> $($b:tt)+) => { _middleware_inner!($req, $res, $res, <$data> $($b)+) }; + (|$req:tt| <$data:path> $($b:tt)+) => { middleware!(|$req, _res| <$data> $($b)+) }; (|$req:tt, mut $res:ident| $($b:tt)+) => { _middleware_inner!($req, $res, mut $res, $($b)+) }; (|$req:tt, $res:ident| $($b:tt)+) => { _middleware_inner!($req, $res, $res, $($b)+) }; (|$req:tt| $($b:tt)+) => { middleware!(|$req, _res| $($b)+) }; @@ -34,7 +55,28 @@ macro_rules! middleware { #[doc(hidden)] #[macro_export] macro_rules! _middleware_inner { - ($req:tt, $res:ident, $res_binding:pat, $($b:tt)+) => {{ + ($req:tt, $res:ident, $res_binding:pat, <$data:path> $($b:tt)+) => {{ + use $crate::{MiddlewareResult,Responder, Response, Request}; + + #[inline(always)] + fn restrict<'mw, R: Responder<$data>>(r: R, res: Response<'mw, $data>) + -> MiddlewareResult<'mw, $data> { + res.send(r) + } + + // Inference fails due to thinking it's a (&Request, Response) with + // different mutability requirements + #[inline(always)] + fn restrict_closure(f: F) -> F + where F: for<'r, 'mw, 'conn> + Fn(&'r mut Request<'mw, 'conn, $data>, Response<'mw, $data>) + -> MiddlewareResult<'mw, $data> + Send + Sync { f } + + restrict_closure(move |as_pat!($req), $res_binding| { + restrict(as_block!({$($b)+}), $res) + }) + }}; + ($req:tt, $res:ident, $res_binding:pat, $($b:tt)+) => {{ use $crate::{MiddlewareResult,Responder, Response, Request}; #[inline(always)] From 375435645eff238865aef899b664ddc2d59c04b8 Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Sat, 15 Aug 2015 01:12:47 +0100 Subject: [PATCH 10/18] feat(session): add CookieSession as an implementation of a Session store --- Cargo.toml | 6 ++ examples/session_example.rs | 74 +++++++++++++++++++ src/lib.rs | 3 + src/response.rs | 2 +- src/session.rs | 138 ++++++++++++++++++++++++++++++++++++ 5 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 examples/session_example.rs create mode 100644 src/session.rs diff --git a/Cargo.toml b/Cargo.toml index 5dd05c7282..b3e1724b38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ mustache = "*" lazy_static = "*" modifier = "*" cookie = "^0.1" +byteorder = "^0.3" [dependencies.hyper] version = "=0.6" @@ -45,6 +46,11 @@ path = "examples/example.rs" [[example]] +name = "session_example" +path = "examples/session_example.rs" + +[[example]] + name = "cookies_example" path = "examples/cookies_example.rs" diff --git a/examples/session_example.rs b/examples/session_example.rs new file mode 100644 index 0000000000..c4804560ed --- /dev/null +++ b/examples/session_example.rs @@ -0,0 +1,74 @@ +#[macro_use] extern crate nickel; +extern crate rustc_serialize; +extern crate time; + +use std::io::Write; +use nickel::*; +use nickel::status::StatusCode; +use time::Duration; + +#[derive(RustcDecodable, RustcEncodable)] +struct User { + name: String, + password: String, +} + +struct ServerData; +static SECRET_KEY: &'static cookies::SecretKey = &cookies::SecretKey([0; 32]); + +impl AsRef for ServerData { + fn as_ref(&self) -> &cookies::SecretKey { SECRET_KEY } +} + +impl SessionStore for ServerData { + type Store = Option; + + fn timeout() -> Duration { + Duration::seconds(5) + } +} + + +fn main() { + let mut server = Nickel::with_data(ServerData); + + // Anyone should be able to reach this route + server.get("/", middleware! { |req, mut res| + format!("You are logged in as: {:?}\n", CookieSession::get_mut(req, &mut res)) + }); + + server.post("/login", middleware!{|req, mut res| + if let Ok(u) = req.json_as::() { + if u.name == "foo" && u.password == "bar" { + *CookieSession::get_mut(req, &mut res) = Some(u.name); + return res.send("Successfully logged in.") + } + } + (StatusCode::BadRequest, "Access denied.") + }); + + server.get("/secret", middleware! { |req, mut res| + match *CookieSession::get_mut(req, &mut res) { + Some(ref user) if user == "foo" => (StatusCode::Ok, "Some hidden information!"), + _ => (StatusCode::Forbidden, "Access denied.") + } + }); + + fn custom_403<'a>(err: &mut NickelError, _: &mut Request) -> Action { + if let Some(ref mut res) = err.stream { + if res.status() == StatusCode::Forbidden { + let _ = res.write_all(b"Access denied!\n"); + return Halt(()) + } + } + + Continue(()) + } + + // issue #20178 + let custom_handler: fn(&mut NickelError, &mut Request) -> Action = custom_403; + + server.handle_error(custom_handler); + + server.listen("127.0.0.1:6767"); +} diff --git a/src/lib.rs b/src/lib.rs index f55d7172b2..ca7f1acf28 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ extern crate mustache; extern crate groupable; extern crate modifier; extern crate cookie; +extern crate byteorder; #[macro_use] extern crate log; #[macro_use] extern crate lazy_static; @@ -30,6 +31,7 @@ pub use nickel_error::NickelError; pub use mimes::MediaType; pub use responder::Responder; pub use cookies::Cookies; +pub use session::{Session, SessionStore, CookieSession}; #[macro_use] pub mod macros; @@ -50,6 +52,7 @@ mod urlencoded; mod nickel_error; mod default_error_handler; pub mod cookies; +pub mod session; pub mod status { pub use hyper::status::StatusCode; diff --git a/src/response.rs b/src/response.rs index 03f34da52f..e960945d3e 100644 --- a/src/response.rs +++ b/src/response.rs @@ -252,7 +252,7 @@ impl<'a, D> Response<'a, D, Fresh> { pub fn start(mut self) -> Result, NickelError<'a, D>> { let on_send = mem::replace(&mut self.on_send, vec![]); - for mut f in on_send.into_iter() { + for mut f in on_send.into_iter().rev() { // TODO: Ensure `f` doesn't call on_send again f(&mut self) } diff --git a/src/session.rs b/src/session.rs new file mode 100644 index 0000000000..067123923c --- /dev/null +++ b/src/session.rs @@ -0,0 +1,138 @@ +use {Request, Response, Cookies}; +use cookie::Cookie; +use plugin::{Plugin, Pluggable}; +use typemap::Key; +use std::marker::PhantomData; +use std::any::Any; +use time::{Timespec, Duration, self}; +use serialize::{Encodable, Decodable, json}; +use byteorder::{ByteOrder, BigEndian}; +use std::error::Error; +use std::str; +use std::fmt::Debug; + +static COOKIE_KEY : &'static str = "__SESSION"; + +pub trait SessionStore { + type Store: Encodable + Decodable + Default + Debug; + + fn timeout() -> Duration { Duration::minutes(60) } +} + +// Plugin boilerplate +pub struct SessionPlugin(PhantomData); +impl Key for SessionPlugin { type Value = Option; } + +impl<'mw, D, T> Plugin> for SessionPlugin +where Response<'mw, D> : Cookies, + T: 'static + Any + Encodable + Decodable + Default + Debug, + D: SessionStore { + type Error = (); + + fn eval(response: &mut Response<'mw, D>) -> Result, ()> { + // Ensure our dependencies register their on_send + // FIXME: would be nice if this was more robust, but at least for now + // this minimizes the 'bug potential' to the library author rather than + // the library user. + let _ = response.cookies_mut().encrypted(); + + // Schedule the session to be written when headers are being sent + response.on_send(|response| { + let encoded = { + // This should only ever get called after an initial setup, so these + // unwraps should be fine! + let session = response.get_mut::>() + .unwrap(); + + // Todo: track when a write has occurred and only create the cookie in + // that case + encode_data(session.as_ref().unwrap()) + }; + + let jar = response.cookies_mut().encrypted(); + let mut cookie = Cookie::new(COOKIE_KEY.into(), encoded); + cookie.httponly = true; + jar.add(cookie); + }); + + Ok(None) + } +} + +fn decode_data(raw: &str, timeout: Duration) -> Result> { + use serialize::base64::FromBase64; + + let timestamp_and_plaintext = try!(raw.from_base64()); + + let len = timestamp_and_plaintext.len(); + let (plaintext, timestamp) = timestamp_and_plaintext.split_at(len - 8); + + let timestamp = BigEndian::read_i64(timestamp); + let plaintext = try!(str::from_utf8(plaintext)); + let timestamp = Timespec::new(timestamp, 0); + + if timestamp + timeout > time::now().to_timespec() { + let decoded = try!(json::decode(plaintext)); + Ok(decoded) + } else { + // Reset the session, not an error + Ok(T::default()) + } +} + +fn encode_data(data: &T) -> String { + use serialize::base64::{ToBase64, STANDARD}; + + // TODO: log if this fails + let json = match json::encode(data) { + Ok(json) => json, + Err(e) => { + println!("[Session] Failed to encode '{:?}' as json: {:?}", data, e); + return "".into() + }, + }; + + let mut raw = json.into_bytes(); + + let mut timestamp = [0u8; 8]; + BigEndian::write_i64(&mut timestamp, time::now().to_timespec().sec); + raw.extend(timestamp.iter().cloned()); + + raw.to_base64(STANDARD) +} + +pub trait Session where D: SessionStore { + /// Provides access to a mutable Session. + fn get_mut<'a>(&mut Request, &'a mut Response) -> &'a mut D::Store; +} + +pub struct CookieSession; + +impl Session for CookieSession +where for <'mw, 'conn> Request<'mw, 'conn, D> : Cookies, + for <'mw> Response<'mw, D> : Cookies, + D: SessionStore, + D::Store : 'static + Any + Encodable + Decodable + Default + Debug { + fn get_mut<'a>(req: &mut Request, res: &'a mut Response) -> &'a mut D::Store { + let cached_session = res.get_mut::>().unwrap(); + if let Some(ref mut session) = *cached_session { + return session + } + + let jar = req.cookies().encrypted(); + let data = jar.find(COOKIE_KEY).and_then(|cookie| { + let timeout = ::timeout(); + match decode_data(&*cookie.value, timeout) { + Ok(data) => Some(data), + Err(e) => { + println!("Error parsing session: {:?}", e); + None + } + } + }); + + // Any error resets the session + *cached_session = data.or_else(|| Some(::default())); + cached_session.as_mut().unwrap() + } +} From 45e25d539bad2bdd3546217055aa909ecb3684a0 Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Wed, 26 Aug 2015 19:49:54 +0100 Subject: [PATCH 11/18] refactor(cookies): add explicit trait required for cookies --- examples/cookies_example.rs | 6 +++--- examples/session_example.rs | 4 ++-- src/cookies.rs | 27 +++++++++++++++++++++------ 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/examples/cookies_example.rs b/examples/cookies_example.rs index 93c3b84ec4..a0aea8f592 100644 --- a/examples/cookies_example.rs +++ b/examples/cookies_example.rs @@ -9,9 +9,9 @@ struct Data { secret_key: cookies::SecretKey } -impl AsRef for Data { - fn as_ref(&self) -> &cookies::SecretKey { - &self.secret_key +impl cookies::KeyProvider for Data { + fn key(&self) -> cookies::SecretKey { + self.secret_key.clone() } } diff --git a/examples/session_example.rs b/examples/session_example.rs index c4804560ed..6b0500fbd0 100644 --- a/examples/session_example.rs +++ b/examples/session_example.rs @@ -16,8 +16,8 @@ struct User { struct ServerData; static SECRET_KEY: &'static cookies::SecretKey = &cookies::SecretKey([0; 32]); -impl AsRef for ServerData { - fn as_ref(&self) -> &cookies::SecretKey { SECRET_KEY } +impl cookies::KeyProvider for ServerData { + fn key(&self) -> cookies::SecretKey { SECRET_KEY.clone() } } impl SessionStore for ServerData { diff --git a/src/cookies.rs b/src/cookies.rs index 2b551b3df9..f1a2eccc1b 100644 --- a/src/cookies.rs +++ b/src/cookies.rs @@ -5,6 +5,8 @@ use hyper::header; use cookie::CookieJar; +#[derive(Clone)] +// Let's not derive `Copy` as that seems like a bad idea for key data pub struct SecretKey(pub [u8; 32]); // Plugin boilerplate @@ -12,11 +14,11 @@ pub struct CookiePlugin; impl Key for CookiePlugin { type Value = CookieJar<'static>; } impl<'mw, 'conn, D> Plugin> for CookiePlugin - where D: AsRef { +where D: KeyProvider { type Error = (); fn eval(req: &mut Request) -> Result, ()> { - let key = req.data().as_ref(); + let key = req.data().key(); let jar = match req.origin.headers.get::() { Some(c) => c.to_cookie_jar(&key.0), None => CookieJar::new(&key.0) @@ -27,7 +29,7 @@ impl<'mw, 'conn, D> Plugin> for CookiePlugin } impl<'a, 'b, 'k, D> Plugin> for CookiePlugin - where D: AsRef { +where D: KeyProvider { type Error = (); fn eval(res: &mut Response<'a, D>) -> Result, ()> { @@ -40,7 +42,7 @@ impl<'a, 'b, 'k, D> Plugin> for CookiePlugin res.set(header); }); - let key = res.data().as_ref(); + let key = res.data().key(); Ok(CookieJar::new(&key.0)) } } @@ -49,6 +51,17 @@ impl<'a, 'b, 'k, D> Plugin> for CookiePlugin pub trait AllowMutCookies {} impl<'a, D> AllowMutCookies for Response<'a, D> {} +/// Provide the key used for decoding secure CookieJars +/// +/// Cookies require a random key for their signed and encrypted cookies to be +/// used. +/// +/// Implementors should aim to provide a stable key between server reboots so +/// as to minimize data loss in client cookies. +pub trait KeyProvider { + fn key(&self) -> SecretKey; +} + /// Provides access to a `CookieJar`. /// /// Access to cookies for a `Request` is read-only and represents the cookies @@ -70,7 +83,8 @@ pub trait Cookies { fn cookies_mut(&mut self) -> &mut CookieJar<'static> where Self: AllowMutCookies; } -impl<'mw, 'conn, D: AsRef> Cookies for Request<'mw, 'conn, D> { +impl<'mw, 'conn, D> Cookies for Request<'mw, 'conn, D> +where D: KeyProvider { fn cookies(&mut self) -> &::Value { self.get_ref::().unwrap() } @@ -80,7 +94,8 @@ impl<'mw, 'conn, D: AsRef> Cookies for Request<'mw, 'conn, D> { } } -impl<'mw, D: AsRef> Cookies for Response<'mw, D> { +impl<'mw, D> Cookies for Response<'mw, D> +where D: KeyProvider { fn cookies(&mut self) -> &::Value { self.get_ref::().unwrap() } From bf001c2a63022bc86aff3bd19ef24cb3b8334e9a Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Wed, 26 Aug 2015 20:12:47 +0100 Subject: [PATCH 12/18] feat(cookies): add default implementation of KeyProvider --- Cargo.toml | 1 + src/cookies.rs | 28 +++++++++++++++++++++++++++- src/lib.rs | 1 + 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b3e1724b38..5994366fb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ lazy_static = "*" modifier = "*" cookie = "^0.1" byteorder = "^0.3" +rand = "^0.3" [dependencies.hyper] version = "=0.6" diff --git a/src/cookies.rs b/src/cookies.rs index f1a2eccc1b..8462bbbb38 100644 --- a/src/cookies.rs +++ b/src/cookies.rs @@ -2,6 +2,7 @@ use {Request, Response}; use plugin::{Plugin, Pluggable}; use typemap::Key; use hyper::header; +use rand::{self, Rng}; use cookie::CookieJar; @@ -9,6 +10,20 @@ use cookie::CookieJar; // Let's not derive `Copy` as that seems like a bad idea for key data pub struct SecretKey(pub [u8; 32]); +impl SecretKey { + pub fn new>(arr: T) -> Result { + let arr = arr.as_ref(); + if arr.len() != 32 { return Err("Key length must be 32") } + + let mut key = [0; 32]; + for idx in 0..32 { + key[idx] = arr[idx] + } + + Ok(SecretKey(key)) + } +} + // Plugin boilerplate pub struct CookiePlugin; impl Key for CookiePlugin { type Value = CookieJar<'static>; } @@ -59,7 +74,18 @@ impl<'a, D> AllowMutCookies for Response<'a, D> {} /// Implementors should aim to provide a stable key between server reboots so /// as to minimize data loss in client cookies. pub trait KeyProvider { - fn key(&self) -> SecretKey; + fn key(&self) -> SecretKey { + lazy_static! { + static ref CACHED_SECRET: SecretKey = { + let mut rng = rand::thread_rng(); + let bytes: Vec = (0..32).map(|_| rng.gen()).collect(); + + SecretKey::new(bytes).unwrap() + }; + }; + + CACHED_SECRET.clone() + } } /// Provides access to a `CookieJar`. diff --git a/src/lib.rs b/src/lib.rs index ca7f1acf28..f55a6860c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ extern crate groupable; extern crate modifier; extern crate cookie; extern crate byteorder; +extern crate rand; #[macro_use] extern crate log; #[macro_use] extern crate lazy_static; From 8591b8de50cb5ce1d40f1bc5bc92921ebf89daee Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Thu, 27 Aug 2015 00:20:46 +0100 Subject: [PATCH 13/18] feat(cookies): allow easier non-encrypted cookie usage --- Cargo.toml | 4 ++- examples/cookies_example.rs | 14 +------- examples/session_example.rs | 8 +++++ src/cookies.rs | 14 ++++++++ src/lib.rs | 2 ++ tests/cfail.rs | 4 +++ tests/compile-fail/unmet_data_requirement.rs | 35 +++++--------------- 7 files changed, 40 insertions(+), 41 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5994366fb3..8230c35b6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,8 @@ keywords = ["nickel", "server", "web", "express"] [features] unstable = ["hyper/nightly", "compiletest_rs"] ssl = ["hyper/ssl"] +secure_cookies = ["cookie/secure"] +session = ["secure_cookies"] [dependencies] url = "*" @@ -33,7 +35,7 @@ byteorder = "^0.3" rand = "^0.3" [dependencies.hyper] -version = "=0.6" +version = "^0.6" default-features = false [dependencies.compiletest_rs] diff --git a/examples/cookies_example.rs b/examples/cookies_example.rs index a0aea8f592..184fedc26f 100644 --- a/examples/cookies_example.rs +++ b/examples/cookies_example.rs @@ -2,22 +2,10 @@ extern crate cookie; use nickel::{Nickel, HttpRouter, Cookies, QueryString}; -use nickel::cookies; use cookie::Cookie; -struct Data { - secret_key: cookies::SecretKey -} - -impl cookies::KeyProvider for Data { - fn key(&self) -> cookies::SecretKey { - self.secret_key.clone() - } -} - fn main() { - let data = Data { secret_key: cookies::SecretKey([0; 32]) }; - let mut server = Nickel::with_data(data); + let mut server = Nickel::new(); // Try curl -b MyCookie=bar localhost:6767 server.get("/", middleware! { |req| diff --git a/examples/session_example.rs b/examples/session_example.rs index 6b0500fbd0..a2e01b8992 100644 --- a/examples/session_example.rs +++ b/examples/session_example.rs @@ -1,3 +1,6 @@ +//! This example only works if you enable the `session` feature. +#![cfg_attr(not(feature = "session"), allow(dead_code, unused_imports))] + #[macro_use] extern crate nickel; extern crate rustc_serialize; extern crate time; @@ -16,10 +19,12 @@ struct User { struct ServerData; static SECRET_KEY: &'static cookies::SecretKey = &cookies::SecretKey([0; 32]); +#[cfg(feature = "session")] impl cookies::KeyProvider for ServerData { fn key(&self) -> cookies::SecretKey { SECRET_KEY.clone() } } +#[cfg(feature = "session")] impl SessionStore for ServerData { type Store = Option; @@ -28,7 +33,10 @@ impl SessionStore for ServerData { } } +#[cfg(not(feature = "session"))] +fn main() {} +#[cfg(feature = "session")] fn main() { let mut server = Nickel::with_data(ServerData); diff --git a/src/cookies.rs b/src/cookies.rs index 8462bbbb38..55a563f319 100644 --- a/src/cookies.rs +++ b/src/cookies.rs @@ -73,6 +73,10 @@ impl<'a, D> AllowMutCookies for Response<'a, D> {} /// /// Implementors should aim to provide a stable key between server reboots so /// as to minimize data loss in client cookies. +/// +/// # Implementing +/// +/// The `secure_cookies` feature needs to be enabled for this to be implementable pub trait KeyProvider { fn key(&self) -> SecretKey { lazy_static! { @@ -88,6 +92,16 @@ pub trait KeyProvider { } } +#[cfg(feature = "secure_cookies")] +impl KeyProvider for () {} + +#[cfg(not(feature = "secure_cookies"))] +impl KeyProvider for T { + fn key(&self) -> SecretKey { + SecretKey([0; 32]) + } +} + /// Provides access to a `CookieJar`. /// /// Access to cookies for a `Request` is read-only and represents the cookies diff --git a/src/lib.rs b/src/lib.rs index f55a6860c1..d91e3c9bdb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,8 @@ pub use nickel_error::NickelError; pub use mimes::MediaType; pub use responder::Responder; pub use cookies::Cookies; + +#[cfg(feature = "session")] pub use session::{Session, SessionStore, CookieSession}; #[macro_use] pub mod macros; diff --git a/tests/cfail.rs b/tests/cfail.rs index b775dba7a9..205b26659e 100644 --- a/tests/cfail.rs +++ b/tests/cfail.rs @@ -12,6 +12,10 @@ fn run_mode(mode: &'static str) { config.mode = cfg_mode; config.src_base = format!("tests/{}", mode).into(); + if cfg!(not(feature = "secure_cookies")) { + config.filter = Some("inference".into()); + } + compiletest::run_tests(&config); } diff --git a/tests/compile-fail/unmet_data_requirement.rs b/tests/compile-fail/unmet_data_requirement.rs index 6a60f90b26..d757af95da 100644 --- a/tests/compile-fail/unmet_data_requirement.rs +++ b/tests/compile-fail/unmet_data_requirement.rs @@ -1,40 +1,21 @@ #[macro_use] extern crate nickel; -extern crate cookie; -use nickel::{Nickel, HttpRouter, Cookies, QueryString}; -use nickel::cookies; -use cookie::Cookie; +use nickel::{Nickel, HttpRouter, Cookies}; -struct Data { - secret_key: cookies::SecretKey -} +struct MyData; fn main() { - let data = Data { secret_key: cookies::SecretKey([0; 32]) }; - let mut server = Nickel::with_data(data); + let mut server = Nickel::with_data(MyData); - // Try curl -b MyCookie=bar localhost:6767 server.get("/", middleware! { |req| - let cookie = req.cookies().find("MyCookie"); - //~^ ERROR: the trait `core::convert::AsRef` is not implemented for the type `Data` - format!("MyCookie={:?}", cookie.map(|c| c.value)) + let cookie = req.cookies(); + //~^ ERROR: the trait `nickel::cookies::KeyProvider` is not implemented for the type `MyData` + //~^^ ERROR: cannot infer an appropriate lifetime }); - // Note: Don't use get for login in real applications ;) - // Try http://localhost:6767/login?name=foo server.get("/login", middleware! { |req, mut res| - let jar = res.cookies_mut() - //~^ ERROR: the trait `core::convert::AsRef` is not implemented for the type `Data` - // long life cookies! - .permanent(); - - let name = req.query().get("name") - .unwrap_or("default_name"); - let cookie = Cookie::new("MyCookie".to_owned(), - name.to_owned()); - jar.add(cookie); - - "Cookie set!" + let jar = res.cookies_mut(); + //~^ ERROR: the trait `nickel::cookies::KeyProvider` is not implemented for the type `MyData` }); server.listen("127.0.0.1:6767"); From 279301e79cdd0be82b9e8a743e3bac62f6eebd20 Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Thu, 27 Aug 2015 00:21:04 +0100 Subject: [PATCH 14/18] chore(travis): update build matrix --- .travis.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 116e59e3a8..7513ccead6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,9 +20,9 @@ before_script: script: - | - travis-cargo build && - travis-cargo test && - travis-cargo doc && + travis-cargo build $FEATURES && + travis-cargo test $FEATURES && + travis-cargo doc $FEATURES && echo "Testing README" && rustdoc --test README.md -L dependency=./target/debug/deps --extern nickel=./target/debug/libnickel.rlib @@ -34,3 +34,10 @@ after_success: env: global: secure: nPXTdkq9TK4LmqJWqB4jxscDG1mnqJzO0UX5ni+oC0SohjgBxdWjbvJ8Kthk3c8Qg/2zGlCryuT1lV4TcD3jF86MHlRlXsO3G8fCbEGdLLzppbV0A2VnEw1knhv2mfUhxA8hsFJE2ncT5qeOVyQ6N3PAMGoYkdhhbD9N/9EWuqs= + matrix: + # travis-cargo requires prefixing additional args with -- + - FEATURES="" + - FEATURES="-- --features ssl" + - FEATURES="-- --features session" + - FEATURES="-- --features secure_cookies" + - FEATURES="-- --no-default-features" From 7def1b30bfc1a3ad0eaf43b1c7f70d88af686cdc Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Thu, 27 Aug 2015 01:20:17 +0100 Subject: [PATCH 15/18] docs(cookies): add example of encrypted cookie usage --- examples/cookies_example.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/examples/cookies_example.rs b/examples/cookies_example.rs index 184fedc26f..13ba26c105 100644 --- a/examples/cookies_example.rs +++ b/examples/cookies_example.rs @@ -29,5 +29,31 @@ fn main() { "Cookie set!" }); + // Try `curl -c /tmp/cookie -b /tmp/cookie http://localhost:6767/secure?value=foobar` + // when the `secure_cookies` feature is enabled + // i.e. `cargo run --example cookies_example --features secure_cookies + if cfg!(feature = "secure_cookies") { + server.get("/secure", middleware! { |req, mut res| + let jar = res.cookies_mut() + .encrypted(); + + let new_value = req.query().get("value") + .unwrap_or("no value") + .to_owned(); + + let cookie = Cookie::new("SecureCookie".to_owned(), + new_value); + jar.add(cookie); + + // Old value from the request's Cookies + let old_value = req.cookies() + .encrypted() + .find("SecureCookie") + .map(|c| c.value); + + format!("Old value was {:?}", old_value) + }); + } + server.listen("127.0.0.1:6767"); } From 23d1494b9d2c792461ff0d28c98de73d5d9b8dbe Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Thu, 27 Aug 2015 01:26:18 +0100 Subject: [PATCH 16/18] refactor(session): require cookies::Keyprovider to implement a Session --- src/session.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/session.rs b/src/session.rs index 067123923c..a5a9bd7a4a 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1,4 +1,4 @@ -use {Request, Response, Cookies}; +use {Request, Response, Cookies, cookies}; use cookie::Cookie; use plugin::{Plugin, Pluggable}; use typemap::Key; @@ -13,7 +13,7 @@ use std::fmt::Debug; static COOKIE_KEY : &'static str = "__SESSION"; -pub trait SessionStore { +pub trait SessionStore : cookies::KeyProvider { type Store: Encodable + Decodable + Default + Debug; fn timeout() -> Duration { Duration::minutes(60) } @@ -24,8 +24,7 @@ pub struct SessionPlugin(PhantomData); impl Key for SessionPlugin { type Value = Option; } impl<'mw, D, T> Plugin> for SessionPlugin -where Response<'mw, D> : Cookies, - T: 'static + Any + Encodable + Decodable + Default + Debug, +where T: 'static + Any + Encodable + Decodable + Default + Debug, D: SessionStore { type Error = (); @@ -109,9 +108,7 @@ pub trait Session where D: SessionStore { pub struct CookieSession; impl Session for CookieSession -where for <'mw, 'conn> Request<'mw, 'conn, D> : Cookies, - for <'mw> Response<'mw, D> : Cookies, - D: SessionStore, +where D: SessionStore, D::Store : 'static + Any + Encodable + Decodable + Default + Debug { fn get_mut<'a>(req: &mut Request, res: &'a mut Response) -> &'a mut D::Store { let cached_session = res.get_mut::>().unwrap(); From 3a3fc6fbfb1b8449f458b712a4febfaba8f69cf8 Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Thu, 27 Aug 2015 01:43:16 +0100 Subject: [PATCH 17/18] refactor(session): rename SessionStore to Store --- examples/session_example.rs | 4 ++-- src/lib.rs | 2 +- src/session.rs | 24 ++++++++++++------------ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/examples/session_example.rs b/examples/session_example.rs index a2e01b8992..6c574e397e 100644 --- a/examples/session_example.rs +++ b/examples/session_example.rs @@ -25,8 +25,8 @@ impl cookies::KeyProvider for ServerData { } #[cfg(feature = "session")] -impl SessionStore for ServerData { - type Store = Option; +impl session::Store for ServerData { + type Session = Option; fn timeout() -> Duration { Duration::seconds(5) diff --git a/src/lib.rs b/src/lib.rs index d91e3c9bdb..2377c852ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,7 +34,7 @@ pub use responder::Responder; pub use cookies::Cookies; #[cfg(feature = "session")] -pub use session::{Session, SessionStore, CookieSession}; +pub use session::{Session, CookieSession}; #[macro_use] pub mod macros; diff --git a/src/session.rs b/src/session.rs index a5a9bd7a4a..6c71c8a97f 100644 --- a/src/session.rs +++ b/src/session.rs @@ -13,8 +13,8 @@ use std::fmt::Debug; static COOKIE_KEY : &'static str = "__SESSION"; -pub trait SessionStore : cookies::KeyProvider { - type Store: Encodable + Decodable + Default + Debug; +pub trait Store : cookies::KeyProvider { + type Session: Encodable + Decodable + Default + Debug; fn timeout() -> Duration { Duration::minutes(60) } } @@ -25,7 +25,7 @@ impl Key for SessionPlugin { type Value = Option; } impl<'mw, D, T> Plugin> for SessionPlugin where T: 'static + Any + Encodable + Decodable + Default + Debug, - D: SessionStore { + D: Store { type Error = (); fn eval(response: &mut Response<'mw, D>) -> Result, ()> { @@ -40,7 +40,7 @@ where T: 'static + Any + Encodable + Decodable + Default + Debug, let encoded = { // This should only ever get called after an initial setup, so these // unwraps should be fine! - let session = response.get_mut::>() + let session = response.get_mut::>() .unwrap(); // Todo: track when a write has occurred and only create the cookie in @@ -100,25 +100,25 @@ fn encode_data(data: &T) -> String { raw.to_base64(STANDARD) } -pub trait Session where D: SessionStore { +pub trait Session where D: Store { /// Provides access to a mutable Session. - fn get_mut<'a>(&mut Request, &'a mut Response) -> &'a mut D::Store; + fn get_mut<'a>(&mut Request, &'a mut Response) -> &'a mut D::Session; } pub struct CookieSession; impl Session for CookieSession -where D: SessionStore, - D::Store : 'static + Any + Encodable + Decodable + Default + Debug { - fn get_mut<'a>(req: &mut Request, res: &'a mut Response) -> &'a mut D::Store { - let cached_session = res.get_mut::>().unwrap(); +where D: Store, + D::Session : 'static + Any + Encodable + Decodable + Default + Debug { + fn get_mut<'a>(req: &mut Request, res: &'a mut Response) -> &'a mut D::Session { + let cached_session = res.get_mut::>().unwrap(); if let Some(ref mut session) = *cached_session { return session } let jar = req.cookies().encrypted(); let data = jar.find(COOKIE_KEY).and_then(|cookie| { - let timeout = ::timeout(); + let timeout = ::timeout(); match decode_data(&*cookie.value, timeout) { Ok(data) => Some(data), Err(e) => { @@ -129,7 +129,7 @@ where D: SessionStore, }); // Any error resets the session - *cached_session = data.or_else(|| Some(::default())); + *cached_session = data.or_else(|| Some(::default())); cached_session.as_mut().unwrap() } } From 7b7742cbf2b7b6fdb33e3bd51ead89d9557333bd Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Mon, 7 Sep 2015 17:13:10 +0100 Subject: [PATCH 18/18] fix(middleware): satisfy lifetime requirements for RFC 1214 --- src/middleware.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/middleware.rs b/src/middleware.rs index 36552d536e..fbfb4d48e5 100644 --- a/src/middleware.rs +++ b/src/middleware.rs @@ -32,7 +32,7 @@ pub trait ErrorHandler: Send + 'static + Sync { fn handle_error(&self, &mut NickelError, &mut Request) -> Action; } -impl ErrorHandler for fn(&mut NickelError, &mut Request) -> Action { +impl ErrorHandler for fn(&mut NickelError, &mut Request) -> Action { fn handle_error(&self, err: &mut NickelError, req: &mut Request) -> Action { (*self)(err, req) } @@ -43,7 +43,7 @@ pub struct MiddlewareStack { error_handlers: Vec + Send + Sync>> } -impl MiddlewareStack { +impl MiddlewareStack { pub fn add_middleware> (&mut self, handler: T) { self.handlers.push(Box::new(handler)); }