From 10c31e42baa52da3408e2b189595b8e445696324 Mon Sep 17 00:00:00 2001 From: Erik Hollensbe Date: Fri, 21 Jan 2022 07:52:09 -0800 Subject: [PATCH] working global state Signed-off-by: Erik Hollensbe --- examples/disk-auth-with-state.rs | 63 +++++++++++++++++++++++ examples/disk-auth.rs | 11 +++- examples/hello-world.rs | 10 +++- examples/log.rs | 17 ++++++- src/app.rs | 43 ++++++++++------ src/handler.rs | 53 ++++++++++++------- src/macros.rs | 19 ++++--- src/router.rs | 87 ++++++++++++++++++++++---------- 8 files changed, 232 insertions(+), 71 deletions(-) create mode 100644 examples/disk-auth-with-state.rs diff --git a/examples/disk-auth-with-state.rs b/examples/disk-auth-with-state.rs new file mode 100644 index 0000000..42c7cf1 --- /dev/null +++ b/examples/disk-auth-with-state.rs @@ -0,0 +1,63 @@ +use http::{Request, Response, StatusCode}; +use hyper::Body; +use ratpack::{app::App, compose_handler, handler::Params, Error, HTTPResult, ServerError}; + +async fn validate_authtoken( + req: Request, + resp: Option>, + _params: Params, + app: App, +) -> HTTPResult { + let token = req.headers().get("X-AuthToken"); + if token.is_none() { + return Err(Error::StatusCode(StatusCode::UNAUTHORIZED)); + } + + let token = token.unwrap(); + + let state = app.state().await; + if state.is_none() { + return Err(Error::StatusCode(StatusCode::UNAUTHORIZED)); + } + + let state = state.unwrap(); + + if !(state.clone().lock().await.authtoken == token) { + return Err(Error::StatusCode(StatusCode::UNAUTHORIZED)); + } + + return Ok((req, resp)); +} + +async fn hello( + req: Request, + _resp: Option>, + params: Params, + _app: App, +) -> HTTPResult { + let name = params.get("name").unwrap(); + let bytes = Body::from(format!("hello, {}!\n", name)); + + return Ok(( + req, + Some(Response::builder().status(200).body(bytes).unwrap()), + )); +} + +#[derive(Clone)] +struct State { + authtoken: &'static str, +} + +#[tokio::main] +async fn main() -> Result<(), ServerError> { + let mut app = App::with_state(State { + authtoken: "867-5309", + }); + app.get("/auth/:name", compose_handler!(validate_authtoken, hello)); + app.get("/:name", compose_handler!(hello)); + + app.serve("127.0.0.1:3000").await?; + + Ok(()) +} diff --git a/examples/disk-auth.rs b/examples/disk-auth.rs index 244467d..0b31ed7 100644 --- a/examples/disk-auth.rs +++ b/examples/disk-auth.rs @@ -5,10 +5,14 @@ use ratpack::{app::App, compose_handler, handler::Params, Error, HTTPResult, Ser const DEFAULT_AUTHTOKEN: &str = "867-5309"; const AUTHTOKEN_FILENAME: &str = "authtoken.secret"; +#[derive(Clone)] +struct State; + async fn validate_authtoken( req: Request, resp: Option>, _params: Params, + _app: App, ) -> HTTPResult { let token = req.headers().get("X-AuthToken"); if token.is_none() { @@ -32,7 +36,12 @@ async fn validate_authtoken( return Ok((req, resp)); } -async fn hello(req: Request, _resp: Option>, params: Params) -> HTTPResult { +async fn hello( + req: Request, + _resp: Option>, + params: Params, + _app: App, +) -> HTTPResult { let name = params.get("name").unwrap(); let bytes = Body::from(format!("hello, {}!\n", name)); diff --git a/examples/hello-world.rs b/examples/hello-world.rs index 52df2ea..ddf704d 100644 --- a/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -2,7 +2,15 @@ use http::{Request, Response}; use hyper::Body; use ratpack::{app::App, compose_handler, handler::Params, HTTPResult, ServerError}; -async fn hello(req: Request, _resp: Option>, params: Params) -> HTTPResult { +#[derive(Clone)] +struct State; + +async fn hello( + req: Request, + _resp: Option>, + params: Params, + _app: App, +) -> HTTPResult { let name = params.get("name").unwrap(); let bytes = Body::from(format!("hello, {}!\n", name)); diff --git a/examples/log.rs b/examples/log.rs index 1a040b3..77a770b 100644 --- a/examples/log.rs +++ b/examples/log.rs @@ -3,12 +3,25 @@ use hyper::Body; use log::LevelFilter; use ratpack::{app::App, compose_handler, handler::Params, HTTPResult, ServerError}; -async fn log(req: Request, resp: Option>, _params: Params) -> HTTPResult { +#[derive(Clone)] +struct State; + +async fn log( + req: Request, + resp: Option>, + _params: Params, + _app: App, +) -> HTTPResult { log::trace!("New request: {}", req.uri().path()); Ok((req, resp)) } -async fn hello(req: Request, _resp: Option>, params: Params) -> HTTPResult { +async fn hello( + req: Request, + _resp: Option>, + params: Params, + _app: App, +) -> HTTPResult { let name = params.get("name").unwrap(); log::info!("Saying hello to {}", name); let bytes = Body::from(format!("hello, {}!\n", name)); diff --git a/src/app.rs b/src/app.rs index 6230bdd..1e70860 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,61 +1,74 @@ -use std::{convert::Infallible, net::SocketAddr}; +use std::{convert::Infallible, net::SocketAddr, sync::Arc}; use http::{Method, Request, Response, StatusCode}; use hyper::{server::conn::Http, service::service_fn, Body}; -use tokio::net::TcpListener; +use tokio::{net::TcpListener, sync::Mutex}; use crate::{handler::Handler, router::Router, Error, ServerError}; #[derive(Clone)] -pub struct App { - router: Router, +pub struct App { + router: Router, + global_state: Option>>, } -impl App { +impl App { pub fn new() -> Self { Self { router: Router::new(), + global_state: None, } } - pub fn get(&mut self, path: &str, ch: Handler) { + pub fn with_state(state: S) -> Self { + Self { + router: Router::new(), + global_state: Some(Arc::new(Mutex::new(state))), + } + } + + pub async fn state(&self) -> Option>> { + self.global_state.clone() + } + + pub fn get(&mut self, path: &str, ch: Handler) { self.router.add(Method::GET, path.to_string(), ch); } - pub fn post(&mut self, path: &str, ch: Handler) { + pub fn post(&mut self, path: &str, ch: Handler) { self.router.add(Method::POST, path.to_string(), ch); } - pub fn delete(&mut self, path: &str, ch: Handler) { + pub fn delete(&mut self, path: &str, ch: Handler) { self.router.add(Method::DELETE, path.to_string(), ch); } - pub fn put(&mut self, path: &str, ch: Handler) { + pub fn put(&mut self, path: &str, ch: Handler) { self.router.add(Method::PUT, path.to_string(), ch); } - pub fn options(&mut self, path: &str, ch: Handler) { + pub fn options(&mut self, path: &str, ch: Handler) { self.router.add(Method::OPTIONS, path.to_string(), ch); } - pub fn patch(&mut self, path: &str, ch: Handler) { + pub fn patch(&mut self, path: &str, ch: Handler) { self.router.add(Method::PATCH, path.to_string(), ch); } - pub fn head(&mut self, path: &str, ch: Handler) { + pub fn head(&mut self, path: &str, ch: Handler) { self.router.add(Method::HEAD, path.to_string(), ch); } - pub fn connect(&mut self, path: &str, ch: Handler) { + pub fn connect(&mut self, path: &str, ch: Handler) { self.router.add(Method::CONNECT, path.to_string(), ch); } - pub fn trace(&mut self, path: &str, ch: Handler) { + pub fn trace(&mut self, path: &str, ch: Handler) { self.router.add(Method::TRACE, path.to_string(), ch); } pub async fn dispatch(&self, req: Request) -> Result, Infallible> { - match self.router.dispatch(req).await { + match self.router.dispatch(req, self.clone()).await { Ok(resp) => Ok(resp), Err(e) => match e { Error::StatusCode(sc) => Ok(Response::builder() diff --git a/src/handler.rs b/src/handler.rs index 425e64c..ce8417e 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,6 +1,6 @@ use std::{collections::BTreeMap, future::Future}; -use crate::{HTTPResult, PinBox}; +use crate::{app::App, HTTPResult, PinBox}; use async_recursion::async_recursion; use http::{Request, Response}; @@ -8,23 +8,25 @@ use hyper::Body; pub type Params = BTreeMap; -pub type HandlerFunc = fn( +pub type HandlerFunc = fn( req: Request, response: Option>, params: Params, + app: App, ) -> PinBox + Send>; #[derive(Clone)] -pub struct Handler { - handler: HandlerFunc, - next: Box>, +pub struct Handler { + handler: HandlerFunc, + next: Box>>, } -impl Handler +impl Handler where Self: Send, + S: Clone + Send, { - pub fn new(handler: HandlerFunc, next: Option) -> Self { + pub fn new(handler: HandlerFunc, next: Option>) -> Self { Self { handler, next: Box::new(next), @@ -37,12 +39,13 @@ where req: Request, response: Option>, params: Params, + app: App, ) -> HTTPResult { - let (req, response) = (self.handler)(req, response, params.clone()).await?; + let (req, response) = (self.handler)(req, response, params.clone(), app.clone()).await?; if self.next.is_some() { return Ok((*self.clone().next) .unwrap() - .perform(req, response, params) + .perform(req, response, params, app) .await?); } @@ -53,12 +56,15 @@ where mod tests { #[tokio::test] async fn test_handler_basic() { - use crate::{Error, HTTPResult}; + use crate::{app::App, Error, HTTPResult}; use http::{HeaderValue, Request, Response, StatusCode}; use hyper::Body; use super::Params; + #[derive(Clone)] + struct State; + // this method adds a header: // wakka: wakka wakka // to the request. that's it! @@ -66,6 +72,7 @@ mod tests { mut req: Request, _response: Option>, _params: Params, + _app: App, ) -> HTTPResult { let headers = req.headers_mut(); headers.insert("wakka", HeaderValue::from_str("wakka wakka").unwrap()); @@ -77,6 +84,7 @@ mod tests { req: Request, mut response: Option>, _params: Params, + _app: App, ) -> HTTPResult { if let Some(header) = req.headers().get("wakka") { if header != "wakka wakka" { @@ -99,26 +107,37 @@ mod tests { } // single stage handler that never yields a response - let bh = super::Handler::new(|req, resp, params| Box::pin(one(req, resp, params)), None); + let bh = super::Handler::new( + |req, resp, params, app| Box::pin(one(req, resp, params, app)), + None, + ); let req = Request::default(); - let (req, response) = bh.perform(req, None, Params::new()).await.unwrap(); + let (req, response) = bh + .perform(req, None, Params::new(), App::new()) + .await + .unwrap(); assert!(req.headers().get("wakka").is_some()); assert!(response.is_none()); // two-stage handler; yields a response if the first one was good. - let bh_two = - super::Handler::new(|req, resp, params| Box::pin(two(req, resp, params)), None); + let bh_two = super::Handler::new( + |req, resp, params, app| Box::pin(two(req, resp, params, app)), + None, + ); let bh = super::Handler::new( - |req, resp, params| Box::pin(one(req, resp, params)), + |req, resp, params, app| Box::pin(one(req, resp, params, app)), Some(bh_two.clone()), ); - let (_, response) = bh.perform(req, None, Params::new()).await.unwrap(); + let (_, response) = bh + .perform(req, None, Params::new(), App::new()) + .await + .unwrap(); assert!(response.is_some() && response.unwrap().status() == StatusCode::OK); assert!(bh_two - .perform(Request::default(), None, Params::new()) + .perform(Request::default(), None, Params::new(), App::new()) .await .is_err()); diff --git a/src/macros.rs b/src/macros.rs index af2e329..c77f13a 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -4,10 +4,10 @@ macro_rules! compose_handler { { use $crate::handler::{HandlerFunc, Handler}; { - let mut funcs: Vec = Vec::new(); + let mut funcs: Vec> = Vec::new(); $( - funcs.push(|req, resp, params| Box::pin($x(req, resp, params))); + funcs.push(|req, resp, params, app| Box::pin($x(req, resp, params, app))); )* if funcs.len() == 0 { @@ -16,7 +16,7 @@ macro_rules! compose_handler { let mut handlers = Vec::new(); - let mut last: Option = None; + let mut last: Option> = None; funcs.reverse(); for func in funcs { @@ -36,7 +36,10 @@ mod tests { use http::{HeaderValue, Request, Response, StatusCode}; use hyper::Body; - use crate::{handler::Params, Error, HTTPResult}; + use crate::{app::App, handler::Params, Error, HTTPResult}; + + #[derive(Clone)] + struct State; // this method adds a header: // wakka: wakka wakka @@ -45,6 +48,7 @@ mod tests { mut req: Request, _response: Option>, _params: Params, + _app: App, ) -> HTTPResult { let headers = req.headers_mut(); headers.insert("wakka", HeaderValue::from_str("wakka wakka").unwrap()); @@ -56,6 +60,7 @@ mod tests { req: Request, mut response: Option>, _params: Params, + _app: App, ) -> HTTPResult { if let Some(header) = req.headers().get("wakka") { if header != "wakka wakka" { @@ -80,7 +85,7 @@ mod tests { let handler = compose_handler!(one, two); let (req, response) = handler - .perform(Request::default(), None, Params::new()) + .perform(Request::default(), None, Params::new(), App::new()) .await .unwrap(); @@ -90,7 +95,7 @@ mod tests { let handler = compose_handler!(one); let (req, response) = handler - .perform(Request::default(), None, Params::new()) + .perform(Request::default(), None, Params::new(), App::new()) .await .unwrap(); @@ -100,7 +105,7 @@ mod tests { let handler = compose_handler!(two); assert!(handler - .perform(Request::default(), None, Params::new()) + .perform(Request::default(), None, Params::new(), App::new()) .await .is_err()); } diff --git a/src/router.rs b/src/router.rs index 5c3ec9a..176af50 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,30 +1,30 @@ use http::{Request, Response}; use hyper::Body; -use crate::{handler::Handler, path::Path, Error, HTTPResult}; +use crate::{app::App, handler::Handler, path::Path, Error, HTTPResult}; #[derive(Clone)] -pub struct Route { +pub struct Route { method: http::Method, path: Path, - handler: Handler, + handler: Handler, } -impl PartialEq for Route { +impl PartialEq for Route { fn eq(&self, other: &Self) -> bool { self.method.to_string() == other.method.to_string() && self.path.eq(&other.path) } } -impl Eq for Route {} +impl Eq for Route {} -impl PartialOrd for Route { +impl PartialOrd for Route { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for Route { +impl Ord for Route { fn cmp(&self, other: &Self) -> std::cmp::Ordering { let left = self.method.to_string() + " " + &self.path.to_string(); let right = other.method.to_string() + " " + &other.path.to_string(); @@ -33,8 +33,8 @@ impl Ord for Route { } } -impl Route { - fn new(method: http::Method, path: String, handler: Handler) -> Self { +impl Route { + fn new(method: http::Method, path: String, handler: Handler) -> Self { Self { method, handler, @@ -43,37 +43,46 @@ impl Route { } #[allow(dead_code)] - async fn dispatch(&self, provided: String, req: Request) -> HTTPResult { + async fn dispatch( + &self, + provided: String, + req: Request, + app: App, + ) -> HTTPResult { let params = self.path.extract(provided)?; if self.method != req.method() { return Err(Error::StatusCode(http::StatusCode::NOT_FOUND)); } - self.handler.perform(req, None, params).await + self.handler.perform(req, None, params, app).await } } #[derive(Clone)] -pub struct Router(Vec); +pub struct Router(Vec>); -impl Router { +impl Router { pub fn new() -> Self { Self(Vec::new()) } - pub(crate) fn add(&mut self, method: http::Method, path: String, ch: Handler) -> Self { + pub(crate) fn add(&mut self, method: http::Method, path: String, ch: Handler) -> Self { self.0.push(Route::new(method, path, ch)); self.clone() } - pub(crate) async fn dispatch(&self, req: Request) -> Result, Error> { + pub(crate) async fn dispatch( + &self, + req: Request, + app: App, + ) -> Result, Error> { let path = req.uri().path().to_string(); for route in self.0.clone() { if route.path.matches(path.to_string()) && route.method.eq(req.method()) { let params = route.path.extract(path)?; - let (_, response) = route.handler.perform(req, None, params).await?; + let (_, response) = route.handler.perform(req, None, params, app).await?; if response.is_none() { return Err(Error::StatusCode(http::StatusCode::INTERNAL_SERVER_ERROR)); } @@ -87,22 +96,28 @@ impl Router { } mod tests { + #[tokio::test] async fn test_route_dynamic() { use http::{Method, Request, Response}; use hyper::Body; use crate::{ + app::App, handler::{Handler, Params}, HTTPResult, }; use super::Route; + #[derive(Clone)] + struct State; + async fn handler_dynamic( req: Request, _response: Option>, params: Params, + _app: App, ) -> HTTPResult { return Ok(( req, @@ -117,13 +132,13 @@ mod tests { Method::GET, "/a/:name/c".to_string(), Handler::new( - |req, resp, params| Box::pin(handler_dynamic(req, resp, params)), + |req, resp, params, app| Box::pin(handler_dynamic(req, resp, params, app)), None, ), ); assert!(route - .dispatch("/a".to_string(), Request::default()) + .dispatch("/a".to_string(), Request::default(), App::new()) .await .is_err()); assert!(route @@ -133,6 +148,7 @@ mod tests { .method(Method::POST) .body(Body::from("one=two".as_bytes())) .unwrap(), + App::new(), ) .await .is_err()); @@ -141,7 +157,7 @@ mod tests { "erik", "adam", "sean", "travis", "joseph", "grant", "joy", "steve", "marc", ] { assert!(route - .dispatch("/a/:name/c".to_string(), Request::default()) + .dispatch("/a/:name/c".to_string(), Request::default(), App::new()) .await .is_ok()); @@ -149,7 +165,7 @@ mod tests { let body = hyper::body::to_bytes( route - .dispatch(path.clone(), Request::default()) + .dispatch(path.clone(), Request::default(), App::new()) .await .unwrap() .1 @@ -162,7 +178,7 @@ mod tests { assert_eq!(body, format!("hello, {}", name).as_bytes()); let status = route - .dispatch(path, Request::default()) + .dispatch(path, Request::default(), App::new()) .await .unwrap() .1 @@ -179,16 +195,21 @@ mod tests { use hyper::Body; use crate::{ + app::App, handler::{Handler, Params}, HTTPResult, }; use super::Route; + #[derive(Clone)] + struct State; + async fn handler_static( req: Request, _response: Option>, _params: Params, + _app: App, ) -> HTTPResult { return Ok(( req, @@ -204,13 +225,13 @@ mod tests { Method::GET, "/a/b/c".to_string(), Handler::new( - |req, resp, params| Box::pin(handler_static(req, resp, params)), + |req, resp, params, app| Box::pin(handler_static(req, resp, params, app)), None, ), ); assert!(route - .dispatch("/a".to_string(), Request::default()) + .dispatch("/a".to_string(), Request::default(), App::new()) .await .is_err()); assert!(route @@ -220,18 +241,19 @@ mod tests { .method(Method::POST) .body(Body::from("one=two".as_bytes())) .unwrap(), + App::new(), ) .await .is_err()); assert!(route - .dispatch("/a/b/c".to_string(), Request::default()) + .dispatch("/a/b/c".to_string(), Request::default(), App::new()) .await .is_ok()); let body = hyper::body::to_bytes( route - .dispatch("/a/b/c".to_string(), Request::default()) + .dispatch("/a/b/c".to_string(), Request::default(), App::new()) .await .unwrap() .1 @@ -244,7 +266,7 @@ mod tests { assert_eq!(body, "hello, world".as_bytes()); let status = route - .dispatch("/a/b/c".to_string(), Request::default()) + .dispatch("/a/b/c".to_string(), Request::default(), App::new()) .await .unwrap() .1 @@ -258,16 +280,21 @@ mod tests { async fn test_router() { use super::Router; use crate::{ + app::App, handler::{Handler, Params}, HTTPResult, }; use http::{Method, Request, Response}; use hyper::Body; + #[derive(Clone)] + struct State; + async fn handler_dynamic( req: Request, _response: Option>, params: Params, + _app: App, ) -> HTTPResult { return Ok(( req, @@ -282,6 +309,7 @@ mod tests { req: Request, _response: Option>, _params: Params, + _app: App, ) -> HTTPResult { return Ok(( req, @@ -299,7 +327,7 @@ mod tests { Method::GET, "/a/b/c".to_string(), Handler::new( - |req, resp, params| Box::pin(handler_static(req, resp, params)), + |req, resp, params, app| Box::pin(handler_static(req, resp, params, app)), None, ), ); @@ -308,7 +336,7 @@ mod tests { Method::GET, "/c/b/a/:name".to_string(), Handler::new( - |req, resp, params| Box::pin(handler_dynamic(req, resp, params)), + |req, resp, params, app| Box::pin(handler_dynamic(req, resp, params, app)), None, ), ); @@ -320,6 +348,7 @@ mod tests { .method(Method::GET) .body(Body::default()) .unwrap(), + App::new(), ) .await; assert!(response.is_ok()); @@ -337,6 +366,7 @@ mod tests { .method(Method::GET) .body(Body::default()) .unwrap(), + App::new(), ) .await; assert!(response.is_ok()); @@ -353,6 +383,7 @@ mod tests { .method(Method::GET) .body(Body::default()) .unwrap(), + App::new(), ) .await; assert!(response.is_err());