working global state

Signed-off-by: Erik Hollensbe <linux@hollensbe.org>
This commit is contained in:
Erik Hollensbe
2022-01-21 07:54:32 -08:00
parent 6bd07df847
commit 10c31e42ba
8 changed files with 232 additions and 71 deletions
+63
View File
@@ -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<Body>,
resp: Option<Response<Body>>,
_params: Params,
app: App<State>,
) -> 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<Body>,
_resp: Option<Response<Body>>,
params: Params,
_app: App<State>,
) -> 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(())
}
+10 -1
View File
@@ -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<Body>,
resp: Option<Response<Body>>,
_params: Params,
_app: App<State>,
) -> 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<Body>, _resp: Option<Response<Body>>, params: Params) -> HTTPResult {
async fn hello(
req: Request<Body>,
_resp: Option<Response<Body>>,
params: Params,
_app: App<State>,
) -> HTTPResult {
let name = params.get("name").unwrap();
let bytes = Body::from(format!("hello, {}!\n", name));
+9 -1
View File
@@ -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<Body>, _resp: Option<Response<Body>>, params: Params) -> HTTPResult {
#[derive(Clone)]
struct State;
async fn hello(
req: Request<Body>,
_resp: Option<Response<Body>>,
params: Params,
_app: App<State>,
) -> HTTPResult {
let name = params.get("name").unwrap();
let bytes = Body::from(format!("hello, {}!\n", name));
+15 -2
View File
@@ -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<Body>, resp: Option<Response<Body>>, _params: Params) -> HTTPResult {
#[derive(Clone)]
struct State;
async fn log(
req: Request<Body>,
resp: Option<Response<Body>>,
_params: Params,
_app: App<State>,
) -> HTTPResult {
log::trace!("New request: {}", req.uri().path());
Ok((req, resp))
}
async fn hello(req: Request<Body>, _resp: Option<Response<Body>>, params: Params) -> HTTPResult {
async fn hello(
req: Request<Body>,
_resp: Option<Response<Body>>,
params: Params,
_app: App<State>,
) -> HTTPResult {
let name = params.get("name").unwrap();
log::info!("Saying hello to {}", name);
let bytes = Body::from(format!("hello, {}!\n", name));
+28 -15
View File
@@ -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<S: Clone + Send> {
router: Router<S>,
global_state: Option<Arc<Mutex<S>>>,
}
impl App {
impl<S: 'static + Clone + Send> App<S> {
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<Arc<Mutex<S>>> {
self.global_state.clone()
}
pub fn get(&mut self, path: &str, ch: Handler<S>) {
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<S>) {
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<S>) {
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<S>) {
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<S>) {
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<S>) {
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<S>) {
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<S>) {
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<S>) {
self.router.add(Method::TRACE, path.to_string(), ch);
}
pub async fn dispatch(&self, req: Request<Body>) -> Result<Response<Body>, 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()
+36 -17
View File
@@ -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<String, String>;
pub type HandlerFunc = fn(
pub type HandlerFunc<S> = fn(
req: Request<Body>,
response: Option<Response<Body>>,
params: Params,
app: App<S>,
) -> PinBox<dyn Future<Output = HTTPResult> + Send>;
#[derive(Clone)]
pub struct Handler {
handler: HandlerFunc,
next: Box<Option<Handler>>,
pub struct Handler<S: Clone + Send> {
handler: HandlerFunc<S>,
next: Box<Option<Handler<S>>>,
}
impl Handler
impl<S> Handler<S>
where
Self: Send,
S: Clone + Send,
{
pub fn new(handler: HandlerFunc, next: Option<Handler>) -> Self {
pub fn new(handler: HandlerFunc<S>, next: Option<Handler<S>>) -> Self {
Self {
handler,
next: Box::new(next),
@@ -37,12 +39,13 @@ where
req: Request<hyper::Body>,
response: Option<Response<hyper::Body>>,
params: Params,
app: App<S>,
) -> 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<Body>,
_response: Option<Response<Body>>,
_params: Params,
_app: App<State>,
) -> HTTPResult {
let headers = req.headers_mut();
headers.insert("wakka", HeaderValue::from_str("wakka wakka").unwrap());
@@ -77,6 +84,7 @@ mod tests {
req: Request<Body>,
mut response: Option<Response<Body>>,
_params: Params,
_app: App<State>,
) -> 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());
+12 -7
View File
@@ -4,10 +4,10 @@ macro_rules! compose_handler {
{
use $crate::handler::{HandlerFunc, Handler};
{
let mut funcs: Vec<HandlerFunc> = Vec::new();
let mut funcs: Vec<HandlerFunc<_>> = 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<Handler> = None;
let mut last: Option<Handler<_>> = 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<Body>,
_response: Option<Response<Body>>,
_params: Params,
_app: App<State>,
) -> HTTPResult {
let headers = req.headers_mut();
headers.insert("wakka", HeaderValue::from_str("wakka wakka").unwrap());
@@ -56,6 +60,7 @@ mod tests {
req: Request<Body>,
mut response: Option<Response<Body>>,
_params: Params,
_app: App<State>,
) -> 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());
}
+59 -28
View File
@@ -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<S: Clone + Send> {
method: http::Method,
path: Path,
handler: Handler,
handler: Handler<S>,
}
impl PartialEq for Route {
impl<S: Clone + Send> PartialEq for Route<S> {
fn eq(&self, other: &Self) -> bool {
self.method.to_string() == other.method.to_string() && self.path.eq(&other.path)
}
}
impl Eq for Route {}
impl<S: Clone + Send> Eq for Route<S> {}
impl PartialOrd for Route {
impl<S: Clone + Send> PartialOrd for Route<S> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Route {
impl<S: Clone + Send> Ord for Route<S> {
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<S: Clone + Send> Route<S> {
fn new(method: http::Method, path: String, handler: Handler<S>) -> Self {
Self {
method,
handler,
@@ -43,37 +43,46 @@ impl Route {
}
#[allow(dead_code)]
async fn dispatch(&self, provided: String, req: Request<hyper::Body>) -> HTTPResult {
async fn dispatch(
&self,
provided: String,
req: Request<hyper::Body>,
app: App<S>,
) -> 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<Route>);
pub struct Router<S: Clone + Send>(Vec<Route<S>>);
impl Router {
impl<S: Clone + Send> Router<S> {
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<S>) -> Self {
self.0.push(Route::new(method, path, ch));
self.clone()
}
pub(crate) async fn dispatch(&self, req: Request<Body>) -> Result<Response<Body>, Error> {
pub(crate) async fn dispatch(
&self,
req: Request<Body>,
app: App<S>,
) -> Result<Response<Body>, 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<Body>,
_response: Option<Response<Body>>,
params: Params,
_app: App<State>,
) -> 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<Body>,
_response: Option<Response<Body>>,
_params: Params,
_app: App<State>,
) -> 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<Body>,
_response: Option<Response<Body>>,
params: Params,
_app: App<State>,
) -> HTTPResult {
return Ok((
req,
@@ -282,6 +309,7 @@ mod tests {
req: Request<Body>,
_response: Option<Response<Body>>,
_params: Params,
_app: App<State>,
) -> 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());