From 738047e40b3482fc0c06e593c230f0380da2270a Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Sun, 2 Apr 2023 00:42:50 +0200 Subject: [PATCH 1/7] feat: `async_trait` support --- Cargo.toml | 8 +-- Makefile | 10 ++++ README.md | 18 ++++--- examples/server.rs | 17 +++--- src/lib.rs | 2 + src/middleware.rs | 131 ++++++++++++++++++++++++++------------------- src/service.rs | 8 +-- 7 files changed, 118 insertions(+), 76 deletions(-) create mode 100644 Makefile diff --git a/Cargo.toml b/Cargo.toml index 0b89082..a8351c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,9 +25,11 @@ keywords = [ ] [dependencies] -hyper = { version = "0.14", default-features = false, features = ["server", "tcp"] } -anyhow = "1.0" -thiserror = "1.0" +hyper = { version = "0.14.25", default-features = false, features = ["server", "tcp"] } +anyhow = "1.0.70" +thiserror = "1.0.40" +async-trait = "0.1.68" +async-recursion = "1.0.4" [dev-dependencies] hyper = { version = "0.14", features = ["tcp", "server", "http1"] } diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8849532 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +docs: + @cargo doc --no-deps +.PHONY: docs + +docs-dev: + @cargo doc --no-deps + @echo "Crate documentation: http://localhost:8787/hyper_middleware" + @static-web-server -p 8787 -d target/doc/ \ + & watchman-make -p 'src/**/*.rs' --run 'cargo doc' +.PHONY: docs-dev diff --git a/README.md b/README.md index f845cd7..c81ddd1 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,9 @@ use hyper::{header, Server, StatusCode}; use std::{net::SocketAddr, path::PathBuf}; - use hyper_middleware::{ - AfterMiddleware, BeforeMiddleware, Body, Chain, Error, Handler, Request, Response, Result, - Service, + async_trait, AfterMiddleware, BeforeMiddleware, Body, Chain, Error, Handler, Request, Response, + Result, Service, }; struct Config { @@ -36,8 +35,9 @@ struct Application { opts: Config, } +#[async_trait] impl Handler for Application { - fn handle(&self, req: &mut Request) -> Result { + async fn handle(&self, req: &mut Request) -> Result { // Access the Hyper incoming Request println!("Handler - URI Path: {}", req.uri().path()); @@ -57,8 +57,9 @@ impl Handler for Application { struct FirstMiddleware {} +#[async_trait] impl BeforeMiddleware for FirstMiddleware { - fn before(&self, req: &mut Request) -> Result { + async fn before(&self, req: &mut Request) -> Result { println!("First Middleware called!"); // Access the Hyper incoming Request @@ -67,15 +68,16 @@ impl BeforeMiddleware for FirstMiddleware { Ok(()) } - fn catch(&self, _: &mut Request, err: Error) -> Result { + async fn catch(&self, _: &mut Request, err: Error) -> Result { Err(err) } } struct SecondMiddleware {} +#[async_trait] impl AfterMiddleware for SecondMiddleware { - fn after(&self, _: &mut Request, mut res: Response) -> Result { + async fn after(&self, _: &mut Request, mut res: Response) -> Result { println!("Second Middleware called!"); // Mutate the Hyper Response at convenience @@ -88,7 +90,7 @@ impl AfterMiddleware for SecondMiddleware { Ok(res) } - fn catch(&self, _: &mut Request, err: Error) -> Result { + async fn catch(&self, _: &mut Request, err: Error) -> Result { Ok(Response::builder() .status(StatusCode::NOT_FOUND) .body(Body::from(err.to_string())) diff --git a/examples/server.rs b/examples/server.rs index bc9ab47..c719413 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -7,8 +7,8 @@ use hyper::{header, Server, StatusCode}; use std::{net::SocketAddr, path::PathBuf}; use hyper_middleware::{ - AfterMiddleware, BeforeMiddleware, Body, Chain, Error, Handler, Request, Response, Result, - Service, + async_trait, AfterMiddleware, BeforeMiddleware, Body, Chain, Error, Handler, Request, Response, + Result, Service, }; struct Config { @@ -19,8 +19,9 @@ struct Application { opts: Config, } +#[async_trait] impl Handler for Application { - fn handle(&self, req: &mut Request) -> Result { + async fn handle(&self, req: &mut Request) -> Result { // Access the Hyper incoming Request println!("Handler - URI Path: {}", req.uri().path()); @@ -40,8 +41,9 @@ impl Handler for Application { struct FirstMiddleware {} +#[async_trait] impl BeforeMiddleware for FirstMiddleware { - fn before(&self, req: &mut Request) -> Result { + async fn before(&self, req: &mut Request) -> Result { println!("First Middleware called!"); // Access the Hyper incoming Request @@ -50,15 +52,16 @@ impl BeforeMiddleware for FirstMiddleware { Ok(()) } - fn catch(&self, _: &mut Request, err: Error) -> Result { + async fn catch(&self, _: &mut Request, err: Error) -> Result { Err(err) } } struct SecondMiddleware {} +#[async_trait] impl AfterMiddleware for SecondMiddleware { - fn after(&self, _: &mut Request, mut res: Response) -> Result { + async fn after(&self, _: &mut Request, mut res: Response) -> Result { println!("Second Middleware called!"); // Mutate the Hyper Response at convenience @@ -71,7 +74,7 @@ impl AfterMiddleware for SecondMiddleware { Ok(res) } - fn catch(&self, _: &mut Request, err: Error) -> Result { + async fn catch(&self, _: &mut Request, err: Error) -> Result { Ok(Response::builder() .status(StatusCode::NOT_FOUND) .body(Body::from(err.to_string())) diff --git a/src/lib.rs b/src/lib.rs index 4be7c82..2408e81 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,8 @@ pub mod middleware; pub mod remote_addr; pub mod service; +pub use async_recursion::*; +pub use async_trait::*; pub use error::{Context, Error, Result}; pub use http::*; pub use middleware::*; diff --git a/src/middleware.rs b/src/middleware.rs index 28c7e8d..c0da3ad 100644 --- a/src/middleware.rs +++ b/src/middleware.rs @@ -146,31 +146,37 @@ //! `Response` should be run during both the normal and error flow by //! implementing the `catch` method to also do the necessary action. +use async_recursion::async_recursion; +use async_trait::async_trait; use std::sync::Arc; use crate::{Error, Request, Response, Result}; +#[async_trait] /// `Handler`s are responsible for handling requests by creating `Response`s from `Request`s. pub trait Handler: Send + Sync + 'static { /// Produce a `Response` from a Request, with the possibility of error. - fn handle(&self, req: &mut Request) -> Result; + async fn handle(&self, req: &mut Request) -> Result; } +#[async_trait] impl Handler for F where F: Send + Sync + 'static + Fn(&mut Request) -> Result, { - fn handle(&self, req: &mut Request) -> Result { + async fn handle(&self, req: &mut Request) -> Result { (*self)(req) } } +#[async_trait] impl Handler for Box { - fn handle(&self, req: &mut Request) -> Result { - (**self).handle(req) + async fn handle(&self, req: &mut Request) -> Result { + (**self).handle(req).await } } +#[async_trait] /// `BeforeMiddleware` are fired before a `Handler` is called inside of a Chain. /// /// `BeforeMiddleware` are responsible for doing request pre-processing that requires @@ -183,7 +189,7 @@ impl Handler for Box { /// instead be `AroundMiddleware`. pub trait BeforeMiddleware: Send + Sync + 'static { /// Do whatever work this middleware should do with a `Request` object. - fn before(&self, _: &mut Request) -> Result<()> { + async fn before(&self, _: &mut Request) -> Result<()> { Ok(()) } @@ -192,11 +198,12 @@ pub trait BeforeMiddleware: Send + Sync + 'static { /// Returning a `Ok` will cause the request to resume the normal flow at the /// next `BeforeMiddleware`, or if this was the last `BeforeMiddleware`, /// at the `Handler`. - fn catch(&self, _: &mut Request, err: Error) -> Result<()> { + async fn catch(&self, _: &mut Request, err: Error) -> Result<()> { Err(err) } } +#[async_trait] /// `AfterMiddleware` are fired after a `Handler` is called inside of a `Chain`. /// /// `AfterMiddleware` receive both a `Request` and a `Response` and are responsible for doing @@ -208,7 +215,7 @@ pub trait BeforeMiddleware: Send + Sync + 'static { /// adding headers or logging. pub trait AfterMiddleware: Send + Sync + 'static { /// Do whatever post-processing this middleware should do. - fn after(&self, _: &mut Request, res: Response) -> Result { + async fn after(&self, _: &mut Request, res: Response) -> Result { Ok(res) } @@ -216,11 +223,12 @@ pub trait AfterMiddleware: Send + Sync + 'static { /// /// Returning `Ok` will cause the request to resume the normal flow at the /// next `AfterMiddleware`. - fn catch(&self, _: &mut Request, err: Error) -> Result { + async fn catch(&self, _: &mut Request, err: Error) -> Result { Err(err) } } +#[async_trait(?Send)] /// `AroundMiddleware` are used to wrap and replace the `Handler` in a `Chain`. /// /// `AroundMiddleware` produce `Handler`s through their `around` method, which is @@ -234,7 +242,7 @@ pub trait AroundMiddleware { /// /// This is called only once, when an `AroundMiddleware` is added to a `Chain` /// using `Chain::around`, it is passed the `Chain`'s current `Handler`. - fn around(self, handler: Box) -> Box; + async fn around(self, handler: Box) -> Box; } /// The middleware chain which can append other middlewares. @@ -300,32 +308,34 @@ impl Chain { } /// Apply an `AroundMiddleware` to the `Handler` in this `Chain`. - pub fn link_around(&mut self, around: A) -> &mut Chain + pub async fn link_around(&mut self, around: A) -> &mut Chain where A: AroundMiddleware, { let mut handler = self.handler.take().unwrap(); - handler = around.around(handler); + handler = around.around(handler).await; self.handler = Some(handler); self } } +#[async_trait] impl Handler for Chain { - fn handle(&self, req: &mut Request) -> Result { + async fn handle(&self, req: &mut Request) -> Result { // Kick off at befores, which will continue into handler // then afters. - self.continue_from_before(req, 0) + self.continue_from_before(req, 0).await } } impl Chain { + #[async_recursion] // Enter the error flow from a before middleware, starting // at the passed index. // // If the index is out of bounds for the before middleware Vec, // this instead behaves the same as fail_from_handler. - fn fail_from_before( + async fn fail_from_before( &self, req: &mut Request, index: usize, @@ -333,44 +343,44 @@ impl Chain { ) -> Result { // If this was the last before, yield to next phase. if index >= self.befores.len() { - return self.fail_from_handler(req, err); + return self.fail_from_handler(req, err).await; } for (i, before) in self.befores[index..].iter().enumerate() { - err = match before.catch(req, err) { + err = match before.catch(req, err).await { Err(err) => err, - Ok(()) => return self.continue_from_before(req, index + i + 1), + Ok(()) => return self.continue_from_before(req, index + i + 1).await, }; } // Next phase - self.fail_from_handler(req, err) + self.fail_from_handler(req, err).await } // Enter the normal flow in the before middleware, starting with the passed // index. - fn continue_from_before(&self, req: &mut Request, index: usize) -> Result { + async fn continue_from_before(&self, req: &mut Request, index: usize) -> Result { // If this was the last beforemiddleware, start at the handler. if index >= self.befores.len() { - return self.continue_from_handler(req); + return self.continue_from_handler(req).await; } for (i, before) in self.befores[index..].iter().enumerate() { - match before.before(req) { + match before.before(req).await { Ok(()) => {} - Err(err) => return self.fail_from_before(req, index + i + 1, err), + Err(err) => return self.fail_from_before(req, index + i + 1, err).await, } } // Yield to next phase. - self.continue_from_handler(req) + self.continue_from_handler(req).await } // Enter the error flow from an errored handle, starting with the // first AfterMiddleware. - fn fail_from_handler(&self, req: &mut Request, err: Error) -> Result { + async fn fail_from_handler(&self, req: &mut Request, err: Error) -> Result { // Yield to next phase, nothing to do here. - self.fail_from_after(req, 0, err) + self.fail_from_after(req, 0, err).await } // Enter the error flow from an errored after middleware, starting @@ -378,16 +388,21 @@ impl Chain { // // If the index is out of bounds for the after middleware Vec, // this instead just returns the passed error. - fn fail_from_after(&self, req: &mut Request, index: usize, mut err: Error) -> Result { + async fn fail_from_after( + &self, + req: &mut Request, + index: usize, + mut err: Error, + ) -> Result { // If this was the last after, we're done. if index == self.afters.len() { return Err(err); } for (i, after) in self.afters[index..].iter().enumerate() { - err = match after.catch(req, err) { + err = match after.catch(req, err).await { Err(err) => err, - Ok(res) => return self.continue_from_after(req, index + i + 1, res), + Ok(res) => return self.continue_from_after(req, index + i + 1, res).await, } } @@ -396,17 +411,18 @@ impl Chain { } // Enter the normal flow at the handler. - fn continue_from_handler(&self, req: &mut Request) -> Result { + async fn continue_from_handler(&self, req: &mut Request) -> Result { // unwrap is safe because it's always Some - match self.handler.as_ref().unwrap().handle(req) { - Ok(res) => self.continue_from_after(req, 0, res), - Err(err) => self.fail_from_handler(req, err), + match self.handler.as_ref().unwrap().handle(req).await { + Ok(res) => self.continue_from_after(req, 0, res).await, + Err(err) => self.fail_from_handler(req, err).await, } } + #[async_recursion] // Enter the normal flow in the after middleware, starting with the passed // index. - fn continue_from_after( + async fn continue_from_after( &self, req: &mut Request, index: usize, @@ -418,9 +434,9 @@ impl Chain { } for (i, after) in self.afters[index..].iter().enumerate() { - res = match after.after(req, res) { + res = match after.after(req, res).await { Ok(res) => res, - Err(err) => return self.fail_from_after(req, index + i + 1, err), + Err(err) => return self.fail_from_after(req, index + i + 1, err).await, } } @@ -429,75 +445,82 @@ impl Chain { } } +#[async_trait] impl BeforeMiddleware for F where F: Send + Sync + 'static + Fn(&mut Request) -> Result<()>, { - fn before(&self, req: &mut Request) -> Result<()> { + async fn before(&self, req: &mut Request) -> Result<()> { (*self)(req) } } +#[async_trait] impl BeforeMiddleware for Box { - fn before(&self, req: &mut Request) -> Result<()> { - (**self).before(req) + async fn before(&self, req: &mut Request) -> Result<()> { + (**self).before(req).await } - fn catch(&self, req: &mut Request, err: Error) -> Result<()> { - (**self).catch(req, err) + async fn catch(&self, req: &mut Request, err: Error) -> Result<()> { + (**self).catch(req, err).await } } +#[async_trait] impl BeforeMiddleware for Arc where T: BeforeMiddleware, { - fn before(&self, req: &mut Request) -> Result<()> { - (**self).before(req) + async fn before(&self, req: &mut Request) -> Result<()> { + (**self).before(req).await } - fn catch(&self, req: &mut Request, err: Error) -> Result<()> { - (**self).catch(req, err) + async fn catch(&self, req: &mut Request, err: Error) -> Result<()> { + (**self).catch(req, err).await } } +#[async_trait] impl AfterMiddleware for F where F: Send + Sync + 'static + Fn(&mut Request, Response) -> Result, { - fn after(&self, req: &mut Request, res: Response) -> Result { + async fn after(&self, req: &mut Request, res: Response) -> Result { (*self)(req, res) } } +#[async_trait] impl AfterMiddleware for Box { - fn after(&self, req: &mut Request, res: Response) -> Result { - (**self).after(req, res) + async fn after(&self, req: &mut Request, res: Response) -> Result { + (**self).after(req, res).await } - fn catch(&self, req: &mut Request, err: Error) -> Result { - (**self).catch(req, err) + async fn catch(&self, req: &mut Request, err: Error) -> Result { + (**self).catch(req, err).await } } +#[async_trait] impl AfterMiddleware for Arc where T: AfterMiddleware, { - fn after(&self, req: &mut Request, res: Response) -> Result { - (**self).after(req, res) + async fn after(&self, req: &mut Request, res: Response) -> Result { + (**self).after(req, res).await } - fn catch(&self, req: &mut Request, err: Error) -> Result { - (**self).catch(req, err) + async fn catch(&self, req: &mut Request, err: Error) -> Result { + (**self).catch(req, err).await } } +#[async_trait(?Send)] impl AroundMiddleware for F where F: FnOnce(Box) -> Box, { - fn around(self, handler: Box) -> Box { + async fn around(self, handler: Box) -> Box { self(handler) } } diff --git a/src/service.rs b/src/service.rs index 95b6083..4dbb369 100644 --- a/src/service.rs +++ b/src/service.rs @@ -15,7 +15,7 @@ //! //! struct Application {} //! impl Handler for Application { -//! fn handle(&self, _req: &mut Request) -> Result { +//! async fn handle(&self, _req: &mut Request) -> Result { //! // Create a response and send it back to the middlewares chain //! Ok(Response::new(Body::from("¡Hola!"))) //! } @@ -114,8 +114,8 @@ mod handler_service { if let Some(remote_addr) = self.remote_addr { req.extensions_mut().insert(remote_addr); } - let res = self.handler.handle(&mut req); - Box::pin(async { res }) + let handler = self.handler.clone(); + Box::pin(async move { handler.handle(&mut req).await }) } } @@ -125,7 +125,7 @@ mod handler_service { impl HandlerServiceBuilder where - H: Handler, + H: Handler + Send + Sync + 'static, { pub fn new(handler: H) -> Self { Self { From c67edc7345fb951625507df7b7aa315d320b974c Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Sun, 2 Apr 2023 00:48:12 +0200 Subject: [PATCH 2/7] 0.0.7 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a8351c7..2de44c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "hyper-middleware" description = "A compact HTTP middleware and handler system for Hyper 0.14" -version = "0.0.6" +version = "0.0.7" authors = ["Jose Quintana "] license = "MIT OR Apache-2.0" repository = "https://github.com/static-web-server/hyper-middleware" From 8e811bb04f38f92262aa7dd23a759430c5710ae7 Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Sun, 2 Apr 2023 22:49:44 +0200 Subject: [PATCH 3/7] refactor: remove error status reference --- src/error/macros.rs | 54 ++++++++++++++++++++++----------------------- src/error/mod.rs | 30 +++++++++++++++++++++---- 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/src/error/macros.rs b/src/error/macros.rs index 71ac58a..6bb1d61 100644 --- a/src/error/macros.rs +++ b/src/error/macros.rs @@ -15,7 +15,7 @@ macro_rules! bail { } // 4xx -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::BAD_REQUEST`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::BAD_REQUEST`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_bad_request { ($($arg:tt)*) => {{ @@ -23,7 +23,7 @@ macro_rules! http_error_bad_request { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::UNAUTHORIZED`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::UNAUTHORIZED`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_unauthorized { ($($arg:tt)*) => {{ @@ -31,7 +31,7 @@ macro_rules! http_error_unauthorized { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::PAYMENT_REQUIRED`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::PAYMENT_REQUIRED`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_payment_required { ($($arg:tt)*) => {{ @@ -39,7 +39,7 @@ macro_rules! http_error_payment_required { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::FORBIDDEN`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::FORBIDDEN`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_forbidden { ($($arg:tt)*) => {{ @@ -47,7 +47,7 @@ macro_rules! http_error_forbidden { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::NOT_FOUND`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::NOT_FOUND`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_not_found { ($($arg:tt)*) => {{ @@ -55,7 +55,7 @@ macro_rules! http_error_not_found { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::METHOD_NOT_ALLOWED`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::METHOD_NOT_ALLOWED`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_method_not_allowed { ($($arg:tt)*) => {{ @@ -63,7 +63,7 @@ macro_rules! http_error_method_not_allowed { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::NOT_ACCEPTABLE`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::NOT_ACCEPTABLE`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_not_acceptable { ($($arg:tt)*) => {{ @@ -71,7 +71,7 @@ macro_rules! http_error_not_acceptable { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::PROXY_AUTHENTICATION_REQUIRED`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::PROXY_AUTHENTICATION_REQUIRED`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_proxy_authentication_required { ($($arg:tt)*) => {{ @@ -79,7 +79,7 @@ macro_rules! http_error_proxy_authentication_required { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::REQUEST_TIMEOUT`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::REQUEST_TIMEOUT`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_request_timeout { ($($arg:tt)*) => {{ @@ -87,7 +87,7 @@ macro_rules! http_error_request_timeout { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::CONFLICT`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::CONFLICT`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_conflict { ($($arg:tt)*) => {{ @@ -95,7 +95,7 @@ macro_rules! http_error_conflict { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::GONE`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::GONE`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_gone { ($($arg:tt)*) => {{ @@ -103,7 +103,7 @@ macro_rules! http_error_gone { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::LENGTH_REQUIRED`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::LENGTH_REQUIRED`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_length_required { ($($arg:tt)*) => {{ @@ -111,7 +111,7 @@ macro_rules! http_error_length_required { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::PRECONDITION_FAILED`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::PRECONDITION_FAILED`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_precondition_failed { ($($arg:tt)*) => {{ @@ -119,7 +119,7 @@ macro_rules! http_error_precondition_failed { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::PAYLOAD_TOO_LARGE`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::PAYLOAD_TOO_LARGE`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_payload_too_large { ($($arg:tt)*) => {{ @@ -127,7 +127,7 @@ macro_rules! http_error_payload_too_large { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::URI_TOO_LONG`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::URI_TOO_LONG`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_uri_too_long { ($($arg:tt)*) => {{ @@ -135,7 +135,7 @@ macro_rules! http_error_uri_too_long { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::UNSUPPORTED_MEDIA_TYPE`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::UNSUPPORTED_MEDIA_TYPE`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_unsupported_media_type { ($($arg:tt)*) => {{ @@ -143,7 +143,7 @@ macro_rules! http_error_unsupported_media_type { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::RANGE_NOT_SATISFIABLE`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::RANGE_NOT_SATISFIABLE`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_range_not_satisfiable { ($($arg:tt)*) => {{ @@ -151,7 +151,7 @@ macro_rules! http_error_range_not_satisfiable { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::EXPECTATION_FAILED`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::EXPECTATION_FAILED`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_expectation_failed { ($($arg:tt)*) => {{ @@ -160,7 +160,7 @@ macro_rules! http_error_expectation_failed { } // 50x -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::INTERNAL_SERVER_ERROR`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::INTERNAL_SERVER_ERROR`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_internal_server_error { ($($arg:tt)*) => {{ @@ -168,7 +168,7 @@ macro_rules! http_error_internal_server_error { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::NOT_IMPLEMENTED`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::NOT_IMPLEMENTED`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_not_implemented { ($($arg:tt)*) => {{ @@ -176,7 +176,7 @@ macro_rules! http_error_not_implemented { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::BAD_GATEWAY`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::BAD_GATEWAY`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_bad_gateway { ($($arg:tt)*) => {{ @@ -184,7 +184,7 @@ macro_rules! http_error_bad_gateway { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::SERVICE_UNAVAILABLE`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::SERVICE_UNAVAILABLE`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_service_unavailable { ($($arg:tt)*) => {{ @@ -192,7 +192,7 @@ macro_rules! http_error_service_unavailable { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::GATEWAY_TIMEOUT`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::GATEWAY_TIMEOUT`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_gateway_timeout { ($($arg:tt)*) => {{ @@ -200,7 +200,7 @@ macro_rules! http_error_gateway_timeout { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::HTTP_VERSION_NOT_SUPPORTED`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::HTTP_VERSION_NOT_SUPPORTED`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_http_version_not_supported { ($($arg:tt)*) => {{ @@ -208,7 +208,7 @@ macro_rules! http_error_http_version_not_supported { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::VARIANT_ALSO_NEGOTIATES`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::VARIANT_ALSO_NEGOTIATES`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_variant_also_negotiates { ($($arg:tt)*) => {{ @@ -216,7 +216,7 @@ macro_rules! http_error_variant_also_negotiates { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::INSUFFICIENT_STORAGE`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::INSUFFICIENT_STORAGE`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_insufficient_storage { ($($arg:tt)*) => {{ @@ -224,7 +224,7 @@ macro_rules! http_error_insufficient_storage { }} } -/// Constructs an [`Error`][`super::Error`] with [`http::StatusCode::LOOP_DETECTED`] from a string or existing non-anyhow error value. +/// Constructs an [`Error`][`super::Error`] with [`hyper::StatusCode::LOOP_DETECTED`] from a string or existing non-anyhow error value. #[macro_export] macro_rules! http_error_loop_detected { ($($arg:tt)*) => {{ diff --git a/src/error/mod.rs b/src/error/mod.rs index bdc01b8..95e4a50 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -4,8 +4,30 @@ //! The `Error` type implements other several common error types //! to ease conversions while consuming the input value via the [`From`] trait. //! -//! Additionally, when used in HTTP contexts, the `Error` type can be associated to an [HTTP Status Code][`http::StatusCode`] +//! Additionally, when used in HTTP contexts, the `Error` type can be associated to an [HTTP Status Code][`hyper::StatusCode`]. //! via the [`Error::with_status`][`super::Error::with_status`] method. +//! +//! a. Construct an [`Error`][`super::Error`] from [`hyper::Error`], [`std::io::Error`], [`anyhow::Error`] or an string. +//! +//! ```rust +//! use hyper_middleware::error +//! +//! let err = Error::from("some error type or string"); +//! // Or using a shortcut macro +//! let err = error!("some error type or string"); +//! ``` +//! +//! b. Construct an [`Error`][`super::Error`] with an associated [HTTP Status Code][`hyper::StatusCode`]. +//! +//! ```rust +//! use hyper::StatusCode; +//! use hyper_middleware::error +//! +//! let err = error!("user or password does not match").with_status(StatusCode::UNAUTHORIZED); +//! // Or using a shortcut macro +//! let err = http_error_unauthorized!("user or password does not match"); +//! ``` +//! use hyper::StatusCode; use std::fmt; @@ -29,9 +51,9 @@ impl Error { self.source } - /// Returns an HTTP Status Code reference associated with the underlying error. - pub fn status(&self) -> Option<&StatusCode> { - self.status.as_ref() + /// Returns the HTTP `StatusCode` associated with the underlying error. + pub fn status(&self) -> Option { + self.status } /// Adds/updates the current HTTP Status Code. From e35d5874900bb07304a7576ee5cf4bc2e0a43202 Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Sun, 2 Apr 2023 22:51:23 +0200 Subject: [PATCH 4/7] v0.0.8 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2de44c7..0c79d38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "hyper-middleware" description = "A compact HTTP middleware and handler system for Hyper 0.14" -version = "0.0.7" +version = "0.0.8" authors = ["Jose Quintana "] license = "MIT OR Apache-2.0" repository = "https://github.com/static-web-server/hyper-middleware" From c0b80a2ac3c4f71cac54e52fc389547bc2c137c1 Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Mon, 20 Nov 2023 01:35:27 +0100 Subject: [PATCH 5/7] chore: update dependencies 20.11.2023 --- Cargo.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0c79d38..cd77433 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,11 +25,11 @@ keywords = [ ] [dependencies] -hyper = { version = "0.14.25", default-features = false, features = ["server", "tcp"] } -anyhow = "1.0.70" -thiserror = "1.0.40" -async-trait = "0.1.68" -async-recursion = "1.0.4" +hyper = { version = "0.14.27", default-features = false, features = ["server", "tcp"] } +anyhow = "1.0.75" +thiserror = "1.0.50" +async-trait = "0.1.74" +async-recursion = "1.0.5" [dev-dependencies] hyper = { version = "0.14", features = ["tcp", "server", "http1"] } From c0aa9cf742f1e81bcc38d2470fb24aca498ca978 Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Mon, 5 Feb 2024 23:57:18 +0100 Subject: [PATCH 6/7] refactor: rename `Chain` struct to `Middlewares` - Improve documentation and fix all doc examples - MSRV 1.56.0 - Update dependencies - Addci pipelines --- .github/FUNDING.yml | 2 + .github/workflows/audit.yml | 28 +++++++ .github/workflows/devel.yml | 151 ++++++++++++++++++++++++++++++++++++ Cargo.toml | 21 +++-- README.md | 47 ++++++----- examples/server.rs | 42 +++++----- src/error/mod.rs | 15 ++-- src/http.rs | 4 +- src/lib.rs | 17 +++- src/middleware.rs | 99 +++++++++++++---------- src/service.rs | 19 +++-- 11 files changed, 334 insertions(+), 111 deletions(-) create mode 100644 .github/FUNDING.yml create mode 100644 .github/workflows/audit.yml create mode 100644 .github/workflows/devel.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..f132139 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: joseluisq +custom: paypal.me/joseluisqs diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml new file mode 100644 index 0000000..3ae0dd2 --- /dev/null +++ b/.github/workflows/audit.yml @@ -0,0 +1,28 @@ +name: audit +on: + schedule: + - cron: '20 01 * * *' # Every day at 01:20 UTC + push: + branches: + - master + paths: + - "**/Cargo.lock" + - "**/Cargo.toml" + pull_request: + branches: + - master + paths: + - "**/Cargo.lock" + - "**/Cargo.toml" + +jobs: + audit: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - uses: actions-rs/audit-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/devel.yml b/.github/workflows/devel.yml new file mode 100644 index 0000000..e59a426 --- /dev/null +++ b/.github/workflows/devel.yml @@ -0,0 +1,151 @@ +name: devel +on: + pull_request: + paths: + - .github/workflows/devel.yml + - .cargo/config.toml + - Cargo.lock + - Cargo.toml + - src/** + push: + branches: + - master + - staging + - trying + paths: + - .github/workflows/devel.yml + - Cargo.lock + - Cargo.toml + - src/** + schedule: + - cron: '15 01 * * *' # Every day at 01:15 UTC + +jobs: + test: + name: test + runs-on: ${{ matrix.os }} + env: + # Cargo binary + CARGO_BIN: cargo + # When CARGO_BIN is set to CROSS, this is set to `--target matrix.target` + TARGET_FLAGS: "" + # When CARGO_BIN is set to CROSS, TARGET_DIR includes matrix.target + TARGET_DIR: ./target + # Emit backtraces on panics + RUST_BACKTRACE: 1 + # Skip tests + SKIP_TESTS: "" + strategy: + matrix: + build: + - pinned + - linux-musl + - linux-gnu + - macos + - windows-msvc + include: + # Specific Rust channels. + # We test against the latest and minimum Rust stable version. + - build: pinned + os: ubuntu-22.04 + rust: 1.70.0 + # Some of our release builds are generated by a nightly compiler to take + # advantage of the latest optimizations/compile time improvements. + - build: linux-musl + os: ubuntu-22.04 + rust: stable + target: x86_64-unknown-linux-musl + - build: linux-gnu + os: ubuntu-22.04 + rust: stable + target: x86_64-unknown-linux-gnu + - build: macos + os: macos-12 + rust: stable + target: x86_64-apple-darwin + - build: windows-msvc + os: windows-2022 + rust: stable + target: x86_64-pc-windows-msvc + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + + - name: Set up Cross + if: ${{ !contains(matrix.os, 'windows') && matrix.target != '' }} + shell: bash + run: | + target='' + case "${{ matrix.os }}" in + *macos*) + target=x86_64-apple-darwin + ;; + *) + target=x86_64-unknown-linux-musl + ;; + esac + + echo "Installing cross..." + curl -sSL \ + "https://github.com/cross-rs/cross/releases/download/v0.2.5/cross-$target.tar.gz" \ + | sudo tar zxf - -C /usr/local/bin/ cross cross-util + cross -V + echo "CARGO_BIN=/usr/local/bin/cross" >> $GITHUB_ENV + + - name: Setup Cargo + shell: bash + run: | + if [[ "${{ matrix.target }}" != "" ]]; then + echo "TARGET_FLAGS=--target=${{ matrix.target }}" >> $GITHUB_ENV + echo "TARGET_DIR=./target/${{ matrix.target }}" >> $GITHUB_ENV + fi + echo "cargo command is: ${{ env.CARGO_BIN }}" + echo "target flag is: ${{ env.TARGET_FLAGS }}" + echo "target dir is: ${{ env.TARGET_DIR }}" + + - name: Run tests + shell: bash + run: | + ${{ env.CARGO_BIN }} test --verbose ${{ env.TARGET_FLAGS }} ${{ env.SKIP_TESTS }} + + - name: Run build + shell: bash + run: | + ${{ env.CARGO_BIN }} build --example server --verbose ${{ env.TARGET_FLAGS }} + + checks: + name: checks + runs-on: ubuntu-22.04 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + components: rustfmt, clippy + + - name: Check formatting + run: | + cargo fmt --all -- --check + + - name: Check via Clippy + run: | + cargo clippy --all-features -- -D warnings + + - name: Check crate docs + run: | + cargo doc --lib --no-deps diff --git a/Cargo.toml b/Cargo.toml index cd77433..5cf6cac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,8 @@ authors = ["Jose Quintana "] license = "MIT OR Apache-2.0" repository = "https://github.com/static-web-server/hyper-middleware" documentation = "https://docs.rs/hyper-middleware" -edition = "2018" +edition = "2021" +rust-version = "1.56.0" categories = ["network-programming", "web-programming::http-server"] include = [ "src/**/*.rs", @@ -25,12 +26,22 @@ keywords = [ ] [dependencies] -hyper = { version = "0.14.27", default-features = false, features = ["server", "tcp"] } -anyhow = "1.0.75" -thiserror = "1.0.50" -async-trait = "0.1.74" +hyper = { version = "0.14.28", default-features = false, features = ["server", "tcp"] } +anyhow = "1.0.79" +thiserror = "1.0.56" +async-trait = "0.1.77" async-recursion = "1.0.5" [dev-dependencies] hyper = { version = "0.14", features = ["tcp", "server", "http1"] } tokio = { version = "1", features = ["rt-multi-thread", "macros"], default-features = false } + +[profile.release] +codegen-units = 1 +debug = false +debug-assertions = false +lto = "fat" +opt-level = 3 +panic = "abort" +rpath = false +strip = true diff --git a/README.md b/README.md index c81ddd1..2f0383c 100644 --- a/README.md +++ b/README.md @@ -12,20 +12,20 @@ - Compact Middleware and Handler System inspired by [The Iron Framework](https://github.com/iron/iron). - Simple [Hyper Service](https://docs.rs/hyper/latest/hyper/service/trait.Service.html) with convenient __Remote Address__ access. - Convenient `Error` and `Result` types powered by [anyhow](https://github.com/dtolnay/anyhow). +- `Async` support via [async-trait](https://github.com/dtolnay/async-trait). +- Macros to facilitate HTTP response errors or error casting. ## Example [examples/server.rs](examples/server.rs) ```rust -#![deny(warnings)] - use hyper::{header, Server, StatusCode}; -use std::{net::SocketAddr, path::PathBuf}; use hyper_middleware::{ - async_trait, AfterMiddleware, BeforeMiddleware, Body, Chain, Error, Handler, Request, Response, - Result, Service, + async_trait, AfterMiddleware, BeforeMiddleware, Body, Error, Handler, Middlewares, Request, + Response, Result, Service, }; +use std::{net::SocketAddr, path::PathBuf}; struct Config { pub root: PathBuf, @@ -39,17 +39,18 @@ struct Application { impl Handler for Application { async fn handle(&self, req: &mut Request) -> Result { // Access the Hyper incoming Request - println!("Handler - URI Path: {}", req.uri().path()); + println!("Application::handle() - URI Path: {}", req.uri().path()); // Access the custom app options - println!("Config Root: {}", self.opts.root.display()); - - // Access the Remote Address println!( - "Remote Addr: {}", - req.extensions().get::().unwrap() + "Application::handle() - Config Root: {}", + self.opts.root.display() ); + // Access the Remote Address + let remote_addr = req.extensions().get::().unwrap(); + println!("Application::handle() - Remote Addr: {}", remote_addr); + // Create a Hyper Response and send it back to the middlewares chain Ok(Response::new(Body::from("¡Hola!"))) } @@ -60,10 +61,10 @@ struct FirstMiddleware {} #[async_trait] impl BeforeMiddleware for FirstMiddleware { async fn before(&self, req: &mut Request) -> Result { - println!("First Middleware called!"); + println!("FirstMiddleware::before()"); // Access the Hyper incoming Request - println!("First - URI Path: {}", req.uri().path()); + println!("FirstMiddleware::before() - URI Path: {}", req.uri().path()); Ok(()) } @@ -78,7 +79,7 @@ struct SecondMiddleware {} #[async_trait] impl AfterMiddleware for SecondMiddleware { async fn after(&self, _: &mut Request, mut res: Response) -> Result { - println!("Second Middleware called!"); + println!("SecondMiddleware::after()"); // Mutate the Hyper Response at convenience // and send it back to other middlewares on the chain @@ -105,17 +106,15 @@ async fn main() -> Result { root: std::env::current_dir().unwrap(), }; - // 1. Create a custom middleware chain - let mut handler = Chain::new(Application { opts }); + // 1. Create a custom middleware chain and plug in some custom middlewares + let mut middlewares = Middlewares::new(Application { opts }); + middlewares.link_before(FirstMiddleware {}); + middlewares.link_after(SecondMiddleware {}); - // 2. Plug in some custom middlewares - handler.link_before(FirstMiddleware {}); - handler.link_after(SecondMiddleware {}); + // 2. Create a Hyper service and set the current handler with its middlewares + let service = Service::new(middlewares); - // 3. Create a Hyper service and set the current handler with its middlewares - let service = Service::new(handler); - - // 4. Finally just run server using the service already created + // 3. Finally just run server using the service already created let addr = ([127, 0, 0, 1], 8787).into(); let server = Server::bind(&addr).serve(service); @@ -129,7 +128,7 @@ async fn main() -> Result { To run the example just type: -``` +```sh cargo run --example server ``` diff --git a/examples/server.rs b/examples/server.rs index c719413..f6c96be 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -4,12 +4,11 @@ #![deny(dead_code)] use hyper::{header, Server, StatusCode}; -use std::{net::SocketAddr, path::PathBuf}; - use hyper_middleware::{ - async_trait, AfterMiddleware, BeforeMiddleware, Body, Chain, Error, Handler, Request, Response, - Result, Service, + async_trait, AfterMiddleware, BeforeMiddleware, Body, Error, Handler, Middlewares, Request, + Response, Result, Service, }; +use std::{net::SocketAddr, path::PathBuf}; struct Config { pub root: PathBuf, @@ -23,17 +22,18 @@ struct Application { impl Handler for Application { async fn handle(&self, req: &mut Request) -> Result { // Access the Hyper incoming Request - println!("Handler - URI Path: {}", req.uri().path()); + println!("Application::handle() - URI Path: {}", req.uri().path()); // Access the custom app options - println!("Config Root: {}", self.opts.root.display()); - - // Access the Remote Address println!( - "Remote Addr: {}", - req.extensions().get::().unwrap() + "Application::handle() - Config Root: {}", + self.opts.root.display() ); + // Access the Remote Address + let remote_addr = req.extensions().get::().unwrap(); + println!("Application::handle() - Remote Addr: {}", remote_addr); + // Create a Hyper Response and send it back to the middlewares chain Ok(Response::new(Body::from("¡Hola!"))) } @@ -44,10 +44,10 @@ struct FirstMiddleware {} #[async_trait] impl BeforeMiddleware for FirstMiddleware { async fn before(&self, req: &mut Request) -> Result { - println!("First Middleware called!"); + println!("FirstMiddleware::before()"); // Access the Hyper incoming Request - println!("First - URI Path: {}", req.uri().path()); + println!("FirstMiddleware::before() - URI Path: {}", req.uri().path()); Ok(()) } @@ -62,7 +62,7 @@ struct SecondMiddleware {} #[async_trait] impl AfterMiddleware for SecondMiddleware { async fn after(&self, _: &mut Request, mut res: Response) -> Result { - println!("Second Middleware called!"); + println!("SecondMiddleware::after()"); // Mutate the Hyper Response at convenience // and send it back to other middlewares on the chain @@ -89,17 +89,15 @@ async fn main() -> Result { root: std::env::current_dir().unwrap(), }; - // 1. Create a custom middleware chain - let mut handler = Chain::new(Application { opts }); - - // 2. Plug in some custom middlewares - handler.link_before(FirstMiddleware {}); - handler.link_after(SecondMiddleware {}); + // 1. Create a custom middleware chain and plug in some custom middlewares + let mut middlewares = Middlewares::new(Application { opts }); + middlewares.link_before(FirstMiddleware {}); + middlewares.link_after(SecondMiddleware {}); - // 3. Create a Hyper service and set the current handler with its middlewares - let service = Service::new(handler); + // 2. Create a Hyper service and set the current handler with its middlewares + let service = Service::new(middlewares); - // 4. Finally just run server using the service already created + // 3. Finally just run server using the service already created let addr = ([127, 0, 0, 1], 8787).into(); let server = Server::bind(&addr).serve(service); diff --git a/src/error/mod.rs b/src/error/mod.rs index 95e4a50..08183be 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -1,8 +1,8 @@ //! The custom error module. //! -//! This module provides a custom [`Error`][`super::Error`] type with HTTP Status functionality as well as useful macros. -//! The `Error` type implements other several common error types -//! to ease conversions while consuming the input value via the [`From`] trait. +//! This module provides a custom [`Error`][`super::Error`] type with HTTP Status functionality as well as useful [`macros`]. +//! The `Error` type implements other several common error types as well +//! to ease conversion while consuming the input value via the [`From`] trait. //! //! Additionally, when used in HTTP contexts, the `Error` type can be associated to an [HTTP Status Code][`hyper::StatusCode`]. //! via the [`Error::with_status`][`super::Error::with_status`] method. @@ -10,7 +10,7 @@ //! a. Construct an [`Error`][`super::Error`] from [`hyper::Error`], [`std::io::Error`], [`anyhow::Error`] or an string. //! //! ```rust -//! use hyper_middleware::error +//! use hyper_middleware::{Error, error}; //! //! let err = Error::from("some error type or string"); //! // Or using a shortcut macro @@ -21,7 +21,7 @@ //! //! ```rust //! use hyper::StatusCode; -//! use hyper_middleware::error +//! use hyper_middleware::{error, http_error_unauthorized}; //! //! let err = error!("user or password does not match").with_status(StatusCode::UNAUTHORIZED); //! // Or using a shortcut macro @@ -33,9 +33,14 @@ use hyper::StatusCode; use std::fmt; use thiserror::Error as ThisError; +/// Macros that provide several facilities for working with HTTP response errors or error casting. pub mod macros; +/// `Result` +/// +/// An alias of [anyhow::Result][`anyhow::Result`] with defaults. pub type Result = anyhow::Result; + pub use anyhow::Context; /// Represents an HTTP Error. diff --git a/src/http.rs b/src/http.rs index 5916b12..573ed3f 100644 --- a/src/http.rs +++ b/src/http.rs @@ -3,8 +3,8 @@ /// A [`hyper::Body`] type alias. pub type Body = hyper::Body; -/// A [`hyper::Request`] type alias. +/// A [`hyper::Request`] type alias with defaults. pub type Request = hyper::Request; -/// A [`hyper::Response`] type alias. +/// A [`hyper::Response`] type alias with defaults. pub type Response = hyper::Response; diff --git a/src/lib.rs b/src/lib.rs index 2408e81..42a112d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,9 @@ -// #![deny(missing_docs)] +#![deny(missing_docs)] +#![forbid(unsafe_code)] +#![deny(warnings)] +#![deny(rust_2018_idioms)] +#![deny(dead_code)] +#![cfg_attr(docsrs, feature(doc_cfg))] //! # hyper_middleware //! @@ -10,6 +15,10 @@ //! - Compact [Middleware & Handler System][`middleware`] inspired by [The Iron Framework](https://github.com/iron/iron). //! - Simple [Hyper Service][`hyper::service::Service`] with [Remote Address][`hyper::server::conn::AddrStream`] access. //! - Convenient [`Error`] and [`Result`] types powered by [anyhow](https://github.com/dtolnay/anyhow). +//! - `Async` support via [async-trait](https://github.com/dtolnay/async-trait). +//! - Macros to facilitate HTTP response errors or error casting. +//! +//! Check it out [`middleware`] module for more details. //! pub mod error; @@ -18,10 +27,12 @@ pub mod middleware; pub mod remote_addr; pub mod service; -pub use async_recursion::*; -pub use async_trait::*; pub use error::{Context, Error, Result}; pub use http::*; pub use middleware::*; pub use remote_addr::*; pub use service::*; + +// Re-export crates +pub use async_recursion::*; +pub use async_trait::*; diff --git a/src/middleware.rs b/src/middleware.rs index c0da3ad..f205dac 100644 --- a/src/middleware.rs +++ b/src/middleware.rs @@ -16,11 +16,16 @@ //! Here's an example of a `Handler`: //! //! ```rust -//! use hyper_middleware::{Request, Response, Result}; +//! use hyper_middleware::{async_trait, Handler, Request, Response, Result, Body}; //! -//! fn hello_handler(req: &mut Request) -> Result { -//! Ok(Response::builder().body(Body::from("¡Hola!")).unwrap()) -//! }; +//! struct Application {} +//! +//! #[async_trait] +//! impl Handler for Application { +//! async fn handle(&self, req: &mut Request) -> Result { +//! Ok(Response::builder().body(Body::from("¡Hola!")).unwrap()) +//! } +//! } //! ``` //! //! # Middleware @@ -47,26 +52,33 @@ //! //! ## Defining the middleware pipeline //! -//! A `Chain` is a `Handler` that wraps another `Handler`. It is used to attach +//! the `Middlewares` chain is a `Handler` that wraps another `Handler`. It is used to attach //! middleware to the wrapped `Handler` using a `link` method corresponding to //! each type of middleware. A sample middleware pipeline is shown below: //! //! ```rust //! use hyper::Server; -//! use hyper_middleware::{BeforeMiddleware, Body, Chain, Request, Response, Result, Service}; -//! -//! fn hello_handler(_req: &mut Request) -> Result { -//! let mut resp = Response::new(Body::from("¡Hola!")); -//! resp.headers_mut().insert( -//! header::CONTENT_TYPE, -//! "text/html; charset=utf-8".parse().unwrap(), -//! ); -//! Ok(resp) +//! use hyper_middleware::{async_trait, Handler, BeforeMiddleware, Body, Middlewares, Request, Response, Result, Service}; +//! +//! struct Application {} +//! +//! #[async_trait] +//! impl Handler for Application { +//! async fn handle(&self, req: &mut Request) -> Result { +//! let mut resp = Response::new(Body::from("¡Hola!")); +//! resp.headers_mut().insert( +//! hyper::header::CONTENT_TYPE, +//! "text/html; charset=utf-8".parse().unwrap(), +//! ); +//! Ok(resp) +//! } //! } //! //! struct RequestLoggingMiddleware {} +//! +//! #[async_trait] //! impl BeforeMiddleware for RequestLoggingMiddleware { -//! fn before(&self, req: &mut Request) -> Result { +//! async fn before(&self, req: &mut Request) -> Result { //! println!("{:?}", req); //! Ok(()) //! } @@ -74,16 +86,16 @@ //! //! #[tokio::main(flavor = "multi_thread")] //! async fn main() -> Result { -//! let mut chain = Chain::new(hello_handler); +//! let mut middlewares = Middlewares::new(Application {}); //! // Plug in the custom middleware(s) -//! chain.link_before(RequestLoggingMiddleware {}); +//! middlewares.link_before(RequestLoggingMiddleware {}); //! -//! let addr = ([127, 0, 0, 1], 8787).into(); -//! let service = Service::new(chain); +//! let addr = ([127, 0, 0, 1], 8080).into(); +//! let service = Service::new(middlewares); //! let server = Server::bind(&addr).serve(service); //! println!("Listening on http://{}", addr); //! -//! server.await?; +//! // server.await?; //! //! Ok(()) //! } @@ -153,7 +165,7 @@ use std::sync::Arc; use crate::{Error, Request, Response, Result}; #[async_trait] -/// `Handler`s are responsible for handling requests by creating `Response`s from `Request`s. +/// `Handler`s are responsible for handling requests by creating `Response`s from those `Request`s. pub trait Handler: Send + Sync + 'static { /// Produce a `Response` from a Request, with the possibility of error. async fn handle(&self, req: &mut Request) -> Result; @@ -177,7 +189,7 @@ impl Handler for Box { } #[async_trait] -/// `BeforeMiddleware` are fired before a `Handler` is called inside of a Chain. +/// `BeforeMiddleware` are fired before a `Handler` is called inside of a Middlewares. /// /// `BeforeMiddleware` are responsible for doing request pre-processing that requires /// the ability to change control-flow, such as authorization middleware, or for editing @@ -204,13 +216,13 @@ pub trait BeforeMiddleware: Send + Sync + 'static { } #[async_trait] -/// `AfterMiddleware` are fired after a `Handler` is called inside of a `Chain`. +/// `AfterMiddleware` are fired after a `Handler` is called inside of the `Middlewares` chain. /// /// `AfterMiddleware` receive both a `Request` and a `Response` and are responsible for doing /// any response post-processing. /// /// `AfterMiddleware` should *not* overwrite the contents of a `Response`. In -/// the common case, a complete response is generated by the Chain's `Handler` and +/// the common case, a complete response is generated by the Middlewares's `Handler` and /// `AfterMiddleware` simply do post-processing of that Response, such as /// adding headers or logging. pub trait AfterMiddleware: Send + Sync + 'static { @@ -229,19 +241,19 @@ pub trait AfterMiddleware: Send + Sync + 'static { } #[async_trait(?Send)] -/// `AroundMiddleware` are used to wrap and replace the `Handler` in a `Chain`. +/// `AroundMiddleware` are used to wrap and replace the `Handler` in the `Middlewares` chain. /// /// `AroundMiddleware` produce `Handler`s through their `around` method, which is -/// called once on insertion into a `Chain` or can be called manually outside of a -/// `Chain`. +/// called once on insertion into the `Middlewares` chain or can be called manually outside of a +/// `Middlewares` chain. pub trait AroundMiddleware { /// Produce a `Handler` from this `AroundMiddleware` given another `Handler`. /// /// Usually this means wrapping the handler and editing the `Request` on the /// way in and the `Response` on the way out. /// - /// This is called only once, when an `AroundMiddleware` is added to a `Chain` - /// using `Chain::around`, it is passed the `Chain`'s current `Handler`. + /// This is called only once, when an `AroundMiddleware` is added to the `Middlewares` chain + /// using `Middlewares::around`, it is passed the `Middlewares` chain's current `Handler`. async fn around(self, handler: Box) -> Box; } @@ -250,7 +262,7 @@ pub trait AroundMiddleware { /// This is a canonical implementation of Iron's middleware system, /// but Iron's infrastructure is flexible enough to allow alternate /// systems. -pub struct Chain { +pub struct Middlewares { befores: Vec>, afters: Vec>, @@ -258,9 +270,12 @@ pub struct Chain { handler: Option>, } -impl Chain { - /// Construct a new `Chain` from a `Handler`. - pub fn new(handler: H) -> Self { +impl Middlewares { + /// Construct a new middleware chain from a `Handler`. + pub fn new(handler: H) -> Self + where + H: Handler, + { Self { befores: vec![], afters: vec![], @@ -272,7 +287,7 @@ impl Chain { /// /// Middleware that have a Before and After piece should have a constructor /// which returns both as a tuple, so it can be passed directly to link. - pub fn link(&mut self, link: (B, A)) -> &mut Chain + pub fn link(&mut self, link: (B, A)) -> &mut Middlewares where A: AfterMiddleware, B: BeforeMiddleware, @@ -285,9 +300,9 @@ impl Chain { self } - /// Link a `BeforeMiddleware` to the `Chain`, after all previously linked + /// Link a `BeforeMiddleware` to the `Middlewares` chain, after all previously linked /// `BeforeMiddleware`s. - pub fn link_before(&mut self, before: B) -> &mut Chain + pub fn link_before(&mut self, before: B) -> &mut Middlewares where B: BeforeMiddleware, { @@ -296,9 +311,9 @@ impl Chain { self } - /// Link a `AfterMiddleware` to the `Chain`, after all previously linked + /// Link a `AfterMiddleware` to the `Middlewares` chain, after all previously linked /// `AfterMiddleware`s. - pub fn link_after(&mut self, after: A) -> &mut Chain + pub fn link_after(&mut self, after: A) -> &mut Middlewares where A: AfterMiddleware, { @@ -307,8 +322,8 @@ impl Chain { self } - /// Apply an `AroundMiddleware` to the `Handler` in this `Chain`. - pub async fn link_around(&mut self, around: A) -> &mut Chain + /// Apply an `AroundMiddleware` to the `Handler` in this `Middlewares` chain. + pub async fn link_around(&mut self, around: A) -> &mut Middlewares where A: AroundMiddleware, { @@ -320,7 +335,7 @@ impl Chain { } #[async_trait] -impl Handler for Chain { +impl Handler for Middlewares { async fn handle(&self, req: &mut Request) -> Result { // Kick off at befores, which will continue into handler // then afters. @@ -328,7 +343,7 @@ impl Handler for Chain { } } -impl Chain { +impl Middlewares { #[async_recursion] // Enter the error flow from a before middleware, starting // at the passed index. diff --git a/src/service.rs b/src/service.rs index 4dbb369..771851f 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,19 +1,21 @@ //! The Hyper service module. //! //! It provides a [Hyper Service][`hyper::service::Service`] implementation intended to work with -//! the [Hyper Server Builder][`hyper::server::Builder`]. +//! the `hyper::server::Builder`. //! -//! The service allows to bind a [`Chain`][`super::Chain`] of middlewares. +//! The service allows to bind a [`Middlewares`][`super::Middlewares`] of middlewares. //! //! ## Example //! //! ```rust //! use hyper::Server; //! use hyper_middleware::{ -//! Body, Handler, Request, Response, Result, Service +//! async_trait, Body, Handler, Request, Response, Middlewares, Result, Service, //! }; //! //! struct Application {} +//! +//! #[async_trait] //! impl Handler for Application { //! async fn handle(&self, _req: &mut Request) -> Result { //! // Create a response and send it back to the middlewares chain @@ -23,16 +25,17 @@ //! //! #[tokio::main(flavor = "multi_thread")] //! async fn main() -> Result { -//! let mut my_handler = Chain::new(Application {}); +//! let mut middlewares = Middlewares::new(Application {}); //! -//! let my_service = Service::new(my_handler); +//! let service = Service::new(middlewares); //! -//! let addr = ([127, 0, 0, 1], 8787).into(); -//! let server = Server::bind(&addr).serve(my_service); +//! let addr = ([127, 0, 0, 1], 8087).into(); +//! let server = Server::bind(&addr).serve(service); //! //! println!("Listening on http://{}", addr); //! -//! server.await?; +//! // server.await?; +//! //! Ok(()) //! } //! ``` From 39604287aad7619d42a49ae87e9bdff6d4c4e2d9 Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Mon, 5 Feb 2024 23:59:54 +0100 Subject: [PATCH 7/7] v0.0.9 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5cf6cac..db55e40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "hyper-middleware" description = "A compact HTTP middleware and handler system for Hyper 0.14" -version = "0.0.8" +version = "0.0.9" authors = ["Jose Quintana "] license = "MIT OR Apache-2.0" repository = "https://github.com/static-web-server/hyper-middleware"