diff --git a/examples/middleware.rs b/examples/middleware.rs index a2ae65bc6..8a317b858 100644 --- a/examples/middleware.rs +++ b/examples/middleware.rs @@ -2,14 +2,14 @@ use std::future::Future; use std::pin::Pin; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; -use tide::{Middleware, Next, Request, Response, Result, StatusCode}; +use tide::{After, Before, Middleware, Next, Request, Response, Result, StatusCode}; #[derive(Debug)] struct User { name: String, } -#[derive(Default)] +#[derive(Default, Debug)] struct UserDatabase; impl UserDatabase { async fn find_user(&self) -> Option { @@ -78,13 +78,47 @@ impl Middleware for RequestCounterMiddlewar } } +const NOT_FOUND_HTML_PAGE: &str = " +

uh oh, we couldn't find that document

+

+ probably, this would be served from the file system or + included with `include_bytes!` +

+"; + +const INTERNAL_SERVER_ERROR_HTML_PAGE: &str = " +

whoops! it's not you, it's us

+

+ we're very sorry, but something seems to have gone wrong on our end +

+"; + #[async_std::main] async fn main() -> Result<()> { tide::log::start(); let mut app = tide::with_state(UserDatabase::default()); + app.middleware(After(|result: Result| async move { + let response = result.unwrap_or_else(|e| Response::new(e.status())); + match response.status() { + StatusCode::NotFound => Ok(response + .set_content_type(tide::http::mime::HTML) + .body_string(NOT_FOUND_HTML_PAGE.into())), + + StatusCode::InternalServerError => Ok(response + .set_content_type(tide::http::mime::HTML) + .body_string(INTERNAL_SERVER_ERROR_HTML_PAGE.into())), + + _ => Ok(response), + } + })); + app.middleware(user_loader); app.middleware(RequestCounterMiddleware::new(0)); + app.middleware(Before(|mut request: Request| async move { + request.set_ext(std::time::Instant::now()); + request + })); app.at("/").get(|req: Request<_>| async move { let count: &RequestCount = req.ext().unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 712aff943..da016d4f1 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -211,7 +211,7 @@ pub mod security; pub mod sse; pub use endpoint::Endpoint; -pub use middleware::{Middleware, Next}; +pub use middleware::{After, Before, Middleware, Next}; pub use redirect::Redirect; pub use request::Request; pub use response::Response; diff --git a/src/middleware.rs b/src/middleware.rs index 573f005ea..12f46f742 100644 --- a/src/middleware.rs +++ b/src/middleware.rs @@ -27,6 +27,83 @@ pub trait Middleware: 'static + Send + Sync { } } +/// Define a middleware that operates on incoming requests. +/// +/// This middleware is useful because it is not possible in Rust yet to use +/// closures to define inline middleware. +/// +/// # Examples +/// +/// ```rust +/// use tide::{Before, Request}; +/// use std::time::Instant; +/// +/// let mut app = tide::new(); +/// app.middleware(Before(|mut request: Request<()>| async move { +/// request.set_ext(Instant::now()); +/// request +/// })); +/// ``` +#[derive(Debug)] +pub struct Before(pub F); +impl Middleware for Before +where + State: Send + Sync + 'static, + F: Fn(Request) -> Fut + Send + Sync + 'static, + Fut: std::future::Future> + Send + Sync, +{ + fn handle<'a>( + &'a self, + request: Request, + next: Next<'a, State>, + ) -> BoxFuture<'a, crate::Result> { + Box::pin(async move { + let request = (self.0)(request).await; + next.run(request).await + }) + } +} + +/// Define a middleware that operates on outgoing responses. +/// +/// This middleware is useful because it is not possible in Rust yet to use +/// closures to define inline middleware. +/// +/// # Examples +/// +/// ```rust +/// use tide::{After, Response, http}; +/// +/// let mut app = tide::new(); +/// app.middleware(After(|res: tide::Result| async move { +/// let res = res.unwrap_or_else(|e| Response::new(e.status())); +/// match res.status() { +/// http::StatusCode::NotFound => Ok("Page not found".into()), +/// http::StatusCode::InternalServerError => Ok("Something went wrong".into()), +/// _ => Ok(res), +/// } +/// })); +/// ``` +#[derive(Debug)] +pub struct After(pub F); +impl Middleware for After +where + State: Send + Sync + 'static, + F: Fn(crate::Result) -> Fut + Send + Sync + 'static, + Fut: std::future::Future + Send + Sync, +{ + fn handle<'a>( + &'a self, + request: Request, + next: Next<'a, State>, + ) -> BoxFuture<'a, crate::Result> { + Box::pin(async move { + let result = next.run(request).await; + (self.0)(result).await + }) + } +} + impl Middleware for F where F: Send