http-rs / tide

Fast and friendly HTTP server framework for async Rust
https://docs.rs/tide
Apache License 2.0
5.06k stars 321 forks source link

Feature Request: Add `impl Middlware` to support simple stateless middlewares #854

Open NfNitLoop opened 3 years ago

NfNitLoop commented 3 years ago

I read a blog post that included a simplified middleware so I wrote one of my own:

/// Middleware that disables browser caching by default.
async fn no_store<'a, Data>(req: tide::Request<Data>, next: tide::Next<'a, Data>) -> tide::Result<Response>
where Data: Clone + Send + Sync + 'static
{
    use tide::http::cache::{CacheControl, CacheDirective};
    let mut response = next.run(req).await;

    if let None = response.header("Cache-Control") {
        let mut header = CacheControl::new();
        header.push(CacheDirective::NoStore);
        header.push(CacheDirective::MaxAge(Duration::from_secs(0)));

        response.insert_header(header.name(), header.value());
    }
    Ok(response)
}

Unfortunately, that didn't work. Off to the API docs, and I find that Middleware only has an impl for:

impl<State, F> Middleware<State> for F
where
    State: Clone + Send + Sync + 'static,
    F: Send + Sync + 'static + for<'a> Fn(Request<State>, Next<'a, State>) -> Pin<Box<dyn Future<Output = Result> + Send + 'a>>, 

At this point, as a merely intermediate Rust user, my eyes glazed over. So I generated my own impl like this: (Though I had to just try out different lifetimes until the compiler was happy. Because I'm still a bit vague on the how async_trait transforms the code, and so what signature I need to implement to match the one in the Rust docs.)


struct NoStore {}

#[async_trait]
impl <State: Clone + Send + Sync + 'static> tide::Middleware<State> for NoStore {
    async fn handle<'a, 'b>(&'a self, req: tide::Request<State>, next: tide::Next<'b, State>) -> tide::Result<Response>
    {
        // same body.
    }
}

But, AFAICT, there's no reason that Tide couldn't just add an impl Middleware for these type of bare/stateless middlewares. Then you could just:

app.with(no_store);

// instead of having to use a struct and manually impl the trait.

app.with(NoStore{});
nyxtom commented 2 years ago

I've implemented a solution to this and simplified the code base to move State into extensions via a StateMiddleware as well as remove the for<'a> requirement on middleware so this is now possible in my branch https://github.com/http-rs/tide/pull/895

#[derive(Clone)]
struct State {
    foo: String,
}

#[async_std::main]
async fn main() -> tide::Result<()> {
    let mut app = tide::with_state(State { foo: "asdf".into() });

    app.with(|req: tide::Request, next: tide::Next| async move {
        use tide::http::cache::{CacheControl, CacheDirective};
        let mut res = next.run(req).await;

        if let None = res.header("Cache-Control") {
            let mut header = CacheControl::new();
            header.push(CacheDirective::NoStore);
            header.push(CacheDirective::MaxAge(std::time::Duration::from_secs(0)));

            res.insert_header(header.name(), header.value());
        }
        Ok(res)
    });
    app.at("/").get(index);
    app.listen("127.0.0.1:7000").await?;

    Ok(())
}

async fn index(req: tide::Request) -> tide::Result {
    println!("{}", req.state::<State>().foo);
    Ok(tide::Response::new(200))
}