Tide: Proposal: Introduce the notion of a middleware stack

Created on 26 Nov 2018  路  10Comments  路  Source: http-rs/tide

Currently, the type of a collection of middlewares is Vec<Arc<dyn Middleware<Data> + Send + Sync>>. I think it would make sense to introduce a proper MiddlewareStack type that itself implements Middleware. This allows better sharing of middleware instances and the way to address them, for example for debugging.

feature good first issue

Most helpful comment

Sorry for the delay, I hacked something together that works but I've introduced some allocations that could be eliminated and never got time to work on it again. I'll submit a PR once I get home from work in order to ask for help

All 10 comments

I'd like to take this to start contributing to tide. Seems pretty simple:

impl<Data> Iterator<Item=&Middleware<Data>> for MiddlewareStack<Data> {
    // ...
}

impl<Data> FromIterator<Item=dyn Middleware<Data> + Send + Sync> for MiddlewareStack<Data> {
    // ...
}

impl<Data> Middleware<Data> for MiddlewareStack<Data> {
    fn handle(/* ... */) -> FutureObj<'a, Response> {
        // Apply internal middlewares in order..
    }
}

Anything I'm missing?

@RoGryza seems like a good starting point! Feel free to make a PR, and then we can build on that over time (with introspection etc)

Is this still valid? If yes, is anyone working on this? If not, I'd like to start working on this.
@RoGryza
@skade
@aturon

Sorry for the delay, I hacked something together that works but I've introduced some allocations that could be eliminated and never got time to work on it again. I'll submit a PR once I get home from work in order to ask for help

This is worth revisiting now that #156 has landed -- anybody want to take it back up?

I can take a look if no one else is already working on it.

I am taking a look at this and came up with the following

pub struct MiddlewareStack<State> {
    middlewares: Vec<Arc<dyn Middleware<State> + Send + Sync>>,
}

impl<State> MiddlewareStack<State> {
    pub fn new() -> MiddlewareStack<State> {
        MiddlewareStack {
            middlewares: Vec::new(),
        }
    }

    pub fn push(&mut self, m: impl Middleware<State>) {
        self.middlewares.push(Arc::new(m));
    }
}

impl<State: 'static> Middleware<State> for MiddlewareStack<State> {
    fn handle<'a>(&'a self, cx: Context<State>, next: Next<'a, State>) -> BoxFuture<'a, Response> {
        if let Some((head, tail)) = self.middlewares.split_first() {
            let next = Next {
                endpoint: next.endpoint,
                next_middleware: tail, // todo: append middlewares from next
            };
            head.handle(cx, next)
        } else {
            next.run(cx)
        }
    }
}

But I got in trouble with the incompatible types of MiddlewareStack.middlewares and Next.next_middlewares
Are there some strong bounds on the type of Next.next_middlewares that I should keep into account or it is possible to work on that?

I don't know of any bounds that next would need. I'm not even sure if the Stack itself needs the 'static bound. (an external user could apply it)

Thanks @skade, I cleaned up my previous attempt.

/// Middleware stack.
pub struct MiddlewareStack<State> {
    stack: Vec<Arc<dyn Middleware<State>>>,
}

impl<State> MiddlewareStack<State> {
    /// Create an empty stack of middlewares.
    pub fn new() -> MiddlewareStack<State> {
        MiddlewareStack {
            stack: Vec::new(),
        }
    }

    /// Add a middleware to the stack.
    pub fn push(&mut self, m: impl Middleware<State>) {
        self.stack.push(Arc::new(m));
    }
}

impl<State: 'static> Middleware<State> for MiddlewareStack<State> {
    fn handle<'a>(&'a self, cx: Context<State>, next: Next<'a, State>) -> BoxFuture<'a, Response> {
        if let Some((last, others)) = self.stack.split_last() {
            last.handle(cx, Next::new(next.endpoint, others))
        } else {
            next.run(cx)
        }
    }
}

I am not sure if the middleware stack should behave like a stack as the name suggests or like a queue (respectively split_last and split_first). Maybe I can start a pull request and collect some comments/reviews?

edit: I just noted a problem. next should be merged into others instead of being used only into the else branch. I tried something like building a new Next with &[others, next.next_middleware].concat() but obviously the new reference does not live long enough. Also persisting it by adding a field in the MiddlewareStack doesn't seem viable since handle takes an immutable reference.

As i see it we have three options how to approach this:

  1. Add another field To Next next: Option<Arc<Next>> and in the MiddlewareStack' create newNextthat warp the original Next, and in theNext::run` function call this next if it exist .
  2. Improvement to 1 :Change Next to be trait instead of struct, this will allow to create a special Next in `MiddlewareStack' without interfering with the Current Next Implementation. But this might present a problem letting users too much control.
  3. Completely inverse the solution and instead of MiddlewareStack' implMiddleware' we should let MiddlewareStack' expose a vec\slice ofMiddlewarethat will be added to the server(add a function that accept vec\slice ofMiddleware`).

I Tried 1 but got stuck because i am lacking knowledge and experience to work good enough with generic and lifetime.

But after second thought i think that 3 is better.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Nemo157 picture Nemo157  路  6Comments

fasterthanlime picture fasterthanlime  路  4Comments

alexreg picture alexreg  路  5Comments

tirr-c picture tirr-c  路  6Comments

cquintana-verbio picture cquintana-verbio  路  6Comments