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.
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:
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 .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.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.
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