-
Notifications
You must be signed in to change notification settings - Fork 19
Just Middleware. #21
Just Middleware. #21
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
use std::marker::PhantomData; | ||
|
||
use Service; | ||
|
||
/// Often, many of the pieces needed for writing network applications | ||
/// can be reused across multiple services. The `Middleware` trait can | ||
/// be used to write reusable components that can be applied to very | ||
/// different kinds of services; for example, it can be applied to | ||
/// services operating on different protocols, and to both the client | ||
/// and server side of a network transaction. | ||
/// | ||
/// # Timeouts | ||
/// | ||
/// Take timeouts as an example: | ||
/// | ||
/// ```rust,ignore | ||
/// use tokio::Service; | ||
/// use tokio::Middleware; | ||
/// use futures::Future; | ||
/// use std::time::Duration; | ||
/// | ||
/// // Not yet implemented, but soon :) | ||
/// use tokio::timer::{Timer, Expired}; | ||
/// | ||
/// | ||
/// pub struct Timeout { | ||
/// delay: Duration, | ||
/// timer: Timer, | ||
/// } | ||
/// | ||
/// impl Timeout { | ||
/// fn timeout(&self) -> impl Future<Item = (), Error = Expired> { | ||
/// self.timer.timeout(self.delay) | ||
/// } | ||
/// } | ||
/// | ||
/// impl<S> Middleware<S> for Timeout | ||
/// where S: Service, | ||
/// S::Error: From<Expired>, | ||
/// { | ||
/// type WrappedService = TimeoutService<S>; | ||
/// | ||
/// fn wrap(self, upstream: S) -> TimeoutService<S> { | ||
/// TimeoutService { timeout: self, upstream } | ||
/// } | ||
/// } | ||
/// | ||
/// | ||
/// // This service implements the Timeout behavior. | ||
/// pub struct TimeoutService<S> { | ||
/// upstream: S, | ||
/// timeout: Timeout, | ||
/// } | ||
/// | ||
/// impl<S> Service for TimeoutService<S> | ||
/// where S: Service, | ||
/// S::Error: From<Expired>, | ||
/// { | ||
/// type Request = S::Request; | ||
/// type Response = S::Response; | ||
/// type Error = S::Error; | ||
/// type Future = Box<Future<Item = Self::Response, Error = Self::Error>>; | ||
/// | ||
/// fn call(&self, req: Self::Req) -> Self::Future { | ||
/// let timeout = self.timeout.timeout() | ||
/// .and_then(|timeout| Err(Self::Error::from(timeout))); | ||
/// | ||
/// self.upstream.call(req) | ||
/// .select(timeout) | ||
/// .map(|(v, _)| v) | ||
/// .map_err(|(e, _)| e) | ||
/// .boxed() | ||
/// } | ||
/// } | ||
/// | ||
/// ``` | ||
/// | ||
/// The above timeout implementation is decoupled from the underlying protocol | ||
/// and is also decoupled from client or server concerns. In other words, the | ||
/// same timeout middleware could be used in either a client or a server. | ||
pub trait Middleware<S: Service> { | ||
/// The service produced by wrapping this middleware around another | ||
/// service. | ||
type WrappedService: Service; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we are going to bike shed, I think it could be nice to come up w/ a shorter name for this associated type... I would say There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could just call it |
||
|
||
/// Wrap the middlware around a Service it is able to wrap. | ||
/// | ||
/// This produces a service of the `WrappedService` associated | ||
/// type, which itself is another service that could possibly be | ||
/// wrapped in other middleware. | ||
fn wrap(self, service: S) -> Self::WrappedService; | ||
|
||
/// Chain two middleware together. The lefthand side of this | ||
/// operation is the "inner" middleware and the righthand side is | ||
/// the "outer" middleware. | ||
/// | ||
/// When wrapping a middleware chain around a service, first the | ||
/// inner middleware is wrapped around that service, and then the | ||
/// outer middleware is wrapped around the service produced by the | ||
/// inner middleware. | ||
/// | ||
/// This allows you to build middleware chains before knowing | ||
/// exactly which service that chain applies to. | ||
fn chain<M>(self, middleware: M) -> MiddlewareChain<S, Self, M> where | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alternative you could use a builder to create let middleware = MiddlewareChainBuilder::new(middleware1)
.chain(middleware2)
.chain(middleware3);
let service = Myservice::new().wrap(middleware); I wonder how often There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I kind of like the idea of keeping the "chain" concept off the Also, I think finagle calls this concept a "stack", but I am not super adept at reading scala... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So I have use for chain in the framework I'm writing. HTTP services are constructed through a DSL, and then users can in that DSL declare a middleware to be applied to them. It can be easier for them if they can chain middleware together to produce a the middleware they're going to be applying. They never have access to the Service type directly so they can't just use wrap. |
||
M: Middleware<Self::WrappedService>, | ||
Self: Sized, | ||
{ | ||
MiddlewareChain { | ||
inner_middleware: self, | ||
outer_middleware: middleware, | ||
_marker: PhantomData, | ||
} | ||
} | ||
} | ||
|
||
/// Two middleware, chained together. This type is produced by the | ||
/// `chain` method on the Middleware trait. | ||
pub struct MiddlewareChain<S, InnerM, OuterM> where | ||
S: Service, | ||
InnerM: Middleware<S>, | ||
OuterM: Middleware<InnerM::WrappedService>, | ||
{ | ||
inner_middleware: InnerM, | ||
outer_middleware: OuterM, | ||
_marker: PhantomData<S>, | ||
} | ||
|
||
impl<S, InnerM, OuterM> Middleware<S> for MiddlewareChain<S, InnerM, OuterM> where | ||
S: Service, | ||
InnerM: Middleware<S>, | ||
OuterM: Middleware<InnerM::WrappedService>, | ||
{ | ||
type WrappedService = OuterM::WrappedService; | ||
|
||
fn wrap(self, service: S) -> Self::WrappedService { | ||
service.wrap(self.inner_middleware).wrap(self.outer_middleware) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically speaking the middleware wraps the service, but the service doesn't wrap the middleware, so I guess that the method should be named
wrapped_by()
. This is slightly confusing, because the middleware also has a method namedwrap()
.I don't have a perfect alternative name, but it might here are some ideas:
apply()
,filter()
,using()
,with()
wrapped()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm OK w/ the name
wrap
here.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think wrap works in both directions, in the sense of
wrap in {middleware}
andwrap around {service}
. But I'm also fine with whatever name.