actix-net icon indicating copy to clipboard operation
actix-net copied to clipboard

Improve `ServiceFactory` trait

Open fakeshadow opened this issue 3 years ago • 0 comments

Add second generic param for the receive argument for new_service method. Default to () because it's all service factory uses at last. actix-server only accept it.

Remove InitError type and use Error type for all instance of error. Most service factory already map the InitError to () at last.

pub trait ServiceFactory<Req, Arg = ()> {
    type Response;
    type Error;
    type Service: Service<Req, Response = Self::Response, Error = Self::Error>;
    type Future: Future<Output = Result<Self::Service, Self::Error>>;

    fn new_service(&self, arg: Arg) -> Self::Future;
}

Remove Transform trait and use the new ServiceFactory trait directly for middleware

impl<S, Req> ServiceFactory<Req, S> for MiddlewareBuilder 
where
    S: Service<Req>
{
    type Response = S::Response;
    type Error = S::Error;
    type Service = Middleware<S>;
    type Future = LocalBoxFuture<'static, Result<Self::Service, Self::Error>;

    fn new_service(&self, service: S) -> Self::Future {
        Box::pin(async { Ok(Middleware(service)) })
    }
}

The actual middleware construct logic would become

impl<F, Req, Arg> ServiceFactoryExt<Req, Arg> for F 
where
    F: ServiceFactory<Req, Arg>
{
    fn wrap<M>(self, middleware_builder: M) -> Builder<Self, M> 
    where
        M: ServiceFactory<Req, Self::Service>
    { 
        Builder { factory: self, middleware_builder: Rc::new(middleware_builder) }
    }
}

impl<F, Req, Arg, M> ServiceFactory<Req, Arg> for Builder<F, M> 
where
    F: ServiceFactory<Req, Arg>
    M: ServiceFactory<Req, F::Service>
    M::Error: From<F::Error>
{
    type Response = M::Response;
    type Error = M::Error;
    type Service = M::Service;
    type Future = LocalBoxFuture<'static, Result<Self::Service, Self::Error>;

    fn new_service(&self,  arg: Arg) -> Self::Future {
        let middleware_builder = self.middleware_builder.clone();
        let service = self.factory.new_service(arg);
        Box::pin(async move { 
            let service = service.await?;
            middleware_builder.new_service(service).await
        })
    }
}

It's also considerable to just throw the typed future on ServiceFactory trait. No one would construct Service all the time and in most case it's one time cost on server start up for every worker thread.

pub trait ServiceFactory<Req, Arg = ()> {
    type Response;
    type Error;
    type Service: Service<Req, Response = Self::Response, Error = Self::Error>;

    fn new_service(&self, arg: Arg) -> Pin<Box<dyn Future<Output = Result<Self::Service, Self::Error>> + '_>>;
}

fakeshadow avatar Feb 09 '22 23:02 fakeshadow