Actix-web calling the NEXT MIDDLEWARE with an already prepared HttpResponse response, please help

Hi,

Inside the call method of my adopted middleware, based on some condition, I prepare a new HttpResponse::Unauthorized(), and I am able to return this new response to the calling clients with no problems.

-- But now, I have some more middleware after this one, instead of returning this prepared HttpResponse::Unauthorized() directly to the clients, I would like to pass it to the next middleware in the chain, AND I DON'T KNOW HOW TO DO THAT. I have searched, and found no example or even documentation discussing this.

Please help me with this issue.

I'm reprinting the original code of my adopted middleware, https://github.com/actix/examples/blob/master/middleware/various/src/redirect.rs:

use std::future::{ready, Ready};

use actix_web::{
    body::EitherBody,
    dev::{self, Service, ServiceRequest, ServiceResponse, Transform},
    http, Error, HttpResponse,
};
use futures_util::future::LocalBoxFuture;

pub struct CheckLogin;

impl<S, B> Transform<S, ServiceRequest> for CheckLogin
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<EitherBody<B>>;
    type Error = Error;
    type InitError = ();
    type Transform = CheckLoginMiddleware<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ready(Ok(CheckLoginMiddleware { service }))
    }
}
pub struct CheckLoginMiddleware<S> {
    service: S,
}

impl<S, B> Service<ServiceRequest> for CheckLoginMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<EitherBody<B>>;
    type Error = Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

    dev::forward_ready!(service);

    fn call(&self, request: ServiceRequest) -> Self::Future {
        // Change this to see the change in outcome in the browser.
        // Usually this boolean would be acquired from a password check or other auth verification.
        let is_logged_in = false;

        // Don't forward to `/login` if we are already on `/login`.
        if !is_logged_in && request.path() != "/login" {
            let (request, _pl) = request.into_parts();

            let response = HttpResponse::Found()
                .insert_header((http::header::LOCATION, "/login"))
                .finish()
                // constructed responses map to "right" body
                .map_into_right_body();

            return Box::pin(async { Ok(ServiceResponse::new(request, response)) });
        }

        let res = self.service.call(request);

        Box::pin(async move {
            // forwarded responses map to "left" body
            res.await.map(ServiceResponse::map_into_left_body)
        })
    }
}

I understand that, the following is the code whereby we call the next middleware in the chain:

        let res = self.service.call(request);

        Box::pin(async move {
            // forwarded responses map to "left" body
            res.await.map(ServiceResponse::map_into_left_body)
        })

whereas the block of code above it is where we do redirection, and I have also used this successfully too.

The minimal call method below is a brief version of what I have working:

    fn call(&self, request: ServiceRequest) -> Self::Future {
        // Redirect to the login page.	
        if !is_logged_in && request.path() != "/login" {
            ...
            return Box::pin(async { Ok(ServiceResponse::new(request, response)) });
        }
        else {
            let (request, _pl) = req.into_parts();

            let response = HttpResponse::Unauthorized()
                .insert_header((header::CONTENT_TYPE, header::ContentType::json()))
                .body( "some_json_data" )
                .map_into_right_body();

            return Box::pin(async { Ok(ServiceResponse::new(request, response)) })
        }		

        // Call the next middleware in the chain.
        let res = self.service.call(request);

        Box::pin(async move {
            // forwarded responses map to "left" body
            res.await.map(ServiceResponse::map_into_left_body)
        })
    }

In the else block, is there a way of calling the next middleware in the chain with the local read-made response, please?

Thank you and best regards,

...behai.

You can't provide the response to the Service directly (Service::call is the only method you can use and it only takes the request as argument, not an already pre-computed response). So what you'd need to do is add the already computed Unauthorized response (or some other type[1] that indicates that your middleware has deemed the requester as not authorized) and add it to the extensions. Then a middleware farther down the call chain can extract it.


  1. I would recommend a custom type here like a struct Unauthorized; and add that to the extensions so that you never run into any issues where your type collides with something that has already been added to the extensions. It's unlikely with a HttpResponse, but if your app grows this might be a weird cause for bugs. ↩ī¸Ž

3 Likes

Good evening @jofas,

Thank you again for your help. I think I will implement the solution that you have suggested.

Thank you and best regards,

...behai.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.