How to return response in middleware using actix_web

This is very strange; no single correct answer is found on the entire internet. im sure that this thread will help others .
I also did a search here and found only this: .
looking also into the examples also no single
simple example for this case
I don't know if this is the right answer. All I want to do is return custom body text in response middleware. What I have in the middleware part doesn't work because I don't know the right way to construct the result.
My motivation here to prevent the request from getting to the main services
Like filters in tomcat

so what i have is :

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

use actix_http::{header::{self, HeaderName, HeaderValue}};
use actix_web::{
    dev::{self, Service, ServiceRequest, ServiceResponse, Transform},
    Error,http::Method, HttpResponseBuilder, HttpResponse
};
use futures_util::future::LocalBoxFuture;
//use crate::constants;

pub struct Heartbeat;

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

    fn new_transform(&self, service: S) -> Self::Future {
        ready(Ok(HeartMiddleware { service }))
    }
}

pub struct HeartMiddleware<S> {
    service: S,
}


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

    dev::forward_ready!(service);

    fn call(&self, req: ServiceRequest) -> Self::Future {
        
        let CONTANT_TYPE: &'static str = "content-type";
        let TEXT_PLAIN: &'static str = "text/plain";
        
        let condition = (Method::POST == req.method() || 
                 Method::GET  == req.method()  ||
                 Method::HEAD == req.method()) && req.path() == "/ping";  

        
        
        let fut = self.service.call(req);
        let (request, _pl) = req.into_parts();


        Box::pin(async move {
            
            let mut res = fut.await?;
           
            let headers = res.headers_mut();
            headers.insert(
                 HeaderName::from_static(CONTANT_TYPE), HeaderValue::from_static(TEXT_PLAIN)
            );

            if condition {
                //Building the response here theoretically should be as simple as this, but it's not..
                let response = HttpResponseBuilder::new(res.status()).body("body test");
                let service_res = ServiceResponse::new(r,response);

                return Ok(service_res)
               
            }          
            Err(actix_web::error::ErrorImATeapot("Tea"))
        })
    }
}

error :

 
error[E0658]: use of unstable library feature 'custom_mir': MIR is an implementation detail and extremely unstable
 --> src\heartbeat.rs:1:35
  |
1 | use std::{future::{ready, Ready}, intrinsics::mir::StaticMut};
  |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0271]: expected `[async block@src\heartbeat.rs:59:18: 78:10]` to be a future that resolves to `Result<ServiceResponse<B>, Error>`, but it resolves to `Result<ServiceResponse, Error>`
  --> src\heartbeat.rs:59:9
   |
35 |   impl<S, B> Service<ServiceRequest> for HeartMiddleware<S>
   |           - this type parameter
...
59 | /         Box::pin(async move {
60 | |
61 | |             let mut res = fut.await?;
62 | |             let r = res.into_parts().0;
...  |
77 | |             Err(actix_web::error::ErrorImATeapot("Tea"))
78 | |         })
   | |__________^ expected `Result<ServiceResponse<B>, Error>`, found `Result<ServiceResponse, Error>`
   |
   = note: expected enum `Result<ServiceResponse<B>, _>`
              found enum `Result<ServiceResponse<BoxBody>, _>`
   = note: required for the cast from `Pin<Box<[async block@src\heartbeat.rs:59:18: 78:10]>>` to `Pin<Box<(dyn futures_util::Future<Output = Result<ServiceResponse<B>, actix_web::Error>> + 'static)>>`

Some errors have detailed explanations: E0271, E0658.
For more information about an error, try `rustc --explain E0271`.
warning: `broker-service` (bin "broker-service") generated 5 warnings
error: could not compile `broker-service` (bin "broker-service") due to 2 previous errors; 5 warnings emitted
 

Thanks

1 Like

your code does not match what you are describing. quote from doc of actix-web about middleware:

Typically, middleware is involved in the following actions:

you code was trying to intercept the request, but you are generate your response after the wrapped service already produced a response.

basically, the HttpRequest is a linear resource, it can only be consumed once. if you are generating your own response, you must consume the HttpRequest, which means you must skip the call to the inner service. if you are forwarding the request to the inner service, you no longer have the request anymore to generate your own response, you can only modify the response of the inner service.

in your example, you should modify the body of the inner response instead of replacing the inner response with new response generated by yourself. you can use the map_body() method, like so:

fn call(&self, req: ServiceRequest) -> Self::Future {
	let condition = (Method::POST == req.method()
		|| Method::GET == req.method()
		|| Method::HEAD == req.method())
		&& req.path() == "/ping";
	let fut = self.service.call(req);
	Box::pin(async move {
		let mut res = fut.await?;
		Ok(res.map_body(|head, body| {
			if condition {
				head.headers_mut().insert(
					HeaderName::from_static("Content-Type"),
					HeaderValue::from_static("text/plain"),
				);
				BoxBody::new("body test")
			} else {
				body
			}
		}))
	})
}

well, I guess technically, you can still use the request, it's just not what a typical middleware is supposed to do.

fn call(&self, req: ServiceRequest) -> Self::Future {
	let fut = self.service.call(req);
	Box::pin(async move {
		let res = fut.await?;
		let method = res.request().method();
		if res.request().path() == "/ping"
			&& (method == Method::GET || method == Method::POST || method == Method::HEAD)
		{
			Ok(ServiceResponse::new(
				res.request().clone(),
				HttpResponseBuilder::new(res.status()).body("body test"),
			))
		} else {
			Ok(res)
		}
	})
}

Thanks , but the code doesnt compile

error[E0308]: `if` and `else` have incompatible types
  --> src\heartbeat.rs:63:21
   |
35 |   impl<S, B> Service<ServiceRequest> for HeartMiddleware<S>
   |           - this type parameter
...
56 | /                 if condition {
57 | |                     head.headers_mut().insert(
58 | |                         HeaderName::from_static("Content-Type"),
59 | |                         HeaderValue::from_static("text/plain"),
60 | |                     );
61 | |                     BoxBody::new("body test")
   | |                     ------------------------- expected because of this
62 | |                 } else {
63 | |                     body
   | |                     ^^^^ expected `BoxBody`, found type parameter `B`
64 | |                 }
   | |_________________- `if` and `else` have incompatible types
   |
   = note:      expected struct `BoxBody`
           found type parameter `B`

For more information about this error, try `rustc --explain E0308`.
warning: `broker-service` (bin "broker-service") generated 4 warnings
error: could not compile `broker-service` (bin "broker-service") due to previous error; 4 warnings emitted

Thanks but its doesnt compile

error[E0308]: `if` and `else` have incompatible types
  --> src\heartbeat.rs:60:17
   |
35 |    impl<S, B> Service<ServiceRequest> for HeartMiddleware<S>
   |            - this type parameter
...
52 | /              if res.request().path() == "/ping"
53 | |                  && (method == Method::GET || method == Method::POST || method == Method::HEAD)
54 | |              {
55 | |/                 Ok(ServiceResponse::new(
56 | ||                     res.request().clone(),
57 | ||                     HttpResponseBuilder::new(res.status()).body("body test"),
58 | ||                 ))
   | ||__________________- expected because of this
59 | |              } else {
60 | |                  Ok(res)
   | |                  ^^^^^^^ expected `Result<ServiceResponse, _>`, found `Result<ServiceResponse<B>, _>`
61 | |              }
   | |______________- `if` and `else` have incompatible types
   |
   = note: expected enum `Result<ServiceResponse<BoxBody>, _>`
              found enum `Result<ServiceResponse<B>, _>`
help: try wrapping the expression in `Err`
   |
60 |                 Err(Ok(res))
   |                 ++++       +

For more information about this error, try `rustc --explain E0308`.
warning: `broker-service` (bin "broker-service") generated 4 warnings
error: could not compile `broker-service` (bin "broker-service") due to previous error; 4 warnings emitted

You can take a look into EitherBody in actix_web::body - Rust so that your middleware returns a single return type.

i dont understand how it relates

whoops, I forgot to paste the necessary changes. sorry. you don't need to implement your middleware for all generic body type B, because you are generating concrete response type, so remove all the B generic type and replace it with BoxBody:

impl<S> Transform<S, ServiceRequest> for Heartbeat
where
	S: Service<ServiceRequest, Response = ServiceResponse<BoxBody>, Error = Error>,
	S::Future: 'static,
{
	// ...
	type Response = ServiceResponse<BoxBody>;
	// ...
}

impl<S> Service<ServiceRequest> for HeartMiddleware<S>
where
	S: Service<ServiceRequest, Response = ServiceResponse<BoxBody>, Error = Error>,
	S::Future: 'static,
{
	//...
	type Response = ServiceResponse<BoxBody>;
	//...
}
1 Like

Thanks for your effort.
First of all, it still doesn't work. Here is the code I changed.
Second, where do I learn the inner logic of this server?
Looks like When trying to dive deep, everything is very confusing in Actix Web. and not much information
I keep compare it to java servers .
For example the BoxBody Class ? what is it , why ?
Also all the right/left things minimum info on the web , thanks .

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

use actix_http::{header::{self, HeaderName, HeaderValue}, body::BoxBody};
use actix_web::{
    dev::{self, Service, ServiceRequest, ServiceResponse, Transform},
    Error,http::Method, HttpResponseBuilder, HttpResponse
};
use futures_util::future::LocalBoxFuture;
//use crate::constants;

pub struct Heartbeat;

impl<S> Transform<S, ServiceRequest> for Heartbeat
where
    S: Service<ServiceRequest, Response = ServiceResponse<BoxBody>, Error = Error>,
    S::Future: 'static,
    
{
    type Response = ServiceResponse<BoxBody>;
    type Error = Error;
    type InitError = ();
    type Transform = HeartMiddleware<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ready(Ok(HeartMiddleware { service }))
    }
}

pub struct HeartMiddleware<S> {
    service: S,
}


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

    dev::forward_ready!(service);

    fn call(&self, req: ServiceRequest) -> Self::Future {
        let condition = (Method::POST == req.method()
            || Method::GET == req.method()
            || Method::HEAD == req.method())
            && req.path() == "/ping";
        let fut = self.service.call(req);
        Box::pin(async move {
            let mut res = fut.await?;
            Ok(res.map_body(|head, body| {
                if condition {
                    head.headers_mut().insert(
                        HeaderName::from_static("Content-Type"),
                        HeaderValue::from_static("text/plain"),
                    );
                    BoxBody::new("body test")
                } else {
                    body
                }
            }))
        })
    }

         
 }
 

main.rs

use actix_web::{get,web,http,Result,App,HttpServer,HttpRequest, Responder, HttpResponse};
use serde::{Deserialize,Serialize};
use actix_cors::Cors;

mod heartbeat;

static PORT :u16 = 9091;

#[get("/")]
async fn index()->impl Responder {
    
    HttpResponse::Ok().body("template")
}

 

#[derive(Serialize)]
pub struct Response {
    pub message: String,
}

async fn not_found() ->Result<HttpResponse> {
    let response = Response {
        message: "Resource not found".to_string(),
    };
    Ok(HttpResponse::NotFound().json(response))
}


#[actix_web::main]
async fn main()-> std::io::Result<()> {
    std::env::set_var("RUST_LOG", "debug");
    env_logger::init();
    HttpServer::new(|| App::new()
    .wrap(
        Cors::default()
            .allowed_origin("http://*") // Allow all http origins
            .allowed_origin("https://*") // Allow all https origins
            .allowed_methods(vec!["GET","POST","PUT","DELETE","OPTIONS"])
            .allowed_headers(vec![http::header::AUTHORIZATION,http::header::CONTENT_TYPE,
                http::header::ACCEPT,http::header::LINK])
            .allowed_header("X-CSRF-TOKEN")
            .supports_credentials()
            .max_age(300)
    )
    .wrap(heartbeat::Heartbeat)
    .service(index)
    .default_service(web::route().to(not_found)))
    .bind(("127.0.0.1",PORT))?
    .run()
    .await        
}


the error :

cargo build
   Compiling broker-service v0.1.0 (C:\dev\my\rust\workspace\broker-service)
warning: unused import: `HttpRequest`
 --> src\main.rs:1:52
  |
1 | use actix_web::{get,web,http,Result,App,HttpServer,HttpRequest, Responder, HttpResponse};
  |                                                    ^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: unused import: `Deserialize`
 --> src\main.rs:2:13
  |
2 | use serde::{Deserialize,Serialize};
  |             ^^^^^^^^^^^

warning: unused import: `self`
 --> src\heartbeat.rs:3:27
  |
3 | use actix_http::{header::{self, HeaderName, HeaderValue}, body::BoxBody};
  |                           ^^^^

warning: unused imports: `HttpResponseBuilder`, `HttpResponse`
 --> src\heartbeat.rs:6:25
  |
6 |     Error,http::Method, HttpResponseBuilder, HttpResponse
  |                         ^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^

error[E0271]: type mismatch resolving `<<impl ServiceFactory<ServiceRequest, Config = (), Response = ServiceResponse<EitherBody<BoxBody>>, Error = Error, InitError = ()> as ServiceFactory<ServiceRequest>>::Service as Service<ServiceRequest>>::Response == ServiceResponse`
   --> src\main.rs:46:11
    |
46  |     .wrap(heartbeat::Heartbeat)
    |      ---- ^^^^^^^^^^^^^^^^^^^^ expected `ServiceResponse`, found `ServiceResponse<EitherBody<BoxBody>>`
    |      |
    |      required by a bound introduced by this call
    |
    = note: expected struct `ServiceResponse<BoxBody>`
               found struct `ServiceResponse<EitherBody<BoxBody>>`
note: required for `Heartbeat` to implement `Transform<<impl ServiceFactory<ServiceRequest, Config = (), Response = ServiceResponse<EitherBody<BoxBody>>, Error = actix_web::Error, InitError = ()> as ServiceFactory<ServiceRequest>>::Service, ServiceRequest>`
   --> src\heartbeat.rs:13:9
    |
13  | impl<S> Transform<S, ServiceRequest> for Heartbeat
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^     ^^^^^^^^^
14  | where
15  |     S: Service<ServiceRequest, Response = ServiceResponse<BoxBody>, Error = Error>,
    |                                ----------------------------------- unsatisfied trait bound introduced here
note: required by a bound in `App::<T>::wrap`
   --> C:\Users\meir.yanovich\.cargo\registry\src\index.crates.io-6f17d22bba15001f\actix-web-4.4.0\src\app.rs:351:12
    |
338 |       pub fn wrap<M, B>(
    |              ---- required by a bound in this associated function
...
351 |           M: Transform<
    |  ____________^
352 | |                 T::Service,
353 | |                 ServiceRequest,
354 | |                 Response = ServiceResponse<B>,
355 | |                 Error = Error,
356 | |                 InitError = (),
357 | |             > + 'static,
    | |_____________^ required by this bound in `App::<T>::wrap`

For more information about this error, try `rustc --explain E0271`.
warning: `broker-service` (bin "broker-service") generated 4 warnings
error: could not compile `broker-service` (bin "broker-service") due to previous error; 4 warnings emitte

sorry, you didn't give the context so I assumed you can just return BoxBody.

BoxBody isn't generic, so you can only use it as the inner-most layer of middleware stack. so this should work (note the order):

App::new()
    .wrap(HeartBeat)
    .wrap(
        Cors::default()
        //...
    )
    .service(index)

but to make it generic, instead of returning BoxBody directly, you wrap it in a EitherBody:

impl<S, B> Transform<S, ServiceRequest> for Heartbeat
where
	S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
	S::Future: 'static,
	B: 'static,
{
	//...
	type Response = ServiceResponse<EitherBody<B, BoxBody>>;
	//...
}
impl<S, B> Service<ServiceRequest> for HeartMiddleware<S>
where
	S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
	S::Future: 'static,
	B: 'static,
{
	//...
	type Response = ServiceResponse<EitherBody<B, BoxBody>>;
	//...
	fn call(&self, req: ServiceRequest) -> Self::Future {
		let fut = self.service.call(req);
		Box::pin(async move {
			let res = fut.await?;
			let method = res.request().method();
			if res.request().path() == "/ping"
				&& (method == Method::GET || method == Method::POST || method == Method::HEAD)
			{
				Ok(res.map_body(|head, _body| {
					head.headers_mut().append(
						HeaderName::from_static("content-type"),
						HeaderValue::from_static("text/plain"),
					);
					let box_body = BoxBody::new("body test");
					EitherBody::right(box_body)
				}))
			} else {
				Ok(res.map_body(|_head, body| EitherBody::left(body}))
			}
		})
	}
}

well, that's an unfortunate fact that the rust language is too complex. the web server (or really most domain problems) itself is not that complicated, it's the language that often get in the way for new learners, there's just so many concepts you have to understand.

unlike, say, Java, where you can grasp the language relative quickly, especially if you have experiences in other object-oriented languages, then you spend a lot of time struggle to use the language solve your real world problems.

for rust, the initial learning curve is very steep indeed, especially for those who are not very familiar with idioms and the type systems in functional languages, there's so many frustrations I have heard about. but the hard work will eventually payoff if you persisted.

2 Likes

:right_anger_bubble: right_anger_bubble: ou nail it with your description of the languge ... i was thinking that someting wrong with me .
How can it be so complex for such simple task ,
Thank you very much this is working fine .
Can you pls explain me or send me to link ( not the reference it is very minimalistic)
The concept of :
EitherBody::right
EitherBody::left

or in the examples :
map_into_right_body / map_into_left_body

1 Like

The difficulty comes mostly from how Actix-web (and other Rust web frameworks, for that matter) abstract away the complexity of creating the different parts using traits and other language constructs for generic code.

You have to bare in mind that the maintainers of such frameworks have to balance ergonomics and simplicity vs. abstractions, and what you see here is just the result of the decisions made by the maintainers of Actix-web.

If you want a simpler framework, then you can take a look at Rocket.

Yes i saw rocket , but all recommendations in the internet goes to actix web and its eco system

I have one more problem in the code , currently it return 404 ,
How can i fource it to return 200 ok on success ?

$ curl -v http://127.0.0.1:9091/ping
*   Trying 127.0.0.1:9091...
* Connected to 127.0.0.1 (127.0.0.1) port 9091 (#0)
> GET /ping HTTP/1.1
> Host: 127.0.0.1:9091
> User-Agent: curl/7.75.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 404 Not Found
< content-length: 9
< content-type: application/json
< content-type: text/plain
< access-control-allow-credentials: true
< vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers
< date: Fri, 01 Sep 2023 15:24:09 GMT
<
body test* Connection #0 to host 127.0.0.1 left intact

If you are still using nerditation's code, then this is the place where you need to do the changes:

HttpResponseBuilder::new(res.status()).body("body test")

You are reusing the status code from the original response. Just use StatusCode::OK instead.

it's 404 because that's the status code of the inner response, since the code only changed the body, the status code remains.

to also change to status code, add this line:

Ok(res.map_body(|head, _body| {
+	head.status = StatusCode::OK;
	head.headers_mut().append(
		HeaderName::from_static("content-type"),
		HeaderValue::from_static("text/plain"),
	);
	let box_body = BoxBody::new("body test");
	EitherBody::right(box_body)
}))

EitherBody is just a slightly customized type for a more general form called Either. the concept is called sum types. in rust, an enum is a sum type. Either is as simple as a sum type can be imagined, and the variant Left and Right is just a convention.

this is how you define your own Either type:

enum Either<A, B> {
    Left(A),
    Right(B),
}

that's it. you can give the variant different names, like This and That, or First, Second, but Left and Right is just a well established convention.

it dosnt work ,
yes im using his code

Thank you ,
So if i understand you right the else here

} else {
                Ok(res.map_body(|_head, body| EitherBody::left({ body })))
}

is forwarding the body response from the inner service which is in the pre response stage without going ever to the outer service ?

Sorry, my last reply was from my smartphone and had not seen that nerditation had already added the code using the EitherBody type.

To understand your code, you have to focus on this part:

if res.request().path() == "/ping" && 
(method == Method::GET || method == Method::POST || method == Method::HEAD)
{
  Ok(res.map_body(|head, _body| {
	head.headers_mut().append(
		HeaderName::from_static("content-type"),
		HeaderValue::from_static("text/plain"),
	);
	let box_body = BoxBody::new("body test");
	EitherBody::right(box_body)
  }))
} else {
	Ok(res.map_body(|_head, body| EitherBody::left(body}))
}

Both arms are forwarding the status code from the initial response, you are just changing the body on the first branch.

In functional programming languages, it's a convention that any method that includes the word map transforms the given entity. In this case, res.map_body is transforming the body of the response. You can read more about it in the documentation.

If you look closer at the documentation, you will find that there's no method to directly change the status code from the service response, but there's a handy method that gives us a mutable reference to the response itself:

pub fn response_mut(&mut self) -> &mut HttpResponse<B>
  Returns mutable reference to response.

Now with a mutable reference to the HttpResponse response type, we have the method that we are looking for:

pub fn status_mut(&mut self) -> &mut StatusCode
  Set the StatusCode for this response

I hope that helps you to continue.

3 Likes

sure it helps , thanks .

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.