Expected `()`, found `Result<ServiceResponse<B>, _>` using midleware in actix_web trying to return Ok

I'm trying to accomplish a very simple task here. I don't understand why it has become so complex. I'm attempting to modify headers before they reach the service and return a failure if a certain condition is not met.
Here is my middleware file:

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

use actix_http::header::{HeaderValue, HeaderName};
use actix_web::{
    dev::{self, Service, ServiceRequest, ServiceResponse, Transform},
    Error,http::Method
};
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 fut = self.service.call(req);
        
        

        Box::pin(async move {
            
            let mut res = fut.await?;
            let headers = res.headers_mut();
            headers.insert(
                 HeaderName::from_static("Content-Type"), HeaderValue::from_static("text/plain")
             );
            
            if (Method::POST == req.method() || 
                 Method::GET  == req.method()  ||
                 Method::HEAD == req.method()) && req.path() == "/ping" {
                 Ok(res)
            }
             
            Err(actix_web::error::ErrorImATeapot("TeaPot"))
        })
    }
}

and the main file :

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)
    .service(web::resource("/ping"))
    .default_service(web::route().to(not_found)))
    .bind(("127.0.0.1",PORT))?
    .run()
    .await        
}


compiling it im getting :

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};
  |             ^^^^^^^^^^^

error[E0308]: mismatched types
  --> src\heartbeat.rs:65:18
   |
62 | /             if (Method::POST == req.method() ||
63 | |                  Method::GET  == req.method()  ||
64 | |                  Method::HEAD == req.method()) && req.path() == "/ping" {
65 | |                  Ok(res)
   | |                  ^^^^^^^ expected `()`, found `Result<ServiceResponse<B>, _>`
66 | |             }
   | |_____________- expected this to be `()`
   |
   = note: expected unit type `()`
                   found enum `Result<ServiceResponse<B>, _>`
note: return type inferred to be `()` here
  --> src\heartbeat.rs:56:27
   |
56 |             let mut res = fut.await?;
   |                           ^^^^^^^^^^

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

UPDATE STILL ERROR:

after updateding the solution suggested still getting error :

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

use actix_http::header::{HeaderValue, HeaderName};
use actix_web::{
    dev::{self, Service, ServiceRequest, ServiceResponse, Transform},
    Error,http::Method
};
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 fut = self.service.call(req);
        
        

        Box::pin(async move {
            
            let mut res = fut.await?;
            let headers = res.headers_mut();
            headers.insert(
                 HeaderName::from_static("Content-Type"), HeaderValue::from_static("text/plain")
             );
            
            if (Method::POST == req.method() || 
                 Method::GET  == req.method()  ||
                 Method::HEAD == req.method()) && req.path() == "/ping" {
                 return Ok(res)
            }
             
            Err(actix_web::error::ErrorImATeapot("TeaPot"))
        })
    }
}
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};
  |             ^^^^^^^^^^^

error[E0382]: use of moved value: `req`
  --> src\heartbeat.rs:54:18
   |
47 |       fn call(&self, req: ServiceRequest) -> Self::Future {
   |                      --- move occurs because `req` has type `ServiceRequest`, which does not implement the `Copy` trait
...
50 |           let fut = self.service.call(req);
   |                                       --- value moved here
...
54 |           Box::pin(async move {
   |  __________________^
55 | |
56 | |             let mut res = fut.await?;
57 | |             let headers = res.headers_mut();
...  |
62 | |             if (Method::POST == req.method() ||
   | |                                 --- use occurs due to use in generator
...  |
68 | |             Err(actix_web::error::ErrorImATeapot("TeaPot"))
69 | |         })
   | |_________^ value used here after move

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


what am i doing wrong ?

To avoid any duplicated effort for people answering your question, please indicate cross-posts:


The call to self.service.call(req) consumes the ServiceRequest value. The information you read from it can however be extracted before you make this call. Either, you could clone and thus store the req.method() and req.path() information; or you could already evaluate the whole if condition earlier, e.g.

    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?;
            let headers = res.headers_mut();
            headers.insert(
                 HeaderName::from_static("Content-Type"), HeaderValue::from_static("text/plain")
             );
            
            if condition {
                 return Ok(res)
            }
             
            Err(actix_web::error::ErrorImATeapot("TeaPot"))
        })
    }

I’m not really trying to understand any of the actual logic here; so the above approach is only addressing how to avoid the compiler error. E.g. I would wonder whether or not you actually want to execute the request (let fut = self.service.call(req); and let mut res = fut.await?;) in the case where condition is false. But that’s not on me to decide ^^

1 Like

Thank you very much, you are right. I'm sorry for the double posting. I didn't realize that the ServiceRequest value might not exist; I couldn't find it mentioned anywhere. Anyway, after implementing your fix, I'm now getting:

$ ./target/debug/broker-service.exe 
[2023-08-31T12:43:58Z INFO  actix_server::builder] starting 8 workers
[2023-08-31T12:43:58Z INFO  actix_server::server] Actix runtime found; starting in Actix runtime
thread 'actix-rt|system:0|arbiter:0' panicked at 'index out of bounds: the len is 0 but the index is 0', C:\Users\xxx\.cargo\registry\src\index.crates.io-6f17d22bba15001f\http-0.2.9\src\header\name.rs:1270:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread 'actix-rt|system:0|arbiter:1' panicked at 'index out of bounds: the len is 0 but the index is 0', C:\Users\xxx\.cargo\registry\src\index.crates.io-6f17d22bba15001f\http-0.2.9\src\header\name.rs:1270:13
thread 'actix-rt|system:0|arbiter:2' panicked at 'index out of bounds: the len is 0 but the index is 0', C:\Users\xxx\.cargo\registry\src\index.crates.io-6f17d22bba15001f\http-0.2.9\src\header\name.rs:1270:13
thread 'actix-rt|system:0|arbiter:3' panicked at 'index out of bounds: the len is 0 but the index is 0', C:\Users\xxx\.cargo\registry\src\index.crates.io-6f17d22bba15001f\http-0.2.9\src\header\name.rs:1270:13
thread 'actix-rt|system:0|arbiter:4' panicked at 'index out of bounds: the len is 0 but the index is 0', C:\Users\xxx\.cargo\registry\src\index.crates.io-6f17d22bba15001f\http-0.2.9\src\header\name.rs:1270:13

while im invoking :
curl -v http:127.0.0.1:9091/ping

This is the output :

$ 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: */*
>
* Empty reply from server
* Closing connection 0
curl: (52) Empty reply from server

what can i do to remove this wrrning ? and return valied response ?

The way you are constructing the header is invalid (using the capitalized casing). Check the documentation: HeaderName in http::header - Rust.

TLDR:

Change this:

HeaderName::from_static("Content-Type")

To this:

HeaderName::from_static("content-type")
1 Like