How to pass data to an actix middleware

I'm trying to learn more about how actix web works and I'm the point where I want to create a middleware. Ultimately I want to create a db connection in the middleware and I'm trying to figure out how to get the connection details to the middleware. I've broken the problem down into a small a sample as I could.

// main.rs
// Data I'd want to share would go here
pub struct Data {
    pub color: String
}

impl Data {
    pub async fn new() -> Arc<Self> {
        let data = Data { color: "red".to_string() };

        Arc::new(data)
    }
}

// MIDDLEWARE
pub struct MyData;

impl<S, B> Transform<S, ServiceRequest> for MyData
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 = MyDataMiddleware<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ok(MyDataMiddleware { service })
    }
}

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

impl<S, B> Service<ServiceRequest> for MyDataMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;

    actix_web::dev::forward_ready!(service);

    fn call(&self, req: ServiceRequest) -> Self::Future {
        println!("In middleware");
        let app_data = req.app_data::<actix_web::web::Data<Data>>();
        // THIS NEXT LINE FAILS
        // thread 'actix-rt|system:0|arbiter:0' panicked at 'called `Option::unwrap()` on a `None` value'
        let state = app_data.as_ref().unwrap();
        let color = state.as_ref().color.clone();
        let fut = self.service.call(req);

        println!("Color in middleware:{}", color);
        Box::pin(async move {
            let res = match fut.await {
                Ok(response) => response,
                Err(error) => panic!("Unable to process middleware:{}", error),
            };

            Ok(res)
        })
    }
}
// END MIDDLEWARE

// MAIN CODE
#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let new_data = Data::new().await;
    let data = actix_web::web::Data::new(new_data);
    let server = match HttpServer::new(move || {
        App::new()
            .wrap(MyData)
            .app_data(data.clone())
    })
    .bind("0.0.0.0:9000")
    {
        Ok(value) => value,
        Err(error) => panic!("Error binding to socket:{:?}", error),
    };

    server.run().await
}
# Cargo.toml
[package]
name = "actix-middleware"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix = "0.13.0"
actix-web = "4.0.1"
futures = "0.3.21"

I feel like I'm pretty close. Any ideas would be greatly appreciated.

I just figured it out. I was following an example here Triox/main.rs at master · Trioxidation/Triox · GitHub. In the example they get the data with an await and convert it to the proper format.

let app_state = app_state::AppState::new().await;
let app_state = actix_web::web::Data::new(app_state);

I'm guessing this doesn't work for them either due to the .await as the data is probably not ready by the time it is converted to it's proper format. It didn't occur to me until this morning that this might be the problem, as I'm still learning rust and async/await. At any rate, if I refactor without the .await then it starts working!

// let new_data = Data::new().await;
let new_data = Data { color: "red".to_string() };

Well, I've learned even more. While my fix does work, I now understand why the example I was following works and why my implementation of it did not. It has nothing to do with the .await as I had suspected. They created a type that I was missing.

pub type AppData = web::Data<Arc<Data>>;

If you look carefully, you'll notice an Arc in there, that is the key. When I was trying to retrieve my data, I was missing that:

// notice the missing Arc
let app_data = req.app_data::<actix_web::web::Data<Data>>();

The fix then is to create the type and use that to extract the data.

let app_data = req.app_data::<AppData>();

Hi!
I currently have a different issue with the same kind of snippet you just have here.

fn call(&self, req: ServiceRequest) -> Self::Future {
    let app_data = req.app_data::<Data<Something>>();
    let fut = self.service.call(req);
    // Error: Cannot move out of req because it is borrowed.

.......
}

Did you encounter something similar? Thanks!

fn call(&self, req: ServiceRequest) -> Self::Future {
    let data = req.app_data::<Data<Something>>().cloned().unwrap();
    self.service.call(req)
}

Huge thanks for the fast reply. I’m still a baby rustacean so here’s the thing:
The data is keep in my state is mutable. I’m using the RwLock to have unlimited readers and to block access when something is writing. I’m not sure if I can clone this thing

Data is a specialized Arc so it can be cloned with a RwLock in it; it's a common pattern.

Awesome, this goes right into my notes! HUGE thanks sir!

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.