How can one retrieve app_data within wrap_fn and then append new app_data to the Actix request?

Hello everyone,

I am trying to verify Firebase auth with Actix using this crate. I am able to get user info with every request, but I want to retrieve userdata from my database using the Firebase user's email and attach it to the app_data. This way, I can avoid fetching user data from my database for every request.

I am quite confused about Actix middleware. Is there a way to achieve this somehow?

mod api;
mod common;
mod schema;
mod utils;

use actix_web::web::scope;
use actix_web::{dev::Service as _, web, App};
use actix_web::{middleware::Logger, HttpServer};
use firebase_auth::{FirebaseAuth, FirebaseUser};
use futures_util::future::FutureExt;
use std::{env, io};
use utils::connect::DatabaseConnection;

#[tokio::main]
async fn main() -> io::Result<()> {
    simple_logger::init_with_env().expect("Failed to initialize logger");
    utils::load_env::load_environment_variables()
        .await
        .expect("Failed to load environment variables");

    let bind_addr = format!(
        "localhost:{}",
        env::var("PORT").expect("PORT must be set")
    );
    let firebase_app_id = env::var("FIREBASE_APP_ID").expect("FIREBASE_APP_ID must be set");
    let firebase_data = web::Data::new(FirebaseAuth::new(&firebase_app_id).await);

    let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
    let database = web::Data::new(utils::connect::setup_database(&database_url).await);

    log::info!("HTTP Server listening on {}", bind_addr);
    HttpServer::new(move || {
        App::new()
            .wrap(Logger::default())
            .app_data(firebase_data.clone())
            .app_data(database.clone())
            .wrap_fn(|req, srv| {
                // Need to get user info from my database and attach it as app_data using firebase email
                let firebase_data = req.app_data::<web::Data<FirebaseUser>>().unwrap(); // none
                let database = req.app_data::<web::Data<DatabaseConnection>>().unwrap(); //none
                println!("Hi from start. You requested: {}", req.path());
                println!("Firebase data: {:?}", firebase_data.email);
                println!("Database data: {:?}", database);
                srv.call(req).map(|res| {
                    println!("Hi from response");
                    res
                })
            })
            .configure(api::example::config_example_api)
    })
    .bind(&bind_addr)?
    .run()
    .await
}

You can't attach new app_data inside of your middleware. You can attach an extension to your request and extract it in your route with the ReqData extractor, but this alone is not what you want. What you want is to add a cache for your user data (so you don't have to query your database with every request). What you can do is get some already existing app data (your cache) and insert the user data there. The easiest cache would be a thread-safe HashMap<String, UserData> where the key is the user's email, I guess. You can retrieve the cache in your middleware like you do with your firebase_data and database. If the cache contains your user data, attach it to the request with req.extensions_mut().insert(user_data). If not, retrieve it from your database, insert it into your cache and then attach it to the request. In your routes, extract the user data with ReqData<UserData>.

1 Like

Thanks for your reply will try this solution :slight_smile:

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.