Namespaces in Actix-Web

Let’s look at namespaces, scope, and state:

use actix_web::{web, App, HttpServer, Responder};

// Define handler functions for each scope
async fn index() -> impl Responder {
    "Hello from /app/index!"
}

async fn dashboard() -> impl Responder {
    "Hello from /dashboard/home!"
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(
                // First scope: handles requests with the /app prefix
                web::scope("/app")
                    .route("/index", web::get().to(index)),
            )
            .service(
                // Second scope: handles requests with the /dashboard prefix
                web::scope("/dashboard")
                    .route("/home", web::get().to(dashboard)),
            )
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}
use actix_web::{get, web, App, HttpServer, Responder};
use std::sync::Mutex;
use std::collections::HashMap;
use tokio::time::{self, Duration};

// This struct represents the state
struct AppState {
    exchange_rates: Mutex<HashMap<String, f64>>, // A cache of exchange rates
}

// Function to simulate fetching exchange rates from an external API
async fn fetch_exchange_rates() -> HashMap<String, f64> {
    // Simulated data; in a real-world scenario, you'd make an external HTTP request here
    let mut rates = HashMap::new();
    rates.insert("USD_EUR".to_string(), 0.92);
    rates.insert("USD_GBP".to_string(), 0.81);
    rates
}

// Background task to update the exchange rates periodically
async fn update_exchange_rates(state: web::Data<AppState>) {
    let mut interval = time::interval(Duration::from_secs(3600)); // Update every hour
    loop {
        interval.tick().await;
        let new_rates = fetch_exchange_rates().await;
        let mut rates = state.exchange_rates.lock().unwrap();
        *rates = new_rates;
    }
}

Here’s a real-world use case where application state is essential in an Actix Web application. Consider a scenario where you have a web service that provides exchange rates for different currencies. You might want to maintain a cache of these rates in the application state to avoid frequent external API calls.

Use Case: Currency Exchange Rate Service

Goal: Create a web service that provides currency exchange rates. The service will keep an up-to-date cache of exchange rates in the application state, refreshing it periodically.

Example Code:

use actix_web::{get, web, App, HttpServer, Responder};
use std::sync::Mutex;
use std::collections::HashMap;
use tokio::time::{self, Duration};

// This struct represents the state
struct AppState {
    exchange_rates: Mutex<HashMap<String, f64>>, // A cache of exchange rates
}

// Function to simulate fetching exchange rates from an external API
async fn fetch_exchange_rates() -> HashMap<String, f64> {
    // Simulated data; in a real-world scenario, you'd make an external HTTP request here
    let mut rates = HashMap::new();
    rates.insert("USD_EUR".to_string(), 0.92);
    rates.insert("USD_GBP".to_string(), 0.81);
    rates
}

// Background task to update the exchange rates periodically
async fn update_exchange_rates(state: web::Data<AppState>) {
    let mut interval = time::interval(Duration::from_secs(3600)); // Update every hour
    loop {
        interval.tick().await;
        let new_rates = fetch_exchange_rates().await;
        let mut rates = state.exchange_rates.lock().unwrap();
        *rates = new_rates;
    }
}

#[get("/rate/{pair}")]
async fn get_exchange_rate(
    data: web::Data<AppState>, 
    path: web::Path<String>
) -> impl Responder {
    let pair = path.into_inner();
    let rates = data.exchange_rates.lock().unwrap();
    if let Some(rate) = rates.get(&pair) {
        format!("The exchange rate for {} is {}", pair, rate)
    } else {
        format!("Exchange rate for {} not found", pair)
    }
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let state = web::Data::new(AppState {
        exchange_rates: Mutex::new(fetch_exchange_rates().await),
    });

    // Spawn a background task to update exchange rates periodically
    let state_clone = state.clone();
    tokio::spawn(update_exchange_rates(state_clone));

    HttpServer::new(move || {
        App::new()
            .app_data(state.clone()) // Share state across routes
            .service(get_exchange_rate)
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

#[get("/rate/{pair}")]
async fn get_exchange_rate(
    data: web::Data<AppState>, 
    path: web::Path<String>
) -> impl Responder {
    let pair = path.into_inner();
    let rates = data.exchange_rates.lock().unwrap();
    if let Some(rate) = rates.get(&pair) {
        format!("The exchange rate for {} is {}", pair, rate)
    } else {
        format!("Exchange rate for {} not found", pair)
    }
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let state = web::Data::new(AppState {
        exchange_rates: Mutex::new(fetch_exchange_rates().await),
    });

    // Spawn a background task to update exchange rates periodically
    let state_clone = state.clone();
    tokio::spawn(update_exchange_rates(state_clone));

    HttpServer::new(move || {
        App::new()
            .app_data(state.clone()) // Share state across routes
            .service(get_exchange_rate)
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

Explanation:

  1. State Structure (AppState): This holds a Mutex-protected HashMap to store exchange rates.
  2. Fetch Function (fetch_exchange_rates): Simulates an external API call to get exchange rates.
  3. Background Task: Uses tokio::spawn to periodically refresh the exchange rates without blocking the main thread.
  4. Route (/rate/{pair}): Users can query for specific exchange rates by providing the currency pair (e.g., USD_EUR, USD_GBP).

Benefits:

  • Efficiency: Reduces the need for frequent external API requests by maintaining a local cache.
  • State Sharing: The application state (exchange_rates) is shared across all incoming requests, ensuring consistent data.

https://docs.rs/actix-web/latest/actix_web/web/struct.Data.html