Can Actix-Web Serve a .Wasm File with the Logic?

Yes, Actix-web can serve a .wasm file, but there are a few nuances when it comes to integrating the logic within the WebAssembly file.

While serving static files like images and scripts is straightforward, serving and running a .wasm file containing logic requires a bit more setup. Here’s how you can serve and execute logic from a WebAssembly (.wasm) file using Actix-web.

Overview:

  • Serving the .wasm File: You can use Actix-web to serve the .wasm file as a static file.
  • Executing Logic in .wasm: To run the logic embedded in the .wasm file, you will need a WebAssembly runtime on the server side. Popular runtimes include wasmer or wasmtime.

This guide demonstrates how to set up an Actix-web server to serve a .wasm file and execute a function within that file, using wasmer to run the WebAssembly logic.

https://docs.rs/wasmer/5.0.4/wasmer/index.html


Step-by-Step Setup

  1. Add Dependencies in Cargo.toml:
[dependencies]
actix-web = "4.0"
tokio = { version = "1", features = ["full"] }
wasmer = "2.0"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
  1. Create the Actix-web Server (main.rs):
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use actix_files::Files;
use wasmer::{Store, Module, Instance, imports};
use serde::{Deserialize, Serialize};
use std::sync::Arc;

#[derive(Deserialize)]
struct Property {
    property_value: f64,
}

// A simple struct to wrap the store and module for Wasmer
struct WasmEnv {
    store: Store,
    module: Module,
}

// Serve the Wasm file
async fn serve_wasm() -> impl Responder {
    HttpResponse::Ok()
        .content_type("application/wasm")
        .body(include_bytes!("sdlt_tool.wasm").to_vec())  // Serve the wasm file
}

// Handle POST request for SDLT calculation
async fn calculate(property: web::Json<Property>, wasm_env: web::Data<Arc<WasmEnv>>) -> impl Responder {
    let result = run_wasm_calculation(wasm_env, property.property_value);

    match result {
        Ok(sdlt) => HttpResponse::Ok().json(sdlt),
        Err(_) => HttpResponse::InternalServerError().body("Error calculating SDLT"),
    }
}

// Function to execute Wasm calculation
fn run_wasm_calculation(wasm_env: web::Data<Arc<WasmEnv>>, property_value: f64) -> Result<f64, String> {
    let import_object = imports! {};  // Can be extended if there are required imports

    let mut instance = match Instance::new(&wasm_env.store, &wasm_env.module, &import_object) {
        Ok(inst) => inst,
        Err(_) => return Err("Failed to instantiate Wasm module".to_string()),
    };

    let calculate_sdlt = match instance.exports.get_function("calculate_sdlt") {
        Ok(func) => func,
        Err(_) => return Err("Failed to find the calculate_sdlt function".to_string()),
    };

    let result = match calculate_sdlt.call(&mut instance, &[property_value.into()]) {
        Ok(res) => res[0].to_f64().unwrap_or(0.0),
        Err(_) => return Err("Error executing Wasm function".to_string()),
    };

    Ok(result)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    // Load the Wasm module
    let wasm_bytes = std::fs::read("sdlt_tool.wasm").expect("Unable to read .wasm file");
    let store = Store::default();
    let module = Module::new(&store, wasm_bytes).expect("Failed to load Wasm module");

    // Wrap Wasm env in Arc for sharing between handlers
    let wasm_env = Arc::new(WasmEnv { store, module });

    // Start the Actix web server
    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(wasm_env.clone())) // Pass the Wasm environment
            .route("/calculate", web::post().to(calculate))
            .route("/sdlt_tool.wasm", web::get().to(serve_wasm)) // Serve the .wasm file
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

Explanation:

  • Serving the .wasm File:
    • The serve_wasm handler serves the WebAssembly file when you access the /sdlt_tool.wasm endpoint. This file can be fetched by a client-side application for execution in the browser or another WebAssembly runtime.
  • Handling the SDLT Calculation:
    • The calculate handler receives a POST request with the property value, executes the SDLT calculation using the WebAssembly logic inside the .wasm file, and returns the result.
  • Running the Wasm Module:
    • The wasmer library is used to load and execute the WebAssembly module on the server. The calculate_sdlt function inside the Wasm module is called with the provided property value, and the result is returned to the client.

How to Test:

  1. Start the Server:
    Run the Rust project using cargo run.
  2. Access the .wasm File: Open your browser and navigate to http://localhost:8080/sdlt_tool.wasm to fetch the .wasm file.
  3. Send a POST Request: Send a POST request to http://localhost:8080/calculate with a JSON body like: { "property_value": 1000000.0 } You will receive the result as a JSON response, for example: { "result": 25000.0 }

Advantages of This Approach:

  • Self-contained Server Logic:
    The server can now run the WebAssembly logic directly without needing an external JavaScript runtime. This keeps everything on the backend.
  • Serving .wasm Files and Handling Execution:
    The server can both serve the .wasm file to clients and execute it on the server side using wasmer.

This approach provides a neat integration of WebAssembly with Actix-web, allowing you to serve and execute WebAssembly logic directly within your Rust server.

WASM