Skip to main content

Rust

Rust workers are compiled into a Wasm module. Then, they are loaded by Wasm Workers Server and start processing requests.

Your first Rust worker

Every worker receives a Request<String> struct and returns a Response<Content>. These structs come from the widely known http crate and the Content struct is defined in our rust kit. It allows you returning different types. Finally, the worker macro connects your worker with wws.

In this example, the worker will get a request and print all the related information.

  1. Create a new Rust project:

    cargo new --name worker worker
  2. Add the dependencies to the Cargo.toml file:

    Cargo.toml
    [package]
    name = "worker"
    version = "0.1.0"
    edition = "2021"

    [dependencies]
    anyhow = "1.0.63"
    wasm-workers-rs = { git = "https://github.com/vmware-labs/wasm-workers-server/", tag = "v1.3.0" }
  3. Add the reply function to the src/main.rs file. You will need to import the required resources from the wasm-workers-rs crate and use the worker macro:

    src/main.rs
    use anyhow::Result;
    use wasm_workers_rs::{
    worker,
    http::{self, Request, Response},
    Content,
    };

    #[worker]
    fn reply(req: Request<String>) -> Result<Response<Content>> {
    Ok(http::Response::builder()
    .status(200)
    .header("x-generated-by", "wasm-workers-server")
    .body(String::from("Hello wasm!").into())?)
    }
  4. Now, you can add all the information from the given Request struct:

    src/main.rs
    use anyhow::Result;
    use wasm_workers_rs::{
    worker,
    http::{self, HeaderValue, Request, Response},
    Content,
    };

    #[worker]
    fn reply(req: Request<String>) -> Result<Response<Content>> {
    // Applied changes here to use the Response method. This requires changes
    // on signature and how it returns the data.
    let response = format!(
    "<!DOCTYPE html>
    <body>
    <h1>Hello World</h1>
    <p>Replying to {}</p>
    <p>Method: {}</p>
    <p>User Agent: {}</p>
    <p>Body: {}</p>
    <p>This page was generated by a Wasm modules built from Rust.</p>
    </body>",
    req.uri(),
    req.method().as_str(),
    req.headers()
    .get("user-agent")
    .unwrap_or(&HeaderValue::from_str("None").unwrap())
    .to_str()
    .unwrap(),
    req.body()
    );

    Ok(http::Response::builder()
    .status(200)
    .header("x-generated-by", "wasm-workers-server")
    .body(response.into())?)
    }
  5. In this case, you need to compile the project to Wasm (WASI):

    # Install the component and build
    rustup target add wasm32-wasi && \
    cargo build --release --target wasm32-wasi
  6. Run your worker with wws. If you didn't download the wws server yet, check our Getting Started guide.

    cd target/wasm32-wasi/release && \
    wws .

    ⚙️ Loading routes from: .
    🗺 Detected routes:
    - http://127.0.0.1:8080/worker
    => worker.wasm (name: default)
    🚀 Start serving requests at http://127.0.0.1:8080
  7. Finally, open http://127.0.0.1:8080/worker in your browser.

Add a Key / Value store

Wasm Workers allows you to add a Key / Value store to your workers. Read more information about this feature in the Key / Value store section.

To add a KV store to your worker, follow these steps:

  1. Create a new Rust project:

    cargo new --name worker-kv worker-kv
  2. Add the dependencies to the Cargo.toml file:

    Cargo.toml
    [package]
    name = "worker-kv"
    version = "0.1.0"
    edition = "2021"

    [dependencies]
    anyhow = "1.0.63"
    wasm-workers-rs = { git = "https://github.com/vmware-labs/wasm-workers-server/", tag = "v1.3.0" }
  3. Add the reply function to the src/main.rs file. You will need to import the required resources from the wasm-workers-rs crate and use the worker macro. In this case, we will add a new attribute to the worker macro called cache and update the function signature:

    src/main.rs
    use anyhow::Result;
    use wasm_workers_rs::{
    worker,
    http::{self, Request, Response},
    Content,
    };

    #[worker(cache)]
    fn reply(_req: Request<String>, cache: &mut Cache) -> Result<Response<Content>> {
    Ok(http::Response::builder()
    .status(200)
    .header("x-generated-by", "wasm-workers-server")
    .body(String::from("Hello wasm!").into())?)
    }
  4. Then, let's read a value from the cache and update it:

    src/main.rs
    use anyhow::Result;
    use wasm_workers_rs::{
    worker,
    http::{self, Request, Response},
    Cache, Content,
    };

    #[worker(cache)]
    fn reply(_req: Request<String>, cache: &mut Cache) -> Result<Response<Content>> {
    // Applied changes here to use the Response method. This requires changes
    // on signature and how it returns the data.
    let count = cache.get("counter");
    let count_num = match count {
    Some(count_str) => count_str.parse::<u32>().unwrap_or(0),
    None => 0,
    };

    let response = format!(
    "<!DOCTYPE html>
    <body>
    <h1>Key / Value store in Rust</h1>
    <p>Counter: {}</p>
    <p>This page was generated by a Wasm modules built from Rust.</p>
    </body>",
    count_num
    );

    cache.insert("counter".to_string(), (count_num + 1).to_string());

    Ok(http::Response::builder()
    .status(200)
    .header("x-generated-by", "wasm-workers-server")
    .body(response.into())?)
    }
  5. Compile the project to Wasm (WASI):

    # Install the component and build
    rustup target add wasm32-wasi && \
    cargo build --release --target wasm32-wasi
  6. Create a worker-kv.toml file with the following content. Note the name of the TOML file must match the name of the worker. In this case we have worker-kv.wasm and worker-kv.toml in the same folder (target/wasm32-wasi/release):

    target/wasm32-wasi/release/worker-kv.toml
    name = "workerkv"
    version = "1"

    [data]
    [data.kv]
    namespace = "workerkv"
  7. Run your worker with wws. If you didn't download the wws server yet, check our Getting Started guide.

    cd target/wasm32-wasi/release && \
    wws .

    ⚙️ Loading routes from: .
    🗺 Detected routes:
    - http://127.0.0.1:8080/worker-kv
    => worker-kv.wasm (name: default)
    🚀 Start serving requests at http://127.0.0.1:8080
  8. Finally, open http://127.0.0.1:8080/worker-kv in your browser.

Send an HTTP request

Wasm Workers allows you to send HTTP requests from your workers. Read more information about this feature in the HTTP Requests section.

To perform HTTP requests from your worker, follow these steps:

  1. Create a new Rust project:

    cargo new --name fetch fetch
  2. Add dependencies to the Cargo.toml file:

    Cargo.toml
    [package]
    name = "fetch"
    version = "0.1.0"
    edition = "2021"

    [dependencies]
    anyhow = "1.0.63"
    wasm-workers-rs = { git = "https://github.com/vmware-labs/wasm-workers-server/", tag = "v1.4.0" }
    serde = { version = "1.0", features = ["derive"] }
    serde_json = "1.0.85"
  3. Add the reply function to the src/main.rs file. You will need to import the required resources from the wasm-workers-rs crate, use the worker macro and the bindings module. In this case, you need to import also the serde library to deserialize the API response from the external API:

    src/main.rs
    use anyhow::Result;
    use serde::{Deserialize, Serialize};
    use wasm_workers_rs::{
    worker,
    bindings,
    http::{self, Request, Response},
    Content,
    };

    #[worker]
    fn reply(_req: Request<String>) -> Result<Response<Content>> {
    Ok(http::Response::builder()
    .status(200)
    .header("x-generated-by", "wasm-workers-server")
    .body(String::from("Hello wasm!").into())?)
    }
  4. Then, let's create the http::Request instance and pass it to the bindings::send_http_request method. In this example, we will call the {JSON} Placeholder API to retrieve a Post. You need to create that struct to deserialize the request response with serde:

    src/main.rs
    use anyhow::Result;
    use serde::{Deserialize, Serialize};
    use wasm_workers_rs::{
    worker,
    bindings,
    http::{self, Request, Response},
    Content,
    };

    #[derive(Serialize, Deserialize)]
    #[serde(rename_all = "camelCase")]
    struct Post {
    id: i32,
    title: String,
    body: String,
    user_id: i32,
    }

    #[worker]
    fn reply(_req: Request<String>) -> Result<Response<Content>> {
    let external_request = Request::builder()
    .uri("https://jsonplaceholder.typicode.com/posts/1")
    .body(String::new())
    .unwrap();

    // Get the request
    let res = bindings::send_http_request(external_request).unwrap();

    // Parse the response
    let data = res.body();

    let post: Post = serde_json::from_slice(&data).unwrap();

    // Prepare the final response
    let response = format!(
    "<!DOCTYPE html>
    <head>
    <title>Wasm Workers Server</title>
    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">
    <meta charset=\"UTF-8\">
    </head>
    <body>
    <main>
    <h1>{}</h1>
    <p>{}</p>
    </main>
    </body>",
    &post.title, &post.body
    );

    Ok(http::Response::builder()
    .status(200)
    .header("x-generated-by", "wasm-workers-server")
    .body(response.into())?)
    }
  5. Compile the project to Wasm (WASI):

    # Install the component and build
    rustup target add wasm32-wasi && \
    cargo build --release --target wasm32-wasi
  6. After you compiled the project, move the worker to the current folder:

    mv ./target/wasm32-wasi/release/fetch.wasm ./
  7. Create a fetch.toml file with the following content. It enables the worker to perform HTTP requests to that host given that, by default, HTTP requests are forbidden.

    Note the name of the TOML file must match the name of the worker. In this case we have fetch.wasm and fetch.toml in the same folder:

    fetch.toml
    name = "fetch"
    version = "1"

    [features]
    [features.http_requests]
    allowed_methods = ["GET"]
    allowed_hosts = ["jsonplaceholder.typicode.com"]
  8. Run your worker with wws. If you didn't download the wws server yet, check our Getting Started guide.

    wws . --ignore "target/**"

    ⚙️ Loading routes from: .
    🗺 Detected routes:
    - http://127.0.0.1:8080/fetch
    => fetch.wasm (name: default)
    🚀 Start serving requests at http://127.0.0.1:8080
  9. Finally, open http://127.0.0.1:8080/fetch in your browser.

Dynamic routes

You can define dynamic routes by adding route parameters to your worker files (like [id].wasm). To read them in Rust, follow these steps:

  1. Add the params configuration parameter to the worker macro and update the method signature to receive the values:

    src/main.rs
    use anyhow::Result;
    use wasm_workers_rs::{
    worker,
    http::{self, Request, Response},
    Content,
    };

    #[worker(params)]
    fn reply(req: Request<String>, params: &HashMap<String, String>) -> Result<Response<Content>> {
    // ...
    }
  2. Then, you can read the values from the params argument:

    src/main.rs
    use anyhow::Result;
    use wasm_workers_rs::{
    worker,
    http::{self, Request, Response},
    Content,
    };

    #[worker(params)]
    fn reply(req: Request<String>, params: &HashMap<String, String>) -> Result<Response<Content>> {
    let missing_param = String::from("none");
    let id = params.get("id").unwrap_or_else(|| &missing_param);

    Ok(http::Response::builder()
    .status(200)
    .header("x-generated-by", "wasm-workers-server")
    .body(format!("Hey! The parameter is: {}", id).into())?)
    }

Read environment variables

Environment variables are configured via the related TOML configuration file. These variables are accessible via std::env in your worker. To read them, just use the same name you configured in your TOML file:

envs.toml
name = "envs"
version = "1"

[vars]
MESSAGE = "Hello 👋! This message comes from an environment variable"

Now, you can read the MESSAGE variable using the std::env Rust library:

src/main.rs
use anyhow::Result;
use std::env;
use wasm_workers_rs::{
worker,
http::{self, Request, Response},
Content,
};

#[worker]
fn handler(req: Request<String>) -> Result<Response<Content>> {
// Read the environment variable using the std::env::var method
let message = env::var("MESSAGE").unwrap_or_else(|_| String::from("Missing message"));

let response = format!(
"The message is: {}",
message,
);

Ok(http::Response::builder()
.status(200)
.body(response.into())?)
}

If you prefer, you can configure the environment variable value dynamically by following these instructions.

Other examples

Feature compatibility

Workers' features that are available in Rust:

K/V StoreEnvironment VariablesDynamic RoutesFoldersHTTP Requests