Write Cloudflare Workers in 100% Rust via WebAssembly

Overview

workers-rs crates.io docs.rs

Work-in-progress ergonomic Rust bindings to Cloudflare Workers environment. Write your entire worker in Rust!

Read the Notes and FAQ

Example Usage

use worker::*;

#[event(fetch)]
pub async fn main(req: Request, env: Env) -> Result<Response> {
    console_log!(
        "{} {}, located at: {:?}, within: {}",
        req.method().to_string(),
        req.path(),
        req.cf().coordinates().unwrap_or_default(),
        req.cf().region().unwrap_or("unknown region".into())
    );

    if !matches!(req.method(), Method::Post) {
        return Response::error("Method Not Allowed", 405);
    }

    if let Some(file) = req.form_data().await?.get("file") {
        return match file {
            FormEntry::File(buf) => {
                Response::ok(&format!("size = {}", buf.bytes().await?.len()))
            }
            _ => Response::error("`file` part of POST form must be a file", 400),
        };
    }

    Response::error("Bad Request", 400)
}

Or use the Router:

Parameterize routes and access the parameter values from within a handler. Each handler function takes a Request, and a RouteContext. The RouteContext has shared data, route params, Env bindings, and more.

use worker::*;

#[event(fetch)]
pub async fn main(req: Request, env: Env) -> Result<Response> {
    
    // Create an instance of the Router, and pass it some shared data to be used within routes.
    // In this case, `()` is used for "no data" so the type information is set for the generic used.
    // Access the shared data in your routes using the `ctx.data()` method.
    let router = Router::new(());

    // useful for JSON APIs
    #[derive(Deserialize, Serialize)]
    struct Account {
        id: u64,
        // ...
    }
    router
        .get_async("/account/:id", |_req, ctx| async move {
            if let Some(id) = ctx.param("id") {
                let accounts = ctx.kv("ACCOUNTS")?;
                return match accounts.get(id).await? {
                    Some(account) => Response::from_json(&account.as_json::<Account>()?),
                    None => Response::error("Not found", 404),
                };
            }

            Response::error("Bad Request", 400)
        })
        // handle files and fields from multipart/form-data requests
        .post_async("/upload", |mut req, _ctx| async move {
            let form = req.form_data().await?;
            if let Some(entry) = form.get("file") {
                match entry {
                    FormEntry::File(file) => {
                        let bytes = file.bytes().await?;
                    }
                    FormEntry::Field(_) => return Response::error("Bad Request", 400),
                }
                // ...

                if let Some(permissions) = form.get("permissions") {
                    // permissions == "a,b,c,d"
                }
                // or call `form.get_all("permissions")` if using multiple entries per field
            }

            Response::error("Bad Request", 400)
        })
        // read/write binary data
        .post_async("/echo-bytes", |mut req, _ctx| async move {
            let data = req.bytes().await?;
            if data.len() < 1024 {
                return Response::error("Bad Request", 400);
            }

            Response::from_bytes(data)
        })
        .run(req, env).await
}   

Getting Started

Make sure you have wrangler installed at a recent version (>=v1.19.2). If you want to publish your Rust worker code, you will need to have a Cloudflare account.

Run wrangler --version to check your installation and if it meets the version requirements.

wrangler generate --type=rust project_name
cd project_name
wrangler build

You should see a new project layout with a src/lib.rs. Start there! Use any local or remote crates and modules (as long as they compile to the wasm32-unknown-unknown target).

Once you're ready to run your project:

wrangler dev

And then go live:

# configure your routes, zones & more in your worker's `wrangler.toml` file
wrangler publish

Durable Object, KV, Secret, & Variable Bindings

All "bindings" to your script (Durable Object & KV Namespaces, Secrets, and Variables) are accessible from the env parameter provided to both the entrypoint (main in this example), and to the route handler callback (in the ctx argument), if you use the Router from the worker crate.

use worker::*;

#[event(fetch, respond_with_errors)]
pub async fn main(req: Request, env: Env) -> Result<Response> {
    utils::set_panic_hook();

    let router = Router::new(()); 

    router
        .on_async("/durable", |_req, ctx| async move {
            let namespace = ctx.durable_object("CHATROOM")?;
            let stub = namespace.id_from_name("A")?.get_stub()?;
            stub.fetch_with_str("/messages").await
        })
        .get("/secret", |_req, ctx| {
            Response::ok(ctx.secret("CF_API_TOKEN")?.to_string())
        })
        .get("/var", |_req, ctx| {
            Response::ok(ctx.var("BUILD_NUMBER")?.to_string())
        })
        .post_async("/kv", |_req, ctx| async move {
            let kv = ctx.kv("SOME_NAMESPACE")?;

            kv.put("key", "value")?.execute().await?;

            Response::empty()
        })
        .run(req, env).await
}

For more information about how to configure these bindings, see:

Durable Objects

BETA WARNING

Durable Objects are still in BETA, so the same rules apply to the Durable Object code and APIs here in these crates.

Define a Durable Object in Rust

To define a Durable Object using the worker crate you need to implement the DurableObject trait on your own struct. Additionally, the #[durable_object] attribute macro must be applied to both your struct definition and the trait impl block for it.

use worker::*;

#[durable_object]
pub struct Chatroom {
    users: Vec<User>,
    messages: Vec<Message>
    state: State,
    env: Env, // access `Env` across requests, use inside `fetch`

}

#[durable_object]
impl DurableObject for Chatroom {
    fn new(state: State, env: Env) -> Self {
        Self {
            users: vec![],
            messages: vec![],
            state: state,
            env,
        }
    }

    async fn fetch(&mut self, _req: Request) -> Result<Response> {
        // do some work when a worker makes a request to this DO
        Response::ok(&format!("{} active users.", self.users.len()))
    }
}

You'll need to "migrate" your worker script when it's published so that it is aware of this new Durable Object, and include a binding in your wrangler.toml.

  • Include the Durable Object binding type in you wrangler.toml file:
# ...

[durable_objects]
bindings = [
  { name = "CHATROOM", class_name = "Chatroom" } # the `class_name` uses the Rust struct identifier name
]

[[migrations]]
tag = "v1" # Should be unique for each entry
new_classes = ["Chatroom"] # Array of new classes

Notes and FAQ

It is exciting to see how much is possible with a framework like this, by expanding the options developers have when building on top of the Workers platform. However, there is still much to be done. Expect a few rough edges, some unimplemented APIs, and maybe a bug or two here and there. It’s worth calling out here that some things that may have worked in your Rust code might not work here - it’s all WebAssembly at the end of the day, and if your code or third-party libraries don’t target wasm32-unknown-unknown, they can’t be used on Workers. Additionally, you’ve got to leave your threaded async runtimes at home; meaning no Tokio or async_std support. However, async/await syntax is still available and supported out of the box when you use the worker crate.

We fully intend to support this crate and continue to build out its missing features, but your help and feedback is a must. We don’t like to build in a vacuum, and we’re in an incredibly fortunate position to have brilliant customers like you who can help steer us towards an even better product.

So give it a try, leave some feedback, and star the repo to encourage us to dedicate more time and resources to this kind of project.

If this is interesting to you and you want to help out, we’d be happy to get outside contributors started. We know there are improvements to be made such as compatibility with popular Rust HTTP ecosystem types (we have an example conversion for Headers if you want to make one), implementing additional Web APIs, utility crates, and more. In fact, we’re always on the lookout for great engineers, and hiring for many open roles - please take a look.

FAQ

  1. Can I deploy a Worker that uses tokio or async_std runtimes?
  • Currently no. All crates in your Worker project must compile to wasm32-unknown-unknown target, which is more limited in some ways than targets for x86 and ARM64.
  1. The worker crate doesn't have X! Why not?
  • Most likely, it should, we just haven't had the time to fully implement it or add a library to wrap the FFI. Please let us know you need a feature by opening an issue.
  1. My bundle size exceeds Workers 1MB limits, what do I do?

Contributing

Your feedback is welcome and appreciated! Please use the issue tracker to talk about potential implementations or make feature requests. If you're interested in making a PR, we suggest opening up an issue to talk about the change you'd like to make as early as possible.

Project Contents

  • worker: the user-facing crate, with Rust-famaliar abstractions over the Rust<->JS/WebAssembly interop via wrappers and convenience library over the FFI bindings.
  • worker-sys: Rust extern "C" definitions for FFI compatibility with the Workers JS Runtime.
  • worker-macros: exports event and durable_object macros for wrapping Rust entry point in a fetch method of an ES Module, and code generation to create and interact with Durable Objects.
  • worker-sandbox: a functioning Cloudflare Worker for testing features and ergonomics.
  • worker-build: a cross-platform build command for workers-rs-based projects.
Issues
  • optimize for size

    optimize for size

    null

    opened by caass 7
  • Fix two compiler errors about iterating arrays and remove one unneeded unsafe block

    Fix two compiler errors about iterating arrays and remove one unneeded unsafe block

    While trying the Getting Started steps I had to locally fix these issues to continue because the latest Rust compiler reports errors on building worker-build. The fixes are simple and after making them I was able to successfully run the example, make changes to the worker and see those changes applied.

    opened by gnp 6
  • Router avoid method conflicts

    Router avoid method conflicts

    Depends on #33

    I noticed that I could not add a catchall route /*whatever without causing a conflict with an existing route /something, even if the 2 routes used different HTTP methods. I am not sure whether this is expected behavior, but I thought that it might make sense to check conflicts only for routes with the same HTTP methods, which is what this PR implements.

    For example, with this PR it is possible to define a /*whatever route for OPTIONS requests without causing conflicts with any existing and more specific GET route.

    opened by fkettelhoit 6
  • Reorganize before an official release

    Reorganize before an official release

    The naming of the crates in this project are a bit everywhere. It'd be nice to consolidate the names of everything and reorganize before committing to any crates.io releases.

    As it stands there are currently five crates each with wildly different names:

    • edgeworker-sys
    • worker-rs-macros
    • libworker
    • worker
    • rust-worker-build

    libworker could be dropped in favor of just defining everything in worker instead of using worker to re-export everything. Currently libworker is separate so it can be imported by worker-rs-macros however the macros crate never uses it so it's an unnecessary dependency. With the base name "worker", we can easily turn this into four separate easily identifiable crates.

    • worker-sys - For low-level FFI bindings
    • worker-macros - For macros
    • worker - For bringing everything together with convenience types
    • worker-build - As a build command

    On top of this, it may be favorable to drop the rust-sandbox crate. Instead of testing or experimenting in a sandbox, things normally written in the sandbox would be better if written as an example in the worker crate. Then things written as examples for testing during development can be referenced later by users.

    opened by ObsidianMinor 6
  • Don't require async functions passed to `*_async` handlers to have a 'static lifetime

    Don't require async functions passed to `*_async` handlers to have a 'static lifetime

    I found this while trying to avoid copying in this example from the README:

        router.on_async("/proxy_request/*url", |_req, _env, params| {
            // Must copy the parameters into the heap here for lifetime purposes
            let url = params
                .get("url")
                .unwrap()
                .strip_prefix('/')
                .unwrap()
                .to_string();
            async move { Fetch::Url(&url).send().await }
        })?;
    

    Unfortunately, that particular example still doesn't compile, but this now allows other borrows.

    opened by jyn514 5
  • Confusing 403 error with `wrangler dev`

    Confusing 403 error with `wrangler dev`

    I had wrangler logined to my Cloudflare account already, but when trying to wrangler dev I received a 403 error that was not very useful in helping me determine the cause:

    wrangler dev
    🌀  Running cargo install -q worker-build && worker-build --release
    [INFO]: Checking for the Wasm target...
    [INFO]: Compiling to Wasm...
        Finished release [optimized] target(s) in 0.03s
    [INFO]: Installing wasm-bindgen...
    [INFO]: Optimizing wasm binaries with `wasm-opt`...
    [INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended
    [INFO]: :-) Done in 2.10s
    [INFO]: :-) Your wasm pkg is ready to publish at /home/jason/src/my-project/build.
    Error: HTTP status client error (403 Forbidden) for url (https://api.cloudflare.com/client/v4/accounts/ae126deafb76cf635e5e028f594434ac/workers/subdomain/edge-preview)
    

    I think I tracked it down to not enabling the free cloudworker component in my account, but 1) I wasn't expecting a local dev cli command to require that and 2) it would have been good to spell that out in the returned 403.

    Now it seems to be working past that (but getting a different error I'm trying to make sense of). Thanks for the cool project!

    opened by binarybana 4
  • Can't run wrker-build, assumes ~/.cargo/bin is in PATH and fails otherwise

    Can't run wrker-build, assumes ~/.cargo/bin is in PATH and fails otherwise

    🐛 Bug report

    Describe the bug

    This is just developer friction / newbie trap.

    Wrangler 1.19.2 installed by building via cargo.

    Reproduce the bug

    With the template Rust workers demo from https://blog.cloudflare.com/workers-rust-sdk/

    $ wrangler dev 
    🌀  Running cargo install -q worker-build && worker-build --release
    sh: worker-build: command not found
    Error: Build failed! Status Code: 127
    
    $ PATH="$PATH:$HOME/.cargo/bin"
    $ wrangler dev 
    🌀  Running cargo install -q worker-build && worker-build --release
    Installing wasm-pack...
    

    Expected behavior

    Ideally:

    1. Don't rely on user $PATH.
    2. Don't even attempt to install things on the fly automatically. (My opinion.)

    Environment and versions

    Fill out the following information about your environment.

    • operating system: Linux, NixOS 21.05
    • output of wrangler -V: wrangler 1.19.2
    • output of node -v: node: command not found
    • content of wrangler.toml: unedited from template
    opened by tv42 3
  • `Response::error` is a footgun

    `Response::error` is a footgun

    Response::error("Error", 200) is perfectly valid and is not correct at all.

    Actually, looking at the implementation it would be fine if we just renamed this to Response::with_status. @nilslice what do you think? We could still have Response::internal_error or Response::request_error convenience wrappers if you think they're helpful.

    (This may become a moot point after implementing #13.)

    opened by jyn514 2
  • cross-platform worker builds

    cross-platform worker builds

    add the rust-worker-build binary which wraps wasm-bindgen and makes the necessary changes for our workers to be working.

    there's no cargo install step yet because we haven't published it, so for now just cargo install --path

    opened by caass 2
  • Make `libworker::Error` public and non-exhaustive

    Make `libworker::Error` public and non-exhaustive

    Making it public allows users to handle errors themselves. Making it non-exhaustive allows us to add more error variants in the future without needing a breaking change.

    Fixes #11

    opened by jyn514 2
  • RequestInit::new() is hanging

    RequestInit::new() is hanging

    Hi!

    It seems like something's wrong with RequestInit::new(). Here is the simple code, it creates empty RequestInit and then returns empty response. And it stucks.

    use worker::*;
    
    mod utils;
    
    fn log_request(req: &Request) {
        console_log!(
            "{} - [{}], located at: {:?}, within: {}",
            Date::now().to_string(),
            req.path(),
            req.cf().coordinates().unwrap_or_default(),
            req.cf().region().unwrap_or("unknown region".into())
        );
    }
    
    #[event(fetch)]
    pub async fn main(req: Request, env: Env) -> Result<Response> {
        log_request(&req);
    
        utils::set_panic_hook();
    
        let _init = RequestInit::new(); // the code stucks here
    
        Response::empty()
    }
    

    And here's the logs from wrangler dev:

    👂  Listening on http://127.0.0.1:8787
    Sun Sep 12 2021 12:06:47 GMT+0000 (Coordinated Universal Time) - [/], located at: (59.8983, 30.2618), within: St.-Petersburg
    Error: Worker exceeded CPU time limit. at line 0, col -2
    {
      "exceptionDetails": {
        "columnNumber": -2,
        "exception": {
          "className": "Error",
          "description": "Error: Worker exceeded CPU time limit.",
          "preview": {
            "description": "Error: Worker exceeded CPU time limit.",
            "entries": null,
            "overflow": false,
            "properties": [],
            "subtype": "error",
            "type": "object"
          },
          "subtype": "error",
          "type": "object",
          "value": null
        },
        "lineNumber": 0,
        "text": "Uncaught (in response)",
        "url": "undefined"
      },
      "timestamp": 1631448407649
    }
    [2021-09-12 15:06:46] GET response-bot-dev.idebugger.workers.dev/ HTTP/1.1 503 Service Unavailable
    
    opened by OlegWasHere 1
  • Cannot use `wasm-bindgen` package newer than 0.2.76

    Cannot use `wasm-bindgen` package newer than 0.2.76

    https://github.com/cloudflare/workers-rs/blob/4da00658d68294ce0853c00898783ab131f1e2e8/worker-macros/Cargo.toml#L20 requires exact wasm-bindgen version 0.2.76, refusing build with newer v0.2.77 dependency. Is it intended for compatibility or convenience? It seems like this 'version pinning' has been used since the first addition.

    opened by cr0sh 1
  • Hello World example

    Hello World example

    Current examples in https://github.com/cloudflare/workers-rs/blame/main/README.md#L9, https://blog.cloudflare.com/workers-rust-sdk/ and project generated by wrangler generate --type=rust all provide some advanced usage of using router, console_log, DO, POST handling. None of them worked out of the box for me.

    For those whose just start out, like me, would it be possible to provide and example worker that just returns Hello world string so we could start from there? Thanks a lot!

    opened by toinbis 1
  • Consider making Router Data not an Option

    Consider making Router Data not an Option

    It seems the only reason Router and RouteContext data method return an Option<Data> is because you've decided to implement Default for a router. Nothing seems to use that default. Could I convince you to remove the Default and make data be not optional? That would remove a .unwrap() from practically every handler of every app using data.

    For inspiration/justification/examples:

    • https://docs.rs/tide/0.16.0/tide/struct.Request.html#method.state
    • https://github.com/http-rs/tide/blob/1d6f120c9e6c0e723bb8929dc0f601cd4f9d1449/src/server.rs#L59-L61
    opened by tv42 3
  • Cache

    Cache

    How do I access Cloudflare cache?

    opened by nuvanti 2
  • gRPC support

    gRPC support

    This is a probably a very big task, since JS Workers don't seem to have an existing library or a nice way to do it. There are some existing crates like tonic which support gRPC but these likely wouldn't work without a lot of changes. gRPC also has streams, which some platforms don't support while still supporting unary requests (possibly for similar reasons that might prevent Workers from supporting them). gRPC-web might be easier, but do not currently support streams.

    Another possible issue is Cloudflare's support for gRPC since HTTP/2 isn't just enabled for everything by default and Workers might not currently support HTTP/2(?).

    opened by nihaals 3
  • Improve README

    Improve README

    null

    opened by Electroid 0
  • WebSocket server support

    WebSocket server support

    It probably makes sense for WebSocket clients (to remote servers) to use the same type as what's returned from the new WebSocketPair() equivalent (although probably only relevant if it's added at the same time as server support).

    enhancement ffi 
    opened by nihaals 1
  • Use `log` instead of `console_log!`

    Use `log` instead of `console_log!`

    This will mean all the logging from libraries that developers don't control will still show up in the JS console. We could implement it by registering a log handler which outputs to the JS console.

    ecosystem-compat 
    opened by jyn514 2
  • Use `http` types directly instead of converting to and from them

    Use `http` types directly instead of converting to and from them

    We'll still need to convert this in worker itself, because it needs to turn back into a wasm_bindgen::JsValue, but we don't need to add our own Response type, we can just use one from the ecosystem. Same for many of the other types (Request etc).

    ecosystem-compat 
    opened by jyn514 3
Owner
Cloudflare
Cloudflare
A cloud-native distributed serverless workers platform.

rusty-workers A cloud-native distributed serverless workers platform. Features JavaScript and WebAssembly engine powered by V8 Fetch API Highly scalab

Heyang Zhou 62 Sep 3, 2021
List of Rust books

Rust Books Books Starter Books Advanced Books Resources Books Starter Books The Rust Programming Language Free Welcome! This book will teach you about

Spiros Gerokostas 1.4k Sep 10, 2021
The missing batteries of Rust

stdx - The missing batteries of Rust New to Rust and don't yet know what crates to use? stdx has the best crates. Current revision: stdx 0.119.0-rc, f

Brian Anderson 1.5k Sep 13, 2021
🍋: A General Lock following paper "Optimistic Lock Coupling: A Scalable and Efficient General-Purpose Synchronization Method"

Optimistic Lock Coupling from paper "Optimistic Lock Coupling: A Scalable and Efficient General-Purpose Synchronization Method" In actual projects, th

LemonHX 20 Aug 31, 2021
Shared Channel for WebAssembly

Shared Channel for WebAssembly This crate provides a way for WebAssembly threads to receive messages from other threads using a JavaScript primitive c

wasm.rs 20 Jun 15, 2021
A bunch of links to blog posts, articles, videos, etc for learning Rust

rust-learning A bunch of links to blog posts, articles, videos, etc for learning Rust. Feel free to submit a pull request if you have some links/resou

Camille TJHOA 7.1k Sep 15, 2021
Leetcode Solutions in Rust, Advent of Code Solutions in Rust and more

RUST GYM Rust Solutions Leetcode Solutions in Rust AdventOfCode Solutions in Rust This project demostrates how to create Data Structures and to implem

Larry Fantasy 197 Sep 11, 2021
Visualization for Timely Dataflow and Differential Dataflow programs

DDShow Visualization for Timely Dataflow and Differential Dataflow programs Getting started with ddshow First, install ddshow via cargo. As of now dds

Chase Wilson 48 Aug 10, 2021
Comprehensive DSP graph and synthesis library for developing a modular synthesizer in Rust, such as HexoSynth.

HexoDSP - Comprehensive DSP graph and synthesis library for developing a modular synthesizer in Rust, such as HexoSynth. This project contains the com

Weird Constructor 13 Sep 7, 2021
An inquiry into nondogmatic software development. An experiment showing double performance of the code running on JVM comparing to equivalent native C code.

java-2-times-faster-than-c An experiment showing double performance of the code running on JVM comparing to equivalent native C code ⚠️ The title of t

xemantic 48 Aug 15, 2021
A peer-reviewed collection of articles/talks/repos which teach concise, idiomatic Rust.

This repository collects resources for writing clean, idiomatic Rust code. Please bring your own. ?? Idiomatic coding means following the conventions

Matthias 2.8k Sep 10, 2021
A cli tool to write your idea in terminal

Ideas ideas is a cli tools to write your idea in your terminal. Demo Features tagged idea, contains tips, idea, todo status switch ascii icon write yo

王祎 12 Aug 26, 2021
Tools for managing GitHub block lists

GitHub block list management Octocrabby is a small set of command-line tools and Octocrab extensions that are focused on managing block lists on GitHu

Travis Brown 95 Sep 15, 2021
a cheat-sheet for mathematical notation in Rust 🦀 code form

math-as-rust ?? Based on math-as-code This is a reference to ease developers into mathematical notation by showing comparisons with Rust code.

Eduardo Pereira 9 Sep 12, 2021
Kerberos laboratory to better understand and then detecting attack on kerberos

Kerlab A Rust implementation of Kerberos for FUn and Detection Kerlab was developped just to drill down kerberos protocol and better understand it. Th

Sylvain Peyrefitte 57 Sep 5, 2021
Rust programs written entirely in Rust

mustang Programs written entirely in Rust Mustang is a system for building programs built entirely in Rust, meaning they do not depend on any part of

Dan Gohman 290 Sep 14, 2021
A Quest to Find a Highly Compressed Emoji :shortcode: Lookup Function

Highly Compressed Emoji Shortcode Mapping An experiment to try and find a highly compressed representation of the entire unicode shortcodes-to-emoji m

Daniel Prilik 14 Aug 9, 2021
Simple daemon built with Rust to track metrics.

Marvin - Metrics Tracker What I cannot create, I do not understand. — Richard Feynman Simple daemon built with Rust to track metrics. The goal is run

João Henrique Machado Silva 3 Aug 30, 2021
The source code that accompanies Hands-on Rust: Effective Learning through 2D Game Development and Play by Herbert Wolverson

Hands-on Rust Source Code This repository contains the source code for the examples found in Hands-on Rust. These are also available from my publisher

Herbert 76 Sep 11, 2021