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)
}
Router
:
Or use the 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:
- https://developers.cloudflare.com/workers/cli-wrangler/configuration#keys
- https://developers.cloudflare.com/workers/learning/using-durable-objects#configuring-durable-object-bindings
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
- For more information about migrating your Durable Object as it changes, see the docs here: https://developers.cloudflare.com/workers/learning/using-durable-objects#durable-object-migrations-in-wranglertoml
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
- Can I deploy a Worker that uses
tokio
orasync_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.
- 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.
- My bundle size exceeds Workers 1MB limits, what do I do?
- We're working on solutions here, but in the meantime you'll need to minimize the number of crates your code depends on, or strip as much from the
.wasm
binary as possible. Here are some extra steps you can try: https://rustwasm.github.io/book/reference/code-size.html#optimizing-builds-for-code-size
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
anddurable_object
macros for wrapping Rust entry point in afetch
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.