通过 WebAssembly 将 Cloudflare 工人写入 100% 的 Rust
Ergonomic Rust bindings to Cloudflare Workers environment. Write your entire worker in Rust!
Read the Notes and FAQ
…
The project uses wrangler for running and publishing your Worker.
Use cargo generate to start from a template:
cargo generate cloudflare/workers-rs
There are several templates to choose from. During generation you will be prompted to enable
panic=unwind and abort recovery (see Panic Recovery
below). 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, run your worker locally:
npx wrangler dev
Finally, go live:
# configure your routes, zones & more in your worker's `wrangler.toml` file
npx wrangler deploy
If you would like to have wrangler installed on your machine, see instructions in wrangler repository.
http Featureworker 0.0.21 introduced an http feature flag which starts to replace custom types with widely used types from the http crate.
This makes it much easier to use crates which use these standard types such as axum and hyper.
This currently does a few things:
Body, which implements http_body::Body and is a simple wrapper around web_sys::ReadableStream. req argument when using the [event(fetch)] macro becomes http::Request<worker::Body>.http::Response<B> where B can be any http_body::Body<Data=Bytes>.Fetcher::fetch_request is http::Request<worker::Body>. Fetcher::fetch_request is Result<http::Response<worker::Body>>.The end result is being able to use frameworks like axum directly (see example):
pub async fn root() -> &'static str {
"Hello Axum!"
}
fn router() -> Router {
Router::new().route("/", get(root))
}
#[event(fetch)]
async fn fetch(
req: HttpRequest,
_env: Env,
_ctx: Context,
) -> Result<http::Response<axum::body::Body>> {
Ok(router().call(req).await?)
}
We also implement try_from between worker::Request and http::Request<worker::Body>, and between worker::Response and http::Response<worker::Body>. This allows you to convert your code incrementally if it is tightly coupled to the original types.
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.
…
All "bindings" to your script (Durable Object & KV Namespaces, Secrets, Variables and Version) 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.
…
For more information about how to configure these bindings, see:
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 the struct definition.
…
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.
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
Durable Objects can use SQLite for persistent storage, providing a relational database interface. To enable SQLite storage, you need to use new_sqlite_classes in your migration and access the SQL storage through state.storage().sql().
…
Configure your wrangler.toml to enable SQLite storage:
# ...
[durable_objects]
bindings = [
{ name = "SQL_COUNTER", class_name = "SqlCounter" }
]
[[migrations]]
tag = "v1" # Should be unique for each entry
new_sqlite_classes = ["SqlCounter"] # Use new_sqlite_classes for SQLite-enabled objects
As queues are in beta you need to enable the queue feature flag.
Enable it by adding it to the worker dependency in your Cargo.toml:
worker = {version = "...", features = ["queue"]}
…
You'll need to ensure you have the correct bindings in your wrangler.toml:
# ...
[[queues.consumers]]
queue = "myqueueotherqueue"
max_batch_size = 10
max_batch_timeout = 30
[[queues.producers]]
queue = "myqueue"
binding = "my_queue"
workers-rs has experimental support for Workers RPC.
For now, this relies on JavaScript bindings and may require some manual usage of wasm-bindgen.
Not all features of RPC are supported yet (or have not been tested), including:
Writing an RPC server with workers-rs is relatively simple. Simply export methods using wasm-bindgen. These
will be automatically detected by worker-build and made available to other Workers. See
example.
Creating types and bindings for invoking another Worker's RPC methods is a bit more involved. You will need to
write more complex wasm-bindgen bindings and some boilerplate to make interacting with the RPC methods more
idiomatic. See example.
With manually written bindings, it should be possible to support non-primitive argument and return types, using
serde-wasm-bindgen.
There are many routes that can be taken to describe RPC interfaces. Under the hood, Workers RPC uses Cap'N Proto. A possible future direction is for Wasm guests to include Cap'N Proto serde support and speak directly to the RPC protocol, bypassing JavaScript. This would likely involve defining the RPC interface in Cap'N Proto schema and generating Rust code from that.
Another popular interface schema in the WebAssembly community is
WIT. This is a lightweight format
designed for the WebAssembly Component model. workers-rs includes an experimental code generator which
allows you to describe your RPC interface using WIT and generate JavaScript bindings as shown in the
rpc-client example. The easiest way to use this code generator is using a build script as shown in the example.
This code generator is pre-alpha, with no support guarantee, and implemented only for primitive types at this time.
Rust Workers have CPU limits assigned by the platform. To gracefully detect these limits we provide a worker::signals API.
This allows for graceful backoff by detecting when the Worker is near its CPU limit and will be terminated using:
use worker::signals;
pub fn do_work () {
while !signals::is_near_cpu_limit() {
// hot loop
}
}
See Signal Example for a full end-to-end workflow.
--panic-unwindBy default, Rust panics in Workers compile with panic=abort, which terminates the WebAssembly
instance. The --panic-unwind flag for worker-build changes this behavior so that panics are
caught and converted to JavaScript exceptions, allowing the Worker to continue serving requests
after a panic.
When running worker-build directly:
worker-build --panic-unwind
Example wrangler.toml build command:
[build]
command = "cargo install worker-build && worker-build --release --panic-unwind"
This flag:
std with -Zbuild-std=std,panic_unwind and -Cpanic=unwind (the rust-src component and wasm32-unknown-unknown target for nightly are installed automatically if missing)PanicError
exceptionsschedule_reinit() via wasm-bindgen's abort handling
hooks to automatically recover from critical errors (e.g. unreachable, stack overflow, or
out-of-memory). After a hard abort the WebAssembly instance is transparently reinitialized on
the next request, with an internal instance ID bump so that Durable Object instances are
recreated.Without this flag, any panic will terminate the isolate. With it, individual requests that trigger a panic will fail with an error response while subsequent requests continue to work normally.
When building with panic=unwind, exported function arguments and closure captures must satisfy
Rust's UnwindSafe trait. The worker crate macros wrap handler callbacks with
AssertUnwindSafe automatically, but if you pass closures to JavaScript via Closure::new or
similar APIs you may need to wrap non-unwind-safe captures (e.g. &mut T, Cell<T>,
RefCell<T>) in std::panic::AssertUnwindSafe:
use std::cell::Cell;
use std::panic::AssertUnwindSafe;
use wasm_bindgen::prelude::*;
let counter = Cell::new(0u32);
let counter_ref = AssertUnwindSafe(&counter);
let closure = Closure::new(move || {
counter_ref.set(counter_ref.get() + 1);
});
Alternatively, Closure::own_aborting and other *_aborting variants skip the UnwindSafe
requirement but will abort on panic instead of catching it. See the
wasm-bindgen closures documentation
for the full set of closure APIs and their unwind behavior.
In order to test your Rust worker locally, the best approach is to use Miniflare. However, because Miniflare is a Node package, you will need to write your end-to-end tests in JavaScript or TypeScript in your project. The official documentation for writing tests using Miniflare is available here. This documentation being focused on JavaScript / TypeScript codebase, you will need to configure as follows to make it work with your Rust-based, WASM-generated worker:
devDependenciesnpm install --save-dev wrangler miniflare
Make s
[Feature] Support reading queue message attempts field
[BUG] ai.run misbehaves with serde_json::Value, should probably use json_compatible serializer
[Feature] support .raw({columnNames: true}) for D1PreparedStatement
[Feature] Honor exact http_body::Body::size_hint via FixedLengthStream
[BUG] Queue: typed `MessageBatch<T>` deserialize via `serde_wasm_bindgen::from_value` mangles payloads — offer a JSON-based path