From the WebAssembly spec:
WebAssembly (abbreviated wasm) is a safe, portable, low-level code format designed for efficient execution and compact representation.
WebAssembly is the first new runnable format supported by all major browsers. It is also showing promise as a standardized way to deploy code to edge environments.
Rust is a popular language for writing code compiled to WebAssembly. This is not just because of Rust's minimal runtime needs, but also because of its community and tools.
Cloudflare Workers offers a low-cost, low-latency, serverless platform, making it an ideal complement for statically generated websites. Cloudflare Workers use V8, the same open source JavaScript engine from Google which is used in Node.js and Deno.
The APIs available to Cloudflare Workers are very limited - Unlike Node, there is no module loader and no access to the host platform.
Workers handle requests from the Web,
and they can call other Web services.
While the default JavaScript is already quite efficient, workers can also run WebAssembly.
This article demonstrates how to build a worker which calls a wasm function written in Rust.
The Cloudflare UI makes it easy to create new workers, but adding WebAssembly requires API calls with wasm Resource Bindings. Since this part of the API is not well documented, using the wrangler CLI is easier.
Install wrangler and authenticate with wrangler login
. Then run the following:
$ wrangler generate wasm-worker -t rust
š§ Creating project called `wasm-worker`...
āØ Done! New project created ./wasm-worker
šµļø You will need to update the following fields in the created wrangler.toml file before continuing:
šµļø You can find your account_id in the right sidebar of your account's Workers page, and zone_id in the right sidebar of a zone's overview tab at https://dash.cloudflare.com
- account_id
This creates a directory called wasm-worker
populated with files from github.com/cloudflare/rustwasm-worker-template.
You can now call wrangler dev
to build and run the worker.
Note: There is no need to run 'wasm-pack' as suggested by the project README, and as of wrangler v1.18.0, your 'account _id' will be auto-inferred by wrangler, if omitted from wrangler.toml.
$ wrangler dev
š Compiling your project to WebAssembly...
[INFO]: šÆ Checking for the Wasm target...
[INFO]: š Compiling to Wasm...
...
Compiling wasm-worker v0.1.0
Finished release [optimized] target(s) in 12.43s
[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 13.03s
[INFO]: š¦ Your wasm pkg is ready to publish at wasm-worker/pkg.
š watching "./"
š Listening on http://127.0.0.1:8787
Now browse to http://127.0.0.1:8787
For learning purposes, I pared the code down and pushed it to jldec/wasm-worker.
.appveyor.yml
, .travis.yml
, .cargo-ok
worker/metadata_wasm.json
- no longer used by wranglerconsole_error_panic_hook
, wee_alloc
, and cfg-if
wasm-bindgen
description
, license
, and repository
in Cargo.toml
Cargo.lock
to .gitignore
I added the shortscale crate, and changed src/lib.rs
.
use shortscale::shortscale;
#[wasm_bindgen]
pub fn numwords(num: u64) -> String {
return shortscale(num);
}
This is called from the worker.js
.
// Return JSON using query param n.
async function handleRequest(request) {
// pick up #[wasm_bindgen] exports from ../src/lib.rs
const { numwords } = wasm_bindgen;
// `wasm` binding name is auto-generated by wrangler
await wasm_bindgen(wasm);
let hello = 'from wasm-worker';
let url = new URL(request.url);
let n = url.searchParams.get('n');
let words;
try {
words = numwords(BigInt(n));
}
catch (e) {
words = 'undefined';
}
return new Response(JSON.stringify({ hello, n, words }),
{
status: 200,
headers: { "Content-Type": "application/json; charset=utf-8" }
}
);
}
Finally, I added a route and zone ID to my wrangler.toml and called wrangler publish
$ wrangler publish
š Compiling your project to WebAssembly...
[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]: āØ Done in 0.47s
[INFO]: š¦ Your wasm pkg is ready to publish at wasm-worker/pkg.
āØ Build succeeded
āØ Successfully published your script to
jldec.net/wasm-worker* => stayed the same
https://wasm-worker.jldec.workers.dev
You can run the result at https://jldec.net/wasm-worker?n=123456789012345678 - Round-trip response times in my area average under 30ms.
To leave a comment
please visit dev.to/jldec
{ "path": "/blog/calling-rust-from-a-cloudflare-worker", "attrs": { "title": "Calling Rust from a Cloudflare Worker", "splash": { "image": "/images/moonbird.jpg" }, "date": "2021-02-14", "layout": "BlogPostLayout", "excerpt": "How to build a Worker which calls a WebAssembly library written in Rust." }, "md": "# Calling Rust from a Cloudflare Worker\n\nFrom the [WebAssembly spec](https://webassembly.github.io/spec/core/intro/introduction.html):\n\n> WebAssembly (abbreviated wasm) is a safe, portable, low-level code format designed for efficient execution and compact representation.\n\nWebAssembly is the first new runnable format supported by all major browsers. It is also showing promise as a standardized way to deploy code to edge environments.\n\nRust is a popular language for writing code compiled to WebAssembly. This is not just because of Rust's minimal runtime needs, but also because of its community and [tools](forays-from-node-to-rust).\n\n## Cloudflare Workers\n\n[Cloudflare Workers](https://workers.cloudflare.com/) offers a low-cost, low-latency, serverless platform, making it an ideal complement for statically generated websites. Cloudflare Workers use [V8](https://github.com/v8/v8#readme), the same open source JavaScript engine from Google which is used in [Node.js](https://nodejs.org/en/about/) and [Deno](https://deno.land/).\n\nThe [APIs](https://developers.cloudflare.com/workers/runtime-apis) available to Cloudflare Workers are very limited - Unlike Node, there is no module loader and no access to the host platform.\n\n> Workers handle requests from the Web, \n> and they can call other Web services.\n\nWhile the default JavaScript is already quite efficient, workers can also run WebAssembly.\n\n## Creating the worker\n\nThis article demonstrates how to build a worker which calls a wasm [function](https://github.com/jldec/shortscale-rs) written in Rust.\n\nThe Cloudflare UI makes it easy to create new workers, but adding WebAssembly requires [API calls](https://api.cloudflare.com/#worker-script-upload-worker) with wasm [Resource Bindings](https://developers.cloudflare.com/workers/platform/scripts#resource-bindings). Since this part of the API is not well documented, using the **wrangler** CLI is easier.\n\n[Install wrangler](https://developers.cloudflare.com/workers/cli-wrangler/install-update) and authenticate with `wrangler login`. Then run the following:\n\n```sh\n$ wrangler generate wasm-worker -t rust\n```\n```\nš§ Creating project called `wasm-worker`...\nāØ Done! New project created ./wasm-worker\nšµļø You will need to update the following fields in the created wrangler.toml file before continuing:\nšµļø You can find your account_id in the right sidebar of your account's Workers page, and zone_id in the right sidebar of a zone's overview tab at https://dash.cloudflare.com\n- account_id\n```\n\nThis creates a directory called `wasm-worker` populated with files from [github.com/cloudflare/rustwasm-worker-template](https://github.com/cloudflare/rustwasm-worker-template/tree/72d390bf22983d43a1da3681faa093874fa32837).\n\n## wrangler dev\n\nYou can now call `wrangler dev` to build and run the worker.\n\n_Note_: There is no need to run 'wasm-pack' as suggested by the project README, and as of wrangler [v1.18.0](https://github.com/cloudflare/wrangler/releases/tag/v1.18.0), your 'account _id' will be auto-inferred by wrangler, if omitted from wrangler.toml.\n\n```\n$ wrangler dev\nš Compiling your project to WebAssembly...\n[INFO]: šÆ Checking for the Wasm target...\n[INFO]: š Compiling to Wasm...\n...\n Compiling wasm-worker v0.1.0\n Finished release [optimized] target(s) in 12.43s\n[INFO]: ā¬ļø Installing wasm-bindgen...\n[INFO]: Optimizing wasm binaries with `wasm-opt`...\n[INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended\n[INFO]: āØ Done in 13.03s\n[INFO]: š¦ Your wasm pkg is ready to publish at wasm-worker/pkg.\nš watching \"./\"\nš Listening on http://127.0.0.1:8787\n```\nNow browse to http://127.0.0.1:8787\n\n!['Hello wasm-worker!' appears in the browser](/images/hello-wasm-worker.png)\n\n## Modifications\n\nFor learning purposes, I pared the code down and pushed it to [jldec/wasm-worker](https://github.com/jldec/wasm-worker).\n\n- Removed unused files: `.appveyor.yml`, `.travis.yml`, `.cargo-ok`\n- Removed `worker/metadata_wasm.json` - no longer used by wrangler\n- Removed optional libraries `console_error_panic_hook`, `wee_alloc`, and `cfg-if`\n- Updated version of `wasm-bindgen`\n- Filled in `description`, `license`, and `repository` in `Cargo.toml`\n- Added `Cargo.lock` to `.gitignore`\n- Rewrote the README\n\nI added the [shortscale](https://crates.io/crates/shortscale) crate, and changed `src/lib.rs`.\n\n```rust\nuse shortscale::shortscale;\n\n#[wasm_bindgen]\npub fn numwords(num: u64) -> String {\n return shortscale(num);\n}\n```\n\nThis is called from the `worker.js`.\n\n```js\n// Return JSON using query param n.\nasync function handleRequest(request) {\n\n // pick up #[wasm_bindgen] exports from ../src/lib.rs\n const { numwords } = wasm_bindgen;\n\n // `wasm` binding name is auto-generated by wrangler\n await wasm_bindgen(wasm);\n\n let hello = 'from wasm-worker';\n let url = new URL(request.url);\n let n = url.searchParams.get('n');\n let words;\n\n try {\n words = numwords(BigInt(n));\n }\n catch (e) {\n words = 'undefined';\n }\n\n return new Response(JSON.stringify({ hello, n, words }),\n {\n status: 200,\n headers: { \"Content-Type\": \"application/json; charset=utf-8\" }\n }\n );\n}\n```\n\n## wrangler publish\n\nFinally, I added a route and zone ID to my wrangler.toml and called `wrangler publish`\n\n```\n$ wrangler publish\nš Compiling your project to WebAssembly...\n[INFO]: šÆ Checking for the Wasm target...\n[INFO]: š Compiling to Wasm...\n Finished release [optimized] target(s) in 0.03s\n[INFO]: ā¬ļø Installing wasm-bindgen...\n[INFO]: Optimizing wasm binaries with `wasm-opt`...\n[INFO]: āØ Done in 0.47s\n[INFO]: š¦ Your wasm pkg is ready to publish at wasm-worker/pkg.\nāØ Build succeeded\nāØ Successfully published your script to\n jldec.net/wasm-worker* => stayed the same\n https://wasm-worker.jldec.workers.dev\n```\n\nYou can run the result at https://jldec.net/wasm-worker?n=123456789012345678 - Round-trip response times in my area average under 30ms.\n\n![hello\t\"from wasm-worker\" n \"123456789012345678\" words\t\"one hundred and twenty three quadrillion four hundred and fifty six trillion seven hundred and eighty nine billion twelve million three hundred and forty five thousand six hundred and seventy eight\"](/images/worker-request.png)\n\n\n## š¦ Keep š¦ Digging š¦\n\n_To leave a comment \nplease visit [dev.to/jldec](https://dev.to/jldec/calling-rust-from-a-cloudflare-worker-17b4)_\n", "html": "<h1>Calling Rust from a Cloudflare Worker</h1>\n<p>From the <a href=\"https://webassembly.github.io/spec/core/intro/introduction.html\">WebAssembly spec</a>:</p>\n<blockquote>\n<p>WebAssembly (abbreviated wasm) is a safe, portable, low-level code format designed for efficient execution and compact representation.</p>\n</blockquote>\n<p>WebAssembly is the first new runnable format supported by all major browsers. It is also showing promise as a standardized way to deploy code to edge environments.</p>\n<p>Rust is a popular language for writing code compiled to WebAssembly. This is not just because of Rust's minimal runtime needs, but also because of its community and <a href=\"forays-from-node-to-rust\">tools</a>.</p>\n<h2>Cloudflare Workers</h2>\n<p><a href=\"https://workers.cloudflare.com/\">Cloudflare Workers</a> offers a low-cost, low-latency, serverless platform, making it an ideal complement for statically generated websites. Cloudflare Workers use <a href=\"https://github.com/v8/v8#readme\">V8</a>, the same open source JavaScript engine from Google which is used in <a href=\"https://nodejs.org/en/about/\">Node.js</a> and <a href=\"https://deno.land/\">Deno</a>.</p>\n<p>The <a href=\"https://developers.cloudflare.com/workers/runtime-apis\">APIs</a> available to Cloudflare Workers are very limited - Unlike Node, there is no module loader and no access to the host platform.</p>\n<blockquote>\n<p>Workers handle requests from the Web,<br>\nand they can call other Web services.</p>\n</blockquote>\n<p>While the default JavaScript is already quite efficient, workers can also run WebAssembly.</p>\n<h2>Creating the worker</h2>\n<p>This article demonstrates how to build a worker which calls a wasm <a href=\"https://github.com/jldec/shortscale-rs\">function</a> written in Rust.</p>\n<p>The Cloudflare UI makes it easy to create new workers, but adding WebAssembly requires <a href=\"https://api.cloudflare.com/#worker-script-upload-worker\">API calls</a> with wasm <a href=\"https://developers.cloudflare.com/workers/platform/scripts#resource-bindings\">Resource Bindings</a>. Since this part of the API is not well documented, using the <strong>wrangler</strong> CLI is easier.</p>\n<p><a href=\"https://developers.cloudflare.com/workers/cli-wrangler/install-update\">Install wrangler</a> and authenticate with <code>wrangler login</code>. Then run the following:</p>\n<pre><code class=\"language-sh\">$ wrangler generate wasm-worker -t rust\n</code></pre>\n<pre><code>š§ Creating project called `wasm-worker`...\nāØ Done! New project created ./wasm-worker\nšµļø You will need to update the following fields in the created wrangler.toml file before continuing:\nšµļø You can find your account_id in the right sidebar of your account's Workers page, and zone_id in the right sidebar of a zone's overview tab at https://dash.cloudflare.com\n- account_id\n</code></pre>\n<p>This creates a directory called <code>wasm-worker</code> populated with files from <a href=\"https://github.com/cloudflare/rustwasm-worker-template/tree/72d390bf22983d43a1da3681faa093874fa32837\">github.com/cloudflare/rustwasm-worker-template</a>.</p>\n<h2>wrangler dev</h2>\n<p>You can now call <code>wrangler dev</code> to build and run the worker.</p>\n<p><em>Note</em>: There is no need to run 'wasm-pack' as suggested by the project README, and as of wrangler <a href=\"https://github.com/cloudflare/wrangler/releases/tag/v1.18.0\">v1.18.0</a>, your 'account _id' will be auto-inferred by wrangler, if omitted from wrangler.toml.</p>\n<pre><code>$ wrangler dev\nš Compiling your project to WebAssembly...\n[INFO]: šÆ Checking for the Wasm target...\n[INFO]: š Compiling to Wasm...\n...\n Compiling wasm-worker v0.1.0\n Finished release [optimized] target(s) in 12.43s\n[INFO]: ā¬ļø Installing wasm-bindgen...\n[INFO]: Optimizing wasm binaries with `wasm-opt`...\n[INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended\n[INFO]: āØ Done in 13.03s\n[INFO]: š¦ Your wasm pkg is ready to publish at wasm-worker/pkg.\nš watching "./"\nš Listening on http://127.0.0.1:8787\n</code></pre>\n<p>Now browse to <a href=\"http://127.0.0.1:8787\">http://127.0.0.1:8787</a></p>\n<p><img src=\"/images/hello-wasm-worker.png\" alt=\"'Hello wasm-worker!' appears in the browser\"></p>\n<h2>Modifications</h2>\n<p>For learning purposes, I pared the code down and pushed it to <a href=\"https://github.com/jldec/wasm-worker\">jldec/wasm-worker</a>.</p>\n<ul>\n<li>Removed unused files: <code>.appveyor.yml</code>, <code>.travis.yml</code>, <code>.cargo-ok</code></li>\n<li>Removed <code>worker/metadata_wasm.json</code> - no longer used by wrangler</li>\n<li>Removed optional libraries <code>console_error_panic_hook</code>, <code>wee_alloc</code>, and <code>cfg-if</code></li>\n<li>Updated version of <code>wasm-bindgen</code></li>\n<li>Filled in <code>description</code>, <code>license</code>, and <code>repository</code> in <code>Cargo.toml</code></li>\n<li>Added <code>Cargo.lock</code> to <code>.gitignore</code></li>\n<li>Rewrote the README</li>\n</ul>\n<p>I added the <a href=\"https://crates.io/crates/shortscale\">shortscale</a> crate, and changed <code>src/lib.rs</code>.</p>\n<pre><code class=\"language-rust\">use shortscale::shortscale;\n\n#[wasm_bindgen]\npub fn numwords(num: u64) -> String {\n return shortscale(num);\n}\n</code></pre>\n<p>This is called from the <code>worker.js</code>.</p>\n<pre><code class=\"language-js\">// Return JSON using query param n.\nasync function handleRequest(request) {\n\n // pick up #[wasm_bindgen] exports from ../src/lib.rs\n const { numwords } = wasm_bindgen;\n\n // `wasm` binding name is auto-generated by wrangler\n await wasm_bindgen(wasm);\n\n let hello = 'from wasm-worker';\n let url = new URL(request.url);\n let n = url.searchParams.get('n');\n let words;\n\n try {\n words = numwords(BigInt(n));\n }\n catch (e) {\n words = 'undefined';\n }\n\n return new Response(JSON.stringify({ hello, n, words }),\n {\n status: 200,\n headers: { "Content-Type": "application/json; charset=utf-8" }\n }\n );\n}\n</code></pre>\n<h2>wrangler publish</h2>\n<p>Finally, I added a route and zone ID to my wrangler.toml and called <code>wrangler publish</code></p>\n<pre><code>$ wrangler publish\nš Compiling your project to WebAssembly...\n[INFO]: šÆ Checking for the Wasm target...\n[INFO]: š Compiling to Wasm...\n Finished release [optimized] target(s) in 0.03s\n[INFO]: ā¬ļø Installing wasm-bindgen...\n[INFO]: Optimizing wasm binaries with `wasm-opt`...\n[INFO]: āØ Done in 0.47s\n[INFO]: š¦ Your wasm pkg is ready to publish at wasm-worker/pkg.\nāØ Build succeeded\nāØ Successfully published your script to\n jldec.net/wasm-worker* => stayed the same\n https://wasm-worker.jldec.workers.dev\n</code></pre>\n<p>You can run the result at <a href=\"https://jldec.net/wasm-worker?n=123456789012345678\">https://jldec.net/wasm-worker?n=123456789012345678</a> - Round-trip response times in my area average under 30ms.</p>\n<p><img src=\"/images/worker-request.png\" alt=\"hello\t"from wasm-worker" n "123456789012345678" words\t"one hundred and twenty three quadrillion four hundred and fifty six trillion seven hundred and eighty nine billion twelve million three hundred and forty five thousand six hundred and seventy eight"\"></p>\n<h2>š¦ Keep š¦ Digging š¦</h2>\n<p><em>To leave a comment<br>\nplease visit <a href=\"https://dev.to/jldec/calling-rust-from-a-cloudflare-worker-17b4\">dev.to/jldec</a></em></p>\n" }