splash image

February 14, 2021

Calling Rust from a Cloudflare Worker

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

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.

Creating the worker

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.

wrangler dev

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

'Hello wasm-worker!' appears in the browser

Modifications

For learning purposes, I pared the code down and pushed it to jldec/wasm-worker.

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" }
    }
  );
}

wrangler publish

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.

hello	"from wasm-worker" n "123456789012345678" words	"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"

šŸ¦€ Keep šŸ¦€ Digging šŸ¦€

To leave a comment
please visit dev.to/jldec

debug

user: anonymous

{
  "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 &quot;./&quot;\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) -&gt; 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: { &quot;Content-Type&quot;: &quot;application/json; charset=utf-8&quot; }\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* =&gt; 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&quot;from wasm-worker&quot; n &quot;123456789012345678&quot; words\t&quot;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&quot;\"></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"
}