home
menu
lock
Editing — Serving Static Assets with Rust WASM Workers Sites
arrow_back
Back
add
Create a new page
delete
Delete this page
Editing
You must be authenticated for your changes to save. Styling with
Markdown
is supported.
> This post is a follow-up to a question I asked on the [CloudFlare Community forum](https://community.cloudflare.com/t/serving-static-assets-with-rust-wasm-workers-sites/179929). Sure! After poking around, it seems that kv namespaces are the way to go. (The source for my project can be found here, [btw](https://github.com/slightknack/website)). ## About KV Any files other than those used to compile the WASM executable won't be included in the package, which means that they have to be requested over database. Cloudflare's KV is a distributed key-value database that we can use for this purpose - [here's the documentation](https://developers.cloudflare.com/workers/learning/how-kv-works). You'll first need to create a namespace. You can use the web interface, or the `wrangler kv:create` command. ## Adding KV Namespace to Project Each namespace has a unique `id`. To use the namespace in your project, you need to add this to your `wrangler.toml` (if you haven't already): ```toml account_id = "..." zone_id = "..." kv-namespaces = [ { binding = "Name", id = "..." }, # ... ] ``` ***By the way:** The tutorial seems to be incorrect, remove `[site] ...` from your `wrangler.toml`.* Your account id and your zone id can be found through Cloudflare's dashboard. You should also make sure your Cloudflare account is connected by running `wrangler whoami` - if you aren't authenticated, run `wrangler config` and follow the instructions. ## Making the Namespace Accessible to WASM Worker Our namespace is bound to our project, but our WASM Worker can't access it yet. To make it accessible, we need to do two things: 1. Declare that our WASM worker can access the namespace. 2. Define an interface that allows us to access the namespace from the worker. ### Declaring Namespace Access To declare that our worker can access the namespace, add the following to your `worker/wasm_metadata.json`: ```json { "body_part": "script", "bindings": [ { "name": "wasm", "type": "wasm_module", "part": "wasmprogram" } { "name": "Name", // name of namespace "type": "kv_namespace", "namespace_id": "..." }, // ... repeat the above block to add additional namespaces ] } ``` ## Defining an Interface Now that our worker can access the namespace, we need to write some Rust so we can read/write from/to the namespace. We can use `extern` to call kv javascript functions from our Rust WASM worker. Make a new Rust file, `src/kv.rs`, and add the following: ```rust use wasm_bindgen::prelude::*; use js_sys::Promise; use wasm_bindgen_futures::JsFuture; #[wasm_bindgen] extern "C" { pub type Name; // name of namespace // notice these attributes! #[wasm_bindgen(static_method_of = Name)] pub fn get(key: &str, data_type: &str) -> Promise; #[wasm_bindgen(static_method_of = Name)] pub fn put(key: &str, val: &str) -> Promise; #[wasm_bindgen(static_method_of = Name)] pub fn delete(key: &str) -> Promise; } ``` There are more functions availiable, but this allows us to get, store, and delete keys, which will realistically cover about 95% of use-cases. ### Making it More Ergonomic I also wrote a small helper function, `value` that awaits promises returned from the kv namespace. This is just for convinience: ```rust // add to src/kv.rs pub async fn value(promise: Promise) -> Option
{ JsFuture::from(promise).await.ok() } ``` Then, reading/writing/deleting to/from the kv is as simple as: ``` pub async fn asset(name: &str) -> String { // again, Name is name of namespace kv::value(kv::Name::get(name, "text")) .await.unwrap().as_string().unwrap() } ``` Which I personally find to be really archaic and annoying, which is why I've wrapped it with a function called `asset`. ## Finally Serving Static Assets! To serve these assets, we need to define a route endpoint in our Rust code. You should have some `main` function that's bound so it's externally accessible. To serve static assets, we're going to check that the request is a `GET` request, check that the URL path starts with `example.com/static/`, extract the name of the asset (e.g. `example.com/static/file.txt` → `file.txt`), look up the asset in our KV namespace, make a response, then return it. This may seem needlessly complex (and to some degree, it is) but it's actually quite simple. ```rust // in lib.rs // ... imports hidden /// Takes an event, handles it, and returns a promise containing a response. #[wasm_bindgen] pub async fn main(event: FetchEvent) -> Promise { // get the request let request = event.request(); // extract the url let url = match Url::parse(&request.url()) { Ok(v) => v, Err(_) => return Promise::reject(&JsValue::from("Could not parse url")), }; // get the URL path (e.g. www.example.com/
) // and method (GET, POST, etc.) let path = url.path().to_lowercase(); let method = request.method().to_lowercase(); // split the path into a list of values // e.g. "static/file.txt" becomes ["static", "file.txt"] let route = path.split('/') .filter(|p| p != &"") .map(|p| p.to_string()) .collect::
>(); // I'm using an if statement here, // but if you have more paths to route against, // a match statement is a better idea if route.iter().nth(0).unwrap() == "static" { let file = path.iter().nth(1); // using the previously defined asset function to read from the ns let content = asset(file); // ... snip ... building the response return Promise::resolve(&JsValue::from(response)); } // ... do other things with the event as normal } ``` Of course, the above logic doesn't have to be in your `main` function - you can abstract it out somewhat. (My project, for instance, has one function that handles routing, another that fetches static assets, and yet another that builds responses). I also ***strongly*** suggest you implement a more robust error handling mechanism than just unwrapping errors. ## Uploading Static Assets You can upload static assets to serve via Cloudflare's KV dashboard or in bulk from the command line using `wrangler kv::bulk put`. If there's anything I glossed over or would like me to expand on, let me know. Most of the code above was taken from the repository for my website, so I would check it out. I hope this helps! ***By the way:** I would not recommend workers for serving static assets, it's a bit of a hassle. Workers is a bit rough around the edges in general, so unless you have a really good reason to use it, I recommend taking a more established route instead.**