Deno Deploy や Rust の練習として、Rust から出力した WebAssembly を Deno Deploy を動かしてみた。
その手順をまとめておく。
ローカルでの動作確認は以下の環境で行った。
- rustc 1.50.0
- cargo 1.50.0
- Deno 1.9.2
- deployctl 0.3.0
使用しているクレートのバージョンは以下。
brotli@3.3.0
js-sys@0.3.50
wasm-bindgen@0.2.73
Deno Deploy
Deno Deploy は、Edge Server で JavaScript や TypeScript、WebAssembly を動かせるサービス。
公式ドキュメントによればExtremely fast
とのこと。
以下のHello World!
スクリプトを見れば分かるように、fetch
イベントを使ってリクエストを制御するという、Service Worker と同様の書き方ができる。
addEventListener("fetch", (event) => { const response = new Response("Hello World!", { headers: { "content-type": "text/plain" }, }); event.respondWith(response); });
ローカルでの開発にはdeployctl
という開発ツールを使う。
インストール方法や使い方は公式ドキュメントに分かりやすくまとまっている。
以下の内容のmod.js
を書き、$ deployctl run --watch mod.js
で動かしてみる。
const html = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Deno Deploy</title> </head> <body> <a href="/json">Show JSON</a> </body> </html> `; addEventListener("fetch", (event) => { const { pathname } = new URL(event.request.url); if (pathname === "/json") { return event.respondWith( new Response(JSON.stringify({ a: 1, b: 2 }), { headers: { "content-type": "application/json; charset=UTF-8" }, }) ); } event.respondWith( new Response(html, { headers: { "content-type": "text/html" }, }) ); });
この状態でhttp://localhost:8080/
にアクセスするとShow JSON
が表示され、それをクリックすると JSON が表示される。
このようにdeployctl
を使うことでローカルで開発できるのだが、後述するようにdeployctl
で動いたからといって本番環境でも動くとは限らないので、注意する。
Rust から WebAssembly を出力する
次に、Rust でコードを書いてそれを WebAssembly で出力する。
Rust で書くことや WebAssembly を動かすことそれ自体が目的なので中身は何でもよいのだが、brotli
による圧縮を行うことにする。
?src=http://example.com
のようにsrc
クエリで URL を指定して、そのリソースをbrotli
で圧縮してクライアントに返す。
実用性は一切考えていない。
以下の内容のsrc/lib.rs
を書いた。
use brotli::CompressorReader; use js_sys::Uint8Array; use std::io::Read; use wasm_bindgen::prelude::*; #[wasm_bindgen] pub extern "C" fn compress_by_brotli(text: &str) -> Uint8Array { let bytes = text.as_bytes(); let mut compressor = CompressorReader::new(bytes, 4096, 6, 20); let mut compressed = Vec::new(); compressor.read_to_end(&mut compressed).unwrap(); js_sys::Uint8Array::from(&compressed[..]) }
これを WebAssembly に変換すると、JavaScript からcompress_by_brotli
を呼び出せるようになる。
この関数に文字列を渡すと、brotli
で圧縮されUint8Array
形式で返ってくる。
変換にはwasm-pack
を使うので、インストールする。
そして$ wasm-pack build --target web
を実行すると、/pkg
ディレクトリに WebAssembly が出力される。
WebAssembly を読み込む
続いて、出力した WebAssembly を Deno で読み込む。
以下のコードで動く。
ファイル名のcompress
の部分はCargo.toml
で指定した[package]
のname
によって決まるので、適宜置き換える。
import init, { compress_by_brotli } from "./pkg/compress.js"; await init(Deno.readFile("./pkg/compress_bg.wasm")); const text = "abc"; console.log(compress_by_brotli(text));
$ deno run --allow-read mod.js Uint8Array(7) [ 7, 1, 128, 97, 98, 99, 3 ]
Uint8Array
が返ってきている。
new Response
の第一引数にはUint8Array
をそのまま渡せるので、HTTP レスポンスとして返すのも難しくない。
WebAssembly の読み込みと利用が成功したのであとは JavaScript を書いていくだけなのだが、ひとつだけ注意点がある。
実はDeno.readFile
は Deno Deploy には存在しないので、使おうとするとエラーになる。
Edge Server なのだからreadFile
がないのは当然のような気がするが、deployctl
では動いていしまうので見落としていた。
本番環境では GitHub に置いたファイルを読み込むようにして、解決した。
if (Deno.env.get("ENVIRONMENT") === "production") { const res = await fetch( "https://raw.githubusercontent.com/numb86/brotli-compression/main/pkg/compress_bg.wasm" ); await init(await res.arrayBuffer()); } else { await init(Deno.readFile("./pkg/compress_bg.wasm")); }
Deno Deploy では環境変数を設定できるので、それで処理を分けている。
コードの全文は以下に置いてある。
デプロイ
案内に従ってデプロイする。上述の環境変数の設定も行っておく。
最後に動作確認。
はてなブックマークの新着記事で試してみる。
まずオリジナルのリソース。
https://b.hatena.ne.jp/site/numb86-tech.hatenablog.com/?mode=rss
gzip
でエンコーディングされており、ファイルサイズは21.8kB
。
次に、Deno Deploy 経由でリソースを取得する。
https://brotli-compression.deno.dev/?src=https://b.hatena.ne.jp/site/numb86-tech.hatenablog.com/?mode=rss
brotli
でエンコーディングされており、ファイルサイズが14.1kB
になっている。