[Rust] Cloudflare Workersで画像の加工をやってみる

2022.05.20

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Introduction

最近各所で話題のCloudflare。
Wranglerも2.0になり、worker-rustの
テンプレも動いたので、それを少し変更して画像の加工処理を実装したいと思います。  

Cloudflare Workers?

Cloudflare Workersは、Cloudflare上で実行される
サーバーレスアプリケーションの実行環境です。
AWSで近いサービスを挙げると、AWS Lambda(Lambda@Edge)になるかと思います。

自動スケーリング&Edgeロケーション&コールドスタートなしで起動する、
などの特徴があり、対応言語はJS, Rust, C, C++です。
また、WorkersKV(低レイテンシのKey-Valueデータストア)を使うことで
(SSRやったりとか)Workersの用途が広がります。

Environment

 環境は以下。

  • MacBook Pro (13-inch, M1, 2020)
  • OS : MacOS 11.3.1
  • rust : 1.60.0

Try

今回は、Cloudflare WorkersとRust(workers-rs)を使って
画像のグレースケール処理を実装してみます。
ローカルから画像ファイルをおくるとグレースケールした画像を返します。

CloudflareアカウントやWranglerについてはここにある通り設定して、
worker-rustでgenerateした雛形を使用します。
では実装してみましょう。

依存ライブラリの追加

画像のグレースケール処理をするためにimage crateを使います。
Cargo.tomlのdependenciesに追記。

[dependencies]
cfg-if = "0.1.2"
worker = "0.0.9"
image = "*"

処理の追加

main.rsに/fileリクエスト時の処理を記述します。
ファイルデータをPOSTすると画像を加工して返します。

・・・・・
    router
        .get("/", |_, _| Response::ok("Hello from Workers!"))
        .post_async("/file",|mut req,_ctx| async move {
            console_log!("/file request.");
            if let Some(file) = req.form_data().await?.get("upfile") {
                return match file {
                    FormEntry::File(image_buf) => {
                        let gray_image_buf = grayscale(&(image_buf.bytes().await?));
                        let response = Response::from_bytes(gray_image_buf)?;
                        let mut headers = Headers::new();
                        headers.set("content-type","image/png")?;
                        Ok(response.with_headers(headers))
                    }
                    _ => Response::error("`file` part of POST form must be a file", 400),
                };
            }
            Response::error("Bad Request", 400)
        })
        .get("/worker-version", |_, ctx| {
            let version = ctx.var("WORKERS_RS_VERSION")?.to_string();
            Response::ok(version)
        })
        .run(req, env)
        .await
・・・・・

grayscale関数の中身です。
ファイルデータのbyte配列をimage crateでグレースケールしています。

fn grayscale(image_buf:&Vec<u8>) -> Vec<u8>{
    let base_image = image::load_from_memory(image_buf).expect("error load_from_memory!");
    let gray = base_image.into_luma8();
    let mut result_buf: Vec<u8> = Vec::new();
    gray.write_to(&mut Cursor::new(&mut result_buf), image::ImageOutputFormat::Png).expect("io error");
    result_buf
}

動作確認

wrangler devコマンドを実行してローカルで起動します。

% wrangler dev
 ⛅️ wrangler 2.0.6
-------------------
Running custom build: cargo install -q worker-build && worker-build --release
[INFO]: ?  Checking for the Wasm target...
[INFO]: ?  Compiling to Wasm...
    Finished release [optimized] target(s) in 0.13s
[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 3.66s
[INFO]: ?   Your wasm pkg is ready to publish at /path/your/rust-example/build.
⬣ Listening at http://localhost:8787

適当な画像を用意して、curlで/fileに対してPOSTします。
グレースケール処理が実行され、--outputで指定したファイル名でダウンロードされます。

curl -X POST -F upfile=@/path/your/sample.jpg http://localhost:8787/file --output ./gray-sample.png
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  578k  100  442k  100  136k   758k   234k --:--:-- --:--:-- --:--:--  992k

Cloudflareにpublish

ローカルで動作確認できたらデプロイしてみます。

% wrangler publish --name <Project Name>
 ⛅️ wrangler 2.0.6
-------------------
Running custom build: cargo install -q worker-build && worker-build --release
[INFO]: ?  Checking for the Wasm target...
[INFO]: ?  Compiling to Wasm...
    Finished release [optimized] target(s) in 0.18s
[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 3.81s
[INFO]: ?   Your wasm pkg is ready to publish at /path/your/rust-example/build.
Uploaded rust-example (2.22 sec)
Published rust-example (0.33 sec)
  <Project Name>.<sub domain>.workers.dev

デプロイが成功してURLも表示されました。
こちらに対しても同じようにアクセスしてみます。

% curl -X POST -F upfile=@/path/your/sample.jpg http://<Project Name>.<sub domain>.workers.dev/file --output ./cf-gray-sample.png
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 3489k  100  856k  100 2632k   349k  1073k  0:00:02  0:00:02 --:--:-- 1423k

問題なくアクセスできました。
雛形作成からローカルでの動作確認、デプロイまで簡単です。
Workers&RemixとかCloudflare Workers KVとかもおもしろそうなので、
興味のあるかたはご確認ください。

References