Cloudflare WorkersでRustがサポートされた


## 概要

Cloudflare WorkersでRustがサポートされたようです。

Native Rust support on Cloudflare Workers
https://blog.cloudflare.com/workers-rust-sdk/

早速試してみました。

## Cloudflareに登録

https://workers.cloudflare.com/ からSign upする。

:略:

お、dashboardのCreate a Workerのところに `Rust` の文字が！:eyes:

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120222/8bdbd927-b928-9d55-d4d6-b63add603d8a.png)

が、CLIでworkerを作成するので、このボタンは押さない。

## API keyを取得する

https://dash.cloudflare.com/profile/api-tokens

「Create Token」ボタンを押す
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120222/c721a9c5-697d-cf7a-17b7-5cbb1a011381.png)

「Edit Cloudflare Workers」の「Use template」ボタンを押す
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120222/f5c8ffe0-9898-07b6-decf-17e0e23f7c58.png)

細かい権限の設定ができるみたいだけど、用意されているままにして必須の項目をテキトーに入力してkeyを発行しました。
keyはメモっておく。

## `wrangler` というCLIのツールをインストールしてprojectを生成する

Cloudflare Workersを操作するCLIのツールらしいです。
https://github.com/cloudflare/wrangler

install

```
$ npm i @cloudflare/wrangler -g
```

version

```
$ wrangler -V                                                                                                                                           
wrangler 1.19.2
```

API keyをセットする。

```
$ wrangler config
```
Enter API Token:と出るので、メモっておいたAPI keyを入力する。
こんな感じの出力で上手く設定できたっぽい。

```
💁  Validating credentials...
✨  Successfully configured. You can find your configuration file at: /home/mukai/.wrangler/config/default.toml
```



`--type`にrustを指定してprojectを生成する。

```
$ wrangler generate --type=rust my-first-project                                                                                                         
 Creating project called `my-first-project`...
 Done! New project created /home/mukai/my-first-project
⚠️  The template recommends the "javascript" type. Using type "rust" may cause errors, we recommend changing the type field in wrangler.toml to "javascript"
```

Rustの一式が生成された。

```
$ cd my-first-project
$ ls
CODE_OF_CONDUCT.md  Cargo.lock  Cargo.toml  LICENSE_APACHE  README.md  src  wrangler.toml
$
```


`src/lib.srs` に `main` があった。
ここにコードを書いていけばよさそう。
自動生成されたコードはこうなっていた。

```rust:src/lib.rs
use serde_json::json;
use worker::*;

mod utils;

fn log_request(req: &Request) {
    console_log!(
        "{} - [{}], located at: {:?}, within: {}",
        Date::now().to_string(),
        req.path(),
        req.cf().coordinates().unwrap_or_default(),
        req.cf().region().unwrap_or("unknown region".into())
    );
}

#[event(fetch)]
pub async fn main(req: Request, env: Env) -> Result<Response> {
    log_request(&req);

    // Optionally, get more helpful error messages written to the console in the case of a panic.
    utils::set_panic_hook();

    // Optionally, use the Router to handle matching endpoints, use ":name" placeholders, or "*name"
    // catch-alls to match on specific patterns. The Router takes some data with its `new` method
    // that can be shared throughout all routes. If you don't need any shared data, use `()`.
    let router = Router::new(());

    // Add as many routes as your Worker needs! Each route will get a `Request` for handling HTTP
    // functionality and a `RouteContext` which you can use to  and get route parameters and
    // Enviornment bindings like KV Stores, Durable Objects, Secrets, and Variables.
    router
        .post_async("/form/:field", |mut req, ctx| async move {
            if let Some(name) = ctx.param("field") {
                let form = req.form_data().await?;
                match form.get(name) {
                    Some(FormEntry::Field(value)) => {
                        return Response::from_json(&json!({ name: value }))
                    }
                    Some(FormEntry::File(_)) => {
                        return Response::error("`field` param in form shouldn't be a File", 422);
                    }
                    None => return Response::error("Bad Request", 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
}
```

## buildする

とりあえず生成されたコードには何も手を加えずにdeployまでしたい。
buildして

```
$ wrangler build
🌀  Running cargo install -q worker-build && worker-build --release
Installing wasm-pack...
    Updating crates.io index
  Downloaded wasm-pack v0.10.1
  Downloaded 1 crate (420.1 KB) in 0.67s
  Installing wasm-pack v0.10.1
  Downloaded human-panic v1.0.3
 :略:
   Compiling quote v1.0.9
error: failed to compile `wasm-pack v0.10.1`, intermediate artifacts can be found at `/tmp/cargo-installz3WVFF`

Caused by:
  failed to run custom build command for `openssl-sys v0.9.66`

Caused by:
  process didn't exit successfully: `/tmp/cargo-installz3WVFF/release/build/openssl-sys-38fd3389ac96e4ad/build-script-main` (exit status: 101)
  --- stdout
  cargo:rustc-cfg=const_fn
  cargo:rerun-if-env-changed=X86_64_UNKNOWN_LINUX_GNU_OPENSSL_NO_VENDOR
  X86_64_UNKNOWN_LINUX_GNU_OPENSSL_NO_VENDOR unset
  cargo:rerun-if-env-changed=OPENSSL_NO_VENDOR
  OPENSSL_NO_VENDOR unset
  CC_x86_64-unknown-linux-gnu = None
  CC_x86_64_unknown_linux_gnu = None
  HOST_CC = None
  CC = None
  CFLAGS_x86_64-unknown-linux-gnu = None
  CFLAGS_x86_64_unknown_linux_gnu = None
  HOST_CFLAGS = None
  CFLAGS = None
  CRATE_CC_NO_DEFAULTS = None
  DEBUG = Some("false")
  CARGO_CFG_TARGET_FEATURE = Some("fxsr,sse,sse2")
  running "perl" "./Configure" "--prefix=/tmp/cargo-installz3WVFF/release/build/openssl-sys-abc3acc08e9a4eff/out/openssl-build/install" "no-dso" "no-shared" "no-ssl3" "no-unit-test" "no-comp" "no-zlib" "no-zlib-dynamic" "no-md2" "no-rc5" "no-weak-ssl-ciphers" "no-camellia" "no-idea" "no-seed" "linux-x86_64" "-O2" "-ffunction-sections" "-fdata-sections" "-fPIC" "-m64"
  Configuring OpenSSL version 1.1.1l (0x101010cfL) for linux-x86_64
  Using os-specific seed configuration
  Creating configdata.pm
  Creating Makefile

  **********************************************************************
  ***                                                                ***
  ***   OpenSSL has been successfully configured                     ***
  ***                                                                ***
  ***   If you encounter a problem while building, please open an    ***
  ***   issue on GitHub <https://github.com/openssl/openssl/issues>  ***
  ***   and include the output from the following command:           ***
  ***                                                                ***
  ***       perl configdata.pm --dump                                ***
  ***                                                                ***
  ***   (If you are new to OpenSSL, you might want to consult the    ***
  ***   'Troubleshooting' section in the INSTALL file first)         ***
  ***                                                                ***
  **********************************************************************
  running "make" "depend"

  --- stderr
  thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" }', /home/mukai/.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-src-111.16.0+1.1.1l/src/lib.rs:477:39
  note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Error: installation of wasm-pack exited with status exit status: 101
Error: Build failed! Status Code: 1
$
```

こけよる:sob:
エラーメッセージでググったところ、Ubuntuでは `build-essential` を入れたら解決したという情報が:pray:
https://zenn.dev/dozo/articles/ed2e846847af5d

CentOSだったのでDevelopment Toolsを入れてみる。

```
$ sudo yum groupinstall 'Development Tools'
```

私の環境では `openssl-devel` が既にインストールされていましたが、多分これも必要ですね。
(https://github.com/cloudflare/workers-rs に依存パッケージの説明などがありません)

依存パッケージが揃っていればbuildに成功した。

```
$ wrangler build
🌀  Running cargo install -q worker-build && worker-build --release
Installing wasm-pack...
    Updating crates.io index
  Installing wasm-pack v0.10.1
 :略:
   Compiling my-first-project v0.1.0 (/home/mukai/my-first-project)
    Finished release [optimized] target(s) in 2m 07s
[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 2m 15s
[INFO]: :-) Your wasm pkg is ready to publish at /home/mukai/my-first-project/build.
✨  Build completed successfully!
```

手元で動かすコマンドはこう

```
$ wrangler dev
🌀  Running 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.05s
[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 2.25s
[INFO]: :-) Your wasm pkg is ready to publish at /home/mukai/my-first-project/build.
👂  Listening on http://127.0.0.1:8787
```

curlで叩けるよ。

```
$ curl "http://127.0.0.1:8787/worker-version"
0.0.4
```

## deploy

```
$ wrangler publish
🌀  Running 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.05s
[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 2.26s
[INFO]: :-) Your wasm pkg is ready to publish at /home/mukai/my-first-project/build.
✨  Build completed successfully!
✨  Successfully published your script to
 https://my-first-project.muk-ai.workers.dev
```

これでもう動いているので、ブラウザで開けば表示できた。
https://my-first-project.muk-ai.workers.dev/worker-version

`/form/:field` はPOSTしたデータをjsonで返してくるので動的なページであることがわかる。

```
$ curl -X POST "https://my-first-project.muk-ai.workers.dev/form/hoge" -F 'hoge=this is post data'
{"hoge":"this is post data"}
```

dashboardにdeployしたプロジェクトが表示された。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120222/efed4195-1ead-b733-4df0-aa6839342215.png)


dashboardでは処理したリクエストの回数などが確認できる。
10万req/dayまで無料です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120222/1f2ffa0b-07dc-8d41-7ff9-de6a5c351676.png)

あと無料プランだと処理の実行時間は10msというリミットがあるようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120222/9a52d9a7-6a84-d44d-bbda-1dfc2c82d75f.png)


## 感想

え？ wasmをJavaScriptから呼び出さなくていいのめちゃくちゃ良いですね