Cloudflare Workers からツイートする
以前に twit と bolt を使ってHerokuで動くものを作ったが、コールドスタートで困っていたので作り直した。 Node.jsのモジュールのpolyfillが不要でWebWorkerでも動くTwitterのライブラリがなさそうだったので、そこから実装した。
後はドキュメントを見ながらビルド環境を整備し、PRを作ってとりあえず動くようにした。
所感
azuさんもブログに書いていたように、WebWorkerでも動くライブラリの選定がとにかく難しい。
ブラウザで動くと書いてあっても、window に依存していたりNode.jsのモジュールのpolyfillが必要だったりがよくある。
(polyfillはやれば動くと思うが、webpack@5 では自分で設定する必要があるしあまりメンテナンスされていない。)
とにかく、zero-dependencyなライブラリを頑張って探すかビルドを工夫しましょうという話になって基本的に大変。
wrangler コマンド自体も発展途上で、デフォルトだと webpack@4 でのビルドが強制されるなど癖が強い。
ただコールドスタートは本当に無なので、要件によってはかなり刺さる頼もしい技術ではあると思う。
また、Deno Deploy などのAPI互換な環境の登場や、WorkerをHTTPサーバーの標準にしようという動きもあり、今後が楽しみ。 2021/4/17 追記
Cloudflare Workersの環境で動作するライブラリの一覧も発表され、ライブラリの選定が楽になった。
また wrangler@1.16 で正式にカスタムビルドがサポートされ、webpack@5 が使えるようになった。
類似の技術について
AWS CloudFront Functions
Fastly Compute@Edge
AWS Lambda@Edge
Vercel Edge Network
内部的には AWS Lambda@Edge を使っている?
Netlify Edge Handlers
内部的には AWS Lambda@Edge を使っている?
Fastly VCL
Varnish をベースにしているらしい
実装の様子
ここからは、実装するにあたって具体的にどこで困ったかについて雑に書いていく。
似たようなことをしたい人や wrangler でデプロイする手順を知りたい人向け。
TwitterのAPIを叩く
Twitterのライブラリはたくさんあるが、そこそこ使われておりブラウザでも動くと書いているのは twitter-lite だけ。 ただしtwitter-liteも1行目から require('crypto') しており、Node.jsのモジュールのpolyfillが必須だったりする。
(OAuthの部分は oauth-1.0a というゼロ依存でNode.jsや window にも非依存なライブラリを使っているので問題なし。) 「所感」でも書いたがNode.jsのモジュールをpolyfillするのは気が進まなかったので、最初はこれをforkして不要な部分を削り、 crypto など必須なモジュールだけAPI互換なライブラリや Web Crypto API による実装で置き換えようと考えていた。 しかし実際には、crypto.createHmac() の代替である create-hmac が他のNode.jsのpolyfillに依存していたり、Web Crypto APIが非同期的で扱いにくかったりといった問題があり、twitter-liteを参考に簡単な実装を自分で書く形になった。 crypto 自体は、crypto-js を resolve: { fallback: { "crypto": false } } な環境で使うことで代替できる。 ちなみに没になったWeb Crypto APIを使った crypto.createHmac(...) に相当するコードはこんな感じ。
code:js
// digest === crypto.createHmac('sha1', key).update(base_string).digest('base64')
const encoder = new TextEncoder();
const digest = await crypto.subtle
.importKey(
"raw",
encoder.encode(key),
{ name: "HMAC", hash: { name: "SHA-256" } },
false,
)
.then((ckey) =>
crypto.subtle
.sign("HMAC", ckey, encoder.encode(base_string))
.then((signature) =>
btoa(String.fromCharCode(...new Uint8Array(signature)))
)
);
twitter-lite以外にはCloudflare Workersのチュートリアルなども参考にして実装した。
またこれはTwitterのAPIに特有の話だが、英数字と -._~ は以外は % でエンコードする必要がある。
encodeURIComponent() と new URLSearchParams() では !'()~ の扱いなどが違うので要注意。
例えば new URLSearchParams() を使ったエンコードはこんな感じになる。
code:ts
const params = { status: "foo -._~%7E+*" };
const body = ${new URLSearchParams(params)}
.replace(/%7E/g, "~")
.replace(/\+/g, "%20")
.replace(/\*/g, "%2A");
console.log(body); // status=foo%20-._~%257E%2B%2A
デプロイする
Cloudflare Workersのガイドに公開までのおよその手順が載っているので、まずはそれを読むとよい。
TSで書く場合は、テンプレートと同様に ts-loader を挟み、@cloudflare/workers-types の設定もする。 wrangler secret put で変数を設定するなら、その変数の型を declare global { ... } で宣言しておく必要もある。
なお request.text() などの body を得るメソッドは、実装の関係で2度呼ぶと例外を吐くので気を付ける。
流れとしてはおよそ以下のような感じ。
code:sh
$ yarn add -D @cloudflare/wrangler typescript webpack{,-cli} ts-loader
$ npx wrangler init
# 実装を用意して webpack や wrangler の設定などをする #
$ npx wrangler login
$ npx wrangler whoami # wrangler.toml の account_id を埋める
$ npx wrangler publish
$ npx wrangler secret put TWITTER_CONSUMER_KEY
$ ...