Cloudflare に Node.js の AsyncLocalStorage がサポートされたので試してみた
西田@CX事業本部です
先日、 Cloudflare WorkesでNods.js の一部のAPIが対応 されました。
その中で AsyncLocalStorage も対応されていたので Cloudflare Workers で実際に試して見ました
AsyncLocalStorage について
AsyncLocalStorage は Node.js が提供している非同期処理中に同時にアクセスしてもメモリセーフに使えるデータストアです。同時にストアにアクセスする可能性があっても安全に使えるグローバルなオブジェクトのように使えます。他の言語でいうところの Thread Local な変数のようなものとして扱えます
具体的な例を挙げるとHTTPのリクエスト情報を、AsyncLocalStorage に保持しておけば、それをどこからでもアクセスでき、関数のバケツリレーで引き回す必要がなくなります。また、リクエストごとに独立したストアとして使え、同時にアクセスされても別のストアとして扱うことができます
Cloudflare Workers で実際に試してみる
実際に Cloudflare Workers で試していきます
今から実装するのは、JSONで構造化されたログにHTTPのリクエスト情報を含めれるようにします。その際に Request オブジェクトを引数で渡さなくても良いようにします
使用イメージは以下です
const log = () => { // Output to Console.log } const funcA = () => { log("aa") // { method: "GET", ur: "http:...", "msg": "aa" ... } } const funcB = () => { log("bb") // { method: "GET", ur: "http:...", "msg": "bb" ... } }
プロジェクトの作成
Cloudflare workers を作成する環境を準備します。 wrangler を使用します
wranger init . -y
全体のソース
Node.js 対応のAPIを使うには compatibility_flagsに [ “nodejs_compat ]
を指定する必要があります。詳しくは こちらのブログ を参考にしてください
wrangler.toml
name = "cloudflare-workers-asynclocalstorage" main = "src/index.ts" compatibility_date = "2023-04-01" compatibility_flags = [ "nodejs_compat" ]
今回の作成した全体のソースです
src/index.ts
import { AsyncLocalStorage } from "node:async_hooks"; type Store = { requestId: string; request: Request; }; const storage = new AsyncLocalStorage<Store>(); const doSomethingWithRequestInfo = () => { const store = storage.getStore(); const headers = store?.request.headers; // Do something with headers in request info console.log(headers); log("do something with request info"); }; const log = (msg: string) => { const store = storage.getStore(); const output = { time: new Date().toISOString(), msg, requestId: store?.requestId, url: store?.request.url, method: store?.request.method, }; console.log(JSON.stringify(output)); }; export default { async fetch( request: Request, env: Env, ctx: ExecutionContext ): Promise<Response> { const requestId = crypto.randomUUID(); storage.run({ request, requestId }, () => { log("request start"); doSomethingWithRequestInfo(); log("request end"); }); return new Response("Hello World!"); }, };
ピックアップしてコードの説明をしていきます
AsyncLocalStorage
を宣言してる箇所です。 AsyncLocalStorage は node のコアモジュールなので、 node:
スキーマをつけてインポートしています。
import { AsyncLocalStorage } from "node:async_hooks";
ストレージに保存するストアの型を宣言し、それを AsyncLocalStorage
に渡しストレージのインスタンスを生成しています。このストレージがどこからでもアクセス可能なストレージになります
type Store = { requestId: string; request: Request; }; const storage = new AsyncLocalStorage<Store>();
Cloudflare Worker のエントリポイントとなる fetch
関数です。この中で先ほど宣言した AsyncLocalStorage
インスタンスの run
メソッドをよんでいます。 この run
メソッドのコールバックの中なら、リクエスト毎に独立したメモリ領域を持つストレージに、どこからでもアクセスすることができます。また、 run
メソッドにリクエストの情報を渡しストレージに格納しています
async fetch( request: Request, env: Env, ctx: ExecutionContext ): Promise<Response> { const requestId = crypto.randomUUID(); storage.run({ request, requestId }, () => { log("request start"); doSomethingWithRequestInfo(); log("request end"); }); return new Response("Hello World!"); },
ログを出力する関数です。この中でAsyncLocalStorage
のインスタンスからリクエストの情報を取り出し、引数で渡されたメッセージにマージして、ログに出力しています
const log = (msg: string) => { const store = storage.getStore(); const output = { msg, requestId: store?.requestId, url: store?.request.url, }; console.log(JSON.stringify(output)); };
デプロイ
Cloudflare にデプロイして動作確認していきます
※ npm run deploy で wranger publish
が起動し Cloudflare Woreker にデプロイできます
npm run deploy
まとめ
AsyncLocalStorage を使っている Node.js の npm パッケージも多いです。今後 Node 特有の機能が増えていけば、 Cloudflare Workersで動くパッケージも増えていくのではないでしょうか
この記事が誰かの参考になれば幸いです