この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
西田@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で動くパッケージも増えていくのではないでしょうか
この記事が誰かの参考になれば幸いです