このページでは、Worker Loader バインディング API を説明します。バインディングは env.LOADER として 設定済み であるとします。
env.LOADER.load(code WorkerCode) WorkerStub
指定した WorkerCode から Worker を読み込み、その Worker を呼び出せる WorkerStub を返します。
get() と違い、load() は ID でキャッシュしません。呼び出しごとに新しい Worker を作ります。
コードが毎回新しい場合(使い捨てで AI が生成したツール呼び出しなど)は、load() を使います。
env.LOADER.get(id string, getCodeCallback () => Promise<WorkerCode>): WorkerStub
指定した ID の Worker を読み込み、その Worker を呼び出せる WorkerStub を返します。
便宜上、ローダーはアイソレートのキャッシュを実装しています。新しい ID を初めて受け取ると、新しいアイソレートを読み込みます。そのアイソレートは、しばらくメモリ上でウォームな状態に保たれることがあります。あとから同じ ID でローダーを呼ぶと、新しいアイソレートを作らず、既存のものを返すことがあります。ただし保証はありません。同じ ID でも、あとからゼロから新しいアイソレートを開始する場合があります。
システムが新しいアイソレートの開始が必要と判断し、コードのキャッシュもない場合、codeCallback を呼び出して Worker のコードを取得します。これは非同期コールバックなので、必要ならアプリケーションはリモートストレージからコードを読み込めます。コールバックは WorkerCode オブジェクト(後述)を返します。
キャッシュがあるため、同じ ID で呼ばれたときは、コールバックが常にまったく同じ内容を返すようにしてください。内容が少しでも変わる場合は、新しい ID を使う必要があります。内容が変わっていない場合は、キャッシュを活かすために同じ ID を再利用するのがよいです。WorkerCode が毎回違う場合は、ランダムな ID を渡せます。
たとえば、<worker-name>:<version-number> の形式の ID を使い、コードが変わるたびにバージョン番号を増やせます。あるいは、コードと設定のハッシュから ID を計算し、変更があれば新しい ID になるようにもできます。
get() は WorkerStub を返します。これを使って読み込んだ Worker にリクエストを送れます。スタブは同期的に返される点に注意してください。await する必要はありません。Worker がまだ読み込まれていなければ、スタブへのリクエストは Worker の読み込み完了を待ってから配信されます。読み込みに失敗すると、リクエストは例外を投げます。
2 つのリクエストが同じアイソレートに届く保証はありません。同じ WorkerStub で複数回リクエストしても、別のアイソレートで実行されることがあります。loader.get() に渡したコールバックは、何回呼ばれてもおかしくありません(2 回以上呼ばれることはまれです)。
これは、Worker を表すために getCodeCallback が返す構造です。
Worker の compatibility date です。Wrangler 設定ファイルの compatibility_date と同じ意味です。
compatibility date を補う compatibility flags の任意のリストです。Wrangler 設定ファイルの compatibility_flags と同じ意味です。
true の場合、compatibilityFlags で実験的な compatibility flags を許可します。これを設定するには、ローダーを呼ぶ Worker 自身に compatibility flag "experimental" が必要です。実験的フラグは本番では有効にできません。
Worker のメインモジュール名です。modules に列挙したモジュールのいずれかである必要があります。
モジュール名を文字列の内容に対応付ける辞書オブジェクトです。モジュール内容がプレーンな文字列の場合、モジュール名には種類を示す拡張子(.js または .py)が必要です。
モジュールの内容はオブジェクトとしても指定でき、名前とは独立して種類を指定できます。使えるオブジェクトは次のとおりです。
{js: string}: JavaScript モジュール。import / export は ES modules 構文です。{cjs: string}: CommonJS モジュール。import はrequire()構文です。{py: string}: Python モジュール。下記の警告を参照してください。{text: string}: import 可能な文字列値です。{data: ArrayBuffer}: import 可能なArrayBuffer値です。{wasm: ArrayBuffer}: コンパイル済みの WebAssembly(Wasm)モジュールです。{json: object}: import 可能なオブジェクトです。値は JSON にシリアライズできる必要があります。ただし、値はパース済みオブジェクトとして渡され、パース済みオブジェクトとして届きます。どちらの側も、実際の JSON シリアライズは見えません。
動的 Worker がネットワークにアクセスできるかを制御します。グローバルの fetch() と connect()(それぞれ HTTP リクエストと TCP 接続)は、Worker を隔離するためにブロックまたはリダイレクトできます。
globalOutbound を指定しない場合、デフォルトは親のネットワークアクセスを継承します。通常、動的 Worker はパブリックインターネットへフルアクセスできます。
globalOutbound が null の場合、動的 Worker はネットワークから完全に遮断されます。fetch() と connect() はどちらも例外を投げます。
globalOutbound には任意の Service Binding も設定できます。親 Worker の env にある Service Binding や、ctx.exports のループバックバインディング も含みます。
ctx.exports を使うと、返される ctx.props の値を設定して、特定のサンドボックス向けにバインディングをさらにカスタマイズできるため便利です。props には、リクエストを出した動的 Worker を識別する情報を含められます。
例:
import { WorkerEntrypoint } from "cloudflare:workers";
export class Greeter extends WorkerEntrypoint {
fetch(request) {
return new Response(`Hello, ${this.ctx.props.name}!`);
}
}
export default {
async fetch(request, env, ctx) {
let worker = env.LOADER.get("alice", () => {
return {
// Redirect the worker's global outbound to send all requests
// to the `Greeter` class, filling in `ctx.props.name` with
// the name "Alice", so that it always responds "Hello, Alice!".
globalOutbound: ctx.exports.Greeter({ props: { name: "Alice" } }),
// ... code ...
};
});
return worker.getEntrypoint().fetch(request);
},
};動的 Worker に渡す環境オブジェクトです。
これを使って、Worker にカスタムバインディングを渡せます。
env はシリアライズされ、動的 Worker へ渡されます。そこでは env の値としてそのまま使われます。含められるものは次のとおりです。
2 つ目が、カスタムバインディングを作る要点です。RPC API を実装した WorkerEntrypoint クラス を定義し、Service Binding として動的 Worker に渡すと、任意の API を持つバインディングを定義できます。
さらに、ctx.exports のループバックバインディングを使うと、上の globalOutbound と同様に ctx.props を設定して、特定の動的 Worker 向けにバインディングをカスタマイズできます。
import { WorkerEntrypoint } from "cloudflare:workers";
// Implement a binding which can be called by the dynamic Worker.
export class Greeter extends WorkerEntrypoint {
greet() {
return `Hello, ${this.ctx.props.name}!`;
}
}
export default {
async fetch(request, env, ctx) {
let worker = env.LOADER.get("alice", () => {
return {
env: {
// Provide a binding which has a method greet() which can be called
// to receive a greeting. The binding knows the Worker's name.
GREETER: ctx.exports.Greeter({ props: { name: "Alice" } }),
},
// ... code ...
};
});
return worker.getEntrypoint().fetch(request);
},
};動的に読み込んだ Worker の実行に関するコンソールログ、エラー、その他の詳細を監視する Tail Workers を 1 つ以上指定できます。動的に読み込んだ Worker へのリクエストが完了すると、tail イベントが Tail Worker に届きます。いつものように、Tail Worker は親 Worker の別エントリポイントとして実装し、ctx.exports で参照できます。
import { WorkerEntrypoint } from "cloudflare:workers";
export default {
async fetch(request, env, ctx) {
let worker = env.LOADER.get("alice", () => {
return {
// Send logs, errors, etc. to `LogTailer`. We pass `name` in the
// `ctx.props` so that `LogTailer` knows what generated the logs.
// (You can pass anything you want in `props`.)
tails: [ctx.exports.LogTailer({ props: { name: "alice" } })],
// ... code ...
};
});
return worker.getEntrypoint().fetch(request);
},
};
export class LogTailer extends WorkerEntrypoint {
async tail(events) {
let name = this.ctx.props.name;
// Send the logs off to our log endpoint, specifying the worker name in
// the URL.
//
// Note that `events` will always be an array of size 1 in this scenario,
// describing the event delivered to the dynamically-loaded Worker.
await fetch(`https://example.com/submit-logs/${name}`, {
method: "POST",
body: JSON.stringify(events),
});
}
}