動的ディスパッチ Worker は、受信リクエストをディスパッチ名前空間内の適切なユーザー Worker へ送る、専用のルーティング Worker です。Workers Routes を使う代わりに、ディスパッチ Worker ではコードでリクエストルーティングをプログラム制御できます。
- スケール: 数百万のホスト名へのリクエストを異なる Worker へルーティングできます。それぞれに Workers Routes の設定を定義する必要はありません。
- カスタムルーティングロジック: リクエストのルーティング方法をコードで正確に決められます。例:
- ホスト名と Worker の対応を Workers KV に保存し、動的に参照する
- サブドメイン、パス、ヘッダー、その他のリクエストプロパティに基づいてルーティングする
- カスタムホスト名 に付いた カスタムメタデータ をルーティング判断に使う
- プラットフォーム機能の追加: ルーティング層で追加機能を構築できます。
- リクエストがユーザー Worker に到達する前に認証チェックを実行する
- 受信リクエストからヘッダーやメタデータを削除または追加する
- ユーザー ID やアカウント情報などの有用なコンテキストを付ける
- 必要に応じてリクエストまたは応答を変換する
動的ディスパッチ Worker が名前空間内の Worker へリクエストを動的にルーティングできるようにするには、ディスパッチ名前空間 バインディング を設定する必要があります。このバインディングにより、動的ディスパッチ Worker は env.dispatcher.get() でその名前空間内の任意のユーザー Worker を呼び出せます。
{
"dispatch_namespaces": [
{
"binding": "DISPATCHER",
"namespace": "my-dispatch-namespace"
}
]
}[[dispatch_namespaces]]
binding = "DISPATCHER"
namespace = "my-dispatch-namespace"バインディングを設定すると、動的ディスパッチ Worker は名前空間内の任意の Worker へリクエストをルーティングできます。以下は、ディスパッチャーに実装できる一般的なルーティングパターンです。
ディスパッチ Worker がユーザー Worker を呼び出すとき、その呼び出しに追加の値を送れます。たとえば、ディスパッチ Worker はリクエストを認証し、結果のユーザー ID、権限、またはアカウント情報をユーザー Worker に送れます。
コンテキストを受信リクエストから直接取るのではなく、プラットフォームコードから渡したい場合に便利です。このコンテキストを送るには、env.DISPATCHER.get() の第 2 引数の props に値を追加します。
ディスパッチ Worker では、ディスパッチ名前空間からユーザー Worker を取得するときにユーザー ID と権限を渡します。
export default {
async fetch(request, env) {
const userId = "user-123";
const permissions = ["read", "write"];
const userWorker = env.DISPATCHER.get("user-worker", {
props: { userId, permissions },
});
return userWorker.fetch(request);
},
};export default {
async fetch(request, env): Promise<Response> {
const userId = "user-123";
const permissions = ["read", "write"];
const userWorker = env.DISPATCHER.get("user-worker", {
props: { userId, permissions },
});
return userWorker.fetch(request);
},
} satisfies ExportedHandler<Cloudflare.Env>;ユーザー Worker では、これらの値を ctx.props 経由で受け取ります。WorkerEntrypoint では、this.ctx.props 経由でアクセスします。
import { WorkerEntrypoint } from "cloudflare:workers";
export default class UserWorker extends WorkerEntrypoint {
async fetch(_request) {
const { userId, permissions } = this.ctx.props;
return Response.json({ userId, permissions });
}
}import { WorkerEntrypoint } from "cloudflare:workers";
interface UserWorkerProps {
userId: string;
permissions: string[];
}
export default class UserWorker extends WorkerEntrypoint<
Cloudflare.Env,
UserWorkerProps
> {
async fetch(_request: Request): Promise<Response> {
const { userId, permissions } = this.ctx.props;
return Response.json({ userId, permissions });
}
}これらのデータ値はユーザーコードから見えます。ディスパッチ Worker は、ユーザー Worker を変更または再デプロイせずに、呼び出しごとに異なる props を選べます。
ユーザー Worker に、ディスパッチ Worker からのすべてのコンテキストへの直接アクセスを与えたくない場合があります。たとえば、認証データはプラットフォームが管理し、ユーザーコードからは隠すべきです。
この場合、データを直接渡すのではなく capability(能力)を渡します。capability はユーザー Worker が呼べる特定のメソッドを公開し、背後のデータ、資格情報、リソースはディスパッチ Worker に残ります。Workers は capability を RPC stub として渡し、メソッド呼び出しをディスパッチ Worker へ転送します。
capability を作るには、ディスパッチ Worker から WorkerEntrypoint クラスをエクスポートします。ctx.exports オブジェクトにより、ディスパッチ Worker はそのエクスポートしたクラスの RPC stub を作成し、ユーザー Worker に渡せます。
次の例はこのパターンを示します。ディスパッチ Worker はサイト ID とビジター ID を使い、Connector capability を作成します。それらの ID を別データとして渡さずに、props 経由で capability をユーザー Worker に渡します。ユーザー Worker は Connector が公開するメソッドを呼べます。
ディスパッチ Worker では、Connector メソッドを定義し、サイト ID とビジター ID でコネクターを設定し、ユーザー Worker に渡します。
import { WorkerEntrypoint } from "cloudflare:workers";
export class Connector extends WorkerEntrypoint {
async invoke() {
return `${this.ctx.props.siteId}:${this.ctx.props.visitorId}`;
}
}
export default {
async fetch(request, env, ctx) {
const siteId = "site-123";
const visitorId = "visitor-456";
const connector = ctx.exports.Connector({
// These props configure the Connector stub.
// The user Worker cannot read them directly.
props: { siteId, visitorId },
});
const userWorker = env.DISPATCHER.get("user-worker", {
// The user Worker receives these values through ctx.props.
props: {
CONNECTOR: connector,
},
});
return userWorker.fetch(request);
},
};import { WorkerEntrypoint } from "cloudflare:workers";
interface ConnectorProps {
siteId: string;
visitorId: string;
}
export class Connector extends WorkerEntrypoint<
Cloudflare.Env,
ConnectorProps
> {
async invoke(): Promise<string> {
return `${this.ctx.props.siteId}:${this.ctx.props.visitorId}`;
}
}
export default {
async fetch(request, env, ctx): Promise<Response> {
const siteId = "site-123";
const visitorId = "visitor-456";
const connector = ctx.exports.Connector({
// These props configure the Connector stub.
// The user Worker cannot read them directly.
props: { siteId, visitorId },
});
const userWorker = env.DISPATCHER.get("user-worker", {
// The user Worker receives these values through ctx.props.
props: {
CONNECTOR: connector,
},
});
return userWorker.fetch(request);
},
} satisfies ExportedHandler<Cloudflare.Env>;ユーザー Worker では、this.ctx.props 経由で capability を受け取り、公開メソッドを呼びます。
import { WorkerEntrypoint } from "cloudflare:workers";
export default class UserWorker extends WorkerEntrypoint {
async fetch(_request) {
return Response.json({
connector: await this.ctx.props.CONNECTOR.invoke(),
});
}
}import { WorkerEntrypoint } from "cloudflare:workers";
interface Connector {
invoke(): Promise<string>;
}
interface UserWorkerProps {
CONNECTOR: Connector;
}
export default class UserWorker extends WorkerEntrypoint<
Cloudflare.Env,
UserWorkerProps
> {
async fetch(_request: Request): Promise<Response> {
return Response.json({
connector: await this.ctx.props.CONNECTOR.invoke(),
});
}
}ディスパッチ Worker から Outbound Worker へデータを送るには、まずディスパッチ名前空間バインディングでパラメーター名を宣言します。
{
"dispatch_namespaces": [
{
"binding": "DISPATCHER",
"namespace": "my-dispatch-namespace",
"outbound": {
"service": "outbound-worker",
"parameters": ["requestContext"]
}
}
]
}[[dispatch_namespaces]]
binding = "DISPATCHER"
namespace = "my-dispatch-namespace"
[dispatch_namespaces.outbound]
service = "outbound-worker"
parameters = [ "requestContext" ]ディスパッチ Worker では、同じ名前の値を env.DISPATCHER.get() の第 3 引数の outbound オプション経由で渡します。
export default {
async fetch(request, env) {
const userId = "user-123";
const userWorker = env.DISPATCHER.get(
"user-worker",
{
props: { userId },
},
{
outbound: {
requestContext: {
userId,
requestId: crypto.randomUUID(),
},
},
},
);
return userWorker.fetch(request);
},
};export default {
async fetch(request, env): Promise<Response> {
const userId = "user-123";
const userWorker = env.DISPATCHER.get(
"user-worker",
{
props: { userId },
},
{
outbound: {
requestContext: {
userId,
requestId: crypto.randomUUID(),
},
},
},
);
return userWorker.fetch(request);
},
} satisfies ExportedHandler<Cloudflare.Env>;Outbound Worker では、値を環境バインディングとしてアクセスします。
export default {
async fetch(request, env) {
console.log(env.requestContext.userId, env.requestContext.requestId);
return fetch(request);
},
};interface Env {
requestContext: {
userId: string;
requestId: string;
};
}
export default {
async fetch(request, env): Promise<Response> {
console.log(env.requestContext.userId, env.requestContext.requestId);
return fetch(request);
},
} satisfies ExportedHandler<Env>;Outbound Worker のパラメーターは JSON 値に対応します。ユーザー Worker に渡す第 2 引数の props とは別です。
ルーティングの対応を Workers KV に保存します。動的ディスパッチ Worker を変更または再デプロイせずに、ルーティングロジックを変更できます。
export default {
async fetch(request, env) {
try {
const url = new URL(request.url);
// Use hostname, path, or any combination as the routing key
const routingKey = url.hostname;
// Lookup user Worker name from KV store
const userWorkerName = await env.USER_ROUTING.get(routingKey);
if (!userWorkerName) {
return new Response("Route not configured", { status: 404 });
}
// Optional: Cache the KV lookup result
const userWorker = env.DISPATCHER.get(userWorkerName);
return await userWorker.fetch(request);
} catch (e) {
if (e.message.startsWith("Worker not found")) {
return new Response("", { status: 404 });
}
return new Response(e.message, { status: 500 });
}
},
};サブドメインを対応する Worker へルーティングします。たとえば、my-customer.example.com はディスパッチ名前空間内の my-customer という Worker へルーティングします。
export default {
async fetch(request, env) {
try {
// Extract user Worker name from subdomain
// Example: customer1.example.com -> customer1
const url = new URL(request.url);
const userWorkerName = url.hostname.split(".")[0];
// Get user Worker from dispatch namespace
const userWorker = env.DISPATCHER.get(userWorkerName);
return await userWorker.fetch(request);
} catch (e) {
if (e.message.startsWith("Worker not found")) {
// User Worker doesn't exist in dispatch namespace
return new Response("", { status: 404 });
}
// Could be any other exception from fetch() or from the dispatched Worker
return new Response(e.message, { status: 500 });
}
},
};URL パスを対応する Worker へルーティングします。たとえば、example.com/customer-1 はディスパッチ名前空間内の customer-1 という Worker へルーティングします。
export default {
async fetch(request, env) {
try {
const url = new URL(request.url);
const pathParts = url.pathname.split("/").filter(Boolean);
if (pathParts.length === 0) {
return new Response("Invalid path", { status: 400 });
}
// example.com/customer-1 -> routes to 'customer-1' worker
const userWorkerName = pathParts[0];
const userWorker = env.DISPATCHER.get(userWorkerName);
return await userWorker.fetch(request);
} catch (e) {
if (e.message.startsWith("Worker not found")) {
return new Response("", { status: 404 });
}
return new Response(e.message, { status: 500 });
}
},
};