INTEGRITY Dokumentace

RPC (WorkerEntrypoint)

Service bindings umožňují jednomu Workeru volat jiný, aniž by bylo nutné procházet přes veřejně dostupnou URL adresu.

Pomocí Service bindings můžete vytvářet vlastní interní API, která váš Worker zpřístupní jiným Workerům. Toho dosáhnete rozšířením vestavěné WorkerEntrypoint třídy, a přidáním vlastních veřejných metod. Tyto veřejné metody pak mohou přímo volat další Workery na vašem účtu Cloudflare, které deklarují binding tomuto Workeru.

Systém RPC ve Workers je navržena tak, aby se co nejvíce podobala volání funkce JavaScript v rámci stejného Workeru. Ve většině případů byste měli být schopni psát kód stejným způsobem, jako kdyby vše bylo v jediném Workeru.

Příklad

Následující Worker například implementuje veřejnou metodu add(a, b):

Pokud například Worker B implementuje veřejnou metodu add(a, b):

{
	"$schema": "./node_modules/wrangler/config-schema.json",
	"name": "worker_b",
	"main": "./src/workerB.js"
}
"$schema" = "./node_modules/wrangler/config-schema.json"
name = "worker_b"
main = "./src/workerB.js"
import { WorkerEntrypoint } from "cloudflare:workers";

export default class extends WorkerEntrypoint {
	async fetch() {
		return new Response("Hello from Worker B");
	}

	add(a, b) {
		return a + b;
	}
}
import { WorkerEntrypoint } from "cloudflare:workers";

export default class extends WorkerEntrypoint {
	async fetch() {
		return new Response("Hello from Worker B");
	}

	add(a: number, b: number) {
		return a + b;
	}
}
from workers import WorkerEntrypoint, Response

class Default(WorkerEntrypoint):
    async def fetch(self, request):
        return Response("Hello from Worker B")

    def add(self, a: int, b: int) -> int:
        return a + b

Worker A může deklarovat binding k Workeru B:

{
	"$schema": "./node_modules/wrangler/config-schema.json",
	"name": "worker_a",
	"main": "./src/workerA.js",
	"services": [
		{
			"binding": "WORKER_B",
			"service": "worker_b"
		}
	]
}
"$schema" = "./node_modules/wrangler/config-schema.json"
name = "worker_a"
main = "./src/workerA.js"

[[services]]
binding = "WORKER_B"
service = "worker_b"

Jak umožnit, aby Worker A volal add() metodu z Workeru B:

export default {
	async fetch(request, env) {
		const result = await env.WORKER_B.add(1, 2);
		return new Response(result);
	},
};
export default {
	async fetch(request, env) {
		const result = await env.WORKER_B.add(1, 2);
		return new Response(result);
	},
};
from workers import WorkerEntrypoint, Response

class Default(WorkerEntrypoint):
    async def fetch(self, request):
        result = await self.env.WORKER_B.add(1, 2)
        return Response(f"Result: {result}")

Pro použití systému RPC se nemusíte učit žádné speciální protokoly, implementovat je ani nad nimi přemýšlet. Klient, v tomto případě Worker A, zavolá Worker B a řekne mu, aby provedl konkrétní proceduru s konkrétními argumenty, které klient poskytne. Toho se dosahuje pomocí standardních tříd JavaScriptu.

WorkerEntrypoint Třída

Chcete-li ze svého Workeru poskytovat metody RPC, musíte rozšířit WorkerEntrypoint třída, jak ukazuje následující příklad:

import { WorkerEntrypoint } from "cloudflare:workers";

export default class extends WorkerEntrypoint {
  async add(a, b) { return a + b; }
}
from workers import WorkerEntrypoint

class Default(WorkerEntrypoint):
    async def add(self, a, b):
        return a + b

A new instance of the class is created every time the Worker is called. Note that even though the Worker is implemented as a class, it is still stateless, the class instance only lasts for the duration of the invocation. If you need to persist or coordinate state in Workers, you should use Durable Objects.

Bindings (env)

env objekt je zpřístupněn jako vlastnost třídy WorkerEntrypoint třídu.

Například Worker, který deklaruje vazbu k proměnná prostředí GREETING:

{
	"$schema": "./node_modules/wrangler/config-schema.json",
	"name": "my-worker",
	"vars": {
		"GREETING": "Hello"
	}
}
"$schema" = "./node_modules/wrangler/config-schema.json"
name = "my-worker"

[vars]
GREETING = "Hello"

Lze k němu přistupovat voláním this.env.GREETING:

import { WorkerEntrypoint } from "cloudflare:workers";

export default class extends WorkerEntrypoint {
  fetch() { return new Response("Hello from my-worker"); }

  async greet(name) {
    return this.env.GREETING + name;
  }
}
from workers import WorkerEntrypoint, Response

class Default(WorkerEntrypoint):
    async def fetch(self, request):
        return Response("Hello from my-worker")

    async def greet(self, name):
        return self.env.GREETING + name

Můžete použít jakýkoli typ binding tímto způsobem.

Metody životního cyklu (ctx)

ctx objekt je zpřístupněn jako vlastnost třídy WorkerEntrypoint třídu.

Životnost kontextu volání můžete například prodloužit voláním waitUntil() metoda:

import { WorkerEntrypoint } from "cloudflare:workers";

export default class extends WorkerEntrypoint {
  fetch() { return new Response("Hello from my-worker"); }

  async signup(email, name) {
    // sendEvent() will continue running, even after this method returns a value to the caller
    this.ctx.waitUntil(this.#sendEvent("signup", email))
    // Perform any other work
    return "Success";
  }

  async #sendEvent(eventName, email) {
    //...
  }
}
from workers import WorkerEntrypoint, Response

class Default(WorkerEntrypoint):
    async def fetch(self, request):
        return Response("Hello from my-worker")

    async def signup(self, email, name):
        # _send_event() will continue running, even after this method returns a value to the caller
        self.ctx.waitUntil(self._send_event("signup", email))
        # Perform any other work
        return "Success"

    async def _send_event(self, event_name, email):
        # ...
        pass

Načítání statických souborů

Pokud váš Worker má static assets binding, můžete volat this.env.ASSETS.fetch() z metody RPC. Jelikož metody RPC nedostávají request parametru vytvořte Request nebo URL s libovolným hostname: assets binding hostname ignoruje, záleží pouze na pathname:

import { WorkerEntrypoint } from "cloudflare:workers";

export class ImageWorker extends WorkerEntrypoint {
	async getImage(path) {
		return this.env.ASSETS.fetch(new Request(`https://assets.local${path}`));
	}
}
import { WorkerEntrypoint } from "cloudflare:workers";

export class ImageWorker extends WorkerEntrypoint {
  async getImage(path: string): Promise<Response> {
    return this.env.ASSETS.fetch(
      new Request(`https://assets.local${path}`)
    );
  }
}
from workers import WorkerEntrypoint, Request

class ImageWorker(WorkerEntrypoint):
    async def get_image(self, path: str):
        return await self.env.ASSETS.fetch(
            Request.new(f"https://assets.local{path}")
        )

Volající pak může tuto metodu vyvolat přes RPC:

const response = await env.IMAGE_SERVICE.getImage("/images/logo.png");
const response = await env.IMAGE_SERVICE.getImage("/images/logo.png");
response = await self.env.IMAGE_SERVICE.get_image("/images/logo.png")

Pojmenované vstupní body

Můžete také exportovat libovolný počet pojmenovaných WorkerEntrypoint tříd v rámci jednoho Workeru, kromě výchozího exportu. Poté můžete deklarovat Service binding ke konkrétnímu pojmenovanému entrypointu.

Toho můžete využít ke sloučení více výpočetních jednotek dohromady. Můžete si například vytvořit samostatný WorkerEntrypoint pro každou roli oprávnění ve vaší aplikaci a použijte je k poskytnutí RPC metod specifických pro danou roli:

{
	"$schema": "./node_modules/wrangler/config-schema.json",
	"name": "todo-app",
	"d1_databases": [
		{
			"binding": "D1",
			"database_name": "todo-app-db",
			"database_id": "<unique-ID-for-your-database>"
		}
	]
}
"$schema" = "./node_modules/wrangler/config-schema.json"
name = "todo-app"

[[d1_databases]]
binding = "D1"
database_name = "todo-app-db"
database_id = "<unique-ID-for-your-database>"
import { WorkerEntrypoint } from "cloudflare:workers";

export class AdminEntrypoint extends WorkerEntrypoint {
  async createUser(username) {
    await this.env.D1.prepare("INSERT INTO users (username) VALUES (?)")
      .bind(username)
      .run();
  }

  async deleteUser(username) {
    await this.env.D1.prepare("DELETE FROM users WHERE username = ?")
      .bind(username)
      .run();
  }
}

export class UserEntrypoint extends WorkerEntrypoint {
  async getTasks(userId) {
    return await this.env.D1.prepare(
      "SELECT title FROM tasks WHERE user_id = ?"
    )
      .bind(userId)
      .run();
  }

  async createTask(userId, title) {
    await this.env.D1.prepare(
      "INSERT INTO tasks (user_id, title) VALUES (?, ?)"
    )
      .bind(userId, title)
      .run();
  }
}

export default class extends WorkerEntrypoint {
  async fetch(request, env) {
    return new Response("Hello from my to do app");
  }
}
from workers import WorkerEntrypoint, Response

class AdminEntrypoint(WorkerEntrypoint):
    async def create_user(self, username):
        await self.env.D1.prepare("INSERT INTO users (username) VALUES (?)").bind(username).run()

    async def delete_user(self, username):
        await self.env.D1.prepare("DELETE FROM users WHERE username = ?").bind(username).run()

class UserEntrypoint(WorkerEntrypoint):
    async def get_tasks(self, user_id):
        return await self.env.D1.prepare("SELECT title FROM tasks WHERE user_id = ?").bind(user_id).run()

    async def create_task(self, user_id, title):
        await self.env.D1.prepare("INSERT INTO tasks (user_id, title) VALUES (?, ?)").bind(user_id, title).run()

class Default(WorkerEntrypoint):
    async def fetch(self, request):
        return Response("Hello from my to do app")

Poté můžete deklarovat Service binding přímo na AdminEntrypoint v jiném Workeru:

{
	"$schema": "./node_modules/wrangler/config-schema.json",
	"name": "admin-app",
	"services": [
		{
			"binding": "ADMIN",
			"service": "todo-app",
			"entrypoint": "AdminEntrypoint"
		}
	]
}
"$schema" = "./node_modules/wrangler/config-schema.json"
name = "admin-app"

[[services]]
binding = "ADMIN"
service = "todo-app"
entrypoint = "AdminEntrypoint"
export default {
  async fetch(request, env) {
    await env.ADMIN.createUser("aNewUser");
    return new Response("Hello from admin app");
  },
};
from workers import WorkerEntrypoint, Response

class Default(WorkerEntrypoint):
    async def fetch(self, request):
        await self.env.ADMIN.create_user("aNewUser")
        return Response("Hello from admin app")

Více o konfiguraci D1 se dozvíte v Dokumentace D1.

Kompletní příklad této aplikace pro seznam úkolů, stejně jako Discord bota postaveného na pojmenovaných entrypoints, si můžete vyzkoušet naklonováním repozitář cloudflare/js-rpc-and-entrypoints-demo z GitHubu.

Další čtení