← Cloudflare Workers / workers / runtime-apis / bindings / service-bindings
RPC (WorkerEntrypoint)
Service bindings позволяют одному Worker обращаться к другому напрямую, без публично доступного URL.
С помощью Service bindings можно создавать собственные внутренние API, которые ваш Worker предоставляет другим Workers. Для этого можно расширить встроенный WorkerEntrypoint класс, и добавьте собственные публичные методы. Затем эти публичные методы смогут напрямую вызывать другие Workers в вашем аккаунте Cloudflare, которые объявляют привязка этому Worker.
Система RPC в Workers спроектирован так, чтобы ощущаться максимально похожим на вызов обычной функции JavaScript в том же Worker. В большинстве случаев вы можете писать код так же, как если бы всё находилось в одном Worker.
Пример
Например, следующий Worker реализует публичный метод add(a, b):
Например, если Worker B реализует публичный метод 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 + bWorker A может объявить привязка к Worker 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"Как дать Worker A возможность вызывать add() метод из Worker 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}")Чтобы использовать систему RPC, не нужно изучать, реализовывать или продумывать какие-либо специальные протоколы. Клиент, в данном случае Worker A, вызывает Worker B и указывает ему выполнить определённую процедуру с определёнными аргументами, которые передаёт клиент. Всё это реализуется с помощью стандартных классов JavaScript.
WorkerEntrypoint Класс
Чтобы предоставлять RPC-методы из своего Worker, нужно расширить WorkerEntrypoint класс, как показано в примере ниже:
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 + bA 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.
Привязки (env)
env объект предоставляется как свойство класса WorkerEntrypoint в качестве класса.
Например, Worker, объявляющий привязку к переменная окружения 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"Можно обратиться к нему, вызвав 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Можно использовать любой тип привязка таким образом.
Методы жизненного цикла (ctx)
ctx объект предоставляется как свойство класса WorkerEntrypoint в качестве класса.
Например, можно продлить время жизни контекста вызова, вызвав waitUntil() метод:
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Получение статических ресурсов
Если у вашего Worker есть привязка статических ресурсов, вы можете вызвать this.env.ASSETS.fetch() изнутри метода RPC. Поскольку методы RPC не получают request параметр, создайте Request или URL-адрес с произвольным именем хоста: имя хоста игнорируется привязкой assets, важен только 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}")
)После этого вызывающая сторона может вызвать этот метод через 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")Именованные точки входа
Также можно экспортировать любое количество именованных WorkerEntrypoint классы внутри одного Worker в дополнение к экспорту по умолчанию. Затем вы можете объявить Service binding для конкретной именованной точки входа.
Это можно использовать, чтобы объединить несколько вычислительных единиц. Например, можно создать отдельный WorkerEntrypoint для каждой роли доступа в вашем приложении и используйте их для реализации RPC методов, привязанных к конкретной роли:
{
"$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")Затем можно объявить Service binding напрямую к AdminEntrypoint в другом Worker:
{
"$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")Подробнее о настройке D1 можно узнать в Документация D1.
Полный пример этого todo-приложения, а также Discord-бота с именованными entrypoints, можно попробовать, склонировав репозиторий cloudflare/js-rpc-and-entrypoints-demo ↗ от GitHub.