Cell - RPC 组件
Cell 应用的前端和后端之间,或 Cell 后端之间的类型化远程调用:JSON-RPC 2.0 基于 HTTP 或单条 WebSocket 连接传输,两端使用相同的 TypeScript 接口。WebSocket 上后端也可调用前端。指南:前端与 RPC。
npm install @celljs/rpc// src/common/greeting-protocol.ts
export const GreetingServer = Symbol('GreetingServer');
export interface GreetingServer {
greet(name: string): Promise<string>;
}
// src/node/greeting-server.ts
import { Rpc } from '@celljs/rpc';
@Rpc(GreetingServer)
export class GreetingServerImpl implements GreetingServer {
async greet(name: string) { return `Hello, ${name}`; }
}
// 前端(React 组件)
import { useRpc } from '@celljs/react';
const greetingServer = useRpc<GreetingServer>(GreetingServer);
const text = await greetingServer.greet('web');服务
@Rpc(id) 将类注册为组件(取 ComponentOption 如 @Component)并在 POST /<rpc.path>/<id> 提供服务,默认 /api/GreetingServer(rpc.path: api,在 server.path 下)。请求体是 JSON-RPC 请求({ "jsonrpc": "2.0", "id": 1, "method": "greet", "params": ["web"] })、批量请求(数组),或通知(无 id,返回 204)。只有该类及其父类声明的方法可被调用,constructor 和 Object 的方法不行。参数如果用类标注,当 @celljs/validation 已安装时会被转换和校验。
错误的返回方式与 HTTP 错误相同:ApplicationError 保持其代码、消息和数据;4xx HttpError 保持消息;任何其他错误仅在生产环境返回 Internal Server Error(web.errors.exposeMessages),并被记录。堆栈绝不离开服务器。调用方得到 RpcError,其中 code(JSON-RPC)、status(HTTP)和导向调用的堆栈,或 ApplicationError 本身。
客户端
| 位置 | 如何使用 |
|---|---|
| 同一应用的 React 组件 | @celljs/react 的 useRpc<T>(Id):端点是页面的源 |
| 任何组件(前端或后端) | @Autorpc(Id) protected readonly service: T |
| 任何地方,显式 URL,基于 HTTP | RpcUtil.create<T>('https://host/api', Id) |
服务的端点从以下解析:
rpc:
endpoint: # 按服务 id; 总是 HTTP
GreetingServer: "http://greetings:3000/api/GreetingServer"
defaultEndpoint: "http://gateway:3000{rpcPath}/{serviceIdentifier}"{rpcPath} 和 {serviceIdentifier} 被替换。如果都没有,浏览器使用 location;后端如果没有它们会抛 EndpointNotFoundError。
WebSocket
rpc:
transport: websocket # 默认 http之后代理无需自己的 rpc.endpoint 就能在一条 WebSocket 连接上调用服务,在 /<rpc.path>/ws(方法是 <service>/<method>)。每个调用都经过中间件(如 HTTP 调用),使用连接升级请求的请求头和 Cookie,所以会话和 @celljs/security 适用。当无法建立连接时(旧浏览器、不支持 WebSocket 的平台如无服务器预设、放弃升级的代理),调用会通过 HTTP 进行,客户端在稍后重试;断开的连接会用 RpcError 代码 -32003 回应其待处理的调用(不会重新发送:它们可能已运行)。
后端可通过调用的连接或任何打开的连接调用前端:
// src/common/notice-protocol.ts
export const NoticeClient = Symbol('NoticeClient');
export interface NoticeClient {
notify(text: string): Promise<void>;
}
// src/browser/notice-client.ts (由 src/browser/module.ts 导入)
import { RpcClient } from '@celljs/rpc';
@RpcClient(NoticeClient)
export class NoticeClientImpl implements NoticeClient {
async notify(text: string) { /* 显示它 */ }
}
// src/node/order-server.ts
import { Autowired } from '@celljs/core';
import { Rpc } from '@celljs/rpc';
import { RpcConnectionManager, RpcContext } from '@celljs/rpc/lib/node';
@Rpc(OrderServer)
export class OrderServerImpl implements OrderServer {
@Autowired(RpcConnectionManager)
protected readonly connections: RpcConnectionManager;
async watch(): Promise<void> {
const connection = RpcContext.requireConnection(); // 调用方的连接
connection.onClose(() => { /* 停止监看 */ });
await connection.client<NoticeClient>(NoticeClient).notify('watching');
}
async announce(text: string): Promise<void> {
await Promise.all(this.connections.connections.map(c => c.client<NoticeClient>(NoticeClient).notify(text)));
}
}使用 @RpcClient 组件,前端在启动时打开连接,断开时重新打开。RpcContext.connection() 对 HTTP 调用未定义;requireConnection() 则抛 RpcNoConnectionError(400,CELL_RPC_NO_CONNECTION)。对客户端的调用在 rpc.websocket.callTimeout 后超时;关闭的连接拒绝它们。
| 键 | 默认 | |
|---|---|---|
rpc.websocket.allowedOrigins | 除了服务器自己之外、其页面可以连接的源,或 *。浏览器从任何页面的 WebSocket 请求都会发送服务器的 Cookie,所以其他源被拒(403) | |
rpc.websocket.maxPayload | 1048576 | 最大消息字节数;更大的关闭连接(1009) |
rpc.websocket.heartbeat | 30000 | 毫秒,服务器 ping 的间隔;不回应的客户端被放弃 |
rpc.websocket.callTimeout | 30000 | 毫秒,调用等待其响应的时间 |
rpc.websocket.reconnectDelay | 1000 | 毫秒,客户端重试前的延迟;加倍,最多 30 秒 |
rpc.websocket.endpoint | 页面的源 | ws:// / wss:// URL 连接到;后端调用另一后端的 WebSocket 时需要 |
服务端是包的一部分(无依赖);它需要一个运行时,其 node:http 交付升级:Node.js、Deno 和 Bun 1.4 或更新版本(较旧 Bun 版本记录警告,客户端使用 HTTP)。
一个 RpcWebSocketGuard 组件(allows(request): boolean)在升级请求上决定连接是否可打开,在任何东西运行前;拒绝的连接得 401。所有守卫必须同意。
批处理
基于 HTTP,rpc.merge.enabled: true 让代理在 timerDelay 内发起的调用(35 毫秒)在一个请求中发送(JSON-RPC 批处理),最多 maxCount 个调用或 maxLength 字节。