Files
NapCatQQ/packages/napcat-rpc/src/server.ts
手瓜一十雪 ba473d57f0 Cache object properties to avoid extra RPC
Serialize non-function properties of server-side objects as cachedProps so simple property reads don't require additional RPCs. Added cachedProps to SerializedValue, have RpcServer.storeObjectRef serialize and attach cachedProps (skipping functions), updated serializer to deserialize cachedProps and pass them to proxyCreator, and updated client proxy creation to accept cachedProps and return cached top-level properties directly. Tests updated to expect direct property access for serialized/simple objects and arrays.
2026-02-02 18:59:23 +08:00

578 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
type RpcRequest,
type RpcResponse,
type RpcServerOptions,
type SerializedValue,
RpcOperationType,
SerializedValueType,
} from './types.js';
import { serialize, deserialize, SimpleCallbackRegistry } from './serializer.js';
/**
* 生成唯一引用 ID
*/
function generateRefId (): string {
return `ref_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
}
/**
* 默认的代理判断函数
* 判断返回值是否应该保持代理引用(而非完全序列化)
* 策略class 实例和有方法的对象保持代理,普通对象直接序列化
*/
function defaultShouldProxyResult (value: unknown): boolean {
if (value === null || value === undefined) {
return false;
}
if (typeof value !== 'object' && typeof value !== 'function') {
return false;
}
// 函数保持代理
if (typeof value === 'function') {
return true;
}
// 可安全序列化的内置类型不代理
if (value instanceof Date || value instanceof RegExp || value instanceof Error) {
return false;
}
if (value instanceof Map || value instanceof Set) {
return false;
}
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
return false;
}
// 数组不代理
if (Array.isArray(value)) {
return false;
}
// 检查对象原型是否为 Object.prototype普通对象
const proto = Object.getPrototypeOf(value);
if (proto === Object.prototype || proto === null) {
// 普通对象检查是否有方法
const hasMethod = Object.values(value as object).some(v => typeof v === 'function');
return hasMethod;
}
// 非普通对象class 实例)- 保持代理
return true;
}
/**
* RPC 服务端
*
* 处理来自客户端的 RPC 请求,在目标对象上执行操作
*/
export class RpcServer {
private target: unknown;
private callbackInvoker?: (callbackId: string, args: unknown[]) => Promise<unknown>;
private localCallbacks = new SimpleCallbackRegistry();
/** 对象引用存储 */
private objectRefs = new Map<string, unknown>();
/** 代理判断函数 */
private shouldProxyResult: (value: unknown) => boolean;
constructor (options: RpcServerOptions) {
this.target = options.target;
this.callbackInvoker = options.callbackInvoker;
this.shouldProxyResult = options.shouldProxyResult ?? defaultShouldProxyResult;
}
/**
* 处理 RPC 请求
*/
async handleRequest (request: RpcRequest): Promise<RpcResponse> {
try {
switch (request.type) {
case RpcOperationType.GET:
return this.handleGet(request);
case RpcOperationType.SET:
return this.handleSet(request);
case RpcOperationType.APPLY:
return await this.handleApply(request);
case RpcOperationType.CONSTRUCT:
return await this.handleConstruct(request);
case RpcOperationType.HAS:
return this.handleHas(request);
case RpcOperationType.OWNKEYS:
return this.handleOwnKeys(request);
case RpcOperationType.DELETE:
return this.handleDelete(request);
case RpcOperationType.GET_DESCRIPTOR:
return this.handleGetDescriptor(request);
case RpcOperationType.GET_PROTOTYPE:
return this.handleGetPrototype(request);
case RpcOperationType.RELEASE:
return this.handleRelease(request);
default:
return {
id: request.id,
success: false,
error: `Unknown operation type: ${request.type}`,
};
}
} catch (error) {
return this.createErrorResponse(request.id, error);
}
}
/**
* 解析路径获取目标值,支持 refId
*/
private resolvePath (path: PropertyKey[], refId?: string): { parent: unknown; key: PropertyKey | undefined; value: unknown; } {
// 如果有 refId从引用存储中获取根对象
let current = refId ? this.objectRefs.get(refId) : this.target;
if (refId && current === undefined) {
throw new Error(`Object reference not found: ${refId}`);
}
let parent: unknown = null;
let key: PropertyKey | undefined;
for (let i = 0; i < path.length; i++) {
parent = current;
key = path[i];
if (key === undefined) {
throw new Error('Path contains undefined key');
}
if (current === null || current === undefined) {
throw new Error(`Cannot access property '${String(key)}' of ${current}`);
}
current = (current as Record<PropertyKey, unknown>)[key];
}
return { parent, key, value: current };
}
/**
* 存储对象引用并返回序列化的引用
* 同时序列化可序列化的属性值,避免属性访问需要额外 RPC
*/
private storeObjectRef (value: unknown): SerializedValue {
const refId = generateRefId();
this.objectRefs.set(refId, value);
const className = value?.constructor?.name;
// 序列化非函数属性
const cachedProps: Record<string, SerializedValue> = {};
if (value && typeof value === 'object') {
for (const key of Object.keys(value)) {
const propValue = (value as Record<string, unknown>)[key];
// 跳过函数(方法需要远程调用)
if (typeof propValue === 'function') {
continue;
}
// 序列化属性值
try {
cachedProps[key] = serialize(propValue, { callbackRegistry: this.localCallbacks });
} catch {
// 序列化失败的属性跳过,让客户端通过 RPC 获取
}
}
}
return {
type: SerializedValueType.OBJECT_REF,
refId,
className: className !== 'Object' ? className : undefined,
cachedProps: Object.keys(cachedProps).length > 0 ? cachedProps : undefined,
};
}
/**
* 序列化结果值,如果需要代理则存储引用
*/
private serializeResult (value: unknown): { result: SerializedValue; isProxyable: boolean; refId?: string; } {
const shouldProxy = this.shouldProxyResult(value);
if (shouldProxy) {
const ref = this.storeObjectRef(value);
return {
result: ref,
isProxyable: true,
refId: ref.refId,
};
}
return {
result: serialize(value, { callbackRegistry: this.localCallbacks }),
isProxyable: false,
};
}
/**
* 处理 GET 操作
*/
private handleGet (request: RpcRequest): RpcResponse {
const { value } = this.resolvePath(request.path, request.refId);
const { result, isProxyable, refId } = this.serializeResult(value);
return {
id: request.id,
success: true,
result,
isProxyable,
refId,
};
}
/**
* 处理 SET 操作
*/
private handleSet (request: RpcRequest): RpcResponse {
const path = request.path;
if (path.length === 0 && !request.refId) {
throw new Error('Cannot set root object');
}
const parentPath = path.slice(0, -1);
const key = path[path.length - 1]!;
const { value: parent } = this.resolvePath(parentPath, request.refId);
if (parent === null || parent === undefined) {
throw new Error(`Cannot set property '${String(key)}' of ${parent}`);
}
const newValue = request.args?.[0]
? deserialize(request.args[0], {
callbackResolver: this.createCallbackResolver(request),
refResolver: (refId) => this.objectRefs.get(refId),
})
: undefined;
(parent as Record<PropertyKey, unknown>)[key] = newValue;
return {
id: request.id,
success: true,
};
}
/**
* 处理 APPLY 操作
*/
private async handleApply (request: RpcRequest): Promise<RpcResponse> {
const path = request.path;
// 如果有 refId 且 path 为空,说明引用对象本身是函数
if (path.length === 0 && request.refId) {
const func = this.objectRefs.get(request.refId);
if (typeof func !== 'function') {
throw new Error('Referenced object is not callable');
}
const args = (request.args ?? []).map(arg =>
deserialize(arg, {
callbackResolver: this.createCallbackResolver(request),
refResolver: (refId) => this.objectRefs.get(refId),
})
);
let result = func(...args);
if (result instanceof Promise) {
result = await result;
}
const { result: serializedResult, isProxyable, refId } = this.serializeResult(result);
return {
id: request.id,
success: true,
result: serializedResult,
isProxyable,
refId,
};
}
if (path.length === 0) {
throw new Error('Cannot call root object');
}
const methodPath = path.slice(0, -1);
const methodName = path[path.length - 1]!;
const { value: parent } = this.resolvePath(methodPath, request.refId);
if (parent === null || parent === undefined) {
throw new Error(`Cannot call method on ${parent}`);
}
const method = (parent as Record<PropertyKey, unknown>)[methodName];
if (typeof method !== 'function') {
throw new Error(`${String(methodName)} is not a function`);
}
const args = (request.args ?? []).map(arg =>
deserialize(arg, {
callbackResolver: this.createCallbackResolver(request),
refResolver: (refId) => this.objectRefs.get(refId),
})
);
let result = method.call(parent, ...args);
// 处理 Promise
if (result instanceof Promise) {
result = await result;
}
const { result: serializedResult, isProxyable, refId } = this.serializeResult(result);
return {
id: request.id,
success: true,
result: serializedResult,
isProxyable,
refId,
};
}
/**
* 处理 CONSTRUCT 操作
*/
private async handleConstruct (request: RpcRequest): Promise<RpcResponse> {
const { value: Constructor } = this.resolvePath(request.path, request.refId);
if (typeof Constructor !== 'function') {
throw new Error('Target is not a constructor');
}
const args = (request.args ?? []).map(arg =>
deserialize(arg, {
callbackResolver: this.createCallbackResolver(request),
refResolver: (refId) => this.objectRefs.get(refId),
})
);
const instance = new (Constructor as new (...args: unknown[]) => unknown)(...args);
const { result, isProxyable, refId } = this.serializeResult(instance);
return {
id: request.id,
success: true,
result,
isProxyable,
refId,
};
}
/**
* 处理 HAS 操作
*/
private handleHas (request: RpcRequest): RpcResponse {
const path = request.path;
if (path.length === 0) {
return {
id: request.id,
success: true,
result: serialize(true),
};
}
const parentPath = path.slice(0, -1);
const key = path[path.length - 1]!;
const { value: parent } = this.resolvePath(parentPath, request.refId);
const has = parent !== null && parent !== undefined && key in (parent as object);
return {
id: request.id,
success: true,
result: serialize(has),
};
}
/**
* 处理 OWNKEYS 操作
*/
private handleOwnKeys (request: RpcRequest): RpcResponse {
const { value } = this.resolvePath(request.path, request.refId);
if (value === null || value === undefined) {
return {
id: request.id,
success: true,
result: serialize([]),
};
}
const keys = Reflect.ownKeys(value as object);
return {
id: request.id,
success: true,
result: serialize(keys.map(k => (typeof k === 'symbol' ? k.description ?? '' : String(k)))),
};
}
/**
* 处理 DELETE 操作
*/
private handleDelete (request: RpcRequest): RpcResponse {
const path = request.path;
if (path.length === 0 && !request.refId) {
throw new Error('Cannot delete root object');
}
const parentPath = path.slice(0, -1);
const key = path[path.length - 1]!;
const { value: parent } = this.resolvePath(parentPath, request.refId);
if (parent === null || parent === undefined) {
throw new Error(`Cannot delete property from ${parent}`);
}
const deleted = delete (parent as Record<PropertyKey, unknown>)[key];
return {
id: request.id,
success: true,
result: serialize(deleted),
};
}
/**
* 处理 GET_DESCRIPTOR 操作
*/
private handleGetDescriptor (request: RpcRequest): RpcResponse {
const path = request.path;
if (path.length === 0) {
return {
id: request.id,
success: true,
result: serialize(undefined),
};
}
const parentPath = path.slice(0, -1);
const key = path[path.length - 1]!;
const { value: parent } = this.resolvePath(parentPath, request.refId);
if (parent === null || parent === undefined) {
return {
id: request.id,
success: true,
result: serialize(undefined),
};
}
const descriptor = Object.getOwnPropertyDescriptor(parent as object, key);
if (!descriptor) {
return {
id: request.id,
success: true,
result: serialize(undefined),
};
}
// 序列化描述符(排除 value 和 get/set 函数)
return {
id: request.id,
success: true,
result: serialize({
configurable: descriptor.configurable,
enumerable: descriptor.enumerable,
writable: descriptor.writable,
}),
};
}
/**
* 处理 GET_PROTOTYPE 操作
*/
private handleGetPrototype (request: RpcRequest): RpcResponse {
const { value } = this.resolvePath(request.path, request.refId);
if (value === null || value === undefined) {
return {
id: request.id,
success: true,
result: serialize(null),
};
}
const proto = Object.getPrototypeOf(value);
const name = proto?.constructor?.name ?? 'Object';
return {
id: request.id,
success: true,
result: serialize({ name }),
};
}
/**
* 处理 RELEASE 操作
*/
private handleRelease (request: RpcRequest): RpcResponse {
// 如果有 refId释放该引用
if (request.refId) {
this.objectRefs.delete(request.refId);
}
return {
id: request.id,
success: true,
};
}
/**
* 创建回调解析器
*/
private createCallbackResolver (_request: RpcRequest): (id: string) => Function {
return (callbackId: string) => {
// 创建一个代理函数,调用时会通过 callbackInvoker 发送回客户端
return async (...args: unknown[]) => {
if (!this.callbackInvoker) {
throw new Error('Callback invoker not configured');
}
return this.callbackInvoker(callbackId, args);
};
};
}
/**
* 创建错误响应
*/
private createErrorResponse (requestId: string, error: unknown): RpcResponse {
if (error instanceof Error) {
return {
id: requestId,
success: false,
error: error.message,
stack: error.stack,
};
}
return {
id: requestId,
success: false,
error: String(error),
};
}
/**
* 调用客户端回调
*/
async invokeCallback (callbackId: string, args: unknown[]): Promise<SerializedValue> {
if (!this.callbackInvoker) {
throw new Error('Callback invoker not configured');
}
const result = await this.callbackInvoker(callbackId, args);
return serialize(result, { callbackRegistry: this.localCallbacks });
}
}
/**
* 创建 RPC 服务端
*/
export function createRpcServer (options: RpcServerOptions): RpcServer {
return new RpcServer(options);
}