RPC (Remote Procedure Call) в веб-разработке обычно реализуется поверх HTTP и JSON, где клиент отправляет запрос с описанием вызываемой процедуры и параметров, а сервер возвращает результат выполнения или ошибку. На практике распространён формат JSON-RPC 2.0, в котором сообщение имеет строго определённую структуру:
jsonrpc — версия протоколаmethod — имя вызываемой процедурыparams — параметры вызоваid — идентификатор запроса для сопоставления
ответаПример запроса:
{
"jsonrpc": "2.0",
"method": "user.create",
"params": {
"name": "Ivan",
"age": 30
},
"id": 1
}
Ответ:
{
"jsonrpc": "2.0",
"result": {
"userId": 42
},
"id": 1
}
Ошибка:
{
"jsonrpc": "2.0",
"error": {
"code": -32602,
"message": "Invalid params"
},
"id": 1
}
Строгость структуры делает RPC удобным кандидатом для валидации через JSON Schema и библиотеку Ajv.
Ajv (Another JSON Schema Validator) обеспечивает проверку JSON-данных на соответствие JSON Schema. В контексте RPC это позволяет:
Основная идея заключается в том, что каждая часть RPC-протокола описывается схемой, а Ajv выполняет компиляцию и проверку данных.
Схема запроса может быть описана следующим образом:
const requestSchema = {
type: "object",
additionalProperties: false,
required: ["jsonrpc", "method", "params", "id"],
properties: {
jsonrpc: {
type: "string",
const: "2.0"
},
method: {
type: "string",
minLength: 1
},
params: {
type: "object"
},
id: {
type: ["string", "number", "null"]
}
}
};
Компиляция схемы в Ajv:
import Ajv from "ajv";
const ajv = new Ajv();
const validateRequest = ajv.compile(requestSchema);
Проверка входящего сообщения:
const isValid = validateRequest(data);
if (!isValid) {
console.log(validateRequest.errors);
}
RPC-методы имеют разные сигнатуры, поэтому универсальная схема
params часто заменяется на специфические схемы для каждого
метода.
Пример схемы для метода user.create:
const createUserParamsSchema = {
type: "object",
additionalProperties: false,
required: ["name", "age"],
properties: {
name: {
type: "string",
minLength: 1
},
age: {
type: "integer",
minimum: 0
}
}
};
Далее создаётся реестр схем по методам:
const methodSchemas = {
"user.create": ajv.compile(createUserParamsSchema)
};
Использование при обработке RPC:
function validateMethod(method, params) {
const validate = methodSchemas[method];
if (!validate) {
return { valid: false, error: "Unknown method" };
}
const valid = validate(params);
return {
valid,
errors: validate.errors
};
}
Хотя чаще валидируются входящие данные, контроль исходящих ответов повышает стабильность API.
Схема успешного ответа:
const successResponseSchema = {
type: "object",
required: ["jsonrpc", "result", "id"],
additionalProperties: false,
properties: {
jsonrpc: { const: "2.0" },
result: {},
id: {
type: ["string", "number", "null"]
}
}
};
Схема ошибки:
const errorResponseSchema = {
type: "object",
required: ["jsonrpc", "error", "id"],
additionalProperties: false,
properties: {
jsonrpc: { const: "2.0" },
error: {
type: "object",
required: ["code", "message"],
properties: {
code: { type: "integer" },
message: { type: "string" },
data: {}
},
additionalProperties: true
},
id: {
type: ["string", "number", "null"]
}
}
};
JSON-RPC поддерживает пакетные вызовы — массив запросов в одном HTTP-запросе.
Схема batch:
const batchSchema = {
type: "array",
items: requestSchema,
minItems: 1
};
Проверка:
const validateBatch = ajv.compile(batchSchema);
const isValidBatch = validateBatch(batch);
if (!isValidBatch) {
console.log(validateBatch.errors);
}
При обработке batch важно валидировать каждый элемент отдельно после общей проверки массива.
При росте RPC API фиксированные схемы становятся неудобными. Используется динамическая регистрация:
const registry = new Map();
function registerMethod(name, schema) {
registry.set(name, ajv.compile(schema));
}
function validateRpc(method, params) {
const validator = registry.get(method);
if (!validator) {
return { valid: false, error: "Method not found" };
}
const valid = validator(params);
return { valid, errors: validator.errors };
}
Такой подход позволяет масштабировать RPC без изменения ядра валидации.
Ajv поддерживает строгую проверку схем, которая полезна для RPC:
const ajv = new Ajv({
allErrors: true,
strict: true
});
Строгий режим выявляет:
Это снижает вероятность логических ошибок в контракте RPC.
Ajv возвращает массив ошибок, который требует преобразования в RPC-формат:
function formatAjvErrors(errors) {
return errors.map(err => ({
path: err.instancePath,
message: err.message,
keyword: err.keyword
}));
}
Пример интеграции:
if (!valid) {
return {
jsonrpc: "2.0",
error: {
code: -32602,
message: "Invalid params",
data: formatAjvErrors(validate.errors)
},
id
};
}
В серверных приложениях часто используется промежуточный слой:
async function rpcMiddleware(req, res) {
const { method, params, id } = req.body;
const validation = validateRpc(method, params);
if (!validation.valid) {
res.json({
jsonrpc: "2.0",
error: {
code: -32602,
message: validation.error || "Invalid params",
data: validation.errors
},
id
});
return;
}
const result = await handlers[method](params);
res.json({
jsonrpc: "2.0",
result,
id
});
}
Такой слой отделяет бизнес-логику от проверки структуры данных.
Ajv поддерживает сложные конструкции JSON Schema, которые полезны для RPC:
const paramsSchema = {
oneOf: [
{
type: "object",
required: ["email"],
properties: {
email: { type: "string" }
}
},
{
type: "object",
required: ["phone"],
properties: {
phone: { type: "string" }
}
}
]
};
const schema = {
type: "object",
properties: {
token: { type: "string" },
refreshToken: { type: "string" }
},
dependentRequired: {
refreshToken: ["token"]
}
};
При больших системах схемы разбиваются на модули:
const baseRequest = {
type: "object",
required: ["jsonrpc", "method", "id"],
properties: {
jsonrpc: { const: "2.0" },
method: { type: "string" },
id: {}
}
};
const fullRequest = {
allOf: [
baseRequest,
{
properties: {
params: { type: "object" }
}
}
]
};
Такой подход снижает дублирование и упрощает сопровождение RPC-протокола.
В RPC важно ограничивать входные данные:
additionalProperties: false
Это предотвращает:
В некоторых случаях допускается мягкий режим:
additionalProperties: true
но он снижает строгость API и усложняет контроль версий.
Ajv компилирует схемы в функции, что делает проверку высокопроизводительной. Ключевые оптимизации:
Типичный поток:
При использовании TypeScript схема Ajv часто становится источником правды:
Это позволяет поддерживать строгий контракт между сторонами RPC без дублирования логики валидации.