@codingame/json-rpc-2.0

中文文档
1.7.0-next.1发布于 2024/11/27MITTypeScriptES Module零依赖近 30 天下载 2794

JSON-RPC 2.0 客户端和服务器

中文使用文档
文档目录10 节
  1. 安装
  2. 示例
  3. 服务器
  4. 客户端
  5. 双向 JSON-RPC
  6. 处理错误
  7. 高级 API
  8. 输入客户端和服务器
  9. 构建
  10. 测试

json - rpc - 2.0 (韩语)

让您的客户端和服务器在函数调用下聊天 JSON- RPC 2. 0 spec。 。 。 .

  • 协议不可知
    • 使用 HTTP, WebSocket, TCP, UDP, 进程间, 任意
      • 例如,从HTTP到WebSocket的简单迁移
  • 无外部依赖关系
    • 保持您的包装小
    • 远离依赖地狱
  • 在浏览器和节点中工作
  • 第一等类型脚本支持

安装

npm install --save json-rpc-2.0

示例

例子使用HTTP进行通信协议,但可以是任何东西.

服务器

const express = require("express");
const bodyParser = require("body-parser");
const { JSONRPCServer } = require("json-rpc-2.0");

const server = new JSONRPCServer();

// First parameter is a method name.
// Second parameter is a method itself.
// A method takes JSON-RPC params and returns a result.
// It can also return a promise of the result.
server.addMethod("echo", ({ text }) => text);
server.addMethod("log", ({ message }) => console.log(message));

const app = express();
app.use(bodyParser.json());

app.post("/json-rpc", (req, res) => {
  const jsonRPCRequest = req.body;
  // server.receive takes a JSON-RPC request and returns a promise of a JSON-RPC response.
  // It can also receive an array of requests, in which case it may return an array of responses.
  // Alternatively, you can use server.receiveJSON, which takes JSON string as is (in this case req.body).
  server.receive(jsonRPCRequest).then((jsonRPCResponse) => {
    if (jsonRPCResponse) {
      res.json(jsonRPCResponse);
    } else {
      // If response is absent, it was a JSON-RPC notification method.
      // Respond with no content status (204).
      res.sendStatus(204);
    }
  });
});

app.listen(80);

有认证

要将认证钩入 API,请输入自定义参数 :

const server = new JSONRPCServer();

// The method can also take a custom parameter as the second parameter.
// Use this to inject whatever information that method needs outside the regular JSON-RPC request.
server.addMethod("echo", ({ text }, { userID }) => `${userID} said ${text}`);

app.post("/json-rpc", (req, res) => {
  const jsonRPCRequest = req.body;
  const userID = getUserID(req);

  // server.receive takes an optional second parameter.
  // The parameter will be injected to the JSON-RPC method as the second parameter.
  server.receive(jsonRPCRequest, { userID }).then((jsonRPCResponse) => {
    if (jsonRPCResponse) {
      res.json(jsonRPCResponse);
    } else {
      res.sendStatus(204);
    }
  });
});

const getUserID = (req) => {
  // Do whatever to get user ID out of the request
};

中间软件

使用中间软件拦截请求和响应 :

const server = new JSONRPCServer();

// next will call the next middleware
const logMiddleware = (next, request, serverParams) => {
  console.log(`Received ${JSON.stringify(request)}`);
  return next(request, serverParams).then((response) => {
    console.log(`Responding ${JSON.stringify(response)}`);
    return response;
  });
};

const exceptionMiddleware = async (next, request, serverParams) => {
  try {
    return await next(request, serverParams);
  } catch (error) {
    if (error.code) {
      return createJSONRPCErrorResponse(request.id, error.code, error.message);
    } else {
      throw error;
    }
  }
};

// Middleware will be called in the same order they are applied
server.applyMiddleware(logMiddleware, exceptionMiddleware);

构造选项

选择时,可以将选项传递给 JSONRPCServer 构造器 :

new JSONRPCServer({
  errorListener: (message: string, data: unknown): void => {
    // Listen to error here. By default, it will use console.warn to log errors.
  },
});

客户端

import { JSONRPCClient } from "json-rpc-2.0";

// JSONRPCClient needs to know how to send a JSON-RPC request.
// Tell it by passing a function to its constructor. The function must take a JSON-RPC request and send it.
const client = new JSONRPCClient((jsonRPCRequest) =>
  fetch("http://localhost/json-rpc", {
    method: "POST",
    headers: {
      "content-type": "application/json",
    },
    body: JSON.stringify(jsonRPCRequest),
  }).then((response) => {
    if (response.status === 200) {
      // Use client.receive when you received a JSON-RPC response.
      return response
        .json()
        .then((jsonRPCResponse) => client.receive(jsonRPCResponse));
    } else if (jsonRPCRequest.id !== undefined) {
      return Promise.reject(new Error(response.statusText));
    }
  })
);

// Use client.request to make a JSON-RPC request call.
// The function returns a promise of the result.
client
  .request("echo", { text: "Hello, World!" })
  .then((result) => console.log(result));

// Use client.notify to make a JSON-RPC notification call.
// By definition, JSON-RPC notification does not respond.
client.notify("log", { message: "Hello, World!" });

有认证

碞钩 JSONRPCServer,您可以向 JSONRPCClient 还有:

const client = new JSONRPCClient(
  // It can also take a custom parameter as the second parameter.
  (jsonRPCRequest, { token }) =>
    fetch("http://localhost/json-rpc", {
      method: "POST",
      headers: {
        "content-type": "application/json",
        authorization: `Bearer ${token}`, // Use the passed token
      },
      body: JSON.stringify(jsonRPCRequest),
    }).then((response) => {
      // ...
    })
);

// Pass the custom params as the third argument.
client.request("echo", { text: "Hello, World!" }, { token: "foo's token" });
client.notify("log", { message: "Hello, World!" }, { token: "foo's token" });

超时

有时你不愿无限期地等待回应. 你用得着 timeout 在某些延迟后自动失败请求:

const client = new JSONRPCClient(/* ... */);

client
  .timeout(10 * 1000) // Automatically fails if it didn't get a response within 10 sec
  .request("echo", { text: "Hello, World!" });

// Create a custom error response
const createTimeoutJSONRPCErrorResponse = (
  id: JSONRPCID
): JSONRPCErrorResponse =>
  createJSONRPCErrorResponse(id, 123, "Custom error message");

client
  .timeout(10 * 1000, createTimeoutJSONRPCErrorResponse)
  .request("echo", { text: "Hello, World!" });

双向 JSON-RPC

双向JSON-RPC,使用 JSONRPCServerAndClient。 。 。 .

const webSocket = new WebSocket("ws://localhost");

const serverAndClient = new JSONRPCServerAndClient(
  new JSONRPCServer(),
  new JSONRPCClient((request) => {
    try {
      webSocket.send(JSON.stringify(request));
      return Promise.resolve();
    } catch (error) {
      return Promise.reject(error);
    }
  })
);

webSocket.onmessage = (event) => {
  serverAndClient.receiveAndSend(JSON.parse(event.data.toString()));
};

// On close, make sure to reject all the pending requests to prevent hanging.
webSocket.onclose = (event) => {
  serverAndClient.rejectAllPendingRequests(
    `Connection is closed (${event.reason}).`
  );
};

serverAndClient.addMethod("echo", ({ text }) => text);

serverAndClient
  .request("add", { x: 1, y: 2 })
  .then((result) => console.log(`1 + 2 = ${result}`));

构造选项

选择时,可以将选项传递给 JSONRPCServerAndClient 构造器 :

new JSONRPCServerAndClient(server, client, {
  errorListener: (message: string, data: unknown): void => {
    // Listen to error here. By default, it will use console.warn to log errors.
  },
});

处理错误

回答错误时,用 Error在客户一方,承诺将被拒绝 Error 对象为同一信件.

server.addMethod("fail", () =>
  Promise.reject(new Error("This is an error message."))
);

client.request("fail").then(
  () => console.log("This does not get called"),
  (error) => console.error(error.message) // Outputs "This is an error message."
);

如果您想要返回自定义错误响应, 请使用 JSONRPCErrorException数字 :

import { JSONRPCErrorException } from "json-rpc-2.0";

const server = new JSONRPCServer();

server.addMethod("throws", () => {
  const errorCode = 123;
  const errorData = {
    foo: "bar",
  };

  throw new JSONRPCErrorException(
    "A human readable error message",
    errorCode,
    errorData
  );
});

或者,使用 高级 API 或执行 mapErrorToJSONRPCErrorResponse数字 :

import {
  createJSONRPCErrorResponse,
  JSONRPCErrorResponse,
  JSONRPCID,
  JSONRPCServer,
} from "json-rpc-2.0";

const server = new JSONRPCServer();

server.mapErrorToJSONRPCErrorResponse = (
  id: JSONRPCID,
  error: any
): JSONRPCErrorResponse => {
  return createJSONRPCErrorResponse(
    id,
    error?.code || 0,
    error?.message || "An unexpected error occurred",
    // Optional 4th argument. It maps to error.data of the response.
    { foo: "bar" }
  );
};

高级 API

使用高级API处理原始JSON-RPC消息.

服务器

import { JSONRPC, JSONRPCResponse, JSONRPCServer } from "json-rpc-2.0";

const server = new JSONRPCServer();

// Advanced method takes a raw JSON-RPC request and returns a raw JSON-RPC response
server.addMethodAdvanced(
  "doSomething",
  (jsonRPCRequest: JSONRPCRequest): PromiseLike<JSONRPCResponse> => {
    if (isValid(jsonRPCRequest.params)) {
      return {
        jsonrpc: JSONRPC,
        id: jsonRPCRequest.id,
        result: "Params are valid",
      };
    } else {
      return {
        jsonrpc: JSONRPC,
        id: jsonRPCRequest.id,
        error: {
          code: -100,
          message: "Params are invalid",
          data: jsonRPCRequest.params,
        },
      };
    }
  }
);
// You can remove the added method if needed
server.removeMethod("doSomething");

客户端

import {
  JSONRPC,
  JSONRPCClient,
  JSONRPCRequest,
  JSONRPCResponse,
} from "json-rpc-2.0";

const send = () => {
  // ...
};
let nextID: number = 0;
const createID = () => nextID++;

// To avoid conflict ID between basic and advanced method request, inject a custom ID factory function.
const client = new JSONRPCClient(send, createID);

const jsonRPCRequest: JSONRPCRequest = {
  jsonrpc: JSONRPC,
  id: createID(),
  method: "doSomething",
  params: {
    foo: "foo",
    bar: "bar",
  },
};

// Advanced method takes a raw JSON-RPC request and returns a raw JSON-RPC response
// It can also send an array of requests, in which case it returns an array of responses.
client
  .requestAdvanced(jsonRPCRequest)
  .then((jsonRPCResponse: JSONRPCResponse) => {
    if (jsonRPCResponse.error) {
      console.log(
        `Received an error with code ${jsonRPCResponse.error.code} and message ${jsonRPCResponse.error.message}`
      );
    } else {
      doSomethingWithResult(jsonRPCResponse.result);
    }
  });

输入客户端和服务器

强打字 request 财务报告和审定财务报表 addMethod 方法、用途 TypedJSONRPCClient, (中文(简体) ), TypedJSONRPCServer 财务报告和审定财务报表 TypedJSONRPCServerAndClient 接口.

import {
  JSONRPCClient,
  JSONRPCServer,
  JSONRPCServerAndClient,
  TypedJSONRPCClient,
  TypedJSONRPCServer,
  TypedJSONRPCServerAndClient,
} from "json-rpc-2.0";

// Use `type` instead of `interface`. In TypeScript, interface can be exnteded,
// so the type checker considers the possibility of the interface being extended,
// resulting in an error.
// Reference: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces
type Methods = {
  echo(params: { message: string }): string;
  sum(params: { x: number; y: number }): number;
};

const server: TypedJSONRPCServer<Methods> = new JSONRPCServer(/* ... */);
const client: TypedJSONRPCClient<Methods> = new JSONRPCClient(/* ... */);

// Types are infered from the Methods type
server.addMethod("echo", ({ message }) => message);
server.addMethod("sum", ({ x, y }) => x + y);
// These result in type error
// server.addMethod("ech0", ({ message }) => message); // typo in method name
// server.addMethod("echo", ({ messagE }) => messagE); // typo in param name
// server.addMethod("echo", ({ message }) => 123); // return type must be string

client
  .request("echo", { message: "hello" })
  .then((result) => console.log(result));
client.request("sum", { x: 1, y: 2 }).then((result) => console.log(result));
// These result in type error
// client.request("ech0", { message: "hello" }); // typo in method name
// client.request("echo", { messagE: "hello" }); // typo in param name
// client.request("echo", { message: 123 }); // message param must be string
// client
//   .request("echo", { message: "hello" })
//   .then((result: number) => console.log(result)); // return type must be string

// The same rule applies to TypedJSONRPCServerAndClient
type ServerAMethods = {
  echo(params: { message: string }): string;
};

type ServerBMethods = {
  sum(params: { x: number; y: number }): number;
};

const serverAndClientA: TypedJSONRPCServerAndClient<
  ServerAMethods,
  ServerBMethods
> = new JSONRPCServerAndClient(/* ... */);
const serverAndClientB: TypedJSONRPCServerAndClient<
  ServerBMethods,
  ServerAMethods
> = new JSONRPCServerAndClient(/* ... */);

serverAndClientA.addMethod("echo", ({ message }) => message);
serverAndClientB.addMethod("sum", ({ x, y }) => x + y);

serverAndClientA
  .request("sum", { x: 1, y: 2 })
  .then((result) => console.log(result));
serverAndClientB
  .request("echo", { message: "hello" })
  .then((result) => console.log(result));

构建

npm run build

测试

npm test

版本与发布记录

@codingame/json-rpc-2.0 共发布 1 个版本,首次发布于 2024/11/27;过去 12 个月发布了 0 个版本。

版本发布日期距今
1.7.0-next.1latest2024/11/271.9 年前

关于 @codingame/json-rpc-2.0 的常见问题

@codingame/json-rpc-2.0 是什么?

JSON-RPC 2.0 客户端和服务器

如何安装 @codingame/json-rpc-2.0?

在项目目录运行 npm install @codingame/json-rpc-2.0,或使用 pnpm add / yarn add / bun add。

@codingame/json-rpc-2.0 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@codingame/json-rpc-2.0 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@codingame/json-rpc-2.0 可以商用吗?

@codingame/json-rpc-2.0 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@codingame/json-rpc-2.0 的下载量有多少?

根据 npm 官方下载统计,@codingame/json-rpc-2.0 在 2026/10/10 – 2026/11/08 这 30 天内被下载 2,794 次,日均约 93 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@codingame/json-rpc-2.0 还在维护吗?

最新版本 1.7.0-next.1 发布于 2024/11/27(1.9 年前);至今共发布 1 个版本,其中过去 12 个月发布了 0 个;首次发布于 2024/11/27。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。