什么是JSON-RPC 2.0,在项目中应该怎么使用

它是什么

JSON-RPC 2.0 是一种超轻量、与传输无关的远程调用协议:用 JSON 表达"方法名 + 参数 → 结果/错误"。可跑在 HTTP、WebSocket、Unix 管道,甚至 stdio 上(很多开发协议如 LSP 就用它)。

报文长这样

• 请求:

bash 复制代码
{"jsonrpc":"2.0","method":"sum","params":[1,2,3],"id":"42"}

params 可是数组(按位置)或对象(按名),id 用来对应响应(字符串/数字均可)。

• 通知(不需要回应):去掉 id

bash 复制代码
{"jsonrpc":"2.0","method":"log","params":{"msg":"hi"}}
复制代码
•	响应(二选一:要么 result,要么 error):
bash 复制代码
{"jsonrpc":"2.0","result":6,"id":"42"}
bash 复制代码
{"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not found"},"id":"42"}
复制代码
•	批量:请求或响应都是数组。

常用错误码:

-32700 解析错误、

-32600 非法请求、

-32601 方法不存在、

-32602 参数错误、

-32603 服务器内部错;

-32000~-32099 留给服务端自定义。

什么时候用(和 REST/gRPC 对比)

• 你想要**"方法调用式"接口、低 ceremony、浏览器/脚本语言友好、支持批量/通知**时 → JSON-RPC 很合适。

• 资源/对象语义清晰、需要缓存/幂等/超媒体 → REST 更自然。

• 强类型/高性能/流式、多语言代码生成 → gRPC 更强。

在 FastAPI 中落地(最小实现,支持批量/通知)

pip install fastapi uvicorn httpx

bash 复制代码
from typing import Any, Dict, Callable, List, Optional
from fastapi import FastAPI, Request, Response
from fastapi.responses import JSONResponse

app = FastAPI()

1) 方法注册表

bash 复制代码
methods: Dict[str, Callable[..., Any]] = {}

def rpc_method(name: str):
    def deco(fn: Callable[..., Any]):
        methods[name] = fn
        return fn
    return deco

@rpc_method("sum")
def sum_(numbers: List[float]) -> float:
    return float(sum(numbers))

@rpc_method("echo")
def echo(msg: str) -> str:
    return msg

2) 协议工具

bash 复制代码
def error(code: int, message: str, _id: Any = None, data: Any = None):
    e = {"code": code, "message": message}
    if data is not None:
        e["data"] = data
    return {"jsonrpc": "2.0", "error": e, "id": _id}

def success(result: Any, _id: Any):
    return {"jsonrpc": "2.0", "result": result, "id": _id}

def handle_one(req: Dict[str, Any]) -> Optional[Dict[str, Any]]:
    # 通知:没有 id -> 不返回
    _id = req.get("id", None)
    is_notification = "id" not in req

    if req.get("jsonrpc") != "2.0" or "method" not in req:
        return None if is_notification else error(-32600, "Invalid Request", _id)

    method = req["method"]
    fn = methods.get(method)
    if not fn:
        return None if is_notification else error(-32601, "Method not found", _id)

    params = req.get("params", [])
    try:
        if isinstance(params, list):
            result = fn(*params)
        elif isinstance(params, dict):
            result = fn(**params)
        else:
            return None if is_notification else error(-32602, "Invalid params", _id)
        return None if is_notification else success(result, _id)
    except TypeError as te:
        return None if is_notification else error(-32602, "Invalid params", _id, str(te))
    except Exception as ex:
        return None if is_notification else error(-32603, "Internal error", _id, str(ex))

@app.post("/rpc")
async def rpc_entry(request: Request):
    try:
        payload = await request.json()
    except Exception:
        # 解析失败:id 必须为 null
        return JSONResponse(error(-32700, "Parse error", None), status_code=200)

    # 批量
    if isinstance(payload, list):
        responses = []
        for item in payload:
            if not isinstance(item, dict):
                responses.append(error(-32600, "Invalid Request", None))
                continue
            r = handle_one(item)
            if r is not None:
                responses.append(r)
        # 纯通知批:不返回 body(204)
        if not responses:
            return Response(status_code=204)
        return JSONResponse(responses, status_code=200)

    # 单个
    if not isinstance(payload, dict):
        return JSONResponse(error(-32600, "Invalid Request", None), status_code=200)

    r = handle_one(payload)
    if r is None:  # 通知
        return Response(status_code=204)
    return JSONResponse(r, status_code=200)

调用示例(客户端)

bash 复制代码
import httpx

resp = httpx.post("http://localhost:8000/rpc", json={
    "jsonrpc":"2.0","method":"sum","params":{"numbers":[1,2,3]},"id":"42"
})
print(resp.json())  # -> {"jsonrpc":"2.0","result":6,"id":"42"}

批量:一个请求 + 一个通知

bash 复制代码
batch = [
  {"jsonrpc":"2.0","method":"echo","params":{"msg":"Hi"},"id":1},
  {"jsonrpc":"2.0","method":"echo","params":{"msg":"No reply"}}   # 通知
]
print(httpx.post("http://localhost:8000/rpc", json=batch).json())

说明:JSON-RPC 对 HTTP 状态码不做规定。上面示例把应用层错误都塞在 200 的响应体里;纯通知返回 204。

最佳实践清单

• 鉴权:用 HTTP 头(如 Authorization: Bearer )或在传输层做认证(mTLS/WebSocket 子协议)。不要把密钥放进 params。

• 类型与校验:为方法参数建 Pydantic 模型;服务端在进入方法前先校验,定位错误更清晰。

• 日志与追踪:记录 method 与 id;链路里加超时与重试(注意幂等)。

• 批量与通知:通知无响应,适合非关键且幂等的操作(如埋点)。批量要考虑部分成功。

• ID 生成:客户端生成可追踪的字符串 ID(UUID/雪花);不要用自增数字以免冲突。

• 传输选择:

• HTTP:简单、易部署。

• WebSocket:天然双向,适合实时推送/订阅。

• stdio/管道:本地进程间协议(很多"工具/智能体"生态走这条)。

现成库(按语言)

• Python:fastapi-jsonrpc、jsonrpcserver、msgspec(自实现时做编解码)

• Node.js:jayson、json-rpc-2.0

• Go:github.com/sourcegraph/jsonrpc2

• Rust:jsonrpsee

• Java:jsonrpc4j

相关推荐
小温冲冲1 天前
QtObject 详解:QML 中的轻量级数据容器
qt
今晚务必早点睡1 天前
系统通信方式实战详解:HTTP、RPC、MQ、WebSocket 各用在什么场景?(附 SDK 示例)
websocket·http·rpc
huwei8531 天前
Q打印表格内容类
开发语言·qt
Quz1 天前
QML 常用的基础容器组件(Pane、Frame、GroupBox、ScrollView 和 Page)
qt·交互
墨月白1 天前
[QT] QT中的折线图和散点图
数据库·qt
REDcker1 天前
AIGCJson 库解析行为与异常处理指南
c++·json·aigc·c
问水っ1 天前
Qt Creator快速入门 第三版 第16-7章 其他内容
开发语言·qt
Tianwen_Burning1 天前
qt控件QVTKOpenGLNativeWidget全窗口显示
qt·pcl·halcon3d
全栈前端老曹1 天前
【包管理】read-pkg-up 快速上手教程 - 读取最近的 package.json 文件
前端·javascript·npm·node.js·json·nrm·package.json
小CC吃豆子1 天前
Qt的信号与槽机制
开发语言·数据库·qt