Skip to content

通信协议 ​

Nginx UI 与插件进程通过进程的标准输入和标准输出交换 JSON-RPC 2.0 消息。双方都会发送请求:Nginx UI 调用生命周期和能力方法,插件调用宿主 API。插件还可以通过 gRPC 提供能力调用,这在负载较大时更快,而访问日志推送必须使用 gRPC。

提示

SDK 实现了本页的全部内容。不使用 SDK 编写插件,或者想了解底层细节时,请阅读本页。

消息帧 ​

  • Nginx UI 写入插件的标准输入,插件写入自己的标准输出。
  • 每条消息是一行上的一个 JSON 对象,后跟一个换行符。消息内部永远不含原始换行符。
  • 标准输出只能用于协议消息。 日志、横幅和堆栈跟踪都写到标准错误,Nginx UI 会把它收集为插件日志,但从不解析它。
  • 不支持批量消息(JSON 数组)。
  • 一条消息包括换行符最多 4 MiB,更大的消息会终止连接。

消息 ​

每条消息都包含 "jsonrpc": "2.0",其他成员决定它的类型:

类型idmethodparamsresulterror
请求存在且不为 null存在可选
通知不存在存在可选
成功响应请求的 id存在
错误响应请求的 id存在
  • 每个请求恰好得到一个响应,通知永远不会得到响应。
  • id 是用于把响应和请求对应起来的不透明值,SDK 使用小整数。
  • params 总是一个对象。

请求可以并发执行,并以任意顺序得到响应,所以请用 id 匹配响应。插件可以并发处理请求,也可以逐个处理,但忙碌时仍必须回应健康检查(参见生命周期)。

错误 ​

json
{ "jsonrpc": "2.0", "id": 7, "error": { "code": -32003, "message": "the API token is required", "data": { "field": "MYDNS_API_TOKEN" } } }

message 简短易读,绝不能包含凭据。data 是可选的对象,包含详细信息。

错误码 ​

代码名称使用场景
-32700解析错误消息不是有效的 JSON。
-32600无效请求消息是 JSON,但不是有效的 JSON-RPC 消息。
-32601方法不存在该方法没有处理程序。
-32602参数无效params 的结构不符合预期,或者在能力页面说明的情况下引用了方法不认识的内容。
-32000内部错误调用已执行,但因其他原因失败:服务商故障、网络故障。
-32001权限拒绝插件权限未覆盖的宿主 API 调用。
-32002不支持插件没有实现已声明能力中的某个可选方法。
-32003配置无效用户输入的值缺失或错误。请把 data.field 设为该字段的键。

提示

-32003 与 -32000 的区别很重要:Nginx UI 会把 -32003 显示在出错的字段旁边,方便用户修正;而 -32000 被视为服务故障。不要在 -32000 到 -32099 范围内自行定义其他代码。

JSON 结构 ​

每个 params 和 result 都是 plugin-spec 仓库中 proto 文件所定义消息的 protobuf JSON 映射。本指南的各个页面都以 JSON 展示每条消息,所以只有在生成代码时才需要 proto 文件。

  • 成员名使用 snake_case,例如 effective_fqdn,从不使用 camelCase。
  • 缺失的成员与取默认值(""、0、false、[]、{}、null)的成员含义相同。发送方可以省略默认值,接收方把缺失的成员视为默认值。
  • 数字是 JSON 数字。可能超过 4 GiB 的字节数是双精度浮点数,在 2^53 以内是精确的;5242880 和 5242880.0 都要接受。
  • 二进制数据使用带填充的 base64。
  • 没有参数的方法可以省略 params,没有结果的方法回复 {}。
  • 忽略不认识的成员。 协议正是通过新增可选成员来扩展而不破坏旧插件的。

连接的生命周期 ​

连接就是进程的标准输入和标准输出。进程退出、任意一方关闭自己那一端,或者消息过大时,连接结束。新进程意味着新连接和新握手。

gRPC 传输 ​

插件还可以通过 gRPC 提供能力调用。方法、消息、错误和结果都不变,只是传输方式不同。Nginx UI 能用时就用 gRPC,否则退回标准输入输出,因此插件在标准输入输出上也必须继续提供每个方法。

启用 ​

插件在握手回复的 transports 中列出 grpc,并说明监听位置:

json
{
  "jsonrpc": "2.0", "id": 1,
  "result": {
    "api_version": 1,
    "capabilities": ["dns01"],
    "transports": ["stdio", "grpc"],
    "rpc_socket": "/var/lib/nginx-ui/plugins/.data/com.example.mydns/rpc.sock"
  }
}
  • Unix 套接字。 插件默认监听 <NGINX_UI_PLUGIN_DATA_DIR>/rpc.sock。套接字路径在 macOS 和 BSD 上限制为 104 字节,在 Linux 上为 108 字节;默认路径放不下时,插件在系统临时目录下创建一个私有目录(权限 0700)并在其中监听,然后在 rpc_socket 中报告路径。监听前删除残留的套接字文件,以 0600 权限创建套接字,并在退出时删除它。
  • 命名管道。 在 Windows 上,插件以随机名称创建一个命名管道并在 rpc_pipe 中报告,同时报告 rpc_token(至少 128 位的随机值)。管道遵循与 HTTP 管道相同的规则。Nginx UI 在每次调用中发送 authorization: Bearer <rpc_token>,插件以 UNAUTHENTICATED 拒绝不带这个值的调用。
  • 回环 TCP。 无法打开命名管道的 Windows 插件改为监听 127.0.0.1,并报告 rpc_port 和 rpc_token,令牌检查相同。

监听必须在插件回复握手之前就能接受连接。随后 Nginx UI 通过 gRPC 发送 plugin.ping 检查通道;插件没有列出 grpc 或检查失败时,该进程在整个生命周期内都使用标准输入输出。

各类流量的传输方式 ​

流量传输方式
握手、plugin.initialized、plugin.shutdown、plugin.exit始终使用标准输入输出
plugin.configure 和健康检查标准输入输出
宿主 API 调用及其回复标准输入输出
事件和定时调用标准输入输出
能力调用(dns01.*、http.handle、notify.*、probe.check、mcp.call、storage.*、deploy.*、blocklist.fetch、discovery.resolve)通道正常时使用 gRPC,否则使用标准输入输出
流(log.push)只使用 gRPC

提供 gRPC 的插件必须在 gRPC 上提供每个能力方法和 plugin.ping,并且在两种传输方式上返回相同的结果或错误。如果握手和停止方法通过 gRPC 到达,插件应忽略它们。

进程运行期间通道断开时,Nginx UI 会在该进程剩余的生命周期内改用标准输入输出。以 UNAVAILABLE 失败且不带插件错误详情的调用会在标准输入输出上重试一次,所以插件自己不要返回 UNAVAILABLE。

gRPC 消息与错误 ​

gRPC 服务路径来自 proto 文件,例如 /nginxui.plugin.v1.DNS01/Present,消息使用标准的 protobuf 编码,因此生成的桩代码可以直接使用。双向都接受最大 64 MiB 的消息。Nginx UI 会把自己的截止时间作为 gRPC 截止时间传递,截止时间到达后请停止处理该调用。

失败的调用带有 gRPC 状态码,并附带一个 nginxui.plugin.v1.PluginError 状态详情,其中包含 JSON-RPC 的 code 和 data:

JSON-RPC 代码gRPC 状态码
-32601 方法不存在、-32002 不支持UNIMPLEMENTED
-32602 参数无效、-32003 配置无效INVALID_ARGUMENT
-32001 权限拒绝PERMISSION_DENIED
-32000 内部错误INTERNAL
其他代码UNKNOWN

接收方优先使用详情中的代码,没有详情时再根据状态码反向映射。

流 ​

有些流量是连续的数据流而不是一次调用,目前唯一的是 log.push,它把访问日志行交给插件(参见访问日志推送)。它是客户端流式 gRPC 调用:Nginx UI 发送任意数量的消息后关闭发送端,插件回复一次。流没有 JSON-RPC 形式:它从不通过标准输入输出传输,在那里它的方法名会像任何未知方法一样得到 -32601。

Released under the AGPL-3.0 License. (350eea3f)