跳到主要内容

a6-plugin-grpc-transcode

概览

grpc-transcode 插件会将 HTTP/JSON 请求转换为 gRPC 调用,并将 gRPC 响应返回为 JSON。客户端发送标准 HTTP 请求,Apache APISIX使用预先上传的 protobuf 定义将其转换为 gRPC 请求,转发到 gRPC 上游,再将响应返回为 JSON。gRPC 服务无需修改。

适用场景

  • 通过 RESTful HTTP 端点公开 gRPC 服务。
  • 允许浏览器或移动客户端在没有 gRPC 客户端库的情况下调用 gRPC 服务。
  • 为 gRPC 服务添加 HTTP API 网关功能(认证、限流、日志)。
  • 逐步从 REST 迁移到 gRPC。
  • 将 gRPC 错误详情解码为人类可读的 JSON。

插件配置参考(路由/服务)

字段类型是否必填默认值说明
proto_idstring/integerproto 资源 ID(通过 a6 proto create 上传)。
servicestring完全限定的 gRPC 服务名称(例如 helloworld.Greeter)。
methodstringgRPC 方法名称(例如 SayHello)。
deadlinenumber0gRPC 调用的截止时间,单位为毫秒。0 表示无截止时间。
pb_optionarray[string]Protobuf 序列化选项(见下表)。
show_status_in_bodybooleanfalse出错时将解析后的 grpc-status-details-bin 包含在 JSON 响应体中。
status_detail_typestringgRPC 错误状态中 details 字段的消息类型。解码错误详情时必须设置。

pb 选项值

选项描述
enum_as_name返回 enum 字段作为字符串名称( 如"PENDING")
enum_as_value返回 enum 字段为整数(例如1)
int64_as_number将 int64 作为 JSON 数字返回(JavaScript 可能丢失精度)
int64_as_string返回 int64 作为字符串( JavaScript 客户端的安全性)
int64_as_hexstring返回十六进制字符串 。
auto_default_values自动填充未设置字段的默认值
no_default_values不为未设置字段添加默认值
use_default_values使用原定义的默认值
use_default_metatable默认值使用元表
enable_hooks启用 Proto hooks
disable_hooks禁用 Proto hooks

可合并多个选项:["int64_as_string", "enum_as_name"]

分步指南:设置 gRPC 转码

1. 上传 Proto 定义

给定一个原始文件:

syntax = "proto3";
package helloworld;

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;
}

上传 Proto 文件:

a6 proto create -f - <<'EOF'
{
"id": "1",
"content": "syntax = \"proto3\";\npackage helloworld;\nservice Greeter {\n rpc SayHello (HelloRequest) returns (HelloReply) {}\n}\nmessage HelloRequest {\n string name = 1;\n}\nmessage HelloReply {\n string message = 1;\n}"
}
EOF

2. 使用 grpc-transcode 创建路由

a6 route create -f - <<'EOF'
{
"id": "grpc-hello",
"methods": ["GET", "POST"],
"uri": "/grpc/hello",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "SayHello"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"grpc-server:50051": 1
}
}
}
EOF

**重要:**该上游的 scheme 必须"grpc"(使用 TLS 时为 "grpcs")。

3. 测试端点

# Pass parameters via query string
curl "http://localhost:9080/grpc/hello?name=world"
# Response: {"message":"Hello world"}

# Or via POST body
curl -X POST http://localhost:9080/grpc/hello \
-H "Content-Type: application/json" \
-d '{"name": "world"}'
# Response: {"message":"Hello world"}

常见模式

Proto 使用 imports(使用已编译的 .pb 文件)

如果 proto 包含 import 语句,请先将其编译为 .pb 文件:

protoc --include_imports --descriptor_set_out=service.pb proto/service.proto

然后上传 Base64 编码的 .pb 文件:

a6 proto create -f - <<EOF
{
"id": "2",
"content": "$(base64 -i service.pb)"
}
EOF

JavaScript 客户端的安全 int64 处理

{
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "order.OrderService",
"method": "GetOrder",
"pb_option": ["int64_as_string"]
}
}
}

返回{"order_id": "9223372036854775807"}而不是{"order_id": 9223372036854775807}( JavaScript会损坏)。

人类可读枚举

{
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "order.OrderService",
"method": "GetOrder",
"pb_option": ["enum_as_name", "int64_as_string"]
}
}
}

返回{"status": "PENDING"}而不是{"status": 1}

gRPC 错误细节解码

{
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "SayHello",
"show_status_in_body": true,
"status_detail_type": "helloworld.ErrorDetail"
}
}
}

错误响应正文:

{
"error": {
"code": 14,
"message": "Out of service",
"details": [
{
"type": "service",
"message": "The server is out of service",
"code": 1
}
]
}
}

使用 deadline 控制超时

{
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "slow.SlowService",
"method": "LongRunning",
"deadline": 5000
}
}
}

如果gRPC服务在5秒内没有响应,则请求失败。

不同路由上的多种gRPC方法

// Route 1: GET /api/users/:id → user.UserService/GetUser
{
"id": "grpc-get-user",
"uri": "/api/users/*",
"methods": ["GET"],
"plugins": {
"grpc-transcode": {
"proto_id": "3",
"service": "user.UserService",
"method": "GetUser"
}
},
"upstream": {"scheme": "grpc", "type": "roundrobin", "nodes": {"user-svc:50051": 1}}
}

// Route 2: POST /api/users → user.UserService/CreateUser
{
"id": "grpc-create-user",
"uri": "/api/users",
"methods": ["POST"],
"plugins": {
"grpc-transcode": {
"proto_id": "3",
"service": "user.UserService",
"method": "CreateUser"
}
},
"upstream": {"scheme": "grpc", "type": "roundrobin", "nodes": {"user-svc:50051": 1}}
}

故障排查

症状原因解决方法
Proto not foundProto ID 不存在使用 a6 proto get <id> 检查
"没有找到方法"服务或方法名称不匹配使用完全合格的名称:package.Service, 对大小写敏感
拒绝连接到上游错误的方案或端口设置上游schemegrpc, 验证端口为 gRPC 端口
Proto import 错误Proto 包含 import,但上传的是原始内容使用 protoc --include_imports 编译为 .pb
int64 值已损坏JavaScript 精度丢失使用 pb_option: ["int64_as_string"]
Enum 显示数字而不是名称默认行为使用pb_option: ["enum_as_name"]
未解码的错误细节未设置设置show_status_in_body: truestatus_detail_type
gRPC 调用长时间无响应未设置超时时间设置以毫秒计的 deadline
502 Bad GatewaygRPC 服务无法运行或无法达到检查 gRPC 服务已启动, 从 APISIX 访问端口

配置同步示例

version: "1"
protos:
- id: helloworld-proto
content: |
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
routes:
- id: grpc-hello
methods:
- GET
- POST
uri: /grpc/hello
plugins:
grpc-transcode:
proto_id: helloworld-proto
service: helloworld.Greeter
method: SayHello
pb_option:
- int64_as_string
- enum_as_name
upstream_id: grpc-backend
upstreams:
- id: grpc-backend
scheme: grpc
type: roundrobin
nodes:
"grpc-server:50051": 1

本文根据 api7/a6 仓库中的 a6-plugin-grpc-transcode/SKILL.md 生成。可在 AI Agent Skills 页面浏览全部 Skill。