grpc-transcode
grpc-transcode 插件允许 HTTP 客户端调用 gRPC 服务。它将 HTTP 请求数据编码为 protobuf 消息,转发生成的 gRPC 调用,并将 protobuf 响应解码为 JSON。
每个插件实例都引用一个 proto 资源,该资源定义服务、方法和消息类型。插件 还可以将 gRPC 状态码映射为 HTTP 响应、解码结构化错误详情,并控制 protobuf 值在 JSON 中的表示方式。
示例
以下示例通过 HTTP 路由公开 gRPC 方法,并演示描述符集、错误映射和 JSON 响应选项。
启动示例 gRPC 服务器
以下示例使用维护中的 grpc_server_example 服务。通过专用 Docker 网络连接网关和示例服务器,以便网关可以通过容器名称解析服务器。
将 GATEWAY_CONTAINER 设置为正在运行的 APISIX 容器:
export GATEWAY_CONTAINER=replace-with-gateway-container-name
创建网络并将网关连接到该网络:
docker network create gateway-grpc-transcode-net
docker network connect gateway-grpc-transcode-net "$GATEWAY_CONTAINER"
在同一网络中启动示例服务器:
docker run -d \
--name grpc-transcode-server \
--network gateway-grpc-transcode-net \
api7/grpc-server-example:1.0.2
以下示例通过 APISIX Admin API 创建 proto 资源。ADC 和 Ingress Controller 可以配置引用现有 proto 资源的路由,但无法创建这些示例所需的 proto 资源。
API7 网关通过网关组范围的 Admin API 提供等效的 proto 和路由资源。请参阅配置 gRPC 代理。
安装 jq,用于在 Admin API 请求体中编码 proto 文件。
如果已启用 Admin API 认证,请先导出管理员角色密钥:
export ADMIN_API_KEY=replace-with-admin-api-key
转码 HTTP 请求
以下示例演示如何通过 HTTP 路由公开 gRPC 方法。
创建 echo.proto, 其中包含服务和消息定义:
syntax = "proto3";
package echo;
service EchoService {
rpc Echo (EchoMsg) returns (EchoMsg);
}
message EchoMsg {
string msg = 1;
}
从该文件创建 proto 资源:
- Admin API
- ADC
- Ingress Controller
jq -Rs '{content: .}' echo.proto | \
curl "http://127.0.0.1:9180/apisix/admin/protos/echo-proto" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-H "Content-Type: application/json" \
-d @-
创建将 HTTP 请求映射到 echo.EchoService/Echo 的路由:
curl "http://127.0.0.1:9180/apisix/admin/routes/grpc-transcode-route" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{
"uri": "/echo",
"methods": ["GET"],
"plugins": {
"grpc-transcode": {
"proto_id": "echo-proto",
"service": "echo.EchoService",
"method": "Echo"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"grpc-transcode-server:50051": 1
}
}
}
EOF
发送 HTTP 请求,其中查询参数与 EchoMsg.msg 字段匹配:
curl "http://127.0.0.1:9080/echo?msg=Hello"
响应应为:
{"msg":"Hello"}
ADC 无法创建所需的 proto 资源,因此无法仅使用 ADC 完成此示例。请使用 Admin API 流程复现此示例。
Ingress Controller 无法创建所需的 proto 资源,因此无法仅使用 Ingress Controller 完成此示例。请使用 Admin API 流程复现此示例。
加载 Protobuf 描述符集
当定义导入其他 proto 文件时,请使用 protoc 将它们组合为二进制描述符集,并将其 base64 表示形式存储在 proto 资源中。
从 echo.proto 生成描述符集,并将其编码为单行 base64 值:
protoc --include_imports \
--descriptor_set_out=echo_proto.pb \
echo.proto
export PROTO_DESCRIPTOR="$(base64 < echo_proto.pb | tr -d '\n')"
从描述符创建 proto 资源:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/protos/echo-file-proto" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-H "Content-Type: application/json" \
-d @- <<EOF
{
"content": "$PROTO_DESCRIPTOR"
}
EOF
创建使用描述符所支持的 proto 资源的路由:
curl "http://127.0.0.1:9180/apisix/admin/routes/grpc-file-route" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{
"uri": "/echo-file",
"plugins": {
"grpc-transcode": {
"proto_id": "echo-file-proto",
"service": "echo.EchoService",
"method": "Echo"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"grpc-transcode-server:50051": 1
}
}
}
EOF
发送请求以验证使用描述符的资源:
curl "http://127.0.0.1:9080/echo-file?msg=Hello"
响应应为:
{"msg":"Hello"}
ADC 无法创建所需的 proto 资源,因此无法仅使用 ADC 完成此示例。请使用 Admin API 流程复现此示例。
Ingress Controller 无法创建所需的 proto 资源,因此无法仅使用 Ingress Controller 完成此示例。请使用 Admin API 流程复现此示例。
解码结构化 gRPC 错误
示例服务器通过 GetErrResp 返回结构化的 UNAVAILABLE 错误。创建 hello.proto,其中包含服务和错误详情定义:
syntax = "proto3";
package helloworld;
service Greeter {
rpc GetErrResp (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
repeated string items = 2;
}
message HelloReply {
string message = 1;
repeated string items = 2;
}
message ErrorDetail {
int64 code = 1;
string message = 2;
string type = 3;
}
创建 proto 资源:
- Admin API
- ADC
- Ingress Controller
jq -Rs '{content: .}' hello.proto | \
curl "http://127.0.0.1:9180/apisix/admin/protos/hello-proto" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-H "Content-Type: application/json" \
-d @-
创建一个路由,在响应体中公开状态详情,并解码嵌入的 ErrorDetail 消息:
curl "http://127.0.0.1:9180/apisix/admin/routes/grpc-error-route" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{
"uri": "/hello",
"plugins": {
"grpc-transcode": {
"proto_id": "hello-proto",
"service": "helloworld.Greeter",
"method": "GetErrResp",
"show_status_in_body": true,
"status_detail_type": "helloworld.ErrorDetail"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"grpc-transcode-server:50051": 1
}
}
}
EOF
向路由发送请求:
curl -i "http://127.0.0.1:9080/hello?name=world"
响应应包含 grpc-status: 14,将 gRPC 状态映射为 503 Service Temporarily Unavailable,并包含类似以下内容的已解码错误响应体:
{
"error": {
"code": 14,
"message": "Out of service",
"details": [
{
"code": 1,
"message": "The server is out of service",
"type": "service"
}
]
}
}
如果省略 status_detail_type,网关仍会在响应体中包含错误详情,但每条详情会保留为通用的 protobuf Any 值。
ADC 无法创建所需的 proto 资源,因此无法仅使用 ADC 完成此示例。请使用 Admin API 流程复现此示例。
Ingress Controller 无法创建所需的 proto 资源,因此无法仅使用 Ingress Controller 完成此示例。请使用 Admin API 流程复现此示例。
保留 64 位整数精度
默认的 protobuf JSON 表示会将 64 位整数输出为数字。当客户端需要将精确值表示为字符串时,请配置 int64_as_string。
创建 plus.proto:
syntax = "proto3";
package helloworld;
service Greeter {
rpc Plus (PlusRequest) returns (PlusReply);
}
message PlusRequest {
int64 a = 1;
int64 b = 2;
}
message PlusReply {
int64 result = 1;
}
创建 proto 资源:
- Admin API
- ADC
- Ingress Controller
jq -Rs '{content: .}' plus.proto | \
curl "http://127.0.0.1:9180/apisix/admin/protos/plus-proto" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-H "Content-Type: application/json" \
-d @-
创建使用默认 protobuf 选项的路由:
curl "http://127.0.0.1:9180/apisix/admin/routes/grpc-plus-route" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{
"uri": "/plus",
"plugins": {
"grpc-transcode": {
"proto_id": "plus-proto",
"service": "helloworld.Greeter",
"method": "Plus"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"grpc-transcode-server:50051": 1
}
}
}
EOF
发送一个请求,其精确和无法由默认的 JSON 数字编码保留:
curl "http://127.0.0.1:9080/plus?a=1237528374197491&b=1237528374197491"
默认响应将结果表示为数字:
{"result":2.475056748395e+15}
这种数字表示会对精确和 2475056748394982 进行舍入。
更新路由以使用 int64_as_string:
curl "http://127.0.0.1:9180/apisix/admin/routes/grpc-plus-route" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{
"uri": "/plus",
"plugins": {
"grpc-transcode": {
"proto_id": "plus-proto",
"service": "helloworld.Greeter",
"method": "Plus",
"pb_option": ["int64_as_string"]
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"grpc-transcode-server:50051": 1
}
}
}
EOF
再次发送请求:
curl "http://127.0.0.1:9080/plus?a=1237528374197491&b=1237528374197491"
此时精确结果应表示为字符串:
{"result":"#2475056748394982"}
ADC 无法创建所需的 proto 资源,因此无法仅使用 ADC 完成此示例。请使用 Admin API 流程复现此示例。
Ingress Controller 无法创建所需的 proto 资源,因此无法仅使用 Ingress Controller 完成此示例。请使用 Admin API 流程复现此示例。