跳到主要内容

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,其中包含服务和消息定义:

echo.proto
syntax = "proto3";

package echo;

service EchoService {
rpc Echo (EchoMsg) returns (EchoMsg);
}

message EchoMsg {
string msg = 1;
}

从该文件创建 proto 资源:

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"}

加载 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 资源:

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"}

解码结构化 gRPC 错误​

示例服务器通过 GetErrResp 返回结构化的 UNAVAILABLE 错误。创建 hello.proto,其中包含服务和错误详情定义:

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 资源:

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 值。

保留 64 位整数精度​

默认的 protobuf JSON 表示会将 64 位整数输出为数字。当客户端需要将精确值表示为字符串时,请配置 int64_as_string。

创建 plus.proto:

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 资源:

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"}