grpc-transcode
The grpc-transcode plugin lets HTTP clients call gRPC services. It encodes HTTP request data as protobuf messages, forwards the resulting gRPC call, and decodes the protobuf response as JSON.
Each plugin instance references a proto resource that defines the service, method, and message types. The plugin can also map gRPC status codes to HTTP responses, decode structured error details, and control how protobuf values are represented in JSON.
Examples
The examples expose gRPC methods through HTTP routes and demonstrate descriptor sets, error mapping, and JSON response options.
Start the Example gRPC Server
The examples use the maintained grpc_server_example service. Connect the gateway and example server through a dedicated Docker network so the gateway can resolve the server by container name.
Set GATEWAY_CONTAINER to the running APISIX container:
export GATEWAY_CONTAINER=replace-with-gateway-container-name
Create the network and connect the gateway:
docker network create gateway-grpc-transcode-net
docker network connect gateway-grpc-transcode-net "$GATEWAY_CONTAINER"
Start the example server on the same network:
docker run -d \
--name grpc-transcode-server \
--network gateway-grpc-transcode-net \
api7/grpc-server-example:1.0.2
The examples create proto resources through the APISIX Admin API. ADC and the Ingress Controller can configure routes that reference existing proto resources, but they cannot create the proto resources required by these examples.
API7 Gateway exposes equivalent proto and route resources through its gateway-group-scoped Admin API. See Configure gRPC Proxying.
Install jq to encode proto files in the Admin API request bodies.
If Admin API authentication is enabled, export an admin-role key before continuing:
export ADMIN_API_KEY=replace-with-admin-api-key
Transcode an HTTP Request
The following example demonstrates how to expose a gRPC method through an HTTP route.
Create echo.proto with the service and message definitions:
syntax = "proto3";
package echo;
service EchoService {
rpc Echo (EchoMsg) returns (EchoMsg);
}
message EchoMsg {
string msg = 1;
}
Create a proto resource from the file:
- 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 @-
Create a route that maps HTTP requests to 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
Send an HTTP request whose query parameter matches the EchoMsg.msg field:
curl "http://127.0.0.1:9080/echo?msg=Hello"
The response should be:
{"msg":"Hello"}
This example cannot be completed with ADC alone because ADC cannot create the required proto resource. Use the Admin API workflow to reproduce the example.
This example cannot be completed with the Ingress Controller alone because it cannot create the required proto resource. Use the Admin API workflow to reproduce the example.
Load a Protobuf Descriptor Set
When definitions import other proto files, combine them into a binary descriptor set with protoc and store its base64 representation in the proto resource.
Generate a descriptor set from echo.proto and encode it as a single-line base64 value:
protoc --include_imports \
--descriptor_set_out=echo_proto.pb \
echo.proto
export PROTO_DESCRIPTOR="$(base64 < echo_proto.pb | tr -d '\n')"
Create the proto resource from the descriptor:
- 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
Create a route that uses the descriptor-backed proto resource:
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
Send a request to verify the descriptor-backed resource:
curl "http://127.0.0.1:9080/echo-file?msg=Hello"
The response should be:
{"msg":"Hello"}
This example cannot be completed with ADC alone because ADC cannot create the required proto resource. Use the Admin API workflow to reproduce the example.
This example cannot be completed with the Ingress Controller alone because it cannot create the required proto resource. Use the Admin API workflow to reproduce the example.
Decode Structured gRPC Errors
The example server returns a structured UNAVAILABLE error from GetErrResp. Create hello.proto with the service and error detail definitions:
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;
}
Create the proto resource:
- 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 @-
Create a route that exposes status details in the response body and decodes the embedded ErrorDetail message:
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
Send a request to the route:
curl -i "http://127.0.0.1:9080/hello?name=world"
The response should include grpc-status: 14, map the gRPC status to 503 Service Temporarily Unavailable, and contain a decoded error body similar to:
{
"error": {
"code": 14,
"message": "Out of service",
"details": [
{
"code": 1,
"message": "The server is out of service",
"type": "service"
}
]
}
}
If status_detail_type is omitted, the gateway still includes the error details in the response body, but leaves each detail as a generic protobuf Any value.
This example cannot be completed with ADC alone because ADC cannot create the required proto resource. Use the Admin API workflow to reproduce the example.
This example cannot be completed with the Ingress Controller alone because it cannot create the required proto resource. Use the Admin API workflow to reproduce the example.
Preserve 64-Bit Integer Precision
The default protobuf JSON representation emits 64-bit integers as numbers. Configure int64_as_string when clients need the exact value represented as a string.
Create 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;
}
Create the proto resource:
- 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 @-
Create a route using the default protobuf options:
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
Send a request whose exact sum is not preserved by the default numeric JSON encoding:
curl "http://127.0.0.1:9080/plus?a=1237528374197491&b=1237528374197491"
The default response represents the result as a number:
{"result":2.475056748395e+15}
The numeric representation rounds the exact sum of 2475056748394982.
Update the route to use 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
Send the request again:
curl "http://127.0.0.1:9080/plus?a=1237528374197491&b=1237528374197491"
The exact result should now be represented as a string:
{"result":"#2475056748394982"}
This example cannot be completed with ADC alone because ADC cannot create the required proto resource. Use the Admin API workflow to reproduce the example.
This example cannot be completed with the Ingress Controller alone because it cannot create the required proto resource. Use the Admin API workflow to reproduce the example.