Transcode HTTP to gRPC
Configure a fixed Apache APISIX proto object and Route that transcode an HTTP request to a controlled gRPC service, verify the response, and remove every test resource.
Applies to:Apache APISIX 3.18.0
Deployments:DockerRoles:API developerTime:15 minutesImpact:Changes live traffic
Goal
Create a proto object and a Route that uses grpc-transcode to call echo.EchoService/Echo from an HTTP GET request. Validate the result against a pinned gRPC example server and clean up all resources.
Before you begin
- Complete Get APISIX with the Docker quickstart.
- Install Docker, cURL, and jq.
Set the endpoints and read the Admin API key without placing it in shell history:
export APISIX_ADMIN_URL="http://127.0.0.1:9180"
export GATEWAY_URL="http://127.0.0.1:9080"
read -rsp "Admin API key (press Enter for an unchanged quickstart): " APISIX_ADMIN_KEY && export APISIX_ADMIN_KEY && echoProtect the fixed resources and container used by this guide:
test "$(curl -sS --connect-timeout 3 --max-time 10 -o /dev/null -w '%{http_code}' "${APISIX_ADMIN_URL}/apisix/admin/protos/grpc-transcode-guide-proto" -H "X-API-KEY: ${APISIX_ADMIN_KEY}")" = 404 || { echo 'Stop: proto grpc-transcode-guide-proto already exists'; exit 1; }
test "$(curl -sS --connect-timeout 3 --max-time 10 -o /dev/null -w '%{http_code}' "${APISIX_ADMIN_URL}/apisix/admin/routes/grpc-transcode-guide-route" -H "X-API-KEY: ${APISIX_ADMIN_KEY}")" = 404 || { echo 'Stop: route grpc-transcode-guide-route already exists'; exit 1; }
test -z "$(docker ps -aq --filter name=^/grpc-transcode-guide-upstream$)" || { echo 'Stop: container grpc-transcode-guide-upstream already exists'; exit 1; }Steps
Step 1: Start the controlled gRPC upstream
Start the pinned example server only on the APISIX quickstart network and wait for its listener:
docker run -d --name grpc-transcode-guide-upstream \
--network apisix-quickstart-net \
api7/grpc-server-example:1.0.2
for attempt in {1..15}; do
docker exec apisix-quickstart bash -c '</dev/tcp/grpc-transcode-guide-upstream/50051' >/dev/null 2>&1 && break
sleep 1
done
docker exec apisix-quickstart bash -c '</dev/tcp/grpc-transcode-guide-upstream/50051'The server exposes this protobuf service:
syntax = "proto3";
package echo;
service EchoService {
rpc Echo (EchoMsg) returns (EchoMsg);
}
message EchoMsg {
string msg = 1;
}What changed: the example gRPC service is reachable from APISIX without publishing port 50051 to the host.
Step 2: Create the proto object
Create the proto object at a fixed resource URL and assert its ID:
curl -sS --connect-timeout 3 --max-time 15 "${APISIX_ADMIN_URL}/apisix/admin/protos/grpc-transcode-guide-proto" -X PUT \
-H "X-API-KEY: ${APISIX_ADMIN_KEY}" -H "Content-Type: application/json" \
-d '{
"content": "syntax = \"proto3\"; package echo; service EchoService { rpc Echo (EchoMsg) returns (EchoMsg); } message EchoMsg { string msg = 1; }"
}' | jq -e '.value.id == "grpc-transcode-guide-proto"'What changed: APISIX now stores the exact service definition needed by grpc-transcode.
Step 3: Create the transcoding Route
Create a fixed-ID Route that maps GET /grpc-transcode-guide to the Echo method:
curl -sS --connect-timeout 3 --max-time 15 "${APISIX_ADMIN_URL}/apisix/admin/routes/grpc-transcode-guide-route" -X PUT \
-H "X-API-KEY: ${APISIX_ADMIN_KEY}" -H "Content-Type: application/json" \
-d '{
"methods": ["GET"],
"uri": "/grpc-transcode-guide",
"plugins": {
"grpc-transcode": {
"proto_id": "grpc-transcode-guide-proto",
"service": "echo.EchoService",
"method": "Echo"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {"grpc-transcode-guide-upstream:50051": 1}
}
}' | jq -e '.value.id == "grpc-transcode-guide-route" and .value.plugins["grpc-transcode"].proto_id == "grpc-transcode-guide-proto"'What changed: APISIX accepts an HTTP query parameter, encodes it as echo.EchoMsg, invokes the gRPC method over HTTP/2, and decodes the response as JSON.
Validate
Require a successful response containing the exact echoed value, then prove that the Route does not accept POST:
curl --fail-with-body -sS --connect-timeout 3 --max-time 15 \
"${GATEWAY_URL}/grpc-transcode-guide?msg=Hello" | jq -e '.msg == "Hello"'
test "$(curl -sS --connect-timeout 3 --max-time 15 -o /dev/null -w '%{http_code}' \
"${GATEWAY_URL}/grpc-transcode-guide" -X POST)" = 404Both assertions must pass. They prove the HTTP-to-gRPC round trip and the configured method constraint.
Roll back / Clean up
Delete the Route before the proto object, then remove the disposable upstream:
curl -sS --connect-timeout 3 --max-time 15 -X DELETE "${APISIX_ADMIN_URL}/apisix/admin/routes/grpc-transcode-guide-route" -H "X-API-KEY: ${APISIX_ADMIN_KEY}"
curl -sS --connect-timeout 3 --max-time 15 -X DELETE "${APISIX_ADMIN_URL}/apisix/admin/protos/grpc-transcode-guide-proto" -H "X-API-KEY: ${APISIX_ADMIN_KEY}"
docker rm -f grpc-transcode-guide-upstream
unset APISIX_ADMIN_URL APISIX_ADMIN_KEY GATEWAY_URLExpected: both fixed APISIX resources and the example container are absent.
Troubleshoot
| Symptom | Cause | Fix |
|---|---|---|
The Route write rejects proto_id. | The proto object was not created or its fixed ID changed. | Read the proto by its exact Admin API URL and recreate only the guide-owned resource if it is absent. |
The request returns 502. | APISIX cannot reach the gRPC container or the upstream is not speaking plaintext gRPC on port 50051. | Verify the container is attached to apisix-quickstart-net and rerun the TCP readiness check. |
| The response reports an unknown service or method. | The proto object and example image do not describe the same RPC. | Compare echo.EchoService/Echo in the displayed proto, Route, and pinned server image configuration. |
Related
- Proto objects — explains reusable protobuf definitions.
- gRPC Transcode (opens in Plugin Hub docs) — lists plugin parameters and additional examples.
- gRPC Web (opens in Plugin Hub docs) — adapts browser-compatible gRPC-Web requests to native gRPC upstreams.