Docs
Apache APISIXManage traffic and APIsTransformationTranscode HTTP to gRPC

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

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 && echo

Protect 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:

echo.proto
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)" = 404

Both 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_URL

Expected: both fixed APISIX resources and the example container are absent.

Troubleshoot

SymptomCauseFix
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.