skills/moonming/a6/a6-plugin-grpc-transcode

a6-plugin-grpc-transcode

SKILL.md

a6-plugin-grpc-transcode

Overview

The grpc-transcode plugin converts HTTP/JSON requests into gRPC calls and returns gRPC responses as JSON. Clients send standard HTTP requests; APISIX transcodes them to gRPC using a pre-uploaded protobuf definition, forwards to the gRPC upstream, and returns the response as JSON. The gRPC service needs no modification.

When to Use

  • Expose gRPC services via RESTful HTTP endpoints
  • Allow browser/mobile clients to call gRPC services without gRPC client libraries
  • Add HTTP API gateway features (auth, rate limiting, logging) to gRPC services
  • Migrate from REST to gRPC incrementally
  • Decode gRPC error details into human-readable JSON

Plugin Configuration Reference (Route/Service)

Field Type Required Default Description
proto_id string/integer Yes ID of the proto resource (uploaded via a6 proto create).
service string Yes Fully qualified gRPC service name (e.g., helloworld.Greeter).
method string Yes gRPC method name (e.g., SayHello).
deadline number No 0 Deadline for the gRPC call in milliseconds. 0 = no deadline.
pb_option array[string] No Protobuf serialization options (see table below).
show_status_in_body boolean No false Include parsed grpc-status-details-bin in the JSON response body on errors.
status_detail_type string No Message type for the details field in gRPC error status. Required to decode error details.

pb_option Values

Option Description
enum_as_name Return enum fields as string names (e.g., "PENDING")
enum_as_value Return enum fields as integer values (e.g., 1)
int64_as_number Return int64 as JSON number (may lose precision in JavaScript)
int64_as_string Return int64 as string (safe for JavaScript clients)
int64_as_hexstring Return int64 as hexadecimal string
auto_default_values Auto-populate default values for unset fields
no_default_values Do not add default values for unset fields
use_default_values Use proto-defined default values
use_default_metatable Use metatable for default values
enable_hooks Enable protobuf hooks
disable_hooks Disable protobuf hooks

Multiple options can be combined: ["int64_as_string", "enum_as_name"]

Step-by-Step: Set Up gRPC Transcoding

1. Upload the proto definition

Given a proto file:

syntax = "proto3";
package helloworld;

service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}

Upload it:

a6 proto create -f - <<'EOF'
{
  "id": "1",
  "content": "syntax = \"proto3\";\npackage helloworld;\nservice Greeter {\n    rpc SayHello (HelloRequest) returns (HelloReply) {}\n}\nmessage HelloRequest {\n    string name = 1;\n}\nmessage HelloReply {\n    string message = 1;\n}"
}
EOF

2. Create a route with grpc-transcode

a6 route create -f - <<'EOF'
{
  "id": "grpc-hello",
  "methods": ["GET", "POST"],
  "uri": "/grpc/hello",
  "plugins": {
    "grpc-transcode": {
      "proto_id": "1",
      "service": "helloworld.Greeter",
      "method": "SayHello"
    }
  },
  "upstream": {
    "scheme": "grpc",
    "type": "roundrobin",
    "nodes": {
      "grpc-server:50051": 1
    }
  }
}
EOF

Critical: The upstream scheme must be "grpc" (or "grpcs" for TLS).

3. Test the endpoint

# Pass parameters via query string
curl "http://localhost:9080/grpc/hello?name=world"
# Response: {"message":"Hello world"}

# Or via POST body
curl -X POST http://localhost:9080/grpc/hello \
  -H "Content-Type: application/json" \
  -d '{"name": "world"}'
# Response: {"message":"Hello world"}

Common Patterns

Proto with imports (use compiled .pb file)

When your proto has import statements, compile to a .pb file first:

protoc --include_imports --descriptor_set_out=service.pb proto/service.proto

Then upload the base64-encoded .pb:

a6 proto create -f - <<EOF
{
  "id": "2",
  "content": "$(base64 -i service.pb)"
}
EOF

Safe int64 handling for JavaScript clients

{
  "plugins": {
    "grpc-transcode": {
      "proto_id": "1",
      "service": "order.OrderService",
      "method": "GetOrder",
      "pb_option": ["int64_as_string"]
    }
  }
}

Returns {"order_id": "9223372036854775807"} instead of {"order_id": 9223372036854775807} (which JavaScript would corrupt).

Human-readable enums

{
  "plugins": {
    "grpc-transcode": {
      "proto_id": "1",
      "service": "order.OrderService",
      "method": "GetOrder",
      "pb_option": ["enum_as_name", "int64_as_string"]
    }
  }
}

Returns {"status": "PENDING"} instead of {"status": 1}.

gRPC error detail decoding

{
  "plugins": {
    "grpc-transcode": {
      "proto_id": "1",
      "service": "helloworld.Greeter",
      "method": "SayHello",
      "show_status_in_body": true,
      "status_detail_type": "helloworld.ErrorDetail"
    }
  }
}

Error response body:

{
  "error": {
    "code": 14,
    "message": "Out of service",
    "details": [
      {
        "type": "service",
        "message": "The server is out of service",
        "code": 1
      }
    ]
  }
}

Timeout control with deadline

{
  "plugins": {
    "grpc-transcode": {
      "proto_id": "1",
      "service": "slow.SlowService",
      "method": "LongRunning",
      "deadline": 5000
    }
  }
}

Request fails if the gRPC service doesn't respond within 5 seconds.

Multiple gRPC methods on different routes

// Route 1: GET /api/users/:id → user.UserService/GetUser
{
  "id": "grpc-get-user",
  "uri": "/api/users/*",
  "methods": ["GET"],
  "plugins": {
    "grpc-transcode": {
      "proto_id": "3",
      "service": "user.UserService",
      "method": "GetUser"
    }
  },
  "upstream": {"scheme": "grpc", "type": "roundrobin", "nodes": {"user-svc:50051": 1}}
}

// Route 2: POST /api/users → user.UserService/CreateUser
{
  "id": "grpc-create-user",
  "uri": "/api/users",
  "methods": ["POST"],
  "plugins": {
    "grpc-transcode": {
      "proto_id": "3",
      "service": "user.UserService",
      "method": "CreateUser"
    }
  },
  "upstream": {"scheme": "grpc", "type": "roundrobin", "nodes": {"user-svc:50051": 1}}
}

Troubleshooting

Symptom Cause Fix
"can not find proto" Proto ID doesn't exist Verify with a6 proto get <id>
"method not found" Service or method name mismatch Use fully qualified name: package.Service, case-sensitive
Connection refused to upstream Wrong scheme or port Set upstream scheme to grpc, verify port is gRPC port
Import errors in proto Proto has imports but raw content uploaded Compile to .pb with protoc --include_imports
int64 values corrupted JavaScript precision loss Use pb_option: ["int64_as_string"]
Enum shows numbers instead of names Default behavior Use pb_option: ["enum_as_name"]
Error details not decoded show_status_in_body not set Set show_status_in_body: true and status_detail_type
gRPC call times out silently No deadline set Set deadline in milliseconds
502 Bad Gateway gRPC service not running or not reachable Check gRPC service is up and port is accessible from APISIX

Config Sync Example

version: "1"
protos:
  - id: helloworld-proto
    content: |
      syntax = "proto3";
      package helloworld;
      service Greeter {
          rpc SayHello (HelloRequest) returns (HelloReply) {}
      }
      message HelloRequest {
          string name = 1;
      }
      message HelloReply {
          string message = 1;
      }
routes:
  - id: grpc-hello
    methods:
      - GET
      - POST
    uri: /grpc/hello
    plugins:
      grpc-transcode:
        proto_id: helloworld-proto
        service: helloworld.Greeter
        method: SayHello
        pb_option:
          - int64_as_string
          - enum_as_name
    upstream_id: grpc-backend
upstreams:
  - id: grpc-backend
    scheme: grpc
    type: roundrobin
    nodes:
      "grpc-server:50051": 1
Weekly Installs
1
Repository
moonming/a6
First Seen
7 days ago
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
codex1