Is there an existing issue for this?
Kong version ($ kong version)
Reproduced through the kong/ingress Helm chart (Kong Ingress Controller). The root-cause code path is unchanged on master (7772bd8b3125a57fe457b9e5947faa3f3bdf59bc), so current 3.x is affected (latest release at filing: 3.9.2).
Current Behavior
The grpc-gateway plugin returns HTTP 400 {"message":"failed to encode payload"} when a repeated request field is supplied as a single query-parameter occurrence.
For a method whose request message has e.g. repeated Color colors = N bound to a GET via google.api.http:
| Request |
Result |
?colors=RED (one occurrence) |
400 failed to encode payload |
?colors=RED&colors=BLUE (two occurrences) |
200 ✅ (reaches upstream) |
scalar field, e.g. ?name=bolt |
200 ✅ |
single (non-repeated) enum, e.g. ?mode=AT_MOST |
200 ✅ |
So a repeated field is only transcodable when the client happens to pass two or more values; a single value fails. The single-value case is the common one (one selected filter), and it is deterministic — 20/20 requests returned 400 in our deployment.
Expected Behavior
A repeated field with a single query value should transcode to a one-element list (?colors=RED → colors = [RED]), matching grpc-ecosystem/grpc-gateway query-parameter semantics.
Steps To Reproduce
repro.proto:
syntax = "proto3";
package repro;
import "google/api/annotations.proto";
enum Color { COLOR_UNSPECIFIED = 0; RED = 1; BLUE = 2; }
message GetReq {
Color one = 1; // single enum -> OK
repeated Color many = 2; // repeated enum -> 400 on a single value
}
message GetResp { string ok = 1; }
service Repro {
rpc Get(GetReq) returns (GetResp) {
option (google.api.http) = { get: "/get" };
}
}
kong.yml (DB-less):
_format_version: "3.0"
services:
- name: repro
protocol: grpc
host: 127.0.0.1
port: 9999 # no real upstream needed: the 400 is raised during
routes: # request transcoding, before the upstream is contacted
- name: repro
protocols: [http]
paths: ["/"]
plugins:
- name: grpc-gateway
config:
proto: /repro.proto
Run Kong and probe:
docker run --rm -p 8000:8000 \
-e KONG_DATABASE=off \
-e KONG_DECLARATIVE_CONFIG=/kong.yml \
-e KONG_PROXY_LISTEN="0.0.0.0:8000" \
-v "$PWD/kong.yml:/kong.yml" \
-v "$PWD/repro.proto:/repro.proto" \
kong:3.9
curl -s -o /dev/null -w "%{http_code}\n" 'localhost:8000/get?many=RED' # 400 <-- bug
curl -s -o /dev/null -w "%{http_code}\n" 'localhost:8000/get?many=RED&many=BLUE' # not 400 (transcodes)
curl -s -o /dev/null -w "%{http_code}\n" 'localhost:8000/get?one=RED' # not 400 (scalar enum)
curl -s 'localhost:8000/get?many=RED' # {"message":"failed to encode payload"}
(repro.proto imports google/api/annotations.proto; Kong bundles the google protos on the plugin include path. If your build doesn't, mount google/api/annotations.proto + google/api/http.proto next to repro.proto.)
Anything else?
Root cause — kong/plugins/grpc-gateway/deco.lua. add_to_table assigns the query value to the leaf field with no handling for repeated fields:
|
local function add_to_table( t, path, v, typ ) |
|
local tab = t -- set up pointer to table root |
|
local msg_typ = typ; |
|
for m in re_gmatch( path , "([^.]+)(\\.)?", "jo" ) do |
|
local key, dot = m[1], m[2] |
|
msg_typ = get_field_type(msg_typ, key) |
|
|
|
-- not argument that we concern with |
|
if not msg_typ then |
|
return |
|
end |
|
|
|
if dot then |
|
tab[key] = tab[key] or {} -- create empty nested table if key does not exist |
|
tab = tab[key] |
|
else |
|
tab[key] = encode_fix(v, msg_typ) |
|
end |
|
end |
|
|
local function add_to_table( t, path, v, typ )
...
else
tab[key] = encode_fix(v, msg_typ) -- assigns scalar; a repeated field needs an array
end
...
end
and encode_fix only special-cases bool:
|
local function encode_fix(v, typ) |
|
if typ == "bool" then |
|
-- special case for URI parameters |
|
return v and v ~= "0" and v ~= "false" |
|
end |
|
|
|
return v |
|
end |
|
|
|
--[[ |
|
// Set value `v` at `path` in table `t` |
|
// Path contains value address in dot-syntax. For example: |
|
// `path="a.b.c"` would lead to `t[a][b][c] = v`. |
ngx.req.get_uri_args() returns a string for a single-occurrence key and a table for multiple occurrences. For a repeated field, the single-string case is assigned directly, then pb.encode rejects it (it expects an array) and the plugin returns failed to encode payload:
|
|
|
local pok, msg = pcall(pb.encode, self.endpoint.input_type, payload) |
|
if not pok or not msg then |
|
if msg then |
|
ngx.log(ngx.ERR, msg) |
|
end |
|
-- should return error msg to client? |
|
return nil, "failed to encode payload" |
With two occurrences the value is already a table, so it encodes — which is why single-vs-multiple behave differently.
Suggested fix: in add_to_table, when the resolved leaf field is repeated (descriptor label LABEL_REPEATED), normalize a non-table v to { v } (mapping each element through encode_fix) before assignment, so a single query value becomes a one-element list. This matches upstream grpc-gateway behavior.
Is there an existing issue for this?
Kong version (
$ kong version)Reproduced through the
kong/ingressHelm chart (Kong Ingress Controller). The root-cause code path is unchanged onmaster(7772bd8b3125a57fe457b9e5947faa3f3bdf59bc), so current 3.x is affected (latest release at filing: 3.9.2).Current Behavior
The
grpc-gatewayplugin returnsHTTP 400 {"message":"failed to encode payload"}when a repeated request field is supplied as a single query-parameter occurrence.For a method whose request message has e.g.
repeated Color colors = Nbound to aGETviagoogle.api.http:?colors=RED(one occurrence)failed to encode payload?colors=RED&colors=BLUE(two occurrences)200✅ (reaches upstream)?name=bolt200✅?mode=AT_MOST200✅So a repeated field is only transcodable when the client happens to pass two or more values; a single value fails. The single-value case is the common one (one selected filter), and it is deterministic — 20/20 requests returned 400 in our deployment.
Expected Behavior
A repeated field with a single query value should transcode to a one-element list (
?colors=RED→colors = [RED]), matchinggrpc-ecosystem/grpc-gatewayquery-parameter semantics.Steps To Reproduce
repro.proto:kong.yml(DB-less):Run Kong and probe:
(
repro.protoimportsgoogle/api/annotations.proto; Kong bundles the google protos on the plugin include path. If your build doesn't, mountgoogle/api/annotations.proto+google/api/http.protonext torepro.proto.)Anything else?
Root cause —
kong/plugins/grpc-gateway/deco.lua.add_to_tableassigns the query value to the leaf field with no handling for repeated fields:kong/kong/plugins/grpc-gateway/deco.lua
Lines 194 to 213 in 7772bd8
and
encode_fixonly special-casesbool:kong/kong/plugins/grpc-gateway/deco.lua
Lines 180 to 192 in 7772bd8
ngx.req.get_uri_args()returns a string for a single-occurrence key and a table for multiple occurrences. For arepeatedfield, the single-string case is assigned directly, thenpb.encoderejects it (it expects an array) and the plugin returnsfailed to encode payload:kong/kong/plugins/grpc-gateway/deco.lua
Lines 275 to 282 in 7772bd8
With two occurrences the value is already a table, so it encodes — which is why single-vs-multiple behave differently.
Suggested fix: in
add_to_table, when the resolved leaf field isrepeated(descriptor labelLABEL_REPEATED), normalize a non-tablevto{ v }(mapping each element throughencode_fix) before assignment, so a single query value becomes a one-element list. This matches upstream grpc-gateway behavior.