Skip to content

Commit

Permalink
fix(plugins): grpc-web, grpc-gateway: TE trailers
Browse files Browse the repository at this point in the history
Ensure `TE` headers is properly sent to gRPC upstream server in request
generated from Kong. Previously, the call to `kong.service.request.set_headers`
was not taking effect as the `TE` headers cannot be set through normal
OpenResty APIs; this PR ensures it's set in a similar way as the
`:authority` pseudo-header.
  • Loading branch information
gszr committed Dec 23, 2024
1 parent c77a178 commit ee9396b
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
message: "**grpc-web** and **grpc-gateway**: Fixed a bug where the `TE` (transfer-encoding) header would not be sent to the upstream gRPC servers when `grpc-web` or `grpc-gateweay` are in use."
type: bugfix
scope: Plugin
18 changes: 14 additions & 4 deletions kong/pdk/service/request.lua
Original file line number Diff line number Diff line change
Expand Up @@ -313,14 +313,24 @@ local function new(self)
-- kong.service.request.set_header("X-Foo", "value")
request.set_header = function(header, value)
check_phase(access_rewrite_balancer)

validate_header(header, value)

if string_lower(header) == "host" then
local header_lower = string_lower(header)

if header_lower == "host" then
ngx_var.upstream_host = value
end
return

elseif header_lower == "te" then
if (ngx_var.upstream_scheme == "grpc" or
ngx_var.upstream_scheme == "grpcs") and value ~= "trailers" then
return nil, "grpc requires TE to be set to trailers"
end

ngx.var.upstream_te = value
return

if string_lower(header) == ":authority" then
elseif header_lower == ":authority" then
if ngx_var.upstream_scheme == "grpc" or
ngx_var.upstream_scheme == "grpcs"
then
Expand Down
73 changes: 71 additions & 2 deletions spec/03-plugins/28-grpc-gateway/01-proxy_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,50 @@ for _, strategy in helpers.each_strategy() do
},
})

assert(helpers.start_kong {
local mock_grpc_service = assert(bp.services:insert {
name = "mock_grpc_service",
url = "http://localhost:8765",
})

local mock_grpc_route = assert(bp.routes:insert {
protocols = { "http" },
hosts = { "grpc_mock.example" },
service = mock_grpc_service,
preserve_host = true,
})

assert(bp.plugins:insert {
route = mock_grpc_route,
name = "grpc-gateway",
config = {
proto = "./spec/fixtures/grpc/targetservice.proto",
},
})

local fixtures = {
http_mock = {}
}
fixtures.http_mock.my_server_block = [[
server {
server_name myserver;
listen 8765;
location ~ / {
content_by_lua_block {
local headers = ngx.req.get_headers()
ngx.header.content_type = "application/grpc"
ngx.header.received_host = headers["Host"]
ngx.header.received_te = headers["te"]
}
}
}
]]

assert(helpers.start_kong({
database = strategy,
plugins = "bundled,grpc-gateway",
})
nginx_conf = "spec/fixtures/custom_nginx.template",
}, nil, nil, fixtures))
end)

before_each(function()
Expand All @@ -63,6 +103,35 @@ for _, strategy in helpers.each_strategy() do
helpers.stop_grpc_target()
end)

test("Sets 'TE: trailers'", function()
local res, err = proxy_client:post("/v1/echo", {
headers = {
["Host"] = "grpc_mock.example",
["Content-Type"] = "application/json",
},
})

assert.equal("trailers", res.headers["received-te"])
assert.is_nil(err)
end)

test("Ignores user-agent TE", function()
-- in grpc-gateway, kong acts as a grpc client on behalf of the client
-- (which generally is a web-browser); as such, the Te header must be
-- set by kong, which will append trailers to the response body
local res, err = proxy_client:post("/v1/echo", {
headers = {
["Host"] = "grpc_mock.example",
["Content-Type"] = "application/json",
["TE"] = "chunked",
},
})

assert.equal("trailers", res.headers["received-te"])
assert.is_nil(err)
end)


test("main entrypoint", function()
local res, err = proxy_client:get("/v1/messages/john_doe")

Expand Down
70 changes: 68 additions & 2 deletions spec/03-plugins/32-grpc-web/01-proxy_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,25 @@ for _, strategy in helpers.each_strategy() do
service = service1,
})

local mock_grpc_service = assert(bp.services:insert {
name = "mock_grpc_service",
url = "http://localhost:8765",
})

local mock_grpc_route = assert(bp.routes:insert {
protocols = { "http" },
hosts = { "grpc_mock.example" },
service = mock_grpc_service,
preserve_host = true,
})

assert(bp.plugins:insert {
route = mock_grpc_route,
name = "grpc-web",
config = {
},
})

assert(bp.plugins:insert {
route = route1,
name = "grpc-web",
Expand All @@ -66,10 +85,30 @@ for _, strategy in helpers.each_strategy() do
},
})

assert(helpers.start_kong {
local fixtures = {
http_mock = {}
}
fixtures.http_mock.my_server_block = [[
server {
server_name myserver;
listen 8765;
location ~ / {
content_by_lua_block {
local headers = ngx.req.get_headers()
ngx.header.content_type = "application/grpc"
ngx.header.received_host = headers["Host"]
ngx.header.received_te = headers["te"]
}
}
}
]]

assert(helpers.start_kong({
database = strategy,
plugins = "bundled,grpc-web",
})
nginx_conf = "spec/fixtures/custom_nginx.template",
}, nil, nil, fixtures))
end)

before_each(function()
Expand All @@ -81,6 +120,33 @@ for _, strategy in helpers.each_strategy() do
helpers.stop_kong()
end)

test("Sets 'TE: trailers'", function()
local res, err = proxy_client:post("/", {
headers = {
["Host"] = "grpc_mock.example",
["Content-Type"] = "application/grpc-web-text",
},
})

assert.equal("trailers", res.headers["received-te"])
assert.is_nil(err)
end)

test("Ignores user-agent TE", function()
-- in grpc-web, kong acts as a grpc client on behalf of the client
-- (which generally is a web-browser); as such, the Te header must be
-- set by kong, which will append trailers to the response body
local res, err = proxy_client:post("/", {
headers = {
["Host"] = "grpc_mock.example",
["Content-Type"] = "application/grpc-web-text",
["TE"] = "chunked",
},
})

assert.equal("trailers", res.headers["received-te"])
assert.is_nil(err)
end)

test("Call gRCP-base64 via HTTP", function()
local res, err = proxy_client:post("/hello.HelloService/SayHello", {
Expand Down

0 comments on commit ee9396b

Please sign in to comment.