Files
2026-07-13 12:32:21 +08:00

385 lines
11 KiB
Lua

local helpers = require "spec.helpers"
local cjson = require "cjson"
local pl_path = require "pl.path"
local FILE_LOG_PATH = os.tmpname()
local function reload_router(flavor)
helpers = require("spec.internal.module").reload_helpers(flavor)
end
-- TODO: remove it when we confirm it is not needed
local function gen_route(flavor, r)
return r
end
for _, flavor in ipairs({ "traditional", "traditional_compatible", "expressions" }) do
for _, strategy in helpers.each_strategy() do
describe("gRPC Proxying [#" .. strategy .. ", flavor = " .. flavor .. "]", function()
local proxy_client_grpc
local proxy_client_grpcs
local proxy_client
local proxy_client_ssl
local proxy_client_h2c
local proxy_client_h2
reload_router(flavor)
lazy_setup(function()
local bp = helpers.get_db_utils(strategy, {
"routes",
"services",
})
local service1 = assert(bp.services:insert {
name = "grpc",
url = helpers.grpcbin_url,
})
local service2 = assert(bp.services:insert {
name = "grpcs",
url = helpers.grpcbin_ssl_url,
})
local mock_grpc_service = assert(bp.services:insert {
name = "mock_grpc_service",
url = "grpc://localhost:8765",
})
local mock_grpc_service_retry = assert(bp.services:insert {
name = "mock_grpc_service_retry",
url = "grpc://grpc_retry",
})
local upstream_retry = assert(bp.upstreams:insert {
name = "grpc_retry",
})
assert(bp.targets:insert { -- bad target, this one will timeout
upstream = upstream_retry,
target = "127.0.0.1:54321",
})
assert(bp.targets:insert {
upstream = upstream_retry,
target = "127.0.0.1:8765",
})
assert(bp.routes:insert(gen_route(flavor, {
protocols = { "grpc" },
hosts = { "grpc" },
service = service1,
})))
assert(bp.routes:insert(gen_route(flavor, {
protocols = { "grpcs" },
hosts = { "grpcs" },
service = service2,
})))
assert(bp.routes:insert(gen_route(flavor, {
protocols = { "grpc" },
hosts = { "grpc_authority_1.example" },
service = mock_grpc_service,
preserve_host = true,
})))
assert(bp.routes:insert(gen_route(flavor, {
protocols = { "grpc" },
hosts = { "grpc_authority_2.example" },
service = mock_grpc_service,
preserve_host = false,
})))
assert(bp.routes:insert(gen_route(flavor, {
protocols = { "grpc" },
hosts = { "grpc_authority_retry.example" },
service = mock_grpc_service_retry,
preserve_host = false,
})))
assert(bp.plugins:insert {
service = mock_grpc_service_retry,
name = "file-log",
config = {
path = FILE_LOG_PATH,
reopen = true,
},
})
local fixtures = {
http_mock = {}
}
fixtures.http_mock.my_server_block = [[
server {
server_name myserver;
listen 8765;
http2 on;
location ~ / {
content_by_lua_block {
ngx.header.content_type = "application/grpc"
ngx.header.received_host = ngx.req.get_headers()["Host"]
}
}
}
]]
assert(helpers.start_kong({
router_flavor = flavor,
database = strategy,
nginx_conf = "spec/fixtures/custom_nginx.template",
}, nil, nil, fixtures))
proxy_client_grpc = helpers.proxy_client_grpc()
proxy_client_grpcs = helpers.proxy_client_grpcs()
proxy_client_h2c = helpers.proxy_client_h2c()
proxy_client_h2 = helpers.proxy_client_h2()
proxy_client = helpers.proxy_client()
proxy_client_ssl = helpers.proxy_ssl_client()
end)
before_each(function()
os.remove(FILE_LOG_PATH)
end)
lazy_teardown(function()
helpers.stop_kong()
end)
it("proxies grpc", function()
local ok, resp = assert(proxy_client_grpc({
service = "hello.HelloService.SayHello",
body = {
greeting = "world!"
},
opts = {
["-authority"] = "grpc",
}
}))
assert.truthy(ok)
assert.truthy(resp)
end)
it("proxies grpc, streaming response", function()
local ok, resp = assert(proxy_client_grpc({
service = "hello.HelloService.LotsOfReplies",
body = {
greeting = "world!"
},
opts = {
["-authority"] = "grpc",
}
}))
assert.truthy(ok)
assert.truthy(resp)
end)
it("proxies grpc, streaming request", function()
local ok, resp = assert(proxy_client_grpc({
service = "hello.HelloService.LotsOfGreetings",
body = [[
{ "greeting": "world!" }
{ "greeting": "people!" }
{ "greeting": "y`all!" }
]],
opts = {
["-authority"] = "grpc",
}
}))
assert.truthy(ok)
assert.truthy(resp)
end)
it("proxies grpc, streaming request/response", function()
local ok, resp = assert(proxy_client_grpc({
service = "hello.HelloService.BidiHello",
body = [[
{ "greeting": "world!" }
{ "greeting": "people!" }
{ "greeting": "y`all!" }
]],
opts = {
["-authority"] = "grpc",
}
}))
assert.truthy(ok)
assert.truthy(resp)
end)
it("proxies grpcs", function()
local ok, resp = assert(proxy_client_grpcs({
service = "hello.HelloService.SayHello",
body = {
greeting = "world!"
},
opts = {
["-authority"] = "grpcs",
}
}))
assert.truthy(ok)
assert.truthy(resp)
end)
it("proxies :authority header if `preserve_host` is set", function()
local _, resp = proxy_client_grpc({
service = "hello.HelloService.SayHello",
body = {
greeting = "world!"
},
opts = {
["-authority"] = "grpc_authority_1.example",
["-v"] = true,
}
})
assert.matches("received%-host: grpc_authority_1.example", resp)
end)
it("sets default :authority header if `preserve_host` isn't set", function()
local _, resp = proxy_client_grpc({
service = "hello.HelloService.SayHello",
body = {
greeting = "world!"
},
opts = {
["-authority"] = "grpc_authority_2.example",
["-v"] = true,
}
})
assert.matches("received%-host: localhost:8765", resp)
end)
it("proxies :authority header on balancer retry", function()
local resp
local file_log_json
helpers.wait_until(function()
os.remove(FILE_LOG_PATH)
local _
_, resp = proxy_client_grpc({
service = "hello.HelloService.SayHello",
body = {
greeting = "world!"
},
opts = {
["-authority"] = "grpc_authority_retry.example",
["-v"] = true,
}
})
local f = io.open(FILE_LOG_PATH, 'r')
if not f then
return false
end
file_log_json = cjson.decode((assert(f:read("*a"))))
f:close()
return pl_path.exists(FILE_LOG_PATH) and pl_path.getsize(FILE_LOG_PATH) > 0
and #file_log_json.tries >= 2
end, 5)
assert.matches("received%-host: 127.0.0.1:8765", resp)
end)
describe("errors with", function()
it("non-http2 request on grpc route", function()
local res = assert(proxy_client:post("/", {
headers = {
["Host"] = "grpc",
["Content-Type"] = "application/grpc"
}
}))
local body = assert.res_status(426, res)
local json = cjson.decode(body)
assert.equal("Please use HTTP2 protocol", json.message)
assert.contains("Upgrade", res.headers.connection)
assert.same("HTTP/2", res.headers["upgrade"])
end)
it("non-http2 request on grpcs route", function()
local res = assert(proxy_client_ssl:post("/", {
headers = {
["Host"] = "grpcs",
["Content-Type"] = "application/grpc"
}
}))
local body = assert.res_status(426, res)
local json = cjson.decode(body)
assert.equal("Please use HTTP2 protocol", json.message)
end)
it("non-grpc request on grpc route (no content-type)", function()
local body, headers = proxy_client_h2c({
headers = {
["method"] = "POST",
[":authority"] = "grpc",
}
})
local json = cjson.decode(body)
assert.same("415", headers:get(":status"))
assert.same("Non-gRPC request matched gRPC route", json.message)
end)
it("non-grpc request on grpcs route (no content-type)", function()
local body, headers = proxy_client_h2({
headers = {
["method"] = "POST",
[":authority"] = "grpcs",
}
})
local json = cjson.decode(body)
assert.same("415", headers:get(":status"))
assert.same("Non-gRPC request matched gRPC route", json.message)
end)
it("non-grpc request on grpc route (non-grpc content-type)", function()
local body, headers = proxy_client_h2c({
headers = {
["method"] = "POST",
["content-type"] = "application/json",
[":authority"] = "grpc",
}
})
local json = cjson.decode(body)
assert.same("415", headers:get(":status"))
assert.same("Non-gRPC request matched gRPC route", json.message)
end)
it("non-grpc request on grpcs route (non-grpc content-type)", function()
local body, headers = proxy_client_h2({
headers = {
["method"] = "POST",
["content-type"] = "application/json",
[":authority"] = "grpcs",
}
})
local json = cjson.decode(body)
assert.same("415", headers:get(":status"))
assert.same("Non-gRPC request matched gRPC route", json.message)
end)
it("grpc on grpcs route", function()
local ok, resp = proxy_client_grpc({
service = "hello.HelloService.SayHello",
body = {
greeting = "world!"
},
opts = {
["-authority"] = "grpcs",
}
})
assert.falsy(ok)
assert.matches("Code: Canceled", resp, nil, true)
assert.matches("Message: gRPC request matched gRPCs route", resp, nil, true)
end)
end)
end)
end
end -- flavor