kong/spec/02-integration/05-proxy/02-router_spec.lua (2,027 lines of code) (raw):

local admin_api = require "spec.fixtures.admin_api" local helpers = require "spec.helpers" local cjson = require "cjson" local path_handling_tests = require "spec.fixtures.router_path_handling_tests" local tonumber = tonumber local enable_buffering local enable_buffering_plugin local stream_tls_listen_port = 9020 local function insert_routes(bp, routes) if type(bp) ~= "table" then return error("expected arg #1 to be a table", 2) end if type(routes) ~= "table" then return error("expected arg #2 to be a table", 2) end if not bp.done then -- strategy ~= "off" bp = admin_api end enable_buffering_plugin = bp.plugins:insert({ name = "enable-buffering", protocols = { "http", "https", "grpc", "grpcs" }, service = ngx.null, consumer = ngx.null, route = ngx.null, }) for i = 1, #routes do local route = routes[i] local service if route.service == ngx.null then service = route.service else service = route.service or {} if not service.host then service.host = helpers.mock_upstream_host end if not service.port then service.port = helpers.mock_upstream_port end if not service.protocol then service.protocol = helpers.mock_upstream_protocol end service = bp.named_services:insert(service) end route.service = service if not route.protocols then route.protocols = { "http" } end route.service = service route = bp.routes:insert(route) route.service = service routes[i] = route end if bp.done then local declarative = require "kong.db.declarative" local cfg = bp.done() local yaml = declarative.to_yaml_string(cfg) local admin_client = helpers.admin_client() local res = assert(admin_client:send { method = "POST", path = "/config", body = { config = yaml, }, headers = { ["Content-Type"] = "multipart/form-data", } }) assert.res_status(201, res) admin_client:close() end ngx.sleep(0.5) -- temporary wait for worker events and timers return routes end local function remove_routes(strategy, routes) if strategy == "off" or not routes then return end local services = {} for _, route in ipairs(routes) do if route.service ~= ngx.null then local sid = route.service.id if not services[sid] then services[sid] = route.service table.insert(services, services[sid]) end end end for _, route in ipairs(routes) do admin_api.routes:remove({ id = route.id }) end for _, service in ipairs(services) do admin_api.services:remove(service) end admin_api.plugins:remove(enable_buffering_plugin) end --for _, flavor in ipairs({ "traditional", "traditional_compatible" }) do for _, flavor in ipairs({ "traditional", "traditional_compatible" }) do for _, b in ipairs({ false, true }) do enable_buffering = b for _, strategy in helpers.each_strategy() do describe("Router [#" .. strategy .. ", flavor = " .. flavor .. "] with buffering [" .. (b and "on]" or "off]") , function() local proxy_client local proxy_ssl_client local bp local it_trad_only = (flavor == "traditional") and it or pending lazy_setup(function() local fixtures = { dns_mock = helpers.dns_mock.new() } fixtures.dns_mock:A { name = "grpcs_1.test", address = "127.0.0.1", } fixtures.dns_mock:A { name = "grpcs_2.test", address = "127.0.0.1", } bp = helpers.get_db_utils(strategy, { "routes", "services", "plugins", }, { "enable-buffering", }) assert(helpers.start_kong({ router_flavor = flavor, database = strategy, plugins = "bundled,enable-buffering", nginx_conf = "spec/fixtures/custom_nginx.template", stream_listen = string.format("127.0.0.1:%d ssl", stream_tls_listen_port), }, nil, nil, fixtures)) end) lazy_teardown(function() helpers.stop_kong() end) before_each(function() proxy_client = helpers.proxy_client() proxy_ssl_client = helpers.proxy_ssl_client() end) after_each(function() if proxy_client then proxy_client:close() end if proxy_ssl_client then proxy_ssl_client:close() end end) describe("no routes match", function() it("responds 404 if no route matches", function() local res = assert(proxy_client:send { method = "GET", headers = { host = "inexistent.com" } }) local body = assert.response(res).has_status(404) local json = cjson.decode(body) assert.matches("^kong/", res.headers.server) assert.equal("no Route matched with those values", json.message) end) end) describe("use-cases", function() local routes local first_service_name lazy_setup(function() routes = insert_routes(bp, { { methods = { "GET" }, protocols = { "http" }, strip_path = false, }, { methods = { "POST", "PUT" }, paths = { "/post", "/put" }, protocols = { "http" }, strip_path = false, }, { paths = { "/mock_upstream" }, protocols = { "http" }, strip_path = true, service = { path = "/status", }, }, { paths = { "/private" }, protocols = { "http" }, strip_path = false, service = { path = "/basic-auth/", }, }, { paths = { [[~/users/\d+/profile]] }, protocols = { "http" }, strip_path = true, service = { path = "/anything", }, }, { protocols = { "http", "https" }, hosts = { "serviceless-route-http.test" }, service = ngx.null, }, { paths = { "/disabled-service1" }, protocols = { "http" }, strip_path = false, service = { path = "/disabled-service-path/", enabled = false, }, }, { paths = { [[~/enabled-service/\w+]] }, protocols = { "http" }, strip_path = true, service = { path = "/anything/", enabled = true, name = "enabled-service", }, }, { paths = { "/enabled-service/disabled" }, protocols = { "http" }, strip_path = true, service = { path = "/some-path/", enabled = false, }, }, }) first_service_name = routes[1].service.name end) lazy_teardown(function() remove_routes(strategy, routes) end) it("responds 503 if no service found", function() local res, body helpers.wait_until(function() res = assert(proxy_client:get("/", { headers = { Host = "serviceless-route-http.test", }, })) return pcall(function() body = assert.response(res).has_status(503) end) end, 10) local json = cjson.decode(body) assert.equal("no Service found with those values", json.message) local res = assert(proxy_ssl_client:get("/", { headers = { Host = "serviceless-route-http.test", }, })) local body = assert.response(res).has_status(503) local json = cjson.decode(body) assert.equal("no Service found with those values", json.message) end) it("restricts an route to its methods if specified", function() -- < HTTP/1.1 POST /post -- > 200 OK local res = assert(proxy_client:send { method = "POST", path = "/post", headers = { ["kong-debug"] = 1 }, }) assert.response(res).has_status(200) assert.equal(routes[2].id, res.headers["kong-route-id"]) assert.equal(routes[2].service.id, res.headers["kong-service-id"]) assert.equal(routes[2].service.name, res.headers["kong-service-name"]) -- < HTTP/1.1 DELETE /post -- > 404 NOT FOUND res = assert(proxy_client:send { method = "DELETE", path = "/post", headers = { ["kong-debug"] = 1 }, }) assert.response(res).has_status(404) assert.is_nil(res.headers["kong-route-id"]) assert.is_nil(res.headers["kong-service-id"]) assert.is_nil(res.headers["kong-service-name"]) end) it("routes by method-only if no other match is found", function() local res = assert(proxy_client:send { method = "GET", path = "/get", headers = { ["kong-debug"] = 1 }, }) assert.response(res).has_status(200) assert.equal(routes[1].id, res.headers["kong-route-id"]) assert.equal(routes[1].service.id, res.headers["kong-service-id"]) assert.equal(routes[1].service.name, res.headers["kong-service-name"]) end) describe("requests without Host header", function() it("HTTP/1.0 routes normally", function() -- a very limited HTTP client for sending requests without Host -- header local sock = ngx.socket.tcp() finally(function() sock:close() end) assert(sock:connect(helpers.get_proxy_ip(), helpers.get_proxy_port())) local req = "GET /get HTTP/1.0\r\nKong-Debug: 1\r\n\r\n" assert(sock:send(req)) local line = assert(sock:receive("*l")) local status = tonumber(string.sub(line, 10, 12)) assert.equal(200, status) -- TEST: we matched an API that had no Host header defined local remainder = assert(sock:receive("*a")) assert.matches("kong-service-name: " .. first_service_name, string.lower(remainder), nil, true) end) it("HTTP/1.1 is rejected by NGINX", function() local sock = ngx.socket.tcp() finally(function() sock:close() end) assert(sock:connect(helpers.get_proxy_ip(), helpers.get_proxy_port())) local req = "GET /get HTTP/1.1\r\nKong-Debug: 1\r\n\r\n" assert(sock:send(req)) -- TEST: NGINX rejected this request local line = assert(sock:receive("*l")) local status = tonumber(string.sub(line, 10, 12)) assert.equal(400, status) -- TEST: we ensure that Kong catches this error and -- produces the response from its own error handler local remainder = assert(sock:receive("*a")) assert.matches("Bad request", remainder, nil, true) assert.matches("Server: kong/", remainder, nil, true) end) end) describe("route with a path component in its upstream_url", function() it("with strip_path = true", function() local res = assert(proxy_client:send { method = "GET", path = "/mock_upstream/201", headers = { ["kong-debug"] = 1 }, }) assert.res_status(201, res) assert.equal(routes[3].id, res.headers["kong-route-id"]) assert.equal(routes[3].service.id, res.headers["kong-service-id"]) assert.equal(routes[3].service.name, res.headers["kong-service-name"]) end) end) it("route with a path component in its upstream_url and strip_path = false", function() local res = assert(proxy_client:send { method = "GET", path = "/private/passwd", headers = { ["kong-debug"] = 1 }, }) assert.res_status(401, res) assert.equal(routes[4].id, res.headers["kong-route-id"]) assert.equal(routes[4].service.id, res.headers["kong-service-id"]) assert.equal(routes[4].service.name, res.headers["kong-service-name"]) end) it("route with a path component in its upstream_url and [uri] with a regex", function() local res = assert(proxy_client:send { method = "GET", path = "/users/foo/profile", headers = { ["kong-debug"] = 1 }, }) assert.res_status(404, res) res = assert(proxy_client:send { method = "GET", path = "/users/123/profile", headers = { ["kong-debug"] = 1 }, }) assert.res_status(200, res) assert.equal(routes[5].id, res.headers["kong-route-id"]) assert.equal(routes[5].service.id, res.headers["kong-service-id"]) assert.equal(routes[5].service.name, res.headers["kong-service-name"]) end) describe('handles not enabled services', function() it('ignores route where service enabled=false', function() local res = assert(proxy_client:send { method = "GET", path = "/disabled-service1", headers = { ["kong-debug"] = 1 }, }) assert.res_status(404, res) end) it('routes to regex path when longer path service enabled=false', function() local res = assert(proxy_client:send { method = "GET", path = "/enabled-service/disabled", headers = { ["kong-debug"] = 1 }, }) assert.res_status(200, res) assert.equal(routes[8].id, res.headers["kong-route-id"]) assert.equal(routes[8].service.id, res.headers["kong-service-id"]) assert.equal("enabled-service", res.headers["kong-service-name"]) end) end) end) if not enable_buffering then describe("use cases #grpc", function() local routes local service = { url = helpers.grpcbin_url, } local proxy_client_grpc local proxy_client_grpcs lazy_setup(function() routes = insert_routes(bp, { { protocols = { "grpc", "grpcs" }, hosts = { "grpc1", "grpc1:" .. helpers.get_proxy_port(false, true), "grpc1:" .. helpers.get_proxy_port(true, true), }, service = service, }, { protocols = { "grpc", "grpcs" }, hosts = { "grpc2", "grpc2:" .. helpers.get_proxy_port(false, true), "grpc2:" .. helpers.get_proxy_port(true, true), }, service = service, }, { protocols = { "grpc", "grpcs" }, paths = { "/hello.HelloService/SayHello" }, service = service, }, { protocols = { "grpc", "grpcs" }, paths = { "/hello.HelloService/LotsOfReplies" }, service = service, }, { protocols = { "grpc", "grpcs" }, hosts = { "*.grpc.com" }, service = service, }, { protocols = { "grpc", "grpcs" }, hosts = { "serviceless-route-grpc.test" }, service = ngx.null, } }) proxy_client_grpc = helpers.proxy_client_grpc() proxy_client_grpcs = helpers.proxy_client_grpcs() end) lazy_teardown(function() remove_routes(strategy, routes) end) it("responds 503 if no service found", function() local ok, resp = proxy_client_grpc({ service = "hello.HelloService.SayHello", body = { greeting = "world!" }, opts = { ["-v"] = true, ["-H"] = "'kong-debug: 1'", ["-authority"] = "serviceless-route-grpc.test", } }) assert.falsy(ok) assert.equal("ERROR:\n Code: Unavailable\n Message: no Service found with those values\n", resp) local ok, resp = proxy_client_grpcs({ service = "hello.HelloService.SayHello", body = { greeting = "world!" }, opts = { ["-v"] = true, ["-H"] = "'kong-debug: 1'", ["-authority"] = "serviceless-route-grpc.test", } }) assert.falsy(ok) assert.equal("ERROR:\n Code: Unavailable\n Message: no Service found with those values\n", resp) end) it("restricts a route to its 'hosts' if specified", function() local ok, resp = proxy_client_grpc({ service = "hello.HelloService.SayHello", body = { greeting = "world!" }, opts = { ["-v"] = true, ["-H"] = "'kong-debug: 1'", ["-authority"] = "grpc1", } }) assert.truthy(ok) assert.truthy(resp) assert.matches("kong-route-id: " .. routes[1].id, resp, nil, true) ok, resp = proxy_client_grpc({ service = "hello.HelloService.SayHello", body = { greeting = "world!" }, opts = { ["-v"] = true, ["-H"] = "'kong-debug: 1'", ["-authority"] = "grpc2", } }) assert.truthy(ok) assert.truthy(resp) assert.matches("kong-route-id: " .. routes[2].id, resp, nil, true) end) it("restricts a route to its 'hosts' if specified (grpcs)", function() local ok, resp = proxy_client_grpcs({ service = "hello.HelloService.SayHello", body = { greeting = "world!" }, opts = { ["-v"] = true, ["-H"] = "'kong-debug: 1'", ["-authority"] = "grpc1", } }) assert.truthy(ok) assert.truthy(resp) assert.matches("kong-route-id: " .. routes[1].id, resp, nil, true) ok, resp = proxy_client_grpc({ service = "hello.HelloService.SayHello", body = { greeting = "world!" }, opts = { ["-v"] = true, ["-H"] = "'kong-debug: 1'", ["-authority"] = "grpc2", } }) assert.truthy(ok) assert.truthy(resp) assert.matches("kong-route-id: " .. routes[2].id, resp, nil, true) end) it("restricts a route to its wildcard 'hosts' if specified", function() local ok, resp = proxy_client_grpc({ service = "hello.HelloService.SayHello", body = { greeting = "world!" }, opts = { ["-v"] = true, ["-H"] = "'kong-debug: 1'", ["-authority"] = "service1.grpc.com", } }) assert.truthy(ok) assert.truthy(resp) assert.matches("kong-route-id: " .. routes[5].id, resp, nil, true) end) it("restricts a route to its wildcard 'hosts' if specified (grpcs)", function() local ok, resp = proxy_client_grpcs({ service = "hello.HelloService.SayHello", body = { greeting = "world!" }, opts = { ["-v"] = true, ["-H"] = "'kong-debug: 1'", ["-authority"] = "service1.grpc.com", } }) assert.truthy(ok) assert.truthy(resp) assert.matches("kong-route-id: " .. routes[5].id, resp, nil, true) end) it("restricts a route to its 'paths' if specified", function() local ok, resp = proxy_client_grpc({ service = "hello.HelloService.SayHello", body = { greeting = "world!" }, opts = { ["-v"] = true, ["-H"] = "'kong-debug: 1'", } }) assert.truthy(ok) assert.truthy(resp) assert.matches("kong-route-id: " .. routes[3].id, resp, nil, true) ok, resp = proxy_client_grpcs({ service = "hello.HelloService.LotsOfReplies", body = { greeting = "world!" }, opts = { ["-v"] = true, ["-H"] = "'kong-debug: 1'", } }) assert.truthy(ok) assert.truthy(resp) assert.matches("kong-route-id: " .. routes[4].id, resp, nil, true) end) it("restricts a route to its 'paths' if specified (grpcs)", function() local ok, resp = proxy_client_grpcs({ service = "hello.HelloService.SayHello", body = { greeting = "world!" }, opts = { ["-v"] = true, ["-H"] = "'kong-debug: 1'", } }) assert.truthy(ok) assert.truthy(resp) assert.matches("kong-route-id: " .. routes[3].id, resp, nil, true) ok, resp = proxy_client_grpcs({ service = "hello.HelloService.LotsOfReplies", body = { greeting = "world!" }, opts = { ["-v"] = true, ["-H"] = "'kong-debug: 1'", } }) assert.truthy(ok) assert.truthy(resp) assert.matches("kong-route-id: " .. routes[4].id, resp, nil, true) end) end) end -- not enable_buffering describe("URI regexes order of evaluation with created_at", function() local routes lazy_setup(function() routes = insert_routes(bp, { { created_at = 1234567890, strip_path = true, paths = { "~/status/(re)" }, service = { name = "regex_1", path = "/status/200", }, }, { created_at = 1234567891, strip_path = true, paths = { "~/status/(r)" }, service = { name = "regex_2", path = "/status/200", }, }, { created_at = 1234567892, strip_path = true, paths = { "/status" }, service = { name = "regex_3", path = "/status/200", }, } }) end) lazy_teardown(function() remove_routes(strategy, routes) end) it_trad_only("depends on created_at field", function() local res = assert(proxy_client:send { method = "GET", path = "/status/r", headers = { ["kong-debug"] = 1 }, }) assert.res_status(200, res) assert.equal(routes[2].id, res.headers["kong-route-id"]) assert.equal(routes[2].service.id, res.headers["kong-service-id"]) assert.equal(routes[2].service.name, res.headers["kong-service-name"]) res = assert(proxy_client:send { method = "GET", path = "/status/re", headers = { ["kong-debug"] = 1 }, }) assert.res_status(200, res) assert.equal(routes[1].id, res.headers["kong-route-id"]) assert.equal(routes[1].service.id, res.headers["kong-service-id"]) assert.equal(routes[1].service.name, res.headers["kong-service-name"]) end) end) describe("URI regexes order of evaluation with regex_priority", function() local routes lazy_setup(function() routes = insert_routes(bp, { -- TEST 1 (regex_priority) { strip_path = true, paths = { "~/status/(?P<foo>re)" }, service = { name = "regex_1", path = "/status/200", }, regex_priority = 0, }, { strip_path = true, paths = { "~/status/(re)" }, service = { name = "regex_2", path = "/status/200", }, regex_priority = 4, -- shadows service which is created before and is shorter }, -- TEST 2 (tie breaker by created_at) { created_at = 1234567890, strip_path = true, paths = { "~/status/(ab)" }, service = { name = "regex_3", path = "/status/200", }, regex_priority = 0, }, { created_at = 1234567891, strip_path = true, paths = { "~/status/(ab)c?" }, service = { name = "regex_4", path = "/status/200", }, regex_priority = 0, }, }) end) lazy_teardown(function() remove_routes(strategy, routes) end) it("depends on the regex_priority field", function() local res = assert(proxy_client:send { method = "GET", path = "/status/re", headers = { ["kong-debug"] = 1 }, }) assert.res_status(200, res) assert.equal("regex_2", res.headers["kong-service-name"]) end) it_trad_only("depends on created_at if regex_priority is tie", function() local res = assert(proxy_client:send { method = "GET", path = "/status/ab", headers = { ["kong-debug"] = 1 }, }) assert.res_status(200, res) assert.equal("regex_3", res.headers["kong-service-name"]) end) end) describe("URI arguments (querystring)", function() local routes lazy_setup(function() routes = insert_routes(bp, { { hosts = { "mock_upstream" }, }, }) end) lazy_teardown(function() remove_routes(strategy, routes) end) it("preserves URI arguments", function() local res = assert(proxy_client:send { method = "GET", path = "/get", query = { foo = "bar", hello = "world", }, headers = { ["Host"] = "mock_upstream", }, }) local body = assert.res_status(200, res) local json = cjson.decode(body) assert.equal("bar", json.uri_args.foo) assert.equal("world", json.uri_args.hello) end) it("does proxy an empty querystring if URI does not contain arguments", function() local res = assert(proxy_client:send { method = "GET", path = "/request?", headers = { ["Host"] = "mock_upstream", }, }) local body = assert.res_status(200, res) local json = cjson.decode(body) assert.matches("/request%?$", json.vars.request_uri) end) it("does proxy a querystring with an empty value", function() local res = assert(proxy_client:send { method = "GET", path = "/get?hello", headers = { ["Host"] = "mock_upstream", }, }) local body = assert.res_status(200, res) local json = cjson.decode(body) assert.matches("/get%?hello$", json.url) end) end) describe("URI normalization", function() local routes lazy_setup(function() routes = insert_routes(bp, { { strip_path = true, paths = { "/foo/bar" }, }, { strip_path = true, paths = { "/hello" }, }, { strip_path = false, paths = { "/anything/world" }, }, }) end) lazy_teardown(function() remove_routes(strategy, routes) end) it("matches against normalized URI with \"..\"", function() local res = assert(proxy_client:send { method = "GET", path = "/foo/a/../bar", headers = { ["kong-debug"] = 1 }, }) local body = assert.res_status(200, res) body = cjson.decode(body) assert.equal(routes[1].id, res.headers["kong-route-id"]) assert.equal(routes[1].service.id, res.headers["kong-service-id"]) assert.equal(routes[1].service.name, res.headers["kong-service-name"]) assert.equal("/foo/a/../bar", body.headers["x-forwarded-path"]) end) it("matches against normalized URI with \"//\"", function() local res = assert(proxy_client:send { method = "GET", path = "/foo//bar", headers = { ["kong-debug"] = 1 }, }) local body = assert.res_status(200, res) body = cjson.decode(body) assert.equal(routes[1].id, res.headers["kong-route-id"]) assert.equal(routes[1].service.id, res.headers["kong-service-id"]) assert.equal(routes[1].service.name, res.headers["kong-service-name"]) assert.equal("/foo//bar", body.headers["x-forwarded-path"]) end) it("matches against normalized URI with \"/./\"", function() local res = assert(proxy_client:send { method = "GET", path = "/foo/./bar", headers = { ["kong-debug"] = 1 }, }) local body = assert.res_status(200, res) body = cjson.decode(body) assert.equal(routes[1].id, res.headers["kong-route-id"]) assert.equal(routes[1].service.id, res.headers["kong-service-id"]) assert.equal(routes[1].service.name, res.headers["kong-service-name"]) assert.equal("/foo/./bar", body.headers["x-forwarded-path"]) end) it("matches against normalized URI with percent-encoded characters", function() local res = assert(proxy_client:send { method = "GET", path = "/h%65llo", headers = { ["kong-debug"] = 1 }, }) local body = assert.res_status(200, res) body = cjson.decode(body) assert.equal(routes[2].id, res.headers["kong-route-id"]) assert.equal(routes[2].service.id, res.headers["kong-service-id"]) assert.equal(routes[2].service.name, res.headers["kong-service-name"]) assert.equal("/h%65llo", body.headers["x-forwarded-path"]) end) it("proxies normalized URI to upstream with strip_path = true", function() local res = assert(proxy_client:send { method = "GET", path = "/h%65llo/anything/a/../b", headers = { ["kong-debug"] = 1 }, }) local body = assert.res_status(200, res) body = cjson.decode(body) assert.equal(routes[2].id, res.headers["kong-route-id"]) assert.equal(routes[2].service.id, res.headers["kong-service-id"]) assert.equal(routes[2].service.name, res.headers["kong-service-name"]) assert.equal("/anything/b", body.vars.request_uri) end) it("proxies normalized URI to upstream with strip_path = false", function() local res = assert(proxy_client:send { method = "GET", path = "/anything/./a/../wor%6cd/a/../b", headers = { ["kong-debug"] = 1 }, }) local body = assert.res_status(200, res) body = cjson.decode(body) assert.equal(routes[3].id, res.headers["kong-route-id"]) assert.equal(routes[3].service.id, res.headers["kong-service-id"]) assert.equal(routes[3].service.name, res.headers["kong-service-name"]) assert.equal("/anything/world/b", body.vars.request_uri) end) it("re-encode special characters in request uri when proxying to the upstream", function() local res = assert(proxy_client:send { method = "GET", path = "/anything/world/cat%20and%20dog", headers = { ["kong-debug"] = 1 }, }) local body = assert.res_status(200, res) body = cjson.decode(body) assert.equal(routes[3].id, res.headers["kong-route-id"]) assert.equal(routes[3].service.id, res.headers["kong-service-id"]) assert.equal(routes[3].service.name, res.headers["kong-service-name"]) assert.equal("/anything/world/cat%20and%20dog", body.vars.request_uri) end) end) describe("strip_path", function() local routes lazy_setup(function() routes = insert_routes(bp, { { paths = { "/x/y/z", "/z/y/x" }, strip_path = true, }, }) end) lazy_teardown(function() remove_routes(strategy, routes) end) describe("= true", function() it("strips subsequent calls to an route with different [paths]", function() local res_uri_1 = assert(proxy_client:send { method = "GET", path = "/x/y/z/get", }) local body = assert.res_status(200, res_uri_1) local json = cjson.decode(body) assert.matches("/get", json.url, nil, true) assert.not_matches("/x/y/z/get", json.url, nil, true) local res_uri_2 = assert(proxy_client:send { method = "GET", path = "/z/y/x/get", }) body = assert.res_status(200, res_uri_2) json = cjson.decode(body) assert.matches("/get", json.url, nil, true) assert.not_matches("/z/y/x/get", json.url, nil, true) local res_2_uri_1 = assert(proxy_client:send { method = "GET", path = "/x/y/z/get", }) body = assert.res_status(200, res_2_uri_1) json = cjson.decode(body) assert.matches("/get", json.url, nil, true) assert.not_matches("/x/y/z/get", json.url, nil, true) local res_2_uri_2 = assert(proxy_client:send { method = "GET", path = "/x/y/z/get", }) body = assert.res_status(200, res_2_uri_2) json = cjson.decode(body) assert.matches("/get", json.url, nil, true) assert.not_matches("/x/y/z/get", json.url, nil, true) end) end) end) describe("preserve_host", function() local routes lazy_setup(function() routes = insert_routes(bp, { { preserve_host = true, hosts = { "preserved.com", "preserved.com:123" }, service = { path = "/request" }, }, { preserve_host = false, hosts = { "discarded.com" }, service = { path = "/request" }, }, { strip_path = false, preserve_host = true, paths = { "/request" }, } }) end) lazy_teardown(function() remove_routes(strategy, routes) end) describe("x = false (default)", function() it("uses hostname from upstream_url", function() local res = assert(proxy_client:send { method = "GET", path = "/get", headers = { ["Host"] = "discarded.com" }, }) local body = assert.res_status(200, res) local json = cjson.decode(body) assert.matches(helpers.mock_upstream_host, json.headers.host, nil, true) -- not testing :port end) it("uses port value from upstream_url if not default", function() local res = assert(proxy_client:send { method = "GET", path = "/get", headers = { ["Host"] = "discarded.com" }, }) local body = assert.res_status(200, res) local json = cjson.decode(body) assert.matches(":" .. helpers.mock_upstream_port, json.headers.host, nil, true) -- not testing hostname end) end) describe("= true", function() it("forwards request Host", function() local res = assert(proxy_client:send { method = "GET", path = "/", headers = { ["Host"] = "preserved.com" }, }) local body = assert.res_status(200, res) local json = cjson.decode(body) assert.equal("preserved.com", json.headers.host) end) it_trad_only("forwards request Host:Port even if port is default", function() local res = assert(proxy_client:send { method = "GET", path = "/get", headers = { ["Host"] = "preserved.com:80" }, }) local body = assert.res_status(200, res) local json = cjson.decode(body) assert.equal("preserved.com:80", json.headers.host) end) it("forwards request Host:Port if port isn't default", function() local res = assert(proxy_client:send { method = "GET", path = "/get", headers = { ["Host"] = "preserved.com:123" }, }) local body = assert.res_status(200, res) local json = cjson.decode(body) assert.equal("preserved.com:123", json.headers.host) end) it("forwards request Host even if not matched by [hosts]", function() local res = assert(proxy_client:send { method = "GET", path = "/get", headers = { ["Host"] = "preserved.com" }, }) local body = assert.res_status(200, res) local json = cjson.decode(body) assert.equal("preserved.com", json.headers.host) end) end) end) describe("edge-cases", function() local routes lazy_setup(function() routes = insert_routes(bp, { { strip_path = true, paths = { "/" }, }, { strip_path = true, paths = { "/foobar" }, }, }) end) lazy_teardown(function() remove_routes(strategy, routes) end) it("root / [uri] for a catch-all rule", function() local res = assert(proxy_client:send { method = "GET", path = "/get", headers = { ["kong-debug"] = 1 } }) assert.response(res).has_status(200) assert.equal(routes[1].id, res.headers["kong-route-id"]) assert.equal(routes[1].service.id, res.headers["kong-service-id"]) assert.equal(routes[1].service.name, res.headers["kong-service-name"]) res = assert(proxy_client:send { method = "GET", path = "/foobar/get", headers = { ["kong-debug"] = 1 } }) assert.response(res).has_status(200) assert.equal(routes[2].id, res.headers["kong-route-id"]) assert.equal(routes[2].service.id, res.headers["kong-service-id"]) assert.equal(routes[2].service.name, res.headers["kong-service-name"]) end) end) describe("[snis] for HTTPs connections", function() local routes local proxy_ssl_client lazy_setup(function() routes = insert_routes(bp, { { protocols = { "https" }, snis = { "www.example.org" }, service = { name = "service_behind_www.example.org" }, }, { protocols = { "https" }, snis = { "example.org" }, service = { name = "service_behind_example.org" }, }, }) end) lazy_teardown(function() remove_routes(strategy, routes) end) after_each(function() if proxy_ssl_client then proxy_ssl_client:close() end end) it("matches a Route based on its 'snis' attribute", function() proxy_ssl_client = helpers.proxy_ssl_client(nil, "www.example.org") local res = assert(proxy_ssl_client:send { method = "GET", path = "/status/200", headers = { ["kong-debug"] = 1 }, }) assert.res_status(200, res) assert.equal("service_behind_www.example.org", res.headers["kong-service-name"]) res = assert(proxy_ssl_client:send { method = "GET", path = "/status/201", headers = { ["kong-debug"] = 1 }, }) assert.res_status(201, res) assert.equal("service_behind_www.example.org", res.headers["kong-service-name"]) proxy_ssl_client:close() proxy_ssl_client = helpers.proxy_ssl_client(nil, "example.org") local res = assert(proxy_ssl_client:send { method = "GET", path = "/status/200", headers = { ["kong-debug"] = 1 }, }) assert.res_status(200, res) assert.equal("service_behind_example.org", res.headers["kong-service-name"]) res = assert(proxy_ssl_client:send { method = "GET", path = "/status/201", headers = { ["kong-debug"] = 1 }, }) assert.res_status(201, res) assert.equal("service_behind_example.org", res.headers["kong-service-name"]) end) end) describe("tls_passthrough", function() local routes local proxy_ssl_client lazy_setup(function() routes = insert_routes(bp, { { protocols = { "tls_passthrough" }, snis = { "www.example.org" }, service = { name = "service_behind_www.example.org", host = helpers.mock_upstream_ssl_host, port = helpers.mock_upstream_ssl_port, protocol = "tcp", }, }, { protocols = { "tls_passthrough" }, snis = { "example.org" }, service = { name = "service_behind_example.org", host = helpers.mock_upstream_ssl_host, port = helpers.mock_upstream_ssl_port, protocol = "tcp", }, }, }) end) lazy_teardown(function() remove_routes(strategy, routes) end) after_each(function() if proxy_ssl_client then proxy_ssl_client:close() end end) it_trad_only("matches a Route based on its 'snis' attribute", function() -- config propogates to stream subsystems not instantly -- try up to 10 seconds with step of 2 seconds -- in vagrant it takes around 6 seconds helpers.wait_until(function() proxy_ssl_client = helpers.http_client("127.0.0.1", stream_tls_listen_port) local ok = proxy_ssl_client:ssl_handshake(nil, "www.example.org", false) -- explicit no-verify if not ok then proxy_ssl_client:close() return false end return true end, 10, 2) local res = assert(proxy_ssl_client:send { method = "GET", path = "/status/200", headers = { ["kong-debug"] = 1 }, }) assert.res_status(200, res) res = assert(proxy_ssl_client:send { method = "GET", path = "/status/201", headers = { ["kong-debug"] = 1 }, }) assert.res_status(201, res) proxy_ssl_client:close() proxy_ssl_client = helpers.http_client("127.0.0.1", stream_tls_listen_port) assert(proxy_ssl_client:ssl_handshake(nil, "example.org", false)) -- explicit no-verify local res = assert(proxy_ssl_client:send { method = "GET", path = "/status/200", headers = { ["kong-debug"] = 1 }, }) assert.res_status(200, res) res = assert(proxy_ssl_client:send { method = "GET", path = "/status/201", headers = { ["kong-debug"] = 1 }, }) assert.res_status(201, res) proxy_ssl_client:close() end) end) describe("[#headers]", function() local routes after_each(function() remove_routes(strategy, routes) end) it("matches by header", function() routes = insert_routes(bp, { { headers = { version = { "v1", "v2" } }, }, { headers = { version = { "v3" } }, }, }) local res helpers.wait_until(function() res = assert(proxy_client:send { method = "GET", path = "/", headers = { ["Host"] = "domain.org", ["version"] = "v1", ["kong-debug"] = 1, } }) return pcall(function() assert.res_status(200, res) end) end, 10) assert.equal(routes[1].id, res.headers["kong-route-id"]) assert.equal(routes[1].service.id, res.headers["kong-service-id"]) assert.equal(routes[1].service.name, res.headers["kong-service-name"]) local res = assert(proxy_client:send { method = "GET", path = "/", headers = { ["Host"] = "domain.org", ["version"] = "v3", ["kong-debug"] = 1, } }) assert.res_status(200, res) assert.equal(routes[2].id, res.headers["kong-route-id"]) assert.equal(routes[2].service.id, res.headers["kong-service-id"]) assert.equal(routes[2].service.name, res.headers["kong-service-name"]) end) it("matches headers in a case-insensitive way", function() routes = insert_routes(bp, { { headers = { Version = { "v1", "v2" } }, }, { headers = { version = { "V3" } }, }, }) local res helpers.wait_until(function() res = assert(proxy_client:send { method = "GET", path = "/", headers = { ["Host"] = "domain.org", ["version"] = "v1", ["kong-debug"] = 1, } }) return pcall(function() assert.res_status(200, res) assert.equal(routes[1].id, res.headers["kong-route-id"]) end) end, 10) assert.res_status(200, res) assert.equal(routes[1].id, res.headers["kong-route-id"]) assert.equal(routes[1].service.id, res.headers["kong-service-id"]) assert.equal(routes[1].service.name, res.headers["kong-service-name"]) local res = assert(proxy_client:send { method = "GET", path = "/", headers = { ["Host"] = "domain.org", ["Version"] = "v3", ["kong-debug"] = 1, } }) assert.res_status(200, res) assert.equal(routes[2].id, res.headers["kong-route-id"]) assert.equal(routes[2].service.id, res.headers["kong-service-id"]) assert.equal(routes[2].service.name, res.headers["kong-service-name"]) end) it("prioritizes Routes with more headers", function() routes = insert_routes(bp, { { headers = { version = { "v1", "v2" }, }, }, { headers = { version = { "v3" }, location = { "us-east" }, }, }, { headers = { version = { "v3" }, }, }, }) local res helpers.wait_until(function() res = assert(proxy_client:send { method = "GET", path = "/", headers = { ["Host"] = "domain.org", ["version"] = "v3", ["location"] = "us-east", ["kong-debug"] = 1, } }) return res.headers["kong-route-id"] == routes[2].id end, 5) assert.res_status(200, res) assert.equal(routes[2].id, res.headers["kong-route-id"]) assert.equal(routes[2].service.id, res.headers["kong-service-id"]) assert.equal(routes[2].service.name, res.headers["kong-service-name"]) local res = assert(proxy_client:send { method = "GET", path = "/", headers = { ["Host"] = "domain.org", ["version"] = "v3", ["kong-debug"] = 1, } }) assert.res_status(200, res) assert.equal(routes[3].id, res.headers["kong-route-id"]) assert.equal(routes[3].service.id, res.headers["kong-service-id"]) assert.equal(routes[3].service.name, res.headers["kong-service-name"]) end) it("caching do not ignore headers (regression)", function() routes = insert_routes(bp, { { service = { name = "first", }, hosts = { "example.test" }, paths = { "/test" }, headers = { headertest = { "itsatest" } }, }, { service = { name = "second", }, hosts = { "example.test" }, paths = { "/test" }, }, }) local res helpers.wait_until(function() res = assert(proxy_client:send { method = "GET", path = routes[1].paths[1], headers = { ["Host"] = routes[1].hosts[1], ["headertest"] = "itsatest", ["kong-debug"] = 1, } }) return pcall(function() assert.res_status(200, res) end) end, 10) assert.equal(routes[1].id, res.headers["kong-route-id"]) assert.equal(routes[1].service.id, res.headers["kong-service-id"]) assert.equal(routes[1].service.name, res.headers["kong-service-name"]) local res = assert(proxy_client:send { method = "GET", path = routes[2].paths[1], headers = { ["Host"] = routes[2].hosts[1], ["kong-debug"] = 1, } }) assert.res_status(200, res) assert.equal(routes[2].id, res.headers["kong-route-id"]) assert.equal(routes[2].service.id, res.headers["kong-service-id"]) assert.equal(routes[2].service.name, res.headers["kong-service-name"]) local res = assert(proxy_client:send { method = "GET", path = routes[1].paths[1], headers = { ["Host"] = routes[1].hosts[1], ["headertest"] = "itsatest", ["kong-debug"] = 1, } }) assert.res_status(200, res) assert.equal(routes[1].id, res.headers["kong-route-id"]) assert.equal(routes[1].service.id, res.headers["kong-service-id"]) assert.equal(routes[1].service.name, res.headers["kong-service-name"]) end) end) describe("[paths] + [#headers]", function() local routes lazy_setup(function() routes = insert_routes(bp, { { strip_path = true, headers = { version = { "v1", "v2" }, }, paths = { "/root" }, }, { strip_path = true, headers = { version = { "v1", "v2" }, }, paths = { "/root/fixture" }, }, { strip_path = true, headers = { version = { "v1", "v2" }, location = { "us-east" }, }, paths = { "/root" }, }, }) end) lazy_teardown(function() remove_routes(strategy, routes) end) it("prioritizes Routes with more headers", function() local res = assert(proxy_client:send { method = "GET", path = "/root/fixture/get", headers = { ["version"] = "v2", ["location"] = "us-east", ["kong-debug"] = 1, } }) assert.res_status(404, res) assert.equal(routes[3].id, res.headers["kong-route-id"]) assert.equal(routes[3].service.id, res.headers["kong-service-id"]) assert.equal(routes[3].service.name, res.headers["kong-service-name"]) end) it("prioritizes longer paths if same number of headers", function() local res = assert(proxy_client:send { method = "GET", path = "/root/fixture/get", headers = { ["version"] = "v2", ["kong-debug"] = 1, } }) assert.res_status(200, res) assert.equal(routes[2].id, res.headers["kong-route-id"]) assert.equal(routes[2].service.id, res.headers["kong-service-id"]) assert.equal(routes[2].service.name, res.headers["kong-service-name"]) end) end) if not enable_buffering then describe("[snis] for #grpcs connections", function() local routes local grpcs_proxy_ssl_client lazy_setup(function() routes = insert_routes(bp, { { protocols = { "grpcs" }, snis = { "grpcs_1.test" }, service = { name = "grpcs_1", url = helpers.grpcbin_ssl_url, }, }, { protocols = { "grpcs" }, snis = { "grpcs_2.test" }, service = { name = "grpcs_2", url = helpers.grpcbin_ssl_url, }, }, }) end) lazy_teardown(function() remove_routes(strategy, routes) end) it("matches a Route based on its 'snis' attribute", function() grpcs_proxy_ssl_client = helpers.proxy_client_grpcs("grpcs_1.test") local ok, resp = assert(grpcs_proxy_ssl_client({ service = "hello.HelloService.SayHello", body = { greeting = "world!" }, opts = { ["-H"] = "'kong-debug: 1'", ["-v"] = true, -- verbose so we get response headers } })) assert.truthy(ok) assert.truthy(resp) assert.matches("kong-service-name: grpcs_1", resp, nil, true) grpcs_proxy_ssl_client = helpers.proxy_client_grpcs("grpcs_2.test") local ok, resp = assert(grpcs_proxy_ssl_client({ service = "hello.HelloService.SayHello", body = { greeting = "world!" }, opts = { ["-H"] = "'kong-debug: 1'", ["-v"] = true, -- verbose so we get response headers } })) assert.truthy(ok) assert.truthy(resp) assert.matches("kong-service-name: grpcs_2", resp, nil, true) end) end) end -- not enable_buffering describe("[paths] + [methods]", function() local routes lazy_setup(function() routes = insert_routes(bp, { [1] = { strip_path = true, methods = { "GET" }, paths = { "/unrelated/longer/uri/that/should/not/match", "/root/fixture" }, hosts = { "ahost.test" }, service = { path = "/status/201" }, }, [2] = { strip_path = true, methods = { "GET" }, paths = { "/root/fixture/get" }, hosts = { "ahost.test" }, service = { path = "/status/202" }, }, [3] = { strip_path = true, methods = { "GET" }, paths = { "/root/fixture/get" }, hosts = { "anotherhost.test" }, service = { path = "/status/203" }, }, [4] = { strip_path = true, methods = { "GET" }, paths = { "/root/fixture/get" }, hosts = { "onemorehost.test" }, service = { path = "/status/204" }, }, [5] = { strip_path = true, name = "public-apiv1", paths = { "/rest/devportal/api/v1", "/rest/devportal" }, hosts = { "api.local" }, service = { path = "/status/205" }, }, [6] = { strip_path = true, name = "aux", paths = { "/rest/devportal/aux" }, hosts = { "api.local" }, service = { path = "/status/206" }, }, [7] = { strip_path = true, name = "aux-host", paths = { "/rest/devportal/aux" }, hosts = { "test-api.local" }, service = { path = "/status/207" }, }, [8] = { strip_path = true, name = "aux-host2", paths = { "/rest/devportal/aux" }, hosts = { "atest-api.local" }, service = { path = "/status/208" }, }, [9] = { strip_path = true, name = "devportal-route-2", paths = { "/rest/devportal" }, hosts = { "atest-api.local" }, service = { path = "/status/209" }, }, [10] = { strip_path = true, name = "concat_test-public-apiv1", paths = { "/concat_test/devportal/api/v1", "/concat_test/devportal" }, hosts = { "api.local" }, }, [11] = { strip_path = true, name = "concat_test-aux", paths = { "/concat_test/devportal/aux" }, hosts = { "api.local" }, }, [12] = { strip_path = true, name = "concat_test-aux-host", paths = { "/concat_test/devportal/aux" }, hosts = { "test-api.local" }, }, [13] = { strip_path = true, name = "concat_test-aux-host2", paths = { "/concat_test/devportal/aux" }, hosts = { "atest-api.local" }, }, [14] = { strip_path = true, name = "concat_test-devportal-route-2", paths = { "/concat_test/devportal" }, hosts = { "atest-api.local" }, }, }) end) lazy_teardown(function() remove_routes(strategy, routes) end) it_trad_only("regression test for #5438", function() for i = 1, 9 do for j = 1, #routes[i].paths do local res = assert(proxy_client:send { method = "GET", path = routes[i].paths[j], headers = { ["kong-debug"] = 1, ["host"] = routes[i].hosts[1], } }) assert.res_status(200 + i, res) assert.equal(routes[i].id, res.headers["kong-route-id"]) assert.equal(routes[i].service.id, res.headers["kong-service-id"]) assert.equal(routes[i].service.name, res.headers["kong-service-name"]) end end end) it_trad_only("regression test for #5438 concatenating paths", function() for i = 10, 14 do for j = 1, #routes[i].paths do local res = assert(proxy_client:send { method = "GET", path = routes[i].paths[j] .. "/status/418", headers = { ["kong-debug"] = 1, ["host"] = routes[i].hosts[1], } }) assert.res_status(418, res) assert.equal(routes[i].id, res.headers["kong-route-id"]) assert.equal(routes[i].service.id, res.headers["kong-service-id"]) assert.equal(routes[i].service.name, res.headers["kong-service-name"]) end end end) it_trad_only("regression test for #5438 part 2", function() local res = assert(proxy_client:send { method = "GET", path = "/rest/devportal", headers = { ["kong-debug"] = 1, ["host"] = "atest-api.local", } }) assert.res_status(209, res) assert.equal(routes[9].id, res.headers["kong-route-id"]) assert.equal(routes[9].service.id, res.headers["kong-service-id"]) assert.equal(routes[9].service.name, res.headers["kong-service-name"]) end) it_trad_only("prioritizes longer URIs", function() local res = assert(proxy_client:send { method = "GET", path = "/root/fixture/get", headers = { ["kong-debug"] = 1, ["host"] = "ahost.test", } }) assert.res_status(202, res) assert.equal(routes[2].id, res.headers["kong-route-id"]) assert.equal(routes[2].service.id, res.headers["kong-service-id"]) assert.equal(routes[2].service.name, res.headers["kong-service-name"]) end) it("prioritizes host over longer URIs", function() local res = assert(proxy_client:send { method = "GET", path = "/root/fixture/get", headers = { ["kong-debug"] = 1, ["host"] = "anotherhost.test", } }) assert.res_status(203, res) assert.equal(routes[3].id, res.headers["kong-route-id"]) assert.equal(routes[3].service.id, res.headers["kong-service-id"]) assert.equal(routes[3].service.name, res.headers["kong-service-name"]) end) it("do not match incomplete URIs", function() local res = assert(proxy_client:send { method = "GET", path = "/", headers = { ["kong-debug"] = 1, ["host"] = "ahost.test", } }) assert.res_status(404, res) end) end) describe("[paths] + [hosts]", function() local routes lazy_setup(function() routes = insert_routes(bp, { { strip_path = true, hosts = { "route.com" }, paths = { "/root/fixture", "/root/fixture/non-matching-but-longer" }, }, { strip_path = true, hosts = { "route.com" }, paths = { "/root/fixture/get" }, }, }) end) lazy_teardown(function() remove_routes(strategy, routes) end) it_trad_only("prioritizes longer URIs", function() local res = assert(proxy_client:send { method = "GET", path = "/root/fixture/get", headers = { ["Host"] = "route.com", ["kong-debug"] = 1, } }) assert.res_status(200, res) assert.equal(routes[2].id, res.headers["kong-route-id"]) assert.equal(routes[2].service.id, res.headers["kong-service-id"]) assert.equal(routes[2].service.name, res.headers["kong-service-name"]) end) end) describe("slash handling", function() describe("(plain)", function() local routes lazy_setup(function() routes = {} for i, line in ipairs(path_handling_tests) do for j, test in ipairs(line:expand()) do if flavor == "traditional" or test.path_handling == "v0" then routes[#routes + 1] = { strip_path = test.strip_path, path_handling = test.path_handling, paths = test.route_path and { test.route_path } or nil, hosts = { "localbin-" .. i .. "-" .. j .. ".com" }, service = { name = "plain_" .. i .. "-" .. j, path = test.service_path, } } end end end routes = insert_routes(bp, routes) end) lazy_teardown(function() for _, r in ipairs(routes) do remove_routes(strategy, r) end end) for i, line in ipairs(path_handling_tests) do for j, test in ipairs(line:expand()) do if flavor == "traditional" or test.path_handling == "v0" then local strip = test.strip_path and "on" or "off" local route_uri_or_host if test.route_path then route_uri_or_host = "uri " .. test.route_path else route_uri_or_host = "host localbin-" .. i .. "-" .. j .. ".com" end local description = string.format("(%d-%d) %s with %s, strip = %s, %s when requesting %s", i, j, test.service_path, route_uri_or_host, strip, test.path_handling, test.request_path) it(description, function() helpers.wait_until(function() local res = assert(proxy_client:get(test.request_path, { headers = { ["Host"] = "localbin-" .. i .. "-" .. j .. ".com", } })) return pcall(function() local data = assert.response(res).has.jsonbody() assert.equal(test.expected_path, data.vars.request_uri) end) end, 10) end) end end end end) describe("(regex)", function() local function make_a_regex(path) return "~/[0]?" .. path:sub(2, -1) end local routes lazy_setup(function() routes = {} for i, line in ipairs(path_handling_tests) do if line.route_path then -- skip if hostbased match for j, test in ipairs(line:expand()) do if flavor == "traditional" or test.path_handling == "v0" then routes[#routes + 1] = { strip_path = test.strip_path, paths = test.route_path and { make_a_regex(test.route_path) } or nil, path_handling = test.path_handling, hosts = { "localbin-" .. i .. "-" .. j .. ".com" }, service = { name = "make_regex_" .. i .. "-" .. j, path = test.service_path, } } end end end end routes = insert_routes(bp, routes) end) lazy_teardown(function() remove_routes(strategy, routes) end) for i, line in ipairs(path_handling_tests) do if line.route_path then -- skip if hostbased match for j, test in ipairs(line:expand()) do if flavor == "traditional" or test.path_handling == "v0" then local strip = test.strip_path and "on" or "off" local description = string.format("(%d-%d) %s with uri %s, strip = %s, %s when requesting %s", i, j, test.service_path, make_a_regex(test.route_path), strip, test.path_handling, test.request_path) it(description, function() local res = assert(proxy_client:get(test.request_path, { headers = { Host = "localbin-" .. i .. "-" .. j .. ".com" }, })) local data = assert.response(res).has.jsonbody() assert.truthy(data.vars) assert.equal(test.expected_path, data.vars.request_uri) end) end end end end end) describe("router rebuilds", function() local routes lazy_teardown(function() remove_routes(routes) end) it("when Routes have 'regex_priority = nil'", function() -- Regression test for issue: -- https://github.com/Kong/kong/issues/4254 routes = insert_routes(bp, { { methods = { "GET" }, regex_priority = 1, }, { methods = { "POST", "PUT" }, regex_priority = ngx.null, }, }) local res = assert(proxy_client:send { method = "GET", }) assert.response(res).has_status(200) end) end) end) end) describe("Router [#" .. strategy .. ", flavor = " .. flavor .. "] at startup" , function() local proxy_client local route lazy_setup(function() local bp = helpers.get_db_utils(strategy, { "routes", "services", "plugins", }, { "enable-buffering", }) route = bp.routes:insert({ methods = { "GET" }, protocols = { "http" }, strip_path = false, }) if enable_buffering then bp.plugins:insert { name = "enable-buffering", protocols = { "http", "https", "grpc", "grpcs" }, } end assert(helpers.start_kong({ router_flavor = flavor, database = strategy, nginx_worker_processes = 4, plugins = "bundled,enable-buffering", nginx_conf = "spec/fixtures/custom_nginx.template", })) end) lazy_teardown(function() helpers.stop_kong() end) before_each(function() proxy_client = helpers.proxy_client() end) after_each(function() if proxy_client then proxy_client:close() end end) it("uses configuration from datastore or declarative_config", function() for _ = 1, 1000 do proxy_client = helpers.proxy_client() local res = assert(proxy_client:send { method = "GET", path = "/get", headers = { ["kong-debug"] = 1 }, }) assert.response(res).has_status(200) assert.equal(route.service.name, res.headers["kong-service-name"]) proxy_client:close() end end) it("#db worker respawn correctly rebuilds router", function() local admin_client = helpers.admin_client() local res = assert(admin_client:post("/routes", { headers = { ["Content-Type"] = "application/json" }, body = { paths = { "/foo" }, }, })) assert.res_status(201, res) admin_client:close() assert(helpers.signal_workers(nil, "-TERM")) proxy_client:close() proxy_client = helpers.proxy_client() local res = assert(proxy_client:send { method = "GET", path = "/foo", headers = { ["kong-debug"] = 1 }, }) local body = assert.response(res).has_status(503) local json = cjson.decode(body) assert.equal("no Service found with those values", json.message) end) it("#db rebuilds router correctly after passing invalid route", function() local admin_client = helpers.admin_client() local res = assert(admin_client:post("/routes", { headers = { ["Content-Type"] = "application/json" }, body = { -- this is a invalid regex path paths = { "~/delay/(?<delay>[^\\/]+)$", }, }, })) assert.res_status(201, res) helpers.wait_for_all_config_update() local res = assert(admin_client:post("/routes", { headers = { ["Content-Type"] = "application/json" }, body = { paths = { "/foo" }, }, })) assert.res_status(201, res) admin_client:close() helpers.wait_for_all_config_update() proxy_client:close() proxy_client = helpers.proxy_client() local res = assert(proxy_client:send { method = "GET", path = "/foo", headers = { ["kong-debug"] = 1 }, }) local body = assert.response(res).has_status(503) local json = cjson.decode(body) assert.equal("no Service found with those values", json.message) end) end) end end end