Skip to content

Commit

Permalink
feat(core) make service on routes optional (transparent proxying)
Browse files Browse the repository at this point in the history
Make route.service optional, and when not set, route transparently
on such route.
  • Loading branch information
bungle committed Mar 11, 2019
1 parent 6cfa094 commit 6df1e40
Show file tree
Hide file tree
Showing 12 changed files with 838 additions and 120 deletions.
2 changes: 1 addition & 1 deletion kong/db/schema/entities/routes.lua
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ return {
},
}, },
{ tags = typedefs.tags },
{ service = { type = "foreign", reference = "services", required = true }, },
{ service = { type = "foreign", reference = "services" }, },
},

entity_checks = {
Expand Down
36 changes: 27 additions & 9 deletions kong/router.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ local upper = string.upper
local lower = string.lower
local find = string.find
local sub = string.sub
local tonumber = tonumber
local ipairs = ipairs
local pairs = pairs
local error = error
Expand Down Expand Up @@ -67,6 +68,9 @@ local MATCH_RULES = {
}


local EMPTY_T = {}


local match_route
local reduce

Expand Down Expand Up @@ -333,7 +337,9 @@ local function marshall_route(r)
route_t.upstream_url_t.scheme = protocol
end

local host = service.host
local s = service or EMPTY_T

local host = s.host
if host then
route_t.upstream_url_t.host = host
route_t.upstream_url_t.type = hostname_type(host)
Expand All @@ -342,7 +348,7 @@ local function marshall_route(r)
route_t.upstream_url_t.type = hostname_type("")
end

local port = service.port
local port = s.port
if port then
route_t.upstream_url_t.port = port

Expand All @@ -356,7 +362,7 @@ local function marshall_route(r)
end

if route_t.type == "http" then
route_t.upstream_url_t.path = service.path or "/"
route_t.upstream_url_t.path = s.path or "/"
end

return route_t
Expand Down Expand Up @@ -1129,14 +1135,13 @@ function _M.new(routes)
local upstream_url_t = matched_route.upstream_url_t
local matches = ctx.matches


-- Path construction

if matched_route.type == "http" then
-- if we do not have a path-match, then the postfix is simply the
-- incoming path, without the initial slash
local request_postfix = matches.uri_postfix or sub(req_uri, 2, -1)
local upstream_base = upstream_url_t.path
local upstream_base = upstream_url_t.path or "/"

if matched_route.strip_uri then
-- we drop the matched part, replacing it with the upstream path
Expand Down Expand Up @@ -1229,11 +1234,24 @@ function _M.new(routes)
-- debug HTTP request header logic

if ngx.var.http_kong_debug then
ngx.header["Kong-Route-Id"] = match_t.route.id
ngx.header["Kong-Service-Id"] = match_t.service.id
if match_t.route then
if match_t.route.id then
ngx.header["Kong-Route-Id"] = match_t.route.id
end

if match_t.service.name then
ngx.header["Kong-Service-Name"] = match_t.service.name
if match_t.route.name then
ngx.header["Kong-Route-Name"] = match_t.route.name
end
end

if match_t.service then
if match_t.service.id then
ngx.header["Kong-Service-Id"] = match_t.service.id
end

if match_t.service.name then
ngx.header["Kong-Service-Name"] = match_t.service.name
end
end
end

Expand Down
195 changes: 145 additions & 50 deletions kong/runloop/handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,22 @@ local ipairs = ipairs
local tostring = tostring
local tonumber = tonumber
local sub = string.sub
local find = string.find
local lower = string.lower
local fmt = string.format
local sort = table.sort
local ngx = ngx
local log = ngx.log
local ngx_now = ngx.now
local re_match = ngx.re.match
local re_find = ngx.re.find
local update_time = ngx.update_time
local subsystem = ngx.config.subsystem
local unpack = unpack


local ERR = ngx.ERR
local WARN = ngx.WARN
local DEBUG = ngx.DEBUG


Expand Down Expand Up @@ -251,39 +255,13 @@ local function balancer_setup_stage1(ctx, scheme, host_type, host, port,
-- hash_cookie = nil, -- if Upstream sets hash_on_cookie
}

-- TODO: this is probably not optimal
do
local retries = service.retries
if retries then
balancer_data.retries = retries
local s = service or EMPTY_T

else
balancer_data.retries = 5
end

local connect_timeout = service.connect_timeout
if connect_timeout then
balancer_data.connect_timeout = connect_timeout

else
balancer_data.connect_timeout = 60000
end

local send_timeout = service.write_timeout
if send_timeout then
balancer_data.send_timeout = send_timeout

else
balancer_data.send_timeout = 60000
end

local read_timeout = service.read_timeout
if read_timeout then
balancer_data.read_timeout = read_timeout

else
balancer_data.read_timeout = 60000
end
balancer_data.retries = s.retries or 5
balancer_data.connect_timeout = s.connect_timeout or 60000
balancer_data.send_timeout = s.write_timeout or 60000
balancer_data.read_timeout = s.read_timeout or 60000
end

ctx.service = service
Expand Down Expand Up @@ -321,7 +299,7 @@ end
-- in the table below the `before` and `after` is to indicate when they run:
-- before or after the plugins
return {
build_router = build_router,
build_router = build_router,

-- exported for unit-testing purposes only
_set_check_router_rebuild = _set_check_router_rebuild,
Expand Down Expand Up @@ -656,16 +634,29 @@ return {

ctx.KONG_PREREAD_START = get_now()

local api = match_t.api or EMPTY_T
local route = match_t.route or EMPTY_T
local service = match_t.service or EMPTY_T
local route = match_t.route
local service = match_t.service
local upstream_url_t = match_t.upstream_url_t

if not service then
-----------------------------------------------------------------------
-- Serviceless stream route
-----------------------------------------------------------------------
local service_scheme = ssl_termination_ctx and "tls" or "tcp"
local service_host = var.server_addr

match_t.upstream_scheme = service_scheme
upstream_url_t.scheme = service_scheme -- for completeness
upstream_url_t.type = utils.hostname_type(service_host)
upstream_url_t.host = service_host
upstream_url_t.port = tonumber(var.server_port, 10)
end

balancer_setup_stage1(ctx, match_t.upstream_scheme,
upstream_url_t.type,
upstream_url_t.host,
upstream_url_t.port,
service, route, api)
service, route)
end,
after = function(ctx)
local ok, err, errcode = balancer_setup_stage2(ctx)
Expand Down Expand Up @@ -706,9 +697,13 @@ return {
return kong.response.exit(404, { message = "no Route matched with those values" })
end

local route = match_t.route or EMPTY_T
local service = match_t.service or EMPTY_T
local upstream_url_t = match_t.upstream_url_t
local scheme = var.scheme
local host = var.host
local port = tonumber(var.server_port, 10)

local route = match_t.route
local service = match_t.service
local upstream_url_t = match_t.upstream_url_t

local realip_remote_addr = var.realip_remote_addr
local forwarded_proto
Expand All @@ -726,25 +721,125 @@ return {

local trusted_ip = kong.ip.is_trusted(realip_remote_addr)
if trusted_ip then
forwarded_proto = var.http_x_forwarded_proto or var.scheme
forwarded_host = var.http_x_forwarded_host or var.host
forwarded_port = var.http_x_forwarded_port or var.server_port
forwarded_proto = var.http_x_forwarded_proto or scheme
forwarded_host = var.http_x_forwarded_host or host
forwarded_port = var.http_x_forwarded_port or port

else
forwarded_proto = var.scheme
forwarded_host = var.host
forwarded_port = var.server_port
forwarded_proto = scheme
forwarded_host = host
forwarded_port = port
end

local protocols = route.protocols
if (protocols and
protocols.https and not protocols.http and forwarded_proto ~= "https")
if (protocols and protocols.https and not protocols.http and
forwarded_proto ~= "https")
then
ngx.header["connection"] = "Upgrade"
ngx.header["upgrade"] = "TLS/1.2, HTTP/1.1"
return kong.response.exit(426, { message = "Please use HTTPS protocol" })
end

if not service then
-----------------------------------------------------------------------
-- Serviceless HTTP / HTTPS / HTTP2 route
-----------------------------------------------------------------------
local service_scheme
local service_host
local service_port

-- 1. try to find information from a request-line
local request_line = var.request
if request_line then
local matches, err = re_match(request_line, [[\w+ (https?)://([^/?#\s]+)]], "ajos")
if err then
log(WARN, "pcre runtime error when matching a request-line: ", err)

elseif matches then
local uri_scheme = lower(matches[1])
if uri_scheme == "https" or uri_scheme == "http" then
service_scheme = uri_scheme
service_host = lower(matches[2])
end
--[[ TODO: check if these make sense here?
elseif uri_scheme == "wss" then
service_scheme = "https"
service_host = lower(matches[2])
elseif uri_scheme == "ws" then
service_scheme = "http"
service_host = lower(matches[2])
end
--]]
end
end

-- 2. try to find information from a host header
if not service_host then
local http_host = var.http_host
if http_host then
service_scheme = scheme
service_host = lower(http_host)
end
end

-- 3. split host to host and port
if service_host then
-- remove possible userinfo
local pos = find(service_host, "@", 1, true)
if pos then
service_host = sub(service_host, pos + 1)
end

pos = find(service_host, ":", 2, true)
if pos then
service_port = sub(service_host, pos + 1)
service_host = sub(service_host, 1, pos - 1)

local found, _, err = re_find(service_port, [[[1-9]{1}\d{0,4}$]], "adjo")
if err then
log(WARN, "pcre runtime error when matching a port number: ", err)

elseif found then
service_port = tonumber(service_port, 10)
if not service_port or service_port > 65535 then
service_scheme = nil
service_host = nil
service_port = nil
end

else
service_scheme = nil
service_host = nil
service_port = nil
end
end
end

-- 4. use known defaults
if service_host and not service_port then
if service_scheme == "http" then
service_port = 80
elseif service_scheme == "https" then
service_port = 443
else
service_port = port
end
end

-- 5. fall-back to server address
if not service_host then
service_scheme = scheme
service_host = var.server_addr
service_port = port
end

match_t.upstream_scheme = service_scheme
upstream_url_t.scheme = service_scheme -- for completeness
upstream_url_t.type = utils.hostname_type(service_host)
upstream_url_t.host = service_host
upstream_url_t.port = service_port
end

balancer_setup_stage1(ctx, match_t.upstream_scheme,
upstream_url_t.type,
upstream_url_t.host,
Expand Down Expand Up @@ -888,9 +983,9 @@ return {
local ok, err = cookie:set(hash_cookie)

if not ok then
log(ngx.WARN, "failed to set the cookie for hash-based load balancing: ", err,
" (key=", hash_cookie.key,
", path=", hash_cookie.path, ")")
log(WARN, "failed to set the cookie for hash-based load balancing: ", err,
" (key=", hash_cookie.key,
", path=", hash_cookie.path, ")")
end
end,
after = function(ctx)
Expand Down
14 changes: 11 additions & 3 deletions spec/01-unit/01-db/01-schema/06-routes_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,20 @@ describe("routes schema", function()
assert.falsy(route.strip_path)
end)

it("fails when service is null", function()
it("it does not fail when service is null", function()
local route = { service = ngx.null, paths = {"/"} }
route = Routes:process_auto_fields(route, "insert")
local ok, errs = Routes:validate_insert(route)
assert.falsy(ok)
assert.truthy(errs["service"])
assert.truthy(ok)
assert.is_nil(errs)
end)

it("it does not fail when service is missing", function()
local route = { paths = {"/"} }
route = Routes:process_auto_fields(route, "insert")
local ok, errs = Routes:validate_insert(route)
assert.truthy(ok)
assert.is_nil(errs)
end)

it("fails when service.id is null", function()
Expand Down
Loading

0 comments on commit 6df1e40

Please sign in to comment.