412 lines
10 KiB
Lua
412 lines
10 KiB
Lua
local constants = require("kong.constants")
|
|
local hostname_type = require("kong.tools.ip").hostname_type
|
|
local normalize = require("kong.tools.uri").normalize
|
|
|
|
|
|
local type = type
|
|
local error = error
|
|
local ipairs = ipairs
|
|
local find = string.find
|
|
local sub = string.sub
|
|
local byte = string.byte
|
|
|
|
|
|
local SLASH = byte("/")
|
|
|
|
|
|
local DEFAULT_HOSTNAME_TYPE = hostname_type("")
|
|
|
|
|
|
local protocol_subsystem = constants.PROTOCOLS_WITH_SUBSYSTEM
|
|
|
|
|
|
--[[
|
|
Hypothesis
|
|
----------
|
|
|
|
Item size: 1024 bytes
|
|
Max memory limit: 5 MiBs
|
|
|
|
LRU size must be: (5 * 2^20) / 1024 = 5120
|
|
Floored: 5000 items should be a good default
|
|
--]]
|
|
local DEFAULT_MATCH_LRUCACHE_SIZE = 5000
|
|
|
|
|
|
local function sanitize_uri_postfix(uri_postfix)
|
|
if not uri_postfix or uri_postfix == "" then
|
|
return uri_postfix
|
|
end
|
|
|
|
if uri_postfix == "." or uri_postfix == ".." then
|
|
return ""
|
|
end
|
|
|
|
if sub(uri_postfix, 1, 2) == "./" then
|
|
return sub(uri_postfix, 3)
|
|
end
|
|
|
|
if sub(uri_postfix, 1, 3) == "../" then
|
|
return sub(uri_postfix, 4)
|
|
end
|
|
|
|
return uri_postfix
|
|
end
|
|
|
|
|
|
local function strip_uri_args(req_uri)
|
|
local idx = find(req_uri, "?", 2, true)
|
|
if idx then
|
|
req_uri = sub(req_uri, 1, idx - 1)
|
|
end
|
|
|
|
return normalize(req_uri, true)
|
|
end
|
|
|
|
|
|
local function check_select_params(req_method, req_uri, req_host, req_scheme,
|
|
src_ip, src_port,
|
|
dst_ip, dst_port,
|
|
sni, req_headers, req_queries)
|
|
if req_method and type(req_method) ~= "string" then
|
|
error("method must be a string", 2)
|
|
end
|
|
if req_uri and type(req_uri) ~= "string" then
|
|
error("uri must be a string", 2)
|
|
end
|
|
if req_host and type(req_host) ~= "string" then
|
|
error("host must be a string", 2)
|
|
end
|
|
if req_scheme and type(req_scheme) ~= "string" then
|
|
error("scheme must be a string", 2)
|
|
end
|
|
if src_ip and type(src_ip) ~= "string" then
|
|
error("src_ip must be a string", 2)
|
|
end
|
|
if src_port and type(src_port) ~= "number" then
|
|
error("src_port must be a number", 2)
|
|
end
|
|
if dst_ip and type(dst_ip) ~= "string" then
|
|
error("dst_ip must be a string", 2)
|
|
end
|
|
if dst_port and type(dst_port) ~= "number" then
|
|
error("dst_port must be a number", 2)
|
|
end
|
|
if sni and type(sni) ~= "string" then
|
|
error("sni must be a string", 2)
|
|
end
|
|
if req_headers and type(req_headers) ~= "table" then
|
|
error("headers must be a table", 2)
|
|
end
|
|
if req_queries and type(req_queries) ~= "table" then
|
|
error("queries must be a table", 2)
|
|
end
|
|
end
|
|
|
|
|
|
local get_header
|
|
if ngx.config.subsystem == "http" then
|
|
get_header = require("kong.tools.http").get_header
|
|
end
|
|
|
|
|
|
local function add_debug_headers(ctx, header, match_t)
|
|
if not kong.configuration.allow_debug_header then
|
|
return
|
|
end
|
|
|
|
if not get_header("kong_debug", ctx) then
|
|
return
|
|
end
|
|
|
|
local route = match_t.route
|
|
if route then
|
|
if route.id then
|
|
header["Kong-Route-Id"] = route.id
|
|
end
|
|
|
|
if route.name then
|
|
header["Kong-Route-Name"] = route.name
|
|
end
|
|
end
|
|
|
|
local service = match_t.service
|
|
if service then
|
|
if service.id then
|
|
header["Kong-Service-Id"] = service.id
|
|
end
|
|
|
|
if service.name then
|
|
header["Kong-Service-Name"] = service.name
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local function get_upstream_uri_v0(matched_route, request_postfix, req_uri,
|
|
upstream_base)
|
|
|
|
local strip_path = matched_route.strip_path or matched_route.strip_uri
|
|
|
|
if byte(upstream_base, -1) == SLASH then
|
|
-- ends with / and strip_path = true
|
|
if strip_path then
|
|
if request_postfix == "" then
|
|
if upstream_base == "/" then
|
|
return "/"
|
|
end
|
|
|
|
if byte(req_uri, -1) == SLASH then
|
|
return upstream_base
|
|
end
|
|
|
|
return sub(upstream_base, 1, -2)
|
|
end -- if request_postfix
|
|
|
|
if byte(request_postfix, 1) == SLASH then
|
|
-- double "/", so drop the first
|
|
return sub(upstream_base, 1, -2) .. request_postfix
|
|
end
|
|
|
|
-- ends with / and strip_path = true, no double slash
|
|
return upstream_base .. request_postfix
|
|
end -- if strip_path
|
|
|
|
-- ends with / and strip_path = false
|
|
-- we retain the incoming path, just prefix it with the upstream
|
|
-- path, but skip the initial slash
|
|
return upstream_base .. sub(req_uri, 2)
|
|
end -- byte(upstream_base, -1) == SLASH
|
|
|
|
-- does not end with / and strip_path = true
|
|
if strip_path then
|
|
if request_postfix == "" then
|
|
if #req_uri > 1 and byte(req_uri, -1) == SLASH then
|
|
return upstream_base .. "/"
|
|
end
|
|
|
|
return upstream_base
|
|
end -- if request_postfix
|
|
|
|
if byte(request_postfix, 1) == SLASH then
|
|
return upstream_base .. request_postfix
|
|
end
|
|
|
|
return upstream_base .. "/" .. request_postfix
|
|
end -- if strip_path
|
|
|
|
-- does not end with / and strip_path = false
|
|
if req_uri == "/" then
|
|
return upstream_base
|
|
end
|
|
|
|
return upstream_base .. req_uri
|
|
end
|
|
|
|
|
|
local function get_service_info(service)
|
|
local service_protocol
|
|
local service_type
|
|
local service_host
|
|
local service_port
|
|
|
|
if service then
|
|
service_protocol = service.protocol
|
|
service_host = service.host
|
|
service_port = service.port
|
|
end
|
|
|
|
if service_protocol then
|
|
service_type = protocol_subsystem[service_protocol]
|
|
end
|
|
|
|
local service_hostname_type
|
|
if service_host then
|
|
service_hostname_type = hostname_type(service_host)
|
|
end
|
|
|
|
if not service_port then
|
|
if service_protocol == "https" then
|
|
service_port = 443
|
|
elseif service_protocol == "http" then
|
|
service_port = 80
|
|
end
|
|
end
|
|
|
|
local service_path
|
|
if service_type == "http" then
|
|
service_path = service and service.path or "/"
|
|
end
|
|
|
|
return service_protocol, service_type,
|
|
service_host, service_port,
|
|
service_hostname_type or DEFAULT_HOSTNAME_TYPE,
|
|
service_path
|
|
end
|
|
|
|
|
|
local function route_match_stat(ctx, tag)
|
|
if ctx then
|
|
ctx.route_match_cached = tag
|
|
end
|
|
end
|
|
|
|
|
|
local is_regex_magic
|
|
local phonehome_statistics
|
|
do
|
|
local reports = require("kong.reports")
|
|
local nkeys = require("table.nkeys")
|
|
local yield = require("kong.tools.yield").yield
|
|
local worker_id = ngx.worker.id
|
|
local get_phase = ngx.get_phase
|
|
|
|
local TILDE = byte("~")
|
|
is_regex_magic = function(path)
|
|
return byte(path) == TILDE
|
|
end
|
|
|
|
local empty_table = {}
|
|
-- reuse tables to avoid cost of creating tables and garbage collection
|
|
local protocols = {
|
|
http = 0, -- { "http", "https" },
|
|
stream = 0, -- { "tcp", "tls", "udp" },
|
|
tls_passthrough = 0, -- { "tls_passthrough" },
|
|
grpc = 0, -- { "grpc", "grpcs" },
|
|
unknown = 0, -- all other protocols,
|
|
}
|
|
local path_handlings = {
|
|
v0 = 0,
|
|
v1 = 0,
|
|
}
|
|
local route_report = {
|
|
flavor = "unknown",
|
|
paths = 0,
|
|
headers = 0,
|
|
routes = 0,
|
|
regex_routes = 0,
|
|
protocols = protocols,
|
|
path_handlings = path_handlings,
|
|
}
|
|
|
|
local function traditional_statistics(routes)
|
|
local paths = 0
|
|
local headers = 0
|
|
local regex_routes = 0
|
|
local http = 0
|
|
local stream = 0
|
|
local tls_passthrough = 0
|
|
local grpc = 0
|
|
local unknown = 0
|
|
local v0 = 0
|
|
local v1 = 0
|
|
|
|
local phase = get_phase()
|
|
|
|
for _, route in ipairs(routes) do
|
|
yield(true, phase)
|
|
|
|
local r = route.route
|
|
|
|
local paths_t = r.paths or empty_table
|
|
local headers_t = r.headers or empty_table
|
|
local protocols_t = r.protocols or empty_table
|
|
|
|
paths = paths + #paths_t
|
|
headers = headers + nkeys(headers_t)
|
|
|
|
for _, path in ipairs(paths_t) do
|
|
if is_regex_magic(path) then
|
|
regex_routes = regex_routes + 1
|
|
break
|
|
end
|
|
end
|
|
|
|
local protocol = protocols_t[1] -- only check first protocol
|
|
|
|
if protocol then
|
|
if protocol == "http" or protocol == "https" then
|
|
http = http + 1
|
|
|
|
elseif protocol == "tcp" or protocol == "tls" or protocol == "udp" then
|
|
stream = stream + 1
|
|
|
|
elseif protocol == "tls_passthrough" then
|
|
tls_passthrough = tls_passthrough + 1
|
|
|
|
elseif protocol == "grpc" or protocol == "grpcs" then
|
|
grpc = grpc + 1
|
|
|
|
else
|
|
unknown = unknown + 1
|
|
end
|
|
end
|
|
|
|
local path_handling = r.path_handling or "v0"
|
|
if path_handling == "v0" then
|
|
v0 = v0 + 1
|
|
|
|
elseif path_handling == "v1" then
|
|
v1 = v1 + 1
|
|
end
|
|
end -- for routes
|
|
|
|
route_report.paths = paths
|
|
route_report.headers = headers
|
|
route_report.regex_routes = regex_routes
|
|
protocols.http = http
|
|
protocols.stream = stream
|
|
protocols.tls_passthrough = tls_passthrough
|
|
protocols.grpc = grpc
|
|
protocols.unknown = unknown
|
|
path_handlings.v0 = v0
|
|
path_handlings.v1 = v1
|
|
end
|
|
|
|
function phonehome_statistics(routes)
|
|
local configuration = kong.configuration
|
|
|
|
if not configuration.anonymous_reports or worker_id() ~= 0 then
|
|
return
|
|
end
|
|
|
|
local flavor = configuration.router_flavor
|
|
|
|
route_report.flavor = flavor
|
|
route_report.routes = #routes
|
|
|
|
if flavor ~= "expressions" then
|
|
traditional_statistics(routes)
|
|
|
|
else
|
|
route_report.paths = nil
|
|
route_report.regex_routes = nil
|
|
route_report.headers = nil
|
|
protocols.http = nil
|
|
protocols.stream = nil
|
|
protocols.tls_passthrough = nil
|
|
protocols.grpc = nil
|
|
path_handlings.v0 = nil
|
|
path_handlings.v1 = nil
|
|
end
|
|
|
|
reports.add_ping_value("routes_count", route_report)
|
|
end
|
|
end
|
|
|
|
|
|
return {
|
|
DEFAULT_MATCH_LRUCACHE_SIZE = DEFAULT_MATCH_LRUCACHE_SIZE,
|
|
|
|
sanitize_uri_postfix = sanitize_uri_postfix,
|
|
check_select_params = check_select_params,
|
|
strip_uri_args = strip_uri_args,
|
|
get_service_info = get_service_info,
|
|
add_debug_headers = add_debug_headers,
|
|
get_upstream_uri_v0 = get_upstream_uri_v0,
|
|
|
|
route_match_stat = route_match_stat,
|
|
is_regex_magic = is_regex_magic,
|
|
phonehome_statistics = phonehome_statistics,
|
|
}
|