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

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,
}