-- Kong, the biggest ape in town -- -- /\ ____ -- <> ( oo ) -- <>_| ^^ |_ -- <> @ \ -- /~~\ . . _ | -- /~~~~\ | | -- /~~~~~~\/ _| | -- |[][][]/ / [m] -- |[][][[m] -- |[][][]| -- |[][][]| -- |[][][]| -- |[][][]| -- |[][][]| -- |[][][]| -- |[][][]| -- |[][][]| -- |[|--|]| -- |[| |]| -- ======== -- ========== -- |[[ ]]| -- ========== local pcall = pcall assert(package.loaded["resty.core"], "lua-resty-core must be loaded; make " .. "sure 'lua_load_resty_core' is not ".. "disabled.") local constants = require "kong.constants" do -- let's ensure the required shared dictionaries are -- declared via lua_shared_dict in the Nginx conf for _, dict in ipairs(constants.DICTS) do if not ngx.shared[dict] then return error("missing shared dict '" .. dict .. "' in Nginx " .. "configuration, are you using a custom template? " .. "Make sure the 'lua_shared_dict " .. dict .. " [SIZE];' " .. "directive is defined.") end end -- if we're running `nginx -t` then don't initialize if os.getenv("KONG_NGINX_CONF_CHECK") then return { init = function() end, } end end require("kong.globalpatches")() local kong_global = require "kong.global" local PHASES = kong_global.phases _G.kong = kong_global.new() -- no versioned PDK for plugins for now local DB = require "kong.db" local meta = require "kong.meta" local lapis = require "lapis" local runloop = require "kong.runloop.handler" local stream_api = require "kong.tools.stream_api" local declarative = require "kong.db.declarative" local ngx_balancer = require "ngx.balancer" local kong_resty_ctx = require "kong.resty.ctx" local certificate = require "kong.runloop.certificate" local concurrency = require "kong.concurrency" local cache_warmup = require "kong.cache.warmup" local balancer = require "kong.runloop.balancer" local kong_error_handlers = require "kong.error_handlers" local plugin_servers = require "kong.runloop.plugin_servers" local lmdb_txn = require "resty.lmdb.transaction" local instrumentation = require "kong.observability.tracing.instrumentation" local process = require "ngx.process" local tablepool = require "tablepool" local table_new = require "table.new" local emmy_debugger = require "kong.tools.emmy_debugger" local get_ctx_table = require("resty.core.ctx").get_ctx_table local admin_gui = require "kong.admin_gui" local wasm = require "kong.runloop.wasm" local reports = require "kong.reports" local pl_file = require "pl.file" local req_dyn_hook = require "kong.dynamic_hook" local uuid = require("kong.tools.uuid").uuid local kong_time = require("kong.tools.time") local kong = kong local ngx = ngx local var = ngx.var local arg = ngx.arg local header = ngx.header local ngx_log = ngx.log local ngx_ALERT = ngx.ALERT local ngx_CRIT = ngx.CRIT local ngx_ERR = ngx.ERR local ngx_WARN = ngx.WARN local ngx_NOTICE = ngx.NOTICE local ngx_INFO = ngx.INFO local ngx_DEBUG = ngx.DEBUG local is_http_module = ngx.config.subsystem == "http" local is_stream_module = ngx.config.subsystem == "stream" local worker_id = ngx.worker.id local fmt = string.format local type = type local error = error local ipairs = ipairs local assert = assert local tostring = tostring local coroutine = coroutine local concat = table.concat local fetch_table = tablepool.fetch local release_table = tablepool.release local get_last_failure = ngx_balancer.get_last_failure local set_current_peer = ngx_balancer.set_current_peer local set_timeouts = ngx_balancer.set_timeouts local set_more_tries = ngx_balancer.set_more_tries local enable_keepalive = ngx_balancer.enable_keepalive local time_ns = kong_time.time_ns local get_now_ms = kong_time.get_now_ms local get_start_time_ms = kong_time.get_start_time_ms local get_updated_now_ms = kong_time.get_updated_now_ms local req_dyn_hook_run_hook = req_dyn_hook.run_hook local req_dyn_hook_is_group_enabled = req_dyn_hook.is_group_enabled local DECLARATIVE_LOAD_KEY = constants.DECLARATIVE_LOAD_KEY local CTX_NS = "ctx" local CTX_NARR = 0 local CTX_NREC = 50 -- normally Kong has ~32 keys in ctx local UPSTREAM_KEEPALIVE_POOL_SIZE local UPSTREAM_KEEPALIVE_IDLE_TIMEOUT local UPSTREAM_KEEPALIVE_MAX_REQUESTS local declarative_entities local declarative_meta local declarative_hash local schema_state local stash_init_worker_error local log_init_worker_errors do local init_worker_errors local init_worker_errors_str local ctx_k = {} stash_init_worker_error = function(err) if err == nil then return end err = tostring(err) if not init_worker_errors then init_worker_errors = {} end table.insert(init_worker_errors, err) init_worker_errors_str = concat(init_worker_errors, ", ") return ngx_log(ngx_CRIT, "worker initialization error: ", err, "; this node must be restarted") end log_init_worker_errors = function(ctx) if not init_worker_errors_str or ctx[ctx_k] then return end ctx[ctx_k] = true return ngx_log(ngx_ALERT, "unsafe request processing due to earlier ", "initialization errors; this node must be ", "restarted (", init_worker_errors_str, ")") end end local is_data_plane local is_control_plane local is_dbless do is_data_plane = function(config) return config.role == "data_plane" end is_control_plane = function(config) return config.role == "control_plane" end is_dbless = function(config) return config.database == "off" end end local reset_kong_shm do local preserve_keys = { "kong:node_id", constants.DYN_LOG_LEVEL_KEY, constants.DYN_LOG_LEVEL_TIMEOUT_AT_KEY, "events:requests", "events:requests:http", "events:requests:https", "events:requests:h2c", "events:requests:h2", "events:requests:grpc", "events:requests:grpcs", "events:requests:ws", "events:requests:wss", "events:requests:go_plugins", "events:km:visit", "events:streams", "events:streams:tcp", "events:streams:tls", "events:ai:response_tokens", "events:ai:prompt_tokens", "events:ai:requests", } reset_kong_shm = function(config) local kong_shm = ngx.shared.kong local preserved = {} if is_dbless(config) then if not (config.declarative_config or config.declarative_config_string) then preserved[DECLARATIVE_LOAD_KEY] = kong_shm:get(DECLARATIVE_LOAD_KEY) end end for _, key in ipairs(preserve_keys) do preserved[key] = kong_shm:get(key) -- ignore errors end kong_shm:flush_all() for key, value in pairs(preserved) do kong_shm:set(key, value) end kong_shm:flush_expired(0) end end local function setup_plugin_context(ctx, plugin, conf) if plugin.handler._go then ctx.ran_go_plugin = true end kong_global.set_named_ctx(kong, "plugin", plugin.handler, ctx) kong_global.set_namespaced_log(kong, plugin.name, ctx) ctx.plugin_id = conf.__plugin_id end local function reset_plugin_context(ctx, old_ws) kong_global.reset_log(kong, ctx) if old_ws then ctx.workspace = old_ws end end local function execute_init_worker_plugins_iterator(plugins_iterator, ctx) local iterator, plugins = plugins_iterator:get_init_worker_iterator() if not iterator then return end local errors for _, plugin in iterator, plugins, 0 do kong_global.set_namespaced_log(kong, plugin.name, ctx) -- guard against failed handler in "init_worker" phase only because it will -- cause Kong to not correctly initialize and can not be recovered automatically. local ok, err = pcall(plugin.handler.init_worker, plugin.handler) if not ok then errors = errors or {} errors[#errors + 1] = { plugin = plugin.name, err = err, } end kong_global.reset_log(kong, ctx) end return errors end local function execute_global_plugins_iterator(plugins_iterator, phase, ctx) if not plugins_iterator.has_plugins then return end local iterator, plugins = plugins_iterator:get_global_iterator(phase) if not iterator then return end local old_ws = ctx.workspace local has_timing = ctx.has_timing if has_timing then req_dyn_hook_run_hook("timing", "before:plugin_iterator") end for _, plugin, configuration in iterator, plugins, 0 do local span if phase == "rewrite" then span = instrumentation.plugin_rewrite(plugin) end setup_plugin_context(ctx, plugin, configuration) if has_timing then req_dyn_hook_run_hook("timing", "before:plugin", plugin.name, ctx.plugin_id) end plugin.handler[phase](plugin.handler, configuration) if has_timing then req_dyn_hook_run_hook("timing", "after:plugin") end reset_plugin_context(ctx, old_ws) if span then span:finish() end end if has_timing then req_dyn_hook_run_hook("timing", "after:plugin_iterator") end end local function execute_collecting_plugins_iterator(plugins_iterator, phase, ctx) if not plugins_iterator.has_plugins then return end local iterator, plugins = plugins_iterator:get_collecting_iterator(ctx) if not iterator then return end ctx.delay_response = true local old_ws = ctx.workspace local has_timing = ctx.has_timing if has_timing then req_dyn_hook_run_hook("timing", "before:plugin_iterator") end for _, plugin, configuration in iterator, plugins, 0 do if not ctx.delayed_response then local span if phase == "access" then span = instrumentation.plugin_access(plugin) end setup_plugin_context(ctx, plugin, configuration) if has_timing then req_dyn_hook_run_hook( "timing", "before:plugin", plugin.name, ctx.plugin_id) end local co = coroutine.create(plugin.handler[phase]) local cok, cerr = coroutine.resume(co, plugin.handler, configuration) if has_timing then req_dyn_hook_run_hook("timing", "after:plugin") end if not cok then -- set tracing error if span then span:record_error(cerr) span:set_status(2) end kong.log.err(cerr) ctx.delayed_response = { status_code = 500, content = { message = "An unexpected error occurred" }, } -- plugin that throws runtime exception should be marked as `error` ctx.KONG_UNEXPECTED = true end reset_plugin_context(ctx, old_ws) -- ends tracing span if span then span:finish() end end end if has_timing then req_dyn_hook_run_hook("timing", "after:plugin_iterator") end ctx.delay_response = nil end local function execute_collected_plugins_iterator(plugins_iterator, phase, ctx) if not plugins_iterator.has_plugins then return end local iterator, plugins = plugins_iterator:get_collected_iterator(phase, ctx) if not iterator then return end local old_ws = ctx.workspace local has_timing = ctx.has_timing if has_timing then req_dyn_hook_run_hook("timing", "before:plugin_iterator") end for _, plugin, configuration in iterator, plugins, 0 do local span if phase == "header_filter" then span = instrumentation.plugin_header_filter(plugin) end setup_plugin_context(ctx, plugin, configuration) if has_timing then req_dyn_hook_run_hook("timing", "before:plugin", plugin.name, ctx.plugin_id) end plugin.handler[phase](plugin.handler, configuration) if has_timing then req_dyn_hook_run_hook("timing", "after:plugin") end reset_plugin_context(ctx, old_ws) if span then span:finish() end end if has_timing then req_dyn_hook_run_hook("timing", "after:plugin_iterator") end end local function execute_cache_warmup(kong_config) if is_dbless(kong_config) then return true end if worker_id() == 0 then local ok, err = cache_warmup.execute(kong_config.db_cache_warmup_entities) if not ok then return nil, err end end return true end local function flush_delayed_response(ctx) ctx.delay_response = nil ctx.buffered_proxying = nil if type(ctx.delayed_response_callback) == "function" then ctx.delayed_response_callback(ctx) return -- avoid tail call end local dr = ctx.delayed_response local message = dr.content and dr.content.message or dr.content kong.response.error(dr.status_code, message, dr.headers) end local function has_declarative_config(kong_config) local declarative_config = kong_config.declarative_config local declarative_config_string = kong_config.declarative_config_string return declarative_config or declarative_config_string, declarative_config ~= nil, -- is filename declarative_config_string ~= nil -- is string end local function parse_declarative_config(kong_config, dc) local declarative_config, is_file, is_string = has_declarative_config(kong_config) local entities, err, _, meta, hash if not declarative_config then -- return an empty configuration, -- including only the default workspace entities, _, _, meta, hash = dc:parse_table({ _format_version = "3.0" }) return entities, nil, meta, hash end if is_file then entities, err, _, meta, hash = dc:parse_file(declarative_config) elseif is_string then entities, err, _, meta, hash = dc:parse_string(declarative_config) end if not entities then if is_file then return nil, "error parsing declarative config file " .. declarative_config .. ":\n" .. err elseif is_string then return nil, "error parsing declarative string " .. declarative_config .. ":\n" .. err end end return entities, nil, meta, hash end local function declarative_init_build() local default_ws = kong.db.workspaces:select_by_name("default") kong.default_workspace = default_ws and default_ws.id or kong.default_workspace local ok, err = runloop.build_plugins_iterator("init") if not ok then return nil, "error building initial plugins iterator: " .. err end ok, err = runloop.build_router("init") if not ok then return nil, "error building initial router: " .. err end return true end local function load_declarative_config(kong_config, entities, meta, hash) local opts = { name = "declarative_config", } local kong_shm = ngx.shared.kong local ok, err = concurrency.with_worker_mutex(opts, function() local value = kong_shm:get(DECLARATIVE_LOAD_KEY) if value then return true end local ok, err = declarative.load_into_cache(entities, meta, hash) if not ok then return nil, err end if kong_config.declarative_config then kong.log.notice("declarative config loaded from ", kong_config.declarative_config) end ok, err = kong_shm:safe_set(DECLARATIVE_LOAD_KEY, true) if not ok then kong.log.warn("failed marking declarative_config as loaded: ", err) end return true end) if ok then return declarative_init_build() end return nil, err end local function list_migrations(migtable) local list = {} for _, t in ipairs(migtable) do local mignames = {} for _, mig in ipairs(t.migrations) do table.insert(mignames, mig.name) end table.insert(list, fmt("%s (%s)", t.subsystem, concat(mignames, ", "))) end return concat(list, " ") end -- Kong public context handlers. -- @section kong_handlers local Kong = {} function Kong.init() local pl_path = require "pl.path" local conf_loader = require "kong.conf_loader" -- check if kong global is the correct one if not kong.version then error("configuration error: make sure your template is not setting a " .. "global named 'kong' (please use 'Kong' instead)") end -- retrieve kong_config local conf_path = pl_path.join(ngx.config.prefix(), ".kong_env") local config = assert(conf_loader(conf_path, nil, { from_kong_env = true })) UPSTREAM_KEEPALIVE_POOL_SIZE = config.upstream_keepalive_pool_size UPSTREAM_KEEPALIVE_IDLE_TIMEOUT = config.upstream_keepalive_idle_timeout UPSTREAM_KEEPALIVE_MAX_REQUESTS = config.upstream_keepalive_max_requests -- The dns client has been initialized in conf_loader, so we set it directly. -- Other modules should use 'kong.dns' to avoid reinitialization. kong.dns = assert(package.loaded["kong.resty.dns.client"]) reset_kong_shm(config) -- special math.randomseed from kong.globalpatches not taking any argument. -- Must only be called in the init or init_worker phases, to avoid -- duplicated seeds. math.randomseed() kong_global.init_pdk(kong, config) instrumentation.init(config) wasm.init(config) local db = assert(DB.new(config)) instrumentation.db_query(db.connector) assert(db:init_connector()) -- check state of migration only if there is an external database if not is_dbless(config) then ngx_log(ngx_DEBUG, "checking database schema state") local migrations_utils = require "kong.cmd.utils.migrations" schema_state = assert(db:schema_state()) migrations_utils.check_state(schema_state) if schema_state.missing_migrations or schema_state.pending_migrations then if schema_state.missing_migrations then ngx_log(ngx_WARN, "database is missing some migrations:\n", schema_state.missing_migrations) end if schema_state.pending_migrations then ngx_log(ngx_WARN, "database has pending migrations:\n", schema_state.pending_migrations) end end end assert(db:connect()) kong.db = db if config.proxy_ssl_enabled or config.stream_ssl_enabled then certificate.init() end if is_http_module and (is_data_plane(config) or is_control_plane(config)) then kong.clustering = require("kong.clustering").new(config) if config.cluster_rpc then kong.rpc = require("kong.clustering.rpc.manager").new(config, kong.node.get_id()) if config.cluster_rpc_sync then kong.sync = require("kong.clustering.services.sync").new(db, is_control_plane(config)) kong.sync:init(kong.rpc) end end end assert(db.vaults:load_vault_schemas(config.loaded_vaults)) -- Load plugins as late as possible so that everything is set up assert(db.plugins:load_plugin_schemas(config.loaded_plugins)) if is_stream_module then stream_api.load_handlers() end if is_dbless(config) then local dc, err = declarative.new_config(config) if not dc then error(err) end kong.db.declarative_config = dc if is_http_module or (#config.proxy_listeners == 0 and #config.admin_listeners == 0 and #config.status_listeners == 0) then declarative_entities, err, declarative_meta, declarative_hash = parse_declarative_config(kong.configuration, dc) if not declarative_entities then error(err) end kong.vault.warmup(declarative_entities) end else local default_ws = db.workspaces:select_by_name("default") kong.default_workspace = default_ws and default_ws.id local ok, err = runloop.build_plugins_iterator("init") if not ok then error("error building initial plugins: " .. tostring(err)) end if not is_control_plane(config) then assert(runloop.build_router("init")) ok, err = wasm.check_enabled_filters() if not ok then error("[wasm]: " .. err) end end ok, err = runloop.set_init_versions_in_cache() if not ok then error("error setting initial versions for router and plugins iterator in cache: " .. tostring(err)) end end db:close() require("resty.kong.var").patch_metatable() -- NOTE: privileged_agent is disabled when rpc sync is on if config.dedicated_config_processing and is_data_plane(config) and not kong.sync then -- TODO: figure out if there is better value than 4096 -- 4096 is for the cocurrency of the lua-resty-timer-ng local ok, err = process.enable_privileged_agent(4096) if not ok then error(err) end end if config.request_debug and config.role ~= "control_plane" and is_http_module then local token = config.request_debug_token or uuid() local request_debug_token_file = pl_path.join(config.prefix, constants.REQUEST_DEBUG_TOKEN_FILE) if pl_path.exists(request_debug_token_file) then local ok, err = pl_file.delete(request_debug_token_file) if not ok then ngx.log(ngx.ERR, "failed to delete old .request_debug_token file: ", err) end end local ok, err = pl_file.write(request_debug_token_file, token) if not ok then ngx.log(ngx.ERR, "failed to write .request_debug_token file: ", err) end kong.request_debug_token = token ngx.log(ngx.NOTICE, constants.REQUEST_DEBUG_LOG_PREFIX, " token for request debugging: ", kong.request_debug_token) end end function Kong.init_worker() emmy_debugger.init() local ctx = ngx.ctx ctx.KONG_PHASE = PHASES.init_worker -- special math.randomseed from kong.globalpatches not taking any argument. -- Must only be called in the init or init_worker phases, to avoid -- duplicated seeds. math.randomseed() -- setup timerng to _G.kong kong.timer = _G.timerng _G.timerng = nil kong.timer:set_debug(kong.configuration.log_level == "debug") kong.timer:start() -- init DB local ok, err = kong.db:init_worker() if not ok then stash_init_worker_error("failed to instantiate 'kong.db' module: " .. err) return end if worker_id() == 0 and not is_dbless(kong.configuration) then if schema_state.missing_migrations then ngx_log(ngx_WARN, "missing migrations: ", list_migrations(schema_state.missing_migrations)) end if schema_state.pending_migrations then ngx_log(ngx_INFO, "starting with pending migrations: ", list_migrations(schema_state.pending_migrations)) end end schema_state = nil local worker_events, err = kong_global.init_worker_events(kong.configuration) if not worker_events then stash_init_worker_error("failed to instantiate 'kong.worker_events' " .. "module: " .. err) return end kong.worker_events = worker_events local cluster_events, err = kong_global.init_cluster_events(kong.configuration, kong.db) if not cluster_events then stash_init_worker_error("failed to instantiate 'kong.cluster_events' " .. "module: " .. err) return end kong.cluster_events = cluster_events local cache, err = kong_global.init_cache(kong.configuration, cluster_events, worker_events) if not cache then stash_init_worker_error("failed to instantiate 'kong.cache' module: " .. err) return end kong.cache = cache local core_cache, err = kong_global.init_core_cache(kong.configuration, cluster_events, worker_events) if not core_cache then stash_init_worker_error("failed to instantiate 'kong.core_cache' module: " .. err) return end kong.core_cache = core_cache kong.db:set_events_handler(worker_events) if kong.configuration.admin_gui_listeners then kong.cache:invalidate_local(constants.ADMIN_GUI_KCONFIG_CACHE_KEY) end if kong.clustering then local using_dedicated = kong.configuration.dedicated_config_processing -- CP needs to support both v1 and v2 sync -- v1 sync is only enabled for DP if v2 sync is unavailble kong.clustering:init_worker() -- see is_dp_worker_process() in clustering/utils.lua if using_dedicated and process.type() == "privileged agent" then return end end kong.vault.init_worker() kong.timing = kong_global.init_timing() kong.timing.init_worker(kong.configuration.request_debug) if is_dbless(kong.configuration) then -- databases in LMDB need to be explicitly created, otherwise `get` -- operations will return error instead of `nil`. This ensures the default -- namespace always exists in the local t = lmdb_txn.begin(1) t:db_open(true) ok, err = t:commit() if not ok then stash_init_worker_error("failed to create and open LMDB database: " .. err) return end if not has_declarative_config(kong.configuration) and declarative.get_current_hash() ~= nil then -- if there is no declarative config set and a config is present in LMDB, -- just build the router and plugins iterator ngx_log(ngx_INFO, "found persisted lmdb config, loading...") local ok, err = declarative_init_build() if not ok then stash_init_worker_error("failed to initialize declarative config: " .. err) return end elseif declarative_entities then ok, err = load_declarative_config(kong.configuration, declarative_entities, declarative_meta, declarative_hash) declarative_entities = nil declarative_meta = nil declarative_hash = nil if not ok then stash_init_worker_error("failed to load declarative config file: " .. err) return end else -- stream does not need to load declarative config again, just build -- the router and plugins iterator local ok, err = declarative_init_build() if not ok then stash_init_worker_error("failed to initialize declarative config: " .. err) return end end end local is_not_control_plane = not is_control_plane(kong.configuration) if is_not_control_plane then ok, err = execute_cache_warmup(kong.configuration) if not ok then ngx_log(ngx_ERR, "failed to warm up the DB cache: ", err) end end ok, err = runloop.update_plugins_iterator() if not ok then stash_init_worker_error("failed to build the plugins iterator: " .. err) return end if is_not_control_plane then ok, err = runloop.update_router() if not ok then stash_init_worker_error("failed to build the router: " .. err) return end end runloop.init_worker.before() -- run plugins init_worker context local plugins_iterator = runloop.get_plugins_iterator() local errors = execute_init_worker_plugins_iterator(plugins_iterator, ctx) if errors then for _, e in ipairs(errors) do local err = 'failed to execute the "init_worker" ' .. 'handler for plugin "' .. e.plugin ..'": ' .. e.err stash_init_worker_error(err) end end if is_not_control_plane then plugin_servers.start() end -- rpc and sync if is_http_module then -- init rpc connection if kong.rpc then kong.rpc:init_worker() end -- init sync -- should run after rpc init successfully if kong.sync then kong.sync:init_worker() end end ok, err = wasm.init_worker() if not ok then err = "wasm nginx worker initialization failed: " .. tostring(err) stash_init_worker_error(err) return end plugins_iterator:configure(ctx) end function Kong.exit_worker() if process.type() ~= "privileged agent" and not is_control_plane(kong.configuration) then plugin_servers.stop() end end function Kong.ssl_certificate() -- Note: ctx here is for a connection (not for a single request) local ctx = get_ctx_table(fetch_table(CTX_NS, CTX_NARR, CTX_NREC)) ctx.KONG_PHASE = PHASES.certificate log_init_worker_errors(ctx) -- this is the first phase to run on an HTTPS request ctx.workspace = kong.default_workspace certificate.execute() local plugins_iterator = runloop.get_updated_plugins_iterator() execute_global_plugins_iterator(plugins_iterator, "certificate", ctx) -- TODO: do we want to keep connection context? kong.table.clear(ngx.ctx) end function Kong.ssl_client_hello() local ctx = get_ctx_table(fetch_table(CTX_NS, CTX_NARR, CTX_NREC)) ctx.KONG_PHASE = PHASES.client_hello end function Kong.preread() local ctx = get_ctx_table(fetch_table(CTX_NS, CTX_NARR, CTX_NREC)) if not ctx.KONG_PROCESSING_START then ctx.KONG_PROCESSING_START = get_start_time_ms() end if not ctx.KONG_PREREAD_START then ctx.KONG_PREREAD_START = get_now_ms() end ctx.KONG_PHASE = PHASES.preread log_init_worker_errors(ctx) local preread_terminate = runloop.preread.before(ctx) -- if proxying to a second layer TLS terminator is required -- abort further execution and return back to Nginx if preread_terminate then return end local plugins_iterator = runloop.get_updated_plugins_iterator() execute_collecting_plugins_iterator(plugins_iterator, "preread", ctx) if ctx.delayed_response then ctx.KONG_PREREAD_ENDED_AT = get_updated_now_ms() ctx.KONG_PREREAD_TIME = ctx.KONG_PREREAD_ENDED_AT - ctx.KONG_PREREAD_START ctx.KONG_RESPONSE_LATENCY = ctx.KONG_PREREAD_ENDED_AT - ctx.KONG_PROCESSING_START return flush_delayed_response(ctx) end ctx.delay_response = nil if not ctx.service then ctx.KONG_PREREAD_ENDED_AT = get_updated_now_ms() ctx.KONG_PREREAD_TIME = ctx.KONG_PREREAD_ENDED_AT - ctx.KONG_PREREAD_START ctx.KONG_RESPONSE_LATENCY = ctx.KONG_PREREAD_ENDED_AT - ctx.KONG_PROCESSING_START ngx_log(ngx_WARN, "no Service found with those values") return ngx.exit(503) end runloop.preread.after(ctx) ctx.KONG_PREREAD_ENDED_AT = get_updated_now_ms() ctx.KONG_PREREAD_TIME = ctx.KONG_PREREAD_ENDED_AT - ctx.KONG_PREREAD_START -- we intent to proxy, though balancer may fail on that ctx.KONG_PROXIED = true end function Kong.rewrite() local proxy_mode = var.kong_proxy_mode if proxy_mode == "grpc" or proxy_mode == "unbuffered" then kong_resty_ctx.apply_ref() -- if kong_proxy_mode is gRPC/unbuffered, this is executing local ctx = ngx.ctx -- after an internal redirect. Restore (and restash) kong_resty_ctx.stash_ref(ctx) -- context to avoid re-executing phases ctx.KONG_REWRITE_ENDED_AT = get_now_ms() ctx.KONG_REWRITE_TIME = ctx.KONG_REWRITE_ENDED_AT - ctx.KONG_REWRITE_START return end local is_https = var.https == "on" local ctx if is_https then ctx = ngx.ctx else ctx = get_ctx_table(fetch_table(CTX_NS, CTX_NARR, CTX_NREC)) end if not ctx.KONG_PROCESSING_START then ctx.KONG_PROCESSING_START = get_start_time_ms() end if not ctx.KONG_REWRITE_START then ctx.KONG_REWRITE_START = get_now_ms() end ctx.KONG_PHASE = PHASES.rewrite local has_timing req_dyn_hook_run_hook("timing:auth", "auth") if req_dyn_hook_is_group_enabled("timing") then ctx.has_timing = true has_timing = true end if has_timing then req_dyn_hook_run_hook("timing", "before:rewrite") end kong_resty_ctx.stash_ref(ctx) if not is_https then log_init_worker_errors(ctx) end runloop.rewrite.before(ctx) if not ctx.workspace then ctx.workspace = kong.default_workspace end -- On HTTPS requests, the plugins iterator is already updated in the ssl_certificate phase local plugins_iterator if is_https then plugins_iterator = runloop.get_plugins_iterator() else plugins_iterator = runloop.get_updated_plugins_iterator() end execute_global_plugins_iterator(plugins_iterator, "rewrite", ctx) ctx.KONG_REWRITE_ENDED_AT = get_updated_now_ms() ctx.KONG_REWRITE_TIME = ctx.KONG_REWRITE_ENDED_AT - ctx.KONG_REWRITE_START if has_timing then req_dyn_hook_run_hook("timing", "after:rewrite") end end function Kong.access() local ctx = ngx.ctx local has_timing = ctx.has_timing if has_timing then req_dyn_hook_run_hook("timing", "before:access") end if not ctx.KONG_ACCESS_START then ctx.KONG_ACCESS_START = get_now_ms() if ctx.KONG_REWRITE_START and not ctx.KONG_REWRITE_ENDED_AT then ctx.KONG_REWRITE_ENDED_AT = ctx.KONG_ACCESS_START ctx.KONG_REWRITE_TIME = ctx.KONG_REWRITE_ENDED_AT - ctx.KONG_REWRITE_START end end ctx.KONG_PHASE = PHASES.access runloop.access.before(ctx) local plugins_iterator = runloop.get_plugins_iterator() execute_collecting_plugins_iterator(plugins_iterator, "access", ctx) if ctx.delayed_response then ctx.KONG_ACCESS_ENDED_AT = get_updated_now_ms() ctx.KONG_ACCESS_TIME = ctx.KONG_ACCESS_ENDED_AT - ctx.KONG_ACCESS_START ctx.KONG_RESPONSE_LATENCY = ctx.KONG_ACCESS_ENDED_AT - ctx.KONG_PROCESSING_START if has_timing then req_dyn_hook_run_hook("timing", "after:access") end return flush_delayed_response(ctx) end ctx.delay_response = nil if not ctx.service then ctx.KONG_ACCESS_ENDED_AT = get_updated_now_ms() ctx.KONG_ACCESS_TIME = ctx.KONG_ACCESS_ENDED_AT - ctx.KONG_ACCESS_START ctx.KONG_RESPONSE_LATENCY = ctx.KONG_ACCESS_ENDED_AT - ctx.KONG_PROCESSING_START ctx.buffered_proxying = nil if has_timing then req_dyn_hook_run_hook("timing", "after:access") end return kong.response.error(503, "no Service found with those values") end runloop.wasm_attach(ctx) runloop.access.after(ctx) ctx.KONG_ACCESS_ENDED_AT = get_updated_now_ms() ctx.KONG_ACCESS_TIME = ctx.KONG_ACCESS_ENDED_AT - ctx.KONG_ACCESS_START -- we intent to proxy, though balancer may fail on that ctx.KONG_PROXIED = true if ctx.buffered_proxying then local upgrade = var.upstream_upgrade or "" if upgrade == "" then if has_timing then req_dyn_hook_run_hook("timing", "after:access") end return Kong.response() end ngx_log(ngx_NOTICE, "response buffering was turned off: connection upgrade (", upgrade, ")") ctx.buffered_proxying = nil end if has_timing then req_dyn_hook_run_hook("timing", "after:access") end end function Kong.balancer() local ctx = ngx.ctx local has_timing = ctx.has_timing if has_timing then req_dyn_hook_run_hook("timing", "before:balancer") end -- This may be called multiple times, and no yielding here! local now_ms = get_now_ms() local now_ns = time_ns() if not ctx.KONG_BALANCER_START then ctx.KONG_BALANCER_START = now_ms if is_stream_module then if ctx.KONG_PREREAD_START and not ctx.KONG_PREREAD_ENDED_AT then ctx.KONG_PREREAD_ENDED_AT = ctx.KONG_BALANCER_START ctx.KONG_PREREAD_TIME = ctx.KONG_PREREAD_ENDED_AT - ctx.KONG_PREREAD_START end else if ctx.KONG_REWRITE_START and not ctx.KONG_REWRITE_ENDED_AT then ctx.KONG_REWRITE_ENDED_AT = ctx.KONG_ACCESS_START or ctx.KONG_BALANCER_START ctx.KONG_REWRITE_TIME = ctx.KONG_REWRITE_ENDED_AT - ctx.KONG_REWRITE_START end if ctx.KONG_ACCESS_START and not ctx.KONG_ACCESS_ENDED_AT then ctx.KONG_ACCESS_ENDED_AT = ctx.KONG_BALANCER_START ctx.KONG_ACCESS_TIME = ctx.KONG_ACCESS_ENDED_AT - ctx.KONG_ACCESS_START end end end ctx.KONG_PHASE = PHASES.balancer local balancer_data = ctx.balancer_data local tries = balancer_data.tries local try_count = balancer_data.try_count local current_try = table_new(0, 4) try_count = try_count + 1 balancer_data.try_count = try_count tries[try_count] = current_try current_try.balancer_start = now_ms current_try.balancer_start_ns = now_ns if try_count > 1 then -- only call balancer on retry, first one is done in `runloop.access.after` -- which runs in the ACCESS context and hence has less limitations than -- this BALANCER context where the retries are executed -- record failure data local previous_try = tries[try_count - 1] previous_try.state, previous_try.code = get_last_failure() -- Report HTTP status for health checks local balancer_instance = balancer_data.balancer if balancer_instance then if previous_try.state == "failed" then if previous_try.code == 504 then balancer_instance.report_timeout(balancer_data.balancer_handle) else balancer_instance.report_tcp_failure(balancer_data.balancer_handle) end else balancer_instance.report_http_status(balancer_data.balancer_handle, previous_try.code) end end local ok, err, errcode = balancer.execute(balancer_data, ctx) if not ok then ngx_log(ngx_ERR, "failed to retry the dns/balancer resolver for ", tostring(balancer_data.host), "' with: ", tostring(err)) ctx.KONG_BALANCER_ENDED_AT = get_updated_now_ms() ctx.KONG_BALANCER_TIME = ctx.KONG_BALANCER_ENDED_AT - ctx.KONG_BALANCER_START ctx.KONG_PROXY_LATENCY = ctx.KONG_BALANCER_ENDED_AT - ctx.KONG_PROCESSING_START if has_timing then req_dyn_hook_run_hook("timing", "after:balancer") end return ngx.exit(errcode) end if is_http_module then ok, err = balancer.set_host_header(balancer_data, var.upstream_scheme, var.upstream_host, true) if not ok then ngx_log(ngx_ERR, "failed to set balancer Host header: ", err) if has_timing then req_dyn_hook_run_hook("timing", "after:balancer") end return ngx.exit(500) end end else -- first try, so set the max number of retries local retries = balancer_data.retries if retries > 0 then set_more_tries(retries) end end local balancer_data_ip = balancer_data.ip local balancer_data_port = balancer_data.port local pool if UPSTREAM_KEEPALIVE_POOL_SIZE > 0 and is_http_module then if balancer_data.scheme == "https" then -- upstream_host is SNI local service = ctx.service if service then pool = fmt("%s|%s|%s|%s|%s|%s|%s", balancer_data_ip, balancer_data_port, var.upstream_host, service.tls_verify and "1" or "0", service.tls_verify_depth or "", service.ca_certificates and concat(service.ca_certificates, ",") or "", service.client_certificate and service.client_certificate.id or "") else pool = balancer_data_ip .. "|" .. balancer_data_port .. "|" .. var.upstream_host end else pool = balancer_data_ip .. "|" .. balancer_data_port end end current_try.ip = balancer_data_ip current_try.port = balancer_data_port -- set the targets as resolved ngx_log(ngx_DEBUG, "setting address (try ", try_count, "): ", balancer_data_ip, ":", balancer_data_port) local ok, err = set_current_peer(balancer_data_ip, balancer_data_port, pool) if not ok then ngx_log(ngx_ERR, "failed to set the current peer (address: ", tostring(balancer_data_ip), " port: ", tostring(balancer_data_port), "): ", tostring(err)) ctx.KONG_BALANCER_ENDED_AT = get_updated_now_ms() ctx.KONG_BALANCER_TIME = ctx.KONG_BALANCER_ENDED_AT - ctx.KONG_BALANCER_START ctx.KONG_PROXY_LATENCY = ctx.KONG_BALANCER_ENDED_AT - ctx.KONG_PROCESSING_START if has_timing then req_dyn_hook_run_hook("timing", "after:balancer") end return ngx.exit(500) end ok, err = set_timeouts(balancer_data.connect_timeout / 1000, balancer_data.send_timeout / 1000, balancer_data.read_timeout / 1000) if not ok then ngx_log(ngx_ERR, "could not set upstream timeouts: ", err) end if pool then ok, err = enable_keepalive(UPSTREAM_KEEPALIVE_IDLE_TIMEOUT, UPSTREAM_KEEPALIVE_MAX_REQUESTS) if not ok then ngx_log(ngx_ERR, "could not enable connection keepalive: ", err) end ngx_log(ngx_DEBUG, "enabled connection keepalive (pool=", pool, ", pool_size=", UPSTREAM_KEEPALIVE_POOL_SIZE, ", idle_timeout=", UPSTREAM_KEEPALIVE_IDLE_TIMEOUT, ", max_requests=", UPSTREAM_KEEPALIVE_MAX_REQUESTS, ")") end -- record overall latency ctx.KONG_BALANCER_ENDED_AT = get_updated_now_ms() ctx.KONG_BALANCER_TIME = ctx.KONG_BALANCER_ENDED_AT - ctx.KONG_BALANCER_START -- record try-latency local try_latency = ctx.KONG_BALANCER_ENDED_AT - current_try.balancer_start current_try.balancer_latency = try_latency current_try.balancer_latency_ns = time_ns() - current_try.balancer_start_ns -- time spent in Kong before sending the request to upstream -- start_time() is kept in seconds with millisecond resolution. ctx.KONG_PROXY_LATENCY = ctx.KONG_BALANCER_ENDED_AT - ctx.KONG_PROCESSING_START if has_timing then req_dyn_hook_run_hook("timing", "after:balancer") end end do local HTTP_METHODS = { GET = ngx.HTTP_GET, HEAD = ngx.HTTP_HEAD, PUT = ngx.HTTP_PUT, POST = ngx.HTTP_POST, DELETE = ngx.HTTP_DELETE, OPTIONS = ngx.HTTP_OPTIONS, MKCOL = ngx.HTTP_MKCOL, COPY = ngx.HTTP_COPY, MOVE = ngx.HTTP_MOVE, PROPFIND = ngx.HTTP_PROPFIND, PROPPATCH = ngx.HTTP_PROPPATCH, LOCK = ngx.HTTP_LOCK, UNLOCK = ngx.HTTP_UNLOCK, PATCH = ngx.HTTP_PATCH, TRACE = ngx.HTTP_TRACE, } function Kong.response() local ctx = ngx.ctx local has_timing = ctx.has_timing if has_timing then req_dyn_hook_run_hook("timing", "before:response") end local plugins_iterator = runloop.get_plugins_iterator() -- buffered proxying (that also executes the balancer) ngx.req.read_body() local options = { always_forward_body = true, share_all_vars = true, method = HTTP_METHODS[ngx.req.get_method()], ctx = ctx, } local res = ngx.location.capture("/kong_buffered_http", options) if res.truncated and options.method ~= ngx.HTTP_HEAD then ctx.KONG_PHASE = PHASES.error ngx.status = res.status or 502 if has_timing then req_dyn_hook_run_hook("timing", "after:response") end return kong_error_handlers(ctx) end ctx.KONG_PHASE = PHASES.response local status = res.status local headers = res.header local body = res.body ctx.buffered_status = status ctx.buffered_headers = headers ctx.buffered_body = body -- fake response phase (this runs after the balancer) if not ctx.KONG_RESPONSE_START then ctx.KONG_RESPONSE_START = get_now_ms() if ctx.KONG_BALANCER_START and not ctx.KONG_BALANCER_ENDED_AT then ctx.KONG_BALANCER_ENDED_AT = ctx.KONG_RESPONSE_START ctx.KONG_BALANCER_TIME = ctx.KONG_BALANCER_ENDED_AT - ctx.KONG_BALANCER_START end end if not ctx.KONG_WAITING_TIME then ctx.KONG_WAITING_TIME = ctx.KONG_RESPONSE_START - (ctx.KONG_BALANCER_ENDED_AT or ctx.KONG_ACCESS_ENDED_AT) end if not ctx.KONG_PROXY_LATENCY then ctx.KONG_PROXY_LATENCY = ctx.KONG_RESPONSE_START - ctx.KONG_PROCESSING_START end if not ctx.KONG_UPSTREAM_DNS_TIME and ctx.KONG_UPSTREAM_DNS_END_AT and ctx.KONG_UPSTREAM_DNS_START then ctx.KONG_UPSTREAM_DNS_TIME = ctx.KONG_UPSTREAM_DNS_END_AT - ctx.KONG_UPSTREAM_DNS_START else ctx.KONG_UPSTREAM_DNS_TIME = 0 end kong.response.set_status(status) kong.response.set_headers(headers) execute_collected_plugins_iterator(plugins_iterator, "response", ctx) ctx.KONG_RESPONSE_ENDED_AT = get_updated_now_ms() ctx.KONG_RESPONSE_TIME = ctx.KONG_RESPONSE_ENDED_AT - ctx.KONG_RESPONSE_START -- buffered response ngx.print(body) if has_timing then req_dyn_hook_run_hook("timing", "after:response") end -- jump over the balancer to header_filter ngx.exit(status) end end function Kong.header_filter() local ctx = ngx.ctx local has_timing = ctx.has_timing if has_timing then req_dyn_hook_run_hook("timing", "before:header_filter") end if not ctx.KONG_PROCESSING_START then ctx.KONG_PROCESSING_START = get_start_time_ms() end if not ctx.workspace then ctx.workspace = kong.default_workspace end if not ctx.KONG_HEADER_FILTER_START then ctx.KONG_HEADER_FILTER_START = get_now_ms() if ctx.KONG_REWRITE_START and not ctx.KONG_REWRITE_ENDED_AT then ctx.KONG_REWRITE_ENDED_AT = ctx.KONG_BALANCER_START or ctx.KONG_ACCESS_START or ctx.KONG_RESPONSE_START or ctx.KONG_HEADER_FILTER_START ctx.KONG_REWRITE_TIME = ctx.KONG_REWRITE_ENDED_AT - ctx.KONG_REWRITE_START end if ctx.KONG_ACCESS_START and not ctx.KONG_ACCESS_ENDED_AT then ctx.KONG_ACCESS_ENDED_AT = ctx.KONG_BALANCER_START or ctx.KONG_RESPONSE_START or ctx.KONG_HEADER_FILTER_START ctx.KONG_ACCESS_TIME = ctx.KONG_ACCESS_ENDED_AT - ctx.KONG_ACCESS_START end if ctx.KONG_BALANCER_START and not ctx.KONG_BALANCER_ENDED_AT then ctx.KONG_BALANCER_ENDED_AT = ctx.KONG_RESPONSE_START or ctx.KONG_HEADER_FILTER_START ctx.KONG_BALANCER_TIME = ctx.KONG_BALANCER_ENDED_AT - ctx.KONG_BALANCER_START end if ctx.KONG_RESPONSE_START and not ctx.KONG_RESPONSE_ENDED_AT then ctx.KONG_RESPONSE_ENDED_AT = ctx.KONG_HEADER_FILTER_START ctx.KONG_RESPONSE_TIME = ctx.KONG_RESPONSE_ENDED_AT - ctx.KONG_RESPONSE_START end end if ctx.KONG_PROXIED then if not ctx.KONG_WAITING_TIME then ctx.KONG_WAITING_TIME = (ctx.KONG_RESPONSE_START or ctx.KONG_HEADER_FILTER_START) - (ctx.KONG_BALANCER_ENDED_AT or ctx.KONG_ACCESS_ENDED_AT) end if not ctx.KONG_PROXY_LATENCY then ctx.KONG_PROXY_LATENCY = (ctx.KONG_RESPONSE_START or ctx.KONG_HEADER_FILTER_START) - ctx.KONG_PROCESSING_START end elseif not ctx.KONG_RESPONSE_LATENCY then ctx.KONG_RESPONSE_LATENCY = (ctx.KONG_RESPONSE_START or ctx.KONG_HEADER_FILTER_START) - ctx.KONG_PROCESSING_START end ctx.KONG_PHASE = PHASES.header_filter runloop.header_filter.before(ctx) local plugins_iterator = runloop.get_plugins_iterator() execute_collected_plugins_iterator(plugins_iterator, "header_filter", ctx) runloop.header_filter.after(ctx) ctx.KONG_HEADER_FILTER_ENDED_AT = get_updated_now_ms() ctx.KONG_HEADER_FILTER_TIME = ctx.KONG_HEADER_FILTER_ENDED_AT - ctx.KONG_HEADER_FILTER_START if has_timing then req_dyn_hook_run_hook("timing", "after:header_filter") end end function Kong.body_filter() local ctx = ngx.ctx local has_timing = ctx.has_timing if has_timing then req_dyn_hook_run_hook("timing", "before:body_filter") end if not ctx.KONG_BODY_FILTER_START then ctx.KONG_BODY_FILTER_START = get_now_ms() if ctx.KONG_REWRITE_START and not ctx.KONG_REWRITE_ENDED_AT then ctx.KONG_REWRITE_ENDED_AT = ctx.KONG_ACCESS_START or ctx.KONG_BALANCER_START or ctx.KONG_RESPONSE_START or ctx.KONG_HEADER_FILTER_START or ctx.KONG_BODY_FILTER_START ctx.KONG_REWRITE_TIME = ctx.KONG_REWRITE_ENDED_AT - ctx.KONG_REWRITE_START end if ctx.KONG_ACCESS_START and not ctx.KONG_ACCESS_ENDED_AT then ctx.KONG_ACCESS_ENDED_AT = ctx.KONG_BALANCER_START or ctx.KONG_RESPONSE_START or ctx.KONG_HEADER_FILTER_START or ctx.KONG_BODY_FILTER_START ctx.KONG_ACCESS_TIME = ctx.KONG_ACCESS_ENDED_AT - ctx.KONG_ACCESS_START end if ctx.KONG_BALANCER_START and not ctx.KONG_BALANCER_ENDED_AT then ctx.KONG_BALANCER_ENDED_AT = ctx.KONG_RESPONSE_START or ctx.KONG_HEADER_FILTER_START or ctx.KONG_BODY_FILTER_START ctx.KONG_BALANCER_TIME = ctx.KONG_BALANCER_ENDED_AT - ctx.KONG_BALANCER_START end if ctx.KONG_RESPONSE_START and not ctx.KONG_RESPONSE_ENDED_AT then ctx.KONG_RESPONSE_ENDED_AT = ctx.KONG_HEADER_FILTER_START or ctx.KONG_BODY_FILTER_START ctx.KONG_RESPONSE_TIME = ctx.KONG_RESPONSE_ENDED_AT - ctx.KONG_RESPONSE_START end if ctx.KONG_HEADER_FILTER_START and not ctx.KONG_HEADER_FILTER_ENDED_AT then ctx.KONG_HEADER_FILTER_ENDED_AT = ctx.KONG_BODY_FILTER_START ctx.KONG_HEADER_FILTER_TIME = ctx.KONG_HEADER_FILTER_ENDED_AT - ctx.KONG_HEADER_FILTER_START end end ctx.KONG_PHASE = PHASES.body_filter if ctx.response_body then arg[1] = ctx.response_body arg[2] = true end local plugins_iterator = runloop.get_plugins_iterator() execute_collected_plugins_iterator(plugins_iterator, "body_filter", ctx) if not arg[2] then if has_timing then req_dyn_hook_run_hook("timing", "after:body_filter") end return end ctx.KONG_BODY_FILTER_ENDED_AT = get_updated_now_ms() ctx.KONG_BODY_FILTER_ENDED_AT_NS = time_ns() ctx.KONG_BODY_FILTER_TIME = ctx.KONG_BODY_FILTER_ENDED_AT - ctx.KONG_BODY_FILTER_START if ctx.KONG_PROXIED then -- time spent receiving the response ((response +) header_filter + body_filter) -- we could use $upstream_response_time but we need to distinguish the waiting time -- from the receiving time in our logging plugins (especially ALF serializer). ctx.KONG_RECEIVE_TIME = ctx.KONG_BODY_FILTER_ENDED_AT - (ctx.KONG_RESPONSE_START or ctx.KONG_HEADER_FILTER_START or ctx.KONG_BALANCER_ENDED_AT or ctx.KONG_BALANCER_START or ctx.KONG_ACCESS_ENDED_AT) end if has_timing then req_dyn_hook_run_hook("timing", "after:body_filter") end end function Kong.log() local ctx = ngx.ctx local has_timing = ctx.has_timing if has_timing then req_dyn_hook_run_hook("timing", "before:log") end if not ctx.KONG_LOG_START then ctx.KONG_LOG_START = get_now_ms() ctx.KONG_LOG_START_NS = time_ns() if is_stream_module then if not ctx.KONG_PROCESSING_START then ctx.KONG_PROCESSING_START = get_start_time_ms() end if ctx.KONG_PREREAD_START and not ctx.KONG_PREREAD_ENDED_AT then ctx.KONG_PREREAD_ENDED_AT = ctx.KONG_LOG_START ctx.KONG_PREREAD_TIME = ctx.KONG_PREREAD_ENDED_AT - ctx.KONG_PREREAD_START end if ctx.KONG_BALANCER_START and not ctx.KONG_BALANCER_ENDED_AT then ctx.KONG_BALANCER_ENDED_AT = ctx.KONG_LOG_START ctx.KONG_BALANCER_TIME = ctx.KONG_BALANCER_ENDED_AT - ctx.KONG_BALANCER_START end if ctx.KONG_PROXIED then if not ctx.KONG_PROXY_LATENCY then ctx.KONG_PROXY_LATENCY = ctx.KONG_LOG_START - ctx.KONG_PROCESSING_START end elseif not ctx.KONG_RESPONSE_LATENCY then ctx.KONG_RESPONSE_LATENCY = ctx.KONG_LOG_START - ctx.KONG_PROCESSING_START end else if ctx.KONG_REWRITE_START and not ctx.KONG_REWRITE_ENDED_AT then ctx.KONG_REWRITE_ENDED_AT = ctx.KONG_ACCESS_START or ctx.KONG_BALANCER_START or ctx.KONG_RESPONSE_START or ctx.KONG_HEADER_FILTER_START or ctx.BODY_FILTER_START or ctx.KONG_LOG_START ctx.KONG_REWRITE_TIME = ctx.KONG_REWRITE_ENDED_AT - ctx.KONG_REWRITE_START end if ctx.KONG_ACCESS_START and not ctx.KONG_ACCESS_ENDED_AT then ctx.KONG_ACCESS_ENDED_AT = ctx.KONG_BALANCER_START or ctx.KONG_RESPONSE_START or ctx.KONG_HEADER_FILTER_START or ctx.BODY_FILTER_START or ctx.KONG_LOG_START ctx.KONG_ACCESS_TIME = ctx.KONG_ACCESS_ENDED_AT - ctx.KONG_ACCESS_START end if ctx.KONG_BALANCER_START and not ctx.KONG_BALANCER_ENDED_AT then ctx.KONG_BALANCER_ENDED_AT = ctx.KONG_RESPONSE_START or ctx.KONG_HEADER_FILTER_START or ctx.BODY_FILTER_START or ctx.KONG_LOG_START ctx.KONG_BALANCER_TIME = ctx.KONG_BALANCER_ENDED_AT - ctx.KONG_BALANCER_START end if ctx.KONG_HEADER_FILTER_START and not ctx.KONG_HEADER_FILTER_ENDED_AT then ctx.KONG_HEADER_FILTER_ENDED_AT = ctx.BODY_FILTER_START or ctx.KONG_LOG_START ctx.KONG_HEADER_FILTER_TIME = ctx.KONG_HEADER_FILTER_ENDED_AT - ctx.KONG_HEADER_FILTER_START end if ctx.KONG_BODY_FILTER_START and not ctx.KONG_BODY_FILTER_ENDED_AT then ctx.KONG_BODY_FILTER_ENDED_AT = ctx.KONG_LOG_START ctx.KONG_BODY_FILTER_ENDED_AT_NS = ctx.KONG_LOG_START_NS ctx.KONG_BODY_FILTER_TIME = ctx.KONG_BODY_FILTER_ENDED_AT - ctx.KONG_BODY_FILTER_START end if ctx.KONG_PROXIED and not ctx.KONG_WAITING_TIME then ctx.KONG_WAITING_TIME = ctx.KONG_LOG_START - (ctx.KONG_BALANCER_ENDED_AT or ctx.KONG_ACCESS_ENDED_AT) end end end ctx.KONG_PHASE = PHASES.log runloop.log.before(ctx) local plugins_iterator = runloop.get_plugins_iterator() execute_collected_plugins_iterator(plugins_iterator, "log", ctx) plugins_iterator.release(ctx) runloop.log.after(ctx) if has_timing then req_dyn_hook_run_hook("timing", "after:log") end release_table(CTX_NS, ctx) -- this is not used for now, but perhaps we need it later? --ctx.KONG_LOG_ENDED_AT = get_now_ms() --ctx.KONG_LOG_TIME = ctx.KONG_LOG_ENDED_AT - ctx.KONG_LOG_START end function Kong.handle_error() kong_resty_ctx.apply_ref() local ctx = ngx.ctx ctx.KONG_PHASE = PHASES.error ctx.KONG_UNEXPECTED = true log_init_worker_errors(ctx) return kong_error_handlers(ctx) end local function serve_content(module) local ctx = ngx.ctx ctx.KONG_PROCESSING_START = get_start_time_ms() ctx.KONG_ADMIN_CONTENT_START = ctx.KONG_ADMIN_CONTENT_START or get_now_ms() ctx.KONG_PHASE = PHASES.admin_api log_init_worker_errors(ctx) ngx.header["Access-Control-Allow-Origin"] = ngx.req.get_headers()["Origin"] or "*" lapis.serve(module) ctx.KONG_ADMIN_CONTENT_ENDED_AT = get_updated_now_ms() ctx.KONG_ADMIN_CONTENT_TIME = ctx.KONG_ADMIN_CONTENT_ENDED_AT - ctx.KONG_ADMIN_CONTENT_START ctx.KONG_ADMIN_LATENCY = ctx.KONG_ADMIN_CONTENT_ENDED_AT - ctx.KONG_PROCESSING_START end function Kong.admin_content() kong.worker_events.poll() local ctx = ngx.ctx if not ctx.workspace then ctx.workspace = kong.default_workspace end return serve_content("kong.api") end function Kong.admin_header_filter() local ctx = ngx.ctx if not ctx.KONG_PROCESSING_START then ctx.KONG_PROCESSING_START = get_start_time_ms() end if not ctx.KONG_ADMIN_HEADER_FILTER_START then ctx.KONG_ADMIN_HEADER_FILTER_START = get_now_ms() if ctx.KONG_ADMIN_CONTENT_START and not ctx.KONG_ADMIN_CONTENT_ENDED_AT then ctx.KONG_ADMIN_CONTENT_ENDED_AT = ctx.KONG_ADMIN_HEADER_FILTER_START ctx.KONG_ADMIN_CONTENT_TIME = ctx.KONG_ADMIN_CONTENT_ENDED_AT - ctx.KONG_ADMIN_CONTENT_START end if not ctx.KONG_ADMIN_LATENCY then ctx.KONG_ADMIN_LATENCY = ctx.KONG_ADMIN_HEADER_FILTER_START - ctx.KONG_PROCESSING_START end end local enabled_headers = kong.configuration.enabled_headers local headers = constants.HEADERS if enabled_headers[headers.ADMIN_LATENCY] then header[headers.ADMIN_LATENCY] = ctx.KONG_ADMIN_LATENCY end if enabled_headers[headers.SERVER] then header[headers.SERVER] = meta._SERVER_TOKENS else header[headers.SERVER] = nil end -- this is not used for now, but perhaps we need it later? --ctx.KONG_ADMIN_HEADER_FILTER_ENDED_AT = get_now_ms() --ctx.KONG_ADMIN_HEADER_FILTER_TIME = ctx.KONG_ADMIN_HEADER_FILTER_ENDED_AT - ctx.KONG_ADMIN_HEADER_FILTER_START end function Kong.admin_gui_kconfig_content() local content, err = kong.cache:get( constants.ADMIN_GUI_KCONFIG_CACHE_KEY, nil, admin_gui.generate_kconfig, kong.configuration ) if err then kong.log.err("error occurred while retrieving admin gui config `kconfig.js` from cache", err) kong.response.exit(500, { message = "An unexpected error occurred" }) else ngx.say(content) end end function Kong.admin_gui_log() if kong.configuration.anonymous_reports then reports.admin_gui_log(ngx.ctx) end end function Kong.status_content() return serve_content("kong.status") end Kong.status_header_filter = Kong.admin_header_filter function Kong.serve_cluster_listener() local ctx = ngx.ctx log_init_worker_errors(ctx) ctx.KONG_PHASE = PHASES.cluster_listener return kong.clustering:handle_cp_websocket() end function Kong.stream_api() stream_api.handle() end function Kong.serve_cluster_rpc_listener() local ctx = ngx.ctx log_init_worker_errors(ctx) ctx.KONG_PHASE = PHASES.cluster_listener return kong.rpc:handle_websocket() end do local events = require "kong.runloop.events" Kong.stream_config_listener = events.stream_reconfigure_listener end return Kong