:按请求粒度动态插桩网关行为的内部机制详解)
Kong Dynamic Hookskong.dynamic_hook按请求粒度动态插桩网关行为的内部机制详解【免费下载链接】kong The API and AI Gateway项目地址: https://gitcode.com/GitHub_Trending/ko/kongKong 内置的 dynamic hooks 模块为网关提供了一套按需激活的动态插桩能力既可以给已有函数挂接 before/after 处理器进行原地补丁也可以注册具名 hook 在生命周期特定点被显式触发并支持按组group统一启用或关闭。本文以 kong/dynamic_hook/README.md 为主体结合 kong/dynamic_hook/init.lua 的完整实现以及它在 Kong 请求调试request debug / timing 模块中的真实落地代码讲清楚这套机制的 API、执行语义、性能设计与使用边界。一、模块定位面向内部使用的动态插桩README 对该模块的定义是Dynamic hooks 用于扩展 Kong 的行为在请求/响应生命周期的特定阶段运行代码它既可以给被补丁patch的函数挂接 before 与 after 处理器也支持把 hook 组织成组group从而成批地启用/禁用一组 hook。文档同时明确了两条边界该模块仅面向内部使用intended solely for internal use外部使用需自行承担风险模块提供三种核心操作定义 hookhook_function/hook、启用组enable_by_default/enable_on_this_request、执行 hookrun_hook。从 kong/dynamic_hook/init.lua 的源码结构看模块内部用两张表承载全部状态内部表作用NON_FUNCTION_HOOKS具名 hook 注册表结构为[group_name][hook_name] function供run_hook显式触发ALWAYS_ENABLED_GROUPS全局默认启用组名单由enable_by_default写入而每个请求内的启用状态则存放在ngx.ctx.dynamic_hook.enabled_groups中见下文enable_on_this_request这正是按请求粒度启停的实现基础。二、API 参考五个公开函数1.hook_function(group_name, parent, function_key, max_args, handlers)—— 给函数打补丁签名与参数对应 init.lua#L179-L198参数类型说明group_namestring所属 hook 组名parenttable存放目标函数的表function_keystring目标函数在parent中的键名max_argsnumber 或varargs函数最多接受的参数个数0–8或字符串varargs表示变参handlerstable可含befores原函数调用前执行的处理器数组与afters调用后执行的处理器数组函数会assert校验各参数类型并确认parent[function_key]确实是 function然后把原函数替换为包装版本max_args varargs时走wrap_function_vararginit.lua#L82-L91用...透传全部实参否则走wrap_functioninit.lua#L140-L155把参数固化为a1..a8八个命名变量再按max_args精确转发——这是该模块一个值得注意的性能设计对高频调用的热路径函数如 DNS 查询、HTTP 连接避免每次调用都构造变参表。2.hook(group_name, hook_name, handler)—— 注册具名 hook把handler注册到NON_FUNCTION_HOOKS[group_name][hook_name]init.lua#L209-L221。它不会主动执行只有在组已启用且有人调用run_hook(group_name, hook_name, ...)时才触发因此调用方可以精确控制触发时机。3.run_hook(group_name, hook_name, a1..a8, ...)—— 触发具名 hook执行流程init.lua#L268-L297先查is_group_enabled(group_name)组未启用直接返回——未启用时run_hook只有一次判断开销从注册表取出 handler不存在则直接返回用pcall调用 handler参数最多 8 个命名参数 剩余变参handler 抛错只记录 WARN 日志failed to run dynamic hook ...不会向调用方传播异常保证插桩代码永远不会破坏网关主流程。4.enable_on_this_request(group_name, ngx_ctx)—— 仅对当前请求启用把组标记写入ngx_ctx.dynamic_hook.enabled_groups[group_name] true若该请求还没有此结构则初始化init.lua#L305-L318。注意第二个参数可以显式传入ngx_ctx而非默认的ngx.ctx便于在拿到请求上下文的早期阶段预先登记。5.enable_by_default(group_name)/disable_by_default(group_name)—— 全局默认启停直接增删ALWAYS_ENABLED_GROUPS表项init.lua#L325-L340。默认启用的组对所有请求生效且被补丁函数在该组上走完整插桩路径disable_by_default用于撤销。6.is_group_enabled(group_name)—— 查询组状态判定优先级init.lua#L231-L253在ALWAYS_ENABLED_GROUPS中 →true不在 HTTP 请求上下文get_request()为假如定时器/init 阶段→falsengx.ctx.dynamic_hook.enabled_groups[group_name]为真 →true否则false。三、执行语义未启用时的零开销快路径被hook_function补丁后的包装函数并非无条件执行 hook。核心判定函数是should_execute_original_func(group_name)init.lua#L31-L50满足以下任一条件时直接调用原函数、跳过所有 hook组在ALWAYS_ENABLED_GROUPS之外的常规路径下未对当前请求启用处于init/init_worker阶段无请求上下文。只有当组已启用时才依次执行befores→ 原函数 →afters其中afters接收的是原函数的返回值varargs版在 init.lua#L76-L79 中return ...透传返回值。这套默认直通、按需插桩的语义使得补丁函数可以被全局打补丁而不影响未启用组的请求性能。README 中的完整示例README 给出的两段示例完整展示了两种 hook 模式的用法原样保留如下local dynamic_hook require kong.dynamic_hook ---------------------------------------- -- Define a hook handler local function before_hook(...) io.write(hello, ) end -- Hook a function dynamic_hook.hook_function(my_group, _G, print, varargs, { befores { before_hook }, }) -- Enable the hook group dynamic_hook.enable_by_default(my_group) -- Call the function print(world!) -- prints hello, world! ---------------------------------------- -- Define another hook handler local function log_event_hook(arg1, arg2) ngx.log(ngx.INFO, event triggered with args: , arg1, , , arg2) end -- Register a new hook dynamic_hook.hook(event_group, log_event, log_event_hook) -- Enable the hook group for this request dynamic_hook.enable_on_this_request(event_group) -- Run the hook dynamic_hook.run_hook(event_group, log_event, 10, test)第一段演示hook_functionenable_by_default的函数补丁模式全局启用第二段演示hookenable_on_this_requestrun_hook的具名 hook模式仅当前请求生效。四、Kong 中的真实应用timing 模块与请求调试功能README 指出Kong Gateway 定义、注册并运行了一批 dynamic hooks全部服务于timing 模块且在请求调试request debugging功能启用时才实际工作。以下两张表完整继承自 README。4.1 已注册的 timing hooks 及其触发位置Hook说明运行位置timing:auth - auth对满足条件的请求开启请求调试Timing 模块Kong.rewrite开头timing - before:rewrite进入 rewrite 上下文开始计时 rewrite 阶段Kong.rewrite开头timing - after:rewrite离开 rewrite 上下文结束计时Kong.rewrite结尾timing - dns:cache_lookup设置cache_hit上下文属性每次内存 DNS 缓存查询时timing - before:balancer进入 balancer 上下文开始计时Kong.balancer开头timing - after:balancer离开 balancer 上下文Kong.balancer结尾timing - before:access进入 access 上下文Kong.access开头timing - before:router进入 router 上下文router 初始化前timing - after:router离开 router 上下文router 执行后timing - workspace_id:got设置workspace_id上下文属性Kong.accessworkspace ID 赋值之后timing - after:access离开 access 上下文Kong.access结尾timing - before:response进入 response 上下文Kong.response开头timing - after:response离开 response 上下文Kong.response结尾timing - before:header_filter / after:header_filter进出 header_filter 上下文Kong.header_filter 首尾timing - before:body_filter / after:body_filter进出 body_filter 上下文Kong.body_filter 首尾timing - before:log / after:log进出 log 上下文Kong.log 首尾timing - before:plugin_iterator / after:plugin_iterator进出 plugins 上下文插件迭代开始/结束timing - before:plugin / after:plugin进出每个插件的上下文每个插件 handler 前后这些 hook 的注册集中在 kong/timing/init.lua 的register_hooksinit.lua#L229-L327例如req_dyn_hook.hook(timing, before:rewrite, function() _M.enter_context(rewrite) end) req_dyn_hook.hook(timing, after:rewrite, function() _M.leave_context() -- leave rewrite end)而触发点散布在网关运行循环中before:router/after:router/workspace_id:got在 kong/runloop/handler.lua 中被run_hook调用before:plugin_iterator/before:plugin/after:plugin等插件计时 hook 在 kong/init.lua 的插件迭代路径中触发dns:cache_lookup则在 DNS 缓存每次查询时被触发。4.2 被hook_function补丁的函数README 列出的函数补丁清单如下均可在源码中逐一对应函数说明源码位置resty.dns.client.toip测量 DNS 查询耗时max_args 4kong/timing/hooks/dns.lua补丁对象为kong/resty/dns/client.lua的toipresty.http.connect测量 HTTP 建连耗时max_args 4兼容新旧两种签名kong/timing/hooks/http.luaresty.http.request测量 HTTP 请求耗时max_args 2kong/timing/hooks/http.luaresty.redis.{method}测量 Redis 每个方法的执行耗时varargs遍历resty.redis表内所有函数kong/timing/hooks/redis.luangx.socket.tcp测量 TCP 建连与 SSL 握手耗时max_args 0after 钩子再补丁实例方法connect/sslhandshakekong/timing/hooks/socket.luangx.socket.udp测量 UDPsetpeername执行耗时max_args 0after 钩子补丁实例方法setpeernamekong/timing/hooks/socket.lua各补丁模块由 kong/timing/hooks/init.lua 按固定顺序socket → dns → http → redis注册源码注释标明 order matters。一个有代表性的补丁是toipkong/timing/hooks/dns.lua--[[ The toip() function can receive 4 arguments (including self). function toip(self, qname, port, dnsCacheOnly, try_list) --]] local client assert(package.loaded[kong.resty.dns.client]) req_dyn_hook.hook_function(timing, client, toip, 4, { befores { before_toip }, afters { after_toip }, })其中before_toip依次enter_context(dns)、enter_context(qname)、enter_context(resolve)after_toip对称地逐层leave_context——配合 kong/timing/context.lua 中基于time_ns()的子上下文栈最终形成一棵按阶段/目标地址组织的耗时树。4.3 启用链路从配置到按请求激活timing 功能使用 dynamic hooks 的两个组恰好覆盖了两种启用方式timing:auth默认启用kong/timing/init.lua 的init_worker中当配置启用且子系统为http时调用req_dyn_hook.enable_by_default(timing:auth)使鉴权 hook 对每个请求的 rewrite 阶段开头无条件触发timing按请求启用authhook 内部检查请求头仅当X-Kong-Request-Debug等于*、且来源为回环地址IPv4127.0.0.0/8/ IPv6::1或携带正确X-Kong-Request-Debug-Token时才调用req_dyn_hook.enable_on_this_request(timing, ngx_ctx)init.lua#L68-L114——这正体现了按请求粒度启停的设计目的调试开销只落在被显式标记的请求上。对应的用户侧配置见 kong.conf.defaultrequest_debug on开启功能request_debug_token缺省随机生成并写入{prefix}/.request_debug_token非回环来源必须携带X-Kong-Request-Debug-Token头。启用后请求在 header_filter 阶段会收到X-Kong-Request-Debug-Output响应头超 2KB 截断并强制转记日志log 阶段则把完整 JSON 分片写入 error_logkong/timing/init.lua。五、使用要点与边界仅内部 APIREADME 明确该特性仅供 Kong 内部使用外部依赖需自担风险其行为不承诺向后兼容错误隔离所有 hook handler 与具名 hook 均通过pcall执行失败只产生 WARN 日志绝不影响被补丁函数或网关请求的返回性能语义未启用组走直通快路径无变参包装、无 hook 调用run_hook未启用时仅一次组状态判断固定参数包装a1..a8专为热路径函数设计两种模式选型需要函数一被调用就插桩且无法在调用点插入代码时用hook_function需要在精确时机由调用方触发时用hookrun_hook后者还能把调用现场参数传给 handler组是启停单位无论哪种模式handler 都归属 group通过enable_on_this_request/enable_by_default统一管理这与 timing 模块auth 组全局开、timing 组按请求开的实践一致。理解kong/dynamic_hook是读懂 Kong 请求调试X-Kong-Request-Debug实现的关键一把钥匙网关如何在几乎零开销的前提下为任意一条被标记的请求织入覆盖 rewrite、router、balancer、插件、DNS、HTTP/Redis/socket 建连的完整耗时观测答案就在上述 API 与kong/timing/的注册代码中。【免费下载链接】kong The API and AI Gateway项目地址: https://gitcode.com/GitHub_Trending/ko/kong创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考