APISIX init.lua 源码分析

Categories: APISIX

Read in English

APISIX apisix/init.lua 源码分析

分析对象:apisix/init.lua(本文行号基于仓库 master 分支当前版本) 该文件是 APISIX 网关的请求生命周期总入口,HTTP 与 Stream 两个子系统共用。


1. 文件定位

init.lua 本身不处理具体业务逻辑,它承担三类职责:

  1. 进程生命周期钩子:init / init_worker / exit_worker 阶段的初始化与清理;
  2. 请求生命周期编排:按 Nginx 处理阶段(accessbalancerheader_filterbody_filterlog)依次调用路由、插件、负载均衡等模块;
  3. 安全与协议处理:mTLS 校验、URI 规范化、X-Forwarded-* 防伪造、gRPC/Dubbo 内部重定向等。

该文件的函数由 apisix/cli/ngx_tpl.lua(配置模板)渲染出的 nginx.conf 在各阶段指令中调用。


2. 与 Nginx 阶段的挂接关系

Nginx 指令 init.lua 函数 模板位置
init_by_lua_block(http) http_init(args) ngx_tpl.lua:514
init_worker_by_lua_block(http) http_init_worker() :523
exit_worker_by_lua_block(http) http_exit_worker() :527
init_by_lua_block(stream) stream_init(args) :205
init_worker_by_lua_block(stream) stream_init_worker() :209
ssl_client_hello_by_lua_block ssl_client_hello_phase() :234 / :772
ssl_certificate_by_lua_block ssl_phase() :238 / :776
preread_by_lua_block(stream) stream_preread_phase() :247
log_by_lua_block(stream) stream_log_phase() :259
access_by_lua_block(http 主 server) http_access_phase() :830
balancer_by_lua_block(http upstream) http_balancer_phase() :465 / :469 / :482
header_filter_by_lua_block http_header_filter_phase() :878 / :914 / :937
body_filter_by_lua_block http_body_filter_phase() :882 / :918 / :941
log_by_lua_block(http) http_log_phase() :886 / :922 / :945
location @grpc_passaccess_by_lua grpc_access_phase() :893
location @dubbo_passaccess_by_lua dubbo_access_phase() :929
control server 的 content_by_lua http_control() :549
/apisix/admincontent_by_lua http_admin() :645
/apisix/status status() :561
/apisix/status/ready status_ready() :566

3. 模块加载期初始化(L17 ~ L88)

3.1 JIT 参数(L17 ~ L26)

if require("ffi").os == "Linux" then
    require("ngx.re").opt("jit_stack_size", 200 * 1024)
end
  • 只在 Linux 上调整 PCRE JIT 栈大小,且必须放在任何正则编译之前(否则报 “changing jit stack size is not allowed…”)。
  • jit.opt.start(...) 的调优参数:
    • minstitch=2:至少 2 条 trace 才做拼接;
    • maxtrace=4000 / maxrecord=8000:限制 trace 数量与单条 trace 记录长度,防止 JIT 内存失控;
    • sizemcode=64 / maxmcode=4000:mcode 区域大小(64KB 起步、上限 4000KB);
    • maxirconst=1000:IR 常量上限。

3.2 模块依赖(L28 ~ L69)

核心依赖及其作用:

模块 作用
apisix.patch 对 OpenResty/ngx_lua 打运行时补丁
apisix.core 日志、json、tablepool、lrucache 等基础设施
apisix.plugin / plugin_config / consumer_group 插件体系、插件配置、消费者组
apisix.router 路由匹配:router_http(HTTP)、router_ssl(证书)、router_stream(Stream)
apisix.upstream / apisix.balancer 上游管理、负载均衡
apisix.ssl TLS 相关工具(SNI、协议、会话)
apisix.stream.xrpc Stream 子系统下的协议分发
apisix.tracer 分布式追踪 span
apisix.pubsub.kafka kafka 类型上游的专门处理
apisix.utils.trusted-addresses 可信地址判断(用于 X-Forwarded-* 防伪造)

3.3 关键全局标志

local is_http = false
if ngx.config.subsystem == "http" then
    is_http = true
    control_api_router = require("apisix.control.router")
end
  • is_http:区分子系统;同一份代码里 HTTP 用 core.response.exit(...),Stream 用 ngx_exit(...) 分叉处理。
  • apisix_base_flags(L77 ~ L80):resty.apisix.patch 是 APISIX 定制的 C 模块(apisix-base),pcall 加载失败则降级为空表。典型用途见 L402:client_cert_verified_in_handshake 标志——新版 apisix-base 在握手阶段已完成客户端证书校验时,无需再查 $ssl_client_verify
  • apisix_ngx_client(L86):来自 resty.apisix.client,提供 enable_mirror()(L880 用于 gRPC 流量镜像)。
  • ver_header(L84):响应 Server 头,enable_server_tokens=false 时降级为 "APISIX"(L162 ~ L164)。

4. HTTP 子系统

4.1 http_init(L91 ~ L110)— init 阶段

  1. 初始化 DNS resolver、ID 生成器、环境变量;
  2. 启用 privileged agent 进程(用于动态设置上游等特权操作);
  3. 初始化配置中心(core.config.init,etcd / 独立模式 yaml);
  4. xrpc.init()

4.2 http_init_worker(L113 ~ L171)— init_worker 阶段

按顺序初始化各子系统,顺序即依赖关系:

  1. 随机种子:优先从 /dev/urandom 取(L114);L121 的 “random test” 日志实际是测试断言用的探针(测试代码据此确认 init_worker 已执行);
  2. 事件系统、lrucache、服务发现(discovery)、balancer、admin、后台 timers、debug;
  3. 配置中心 worker 级初始化;
  4. 插件体系:plugin → router → service → plugin_config → consumer → consumer_group → secret → global_rules → upstream → ext-plugin;
  5. control API 路由、local_conf 缓存、Server 头版本;
  6. Prometheus 插件初始化(必须最后做,以保证所有 worker 的指标就绪);
  7. 可信地址工具。

4.3 TLS / mTLS 处理

ssl_phase(L182 ~ L190)

ssl_certificate_by_lua 阶段:把 ssl_client_hello_phase 阶段匹配到的证书配置(ngx.ctx.matched_ssl)设置到当前 TLS 会话。

ssl_client_hello_phase(L193 ~ L240)

  • 必须拿到 SNI(L195 ~ L200),拿不到(按 IP 访问或协议过旧)直接退出;
  • 读取 OCSP status_request 扩展;
  • 从 tablepool 取 api_ctx 做匹配(注意 L214 用完立即释放,该阶段不复用);
  • router_ssl.match_and_set(api_ctx, true, sni) 匹配 SNI 证书;
  • 按匹配结果动态设置 ssl_protocols(L228);
  • L238:把 SNI 存入 ngx.ctx.client_hello_sni —— 因为 Stream 子系统的 preread 阶段 ngx.ssl.server_name() 拿到的是会话主机名而非真实 SNI,这里提前记录供后续校验。

mTLS 校验的三个函数

  • verify_tls_session_resumption(L318 ~ L331):TLS 会话恢复安全校验。会话复用时,校验会话中的 hostname 与本次 client hello 的 SNI 一致,防止通过会话恢复绕过 mTLS 的安全问题。
  • verify_tls_client(L334 ~ L359,Stream 用):按 SNI 重新匹配证书;若配置了 client 证书且支持客户端校验,检查 $ssl_client_verify == "SUCCESS",并做会话恢复校验。
  • verify_https_client(L371 ~ L434,HTTP 用):
    1. 支持 skip_mtls_uri_regex:对匹配的 URI 豁免客户端证书(L363 ~ L368,L381);
    2. 证书校验(优先信任 apisix-base 的握手期校验标志,L402);
    3. SNI 与 Host 一致性校验(L417 ~ L426):防止用户配置 *.domain 通配证书后,用 SNI a.domain 访问却携带 Host b.domain 的跨域证书重用;
    4. 会话恢复校验。

4.4 http_access_phase(L691 ~ L857)— access 阶段核心

执行顺序:

  1. HTTP/3 适配(L694 ~ L696):ngx.req.http_version() == 3 时,把 :authority 伪头转成 upstream_host;
  2. api_ctx 创建(L700 ~ L704):从 tablepool 取,挂到 ngx.ctx,并初始化 var 元表(惰性读取 nginx 变量);
  3. mTLS 校验(L708);
  4. 动态调试(L712);
  5. URI 规范化(L714 ~ L735):
    • delete_uri_tail_slash:删除末尾 /;
    • normalize_uri_like_servlet:模拟 Java Servlet 语义,截断 ; 后的路径参数,并做防绕过检查(./.. 段、空段、%2e 编码点段);
  6. request_uri 防注入重写(L740 ~ L741):规范化后的 URI 才写入 request_uri,原始值存到 real_request_uri;
  7. X-Forwarded-* 防伪造(L743,详见 §5.4);
  8. 路由匹配(L745 ~ L759):router.router_http.match(api_ctx);未命中 → 仍执行 global rules → 返回 404;
  9. 配置合并(L767 ~ L802):plugin_configservice(merge_service_route),设置 conf_type / conf_version / conf_id / route_id 等上下文字段;
  10. 执行 global rules(L807 ~ L808);
  11. 插件/脚本执行(L810 ~ L851):
    • script 路由:直接 script.run("access");
    • 插件路由:plugin.filter 过滤出该路由生效的插件 → 先跑 rewrite 阶段 → 若 rewrite 阶段产生了 consumer(如 key-auth 认证成功),合并 consumer / consumer_group 配置,若配置有变化则重新过滤并补跑 rewrite_in_consumer 阶段 → 最后跑 access 阶段;
  12. handle_upstream(L854);
  13. 设置上游 X-Forwarded-* 头(L856)。

4.5 handle_upstream(L493 ~ L610)

  1. bypass_nginx_upstream:插件(如 ai-proxy)自己用 HTTP 客户端请求上游时,跳过 nginx 代理,只执行 before_proxy 插件阶段(L495 ~ L498);
  2. 确定上游:upstream_id / traffic-split 覆盖的 upstream_id(L500 ~ L535);带域名节点(has_domain)的路由触发 parse_domain_in_route(L258 ~ L287)做 DNS 解析,并用 _nodes_ver + resource 模块维护节点版本;
  3. 上游 mTLS(L537 ~ L558):upstream.tls.client_cert_idrouter_ssl 取客户端证书;
  4. WebSocket(L560 ~ L564):透传 Upgrade / Connection 头;
  5. kafka 上游:交由 pubsub_kafka.access 接管(L569 ~ L571);
  6. set_upstream 设置代理参数 → load_balancer.pick_server 选节点(L573 ~ L587);
  7. set_upstream_headers(L587):按 pass_host 语义设置 Host(pass/rewrite/node,L290 ~ L308);
  8. before_proxy 提前执行(L590):在 access 阶段就执行 before_proxy 插件,注释说明是为了”避免总是 reinit 请求”;
  9. _apisix_proxied 标记(L592 ~ L598):必须在 before_proxy 之后、proxy_pass 派发之前设置,log 阶段据此区分”nginx 代理错误”与”上游响应”;
  10. gRPC / Dubbo 分流(L600 ~ L609):stash_ngx_ctx()ngx.exec("@grpc_pass") / "@dubbo_pass"

4.6 上下文跨内部重定向传递(L243 ~ L256)

ngx.exec 内部重定向会丢失 ngx.ctx,APISIX 用 resty.ctxdump 解决:

  • stash_ngx_ctx():ctxdump.stash_ngx_ctx() 序列化上下文,引用号写入 nginx 变量 $ctx_ref;
  • fetch_ctx():在目标 location 里按引用号恢复 ngx.ctx,并清空变量。

4.7 响应与日志阶段

  • http_header_filter_phase(L910 ~ L938):
    • 设置 Server 头;
    • X-APISIX-Upstream-Status:按配置展示上游状态码,默认只在 5xx 时展示(L885 ~ L907);
    • 执行 header_filter 插件阶段;
    • 输出调试头 Apisix-Plugins(L927 ~ L934);
    • 末尾预创建 body_filter 阶段的 span。
  • http_body_filter_phase(L941 ~ L944):执行 body_filterdelayed_body_filter 插件阶段。
  • http_log_phase(L1088 ~ L1123):
    1. 结束所有 tracer span;
    2. 兜底 apisix_upstream_response_time;
    3. 执行 log 插件阶段;
    4. 被动健康检查上报(L1104,§4.8);
    5. after_balance 钩子;
    6. 资源统一释放:release_vars、释放 plugins / matched_route_record / api_ctx tablepool 对象——access 阶段从 tablepool 借的东西在这里归还。

4.8 被动健康检查 healthcheck_passive(L947 ~ L1006)

  • 依赖 api_ctx.up_checker(balancer 阶段创建);
  • HTTP:按配置的 healthy/unhealthy http_statuses 上报;
  • Stream:非 200 一律上报 TCP 失败。

4.9 管理/控制接口

  • status(L1009):直接返回 200;
  • status_ready(L1064):就绪探针,两步检查:
    • config_ready_check(L1031):role 的 config_provider 必须是 yaml/etcd;status-report shdict 中每个 worker 都有一条就绪记录(key 数 == worker 数,且值非空);
    • discovery_ready_check(L1014):各服务发现模块可选实现 check_discovery_ready;
  • http_admin(L1169):Admin API 路由分发 + CORS(OPTIONS 预检直接 200)+ JSON Content-Type;router 实例缓存在 do-block 的 upvalue 中,只初始化一次;
  • http_control(L1191):control API 路由匹配。

4.10 负载均衡 http_balancer_phase(L1126 ~ L1134)

balancer_by_lua 阶段:校验 api_ctx 存在后调用 load_balancer.run,真正的节点选择(peer 设置)发生在这里,access 阶段的 pick_server 只是预选。


5. X-Forwarded-* 防伪造(L613 ~ L688)

两步走,配合模板中 proxy_set_header X-Forwarded-XXX $var_x_forwarded_xxx; 的写法:

  1. handle_x_forwarded_headers(access 阶段,对非可信来源):
    • 原始值备份到 original_x_forwarded_*;
    • 用 APISIX 自己观测到的值(scheme、host、port)覆盖;
    • 清空 X-Forwarded-For(由 $proxy_add_x_forwarded_for 重新生成)和 RFC 7239 Forwarded 头;
    • 同步更新 var.http_x_forwarded_* 缓存。
  2. set_upstream_x_forwarded_headers(access 末尾):把可信值写回 var_x_forwarded_* 变量,使模板里的 proxy_set_header 生效。

可信来源由 apisix.utils.trusted-addresses(基于 realip_remote_addr)判断。


6. gRPC 与 Dubbo 内部重定向(L860 ~ L882)

  • dubbo_access_phase:仅恢复上下文;
  • grpc_access_phase:恢复上下文 → set_grpcs_upstream_param → 若开启流量镜像且加载了 apisix-ngx-client,调用 enable_mirror()

两个 location 共享主 server 的 header/body/log 阶段处理器(见 §2 表格)。


7. Stream 子系统

7.1 初始化(L1199 ~ L1254)

与 HTTP 类似但更精简:resolver → 配置中心 → xrpc → worker 级初始化(lrucache、配置中心、plugin、xrpc、router.stream、service、upstream、events、admin、discovery、balancer)。

7.2 stream_preread_phase(L1257 ~ L1371)

  1. mTLS 校验(L1262,verify_tls_client);
  2. 路由匹配 router_stream.match;
  3. 上游确定:三种来源 —— upstream_id / service_id(merge_service_stream_route)/ 路由内联 upstream(含 has_domain DNS 解析);
  4. plugin.stream_filter 过滤插件 → 执行 preread 插件阶段;
  5. 协议分发(L1350 ~ L1353):配置了 protocol 时交给 xrpc.run_protocol(如 Redis、Kafka 协议插件),否则走 TCP 直连;
  6. TCP 直连路径:set_upstream → pick_server → before_proxy

7.3 后续阶段

  • stream_balancer_phase(L1374):同 HTTP,调用 load_balancer.run;
  • stream_log_phase(L1386):log 插件 → 被动健康检查 → 释放 api_ctx(注意与 HTTP 不同,直接 plugin.run_plugin("log") 取回 api_ctx)。

8. HTTP 请求生命周期总览

client ──▶ nginx
            │  ssl_client_hello_phase: SNI 匹配证书、记录 client_hello_sni
            │  ssl_phase: 设置证书到会话
            │  access: http_access_phase
            │    ├─ verify_https_client (mTLS)
            │    ├─ URI 规范化 / request_uri 重写
            │    ├─ X-Forwarded-* 防伪造
            │    ├─ router_http.match
            │    ├─ plugin_config / service / consumer 合并
            │    ├─ global rules → rewrite → (consumer) → access 插件
            │    ├─ handle_upstream:
            │    │    ├─ 选上游、DNS 解析、上游 mTLS 证书
            │    │    ├─ pick_server(预选)
            │    │    ├─ before_proxy 插件
            │    │    └─ grpc/dubbo? ──▶ stash ctx ──▶ ngx.exec(@grpc_pass / @dubbo_pass)
            │    └─ set_upstream_x_forwarded_headers
            │  balancer: http_balancer_phase ──▶ load_balancer.run(真正选 peer)
            │  @grpc_pass / @dubbo_pass: fetch ctx ──▶ 恢复上下文
            │  header_filter / body_filter: http_header/body_filter_phase
            │  log: http_log_phase
            │    └─ log 插件 → 被动健康检查 → 释放 tablepool 资源
            └─▶ upstream

9. 安全设计要点汇总

机制 位置 防护目标
TLS 会话恢复 SNI 校验 L318 ~ L331 防止会话复用绕过 mTLS
SNI 与 Host 一致性校验 L417 ~ L426 防止通配证书跨域重用
skip_mtls_uri_regex 精确匹配 L362 ~ L368 证书豁免 URI 的绕过
URI ; 参数规范化 + 点段/编码检查 L437 ~ L472 路径参数注入、目录穿越
request_uri 重写 L740 ~ L741 未规范化 request_uri 注入
X-Forwarded-* 重写与 Forwarded 清除 L613 ~ L656 伪造客户端地址
日志脱敏 L281 ~ L283 打印路由前移除 plugins/auth_conf

10. 性能与资源管理要点

  • tablepool:api_ctx 在 access 阶段从池中获取,log 阶段统一归还(L1122 / L1401);pluginsmatched_route_record 同理。若中途异常退出导致未归还,会有泄漏风险,因此各阶段处理器都做了存在性判断。
  • 惰性变量:core.ctx.set_vars_meta(api_ctx) 建立 var 元表,nginx 变量按需读取。
  • localize 化:L53 ~ L68 把高频函数(ngx.now、ipairs 等)局部化,减少全局查找。
  • JIT 参数:见 §3.1,控制 trace 数量与内存上限。
  • DNS 解析缓存:parse_domain_in_route 通过 resource.set_nodes_ver_and_nodes 缓存解析结果,避免每次请求都做 DNS 查询。
  • 被动健康检查:只在 log 阶段上报,不影响转发路径。

11. 注意事项与易错点

  1. Stream 与 HTTP 的退出码语义不同:同一函数里用 is_http 分叉(如 L510 ~ L514),HTTP 返回带 JSON body 的 502,Stream 只能 ngx_exit(1)
  2. local_conf 会被反复刷新:http_init_worker 缓存一次(L160),但 set_resp_upstream_statuscors_adminstatus_ready 中又调用 core.config.local_conf() 重取,是刻意的(独立模式下配置可能热更新)。
  3. ssl_client_hello_phase 中 api_ctx 用完即还(L214 ~ L215):该阶段的 api_ctx 与 access 阶段不是同一个,不能复用。
  4. before_proxy 的时机:必须在 _apisix_proxied 标记之前执行,因为 before_proxy 插件可能直接 core.response.exit() 结束请求。
  5. fetch_ctx 必须清空 $ctx_ref(L254):防止变量残留导致错误复用。
Read More

Easegress 源码阅读指南

【2026-07-31】Easegress 源码阅读指南 - 从项目概览、核心架构、对象体系到流量管线的系统化阅读路线