APISIX init.lua 源码分析
Categories: APISIX
APISIX apisix/init.lua 源码分析
分析对象:
apisix/init.lua(本文行号基于仓库 master 分支当前版本) 该文件是 APISIX 网关的请求生命周期总入口,HTTP 与 Stream 两个子系统共用。
1. 文件定位
init.lua 本身不处理具体业务逻辑,它承担三类职责:
- 进程生命周期钩子:
init/init_worker/exit_worker阶段的初始化与清理; - 请求生命周期编排:按 Nginx 处理阶段(
access→balancer→header_filter→body_filter→log)依次调用路由、插件、负载均衡等模块; - 安全与协议处理: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_pass 的 access_by_lua |
grpc_access_phase() |
:893 |
location @dubbo_pass 的 access_by_lua |
dubbo_access_phase() |
:929 |
control server 的 content_by_lua |
http_control() |
:549 |
/apisix/admin 的 content_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 阶段
- 初始化 DNS resolver、ID 生成器、环境变量;
- 启用 privileged agent 进程(用于动态设置上游等特权操作);
- 初始化配置中心(
core.config.init,etcd / 独立模式 yaml); xrpc.init()。
4.2 http_init_worker(L113 ~ L171)— init_worker 阶段
按顺序初始化各子系统,顺序即依赖关系:
- 随机种子:优先从
/dev/urandom取(L114);L121 的 “random test” 日志实际是测试断言用的探针(测试代码据此确认 init_worker 已执行); - 事件系统、lrucache、服务发现(discovery)、balancer、admin、后台 timers、debug;
- 配置中心 worker 级初始化;
- 插件体系:plugin → router → service → plugin_config → consumer → consumer_group → secret → global_rules → upstream → ext-plugin;
- control API 路由、
local_conf缓存、Server头版本; - Prometheus 插件初始化(必须最后做,以保证所有 worker 的指标就绪);
- 可信地址工具。
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 用):- 支持
skip_mtls_uri_regex:对匹配的 URI 豁免客户端证书(L363 ~ L368,L381); - 证书校验(优先信任 apisix-base 的握手期校验标志,L402);
- SNI 与 Host 一致性校验(L417 ~ L426):防止用户配置
*.domain通配证书后,用 SNIa.domain访问却携带 Hostb.domain的跨域证书重用; - 会话恢复校验。
- 支持
4.4 http_access_phase(L691 ~ L857)— access 阶段核心
执行顺序:
- HTTP/3 适配(L694 ~ L696):
ngx.req.http_version() == 3时,把:authority伪头转成upstream_host; - api_ctx 创建(L700 ~ L704):从 tablepool 取,挂到
ngx.ctx,并初始化var元表(惰性读取 nginx 变量); - mTLS 校验(L708);
- 动态调试(L712);
- URI 规范化(L714 ~ L735):
delete_uri_tail_slash:删除末尾/;normalize_uri_like_servlet:模拟 Java Servlet 语义,截断;后的路径参数,并做防绕过检查(./..段、空段、%2e编码点段);
- request_uri 防注入重写(L740 ~ L741):规范化后的 URI 才写入
request_uri,原始值存到real_request_uri; - X-Forwarded-* 防伪造(L743,详见 §5.4);
- 路由匹配(L745 ~ L759):
router.router_http.match(api_ctx);未命中 → 仍执行 global rules → 返回 404; - 配置合并(L767 ~ L802):
plugin_config→service(merge_service_route),设置conf_type/conf_version/conf_id/route_id等上下文字段; - 执行 global rules(L807 ~ L808);
- 插件/脚本执行(L810 ~ L851):
- script 路由:直接
script.run("access"); - 插件路由:
plugin.filter过滤出该路由生效的插件 → 先跑rewrite阶段 → 若 rewrite 阶段产生了consumer(如 key-auth 认证成功),合并 consumer / consumer_group 配置,若配置有变化则重新过滤并补跑rewrite_in_consumer阶段 → 最后跑access阶段;
- script 路由:直接
handle_upstream(L854);- 设置上游 X-Forwarded-* 头(L856)。
4.5 handle_upstream(L493 ~ L610)
bypass_nginx_upstream:插件(如 ai-proxy)自己用 HTTP 客户端请求上游时,跳过 nginx 代理,只执行before_proxy插件阶段(L495 ~ L498);- 确定上游:
upstream_id/ traffic-split 覆盖的upstream_id(L500 ~ L535);带域名节点(has_domain)的路由触发parse_domain_in_route(L258 ~ L287)做 DNS 解析,并用_nodes_ver+resource模块维护节点版本; - 上游 mTLS(L537 ~ L558):
upstream.tls.client_cert_id从router_ssl取客户端证书; - WebSocket(L560 ~ L564):透传
Upgrade/Connection头; - kafka 上游:交由
pubsub_kafka.access接管(L569 ~ L571); set_upstream设置代理参数 →load_balancer.pick_server选节点(L573 ~ L587);set_upstream_headers(L587):按pass_host语义设置 Host(pass/rewrite/node,L290 ~ L308);before_proxy提前执行(L590):在 access 阶段就执行 before_proxy 插件,注释说明是为了”避免总是 reinit 请求”;_apisix_proxied标记(L592 ~ L598):必须在 before_proxy 之后、proxy_pass 派发之前设置,log 阶段据此区分”nginx 代理错误”与”上游响应”;- 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_filter与delayed_body_filter插件阶段。http_log_phase(L1088 ~ L1123):- 结束所有 tracer span;
- 兜底
apisix_upstream_response_time; - 执行
log插件阶段; - 被动健康检查上报(L1104,§4.8);
after_balance钩子;- 资源统一释放:release_vars、释放
plugins/matched_route_record/api_ctxtablepool 对象——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-reportshdict 中每个 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; 的写法:
handle_x_forwarded_headers(access 阶段,对非可信来源):- 原始值备份到
original_x_forwarded_*; - 用 APISIX 自己观测到的值(scheme、host、port)覆盖;
- 清空
X-Forwarded-For(由$proxy_add_x_forwarded_for重新生成)和 RFC 7239Forwarded头; - 同步更新
var.http_x_forwarded_*缓存。
- 原始值备份到
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)
- mTLS 校验(L1262,
verify_tls_client); - 路由匹配
router_stream.match; - 上游确定:三种来源 ——
upstream_id/service_id(merge_service_stream_route)/ 路由内联 upstream(含has_domainDNS 解析); plugin.stream_filter过滤插件 → 执行preread插件阶段;- 协议分发(L1350 ~ L1353):配置了
protocol时交给xrpc.run_protocol(如 Redis、Kafka 协议插件),否则走 TCP 直连; - 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);plugins、matched_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. 注意事项与易错点
- Stream 与 HTTP 的退出码语义不同:同一函数里用
is_http分叉(如 L510 ~ L514),HTTP 返回带 JSON body 的 502,Stream 只能ngx_exit(1)。 local_conf会被反复刷新:http_init_worker缓存一次(L160),但set_resp_upstream_status、cors_admin、status_ready中又调用core.config.local_conf()重取,是刻意的(独立模式下配置可能热更新)。ssl_client_hello_phase中 api_ctx 用完即还(L214 ~ L215):该阶段的 api_ctx 与 access 阶段不是同一个,不能复用。- before_proxy 的时机:必须在
_apisix_proxied标记之前执行,因为 before_proxy 插件可能直接core.response.exit()结束请求。 fetch_ctx必须清空$ctx_ref(L254):防止变量残留导致错误复用。