典型插件精读
第 3 周收官。把前面学的插件机制落到实处——精读三个代表性插件:limit-count(限流,最经典)、proxy-rewrite(改写请求)、ai-proxy(AI 代理,呼应上一站)。看真实插件怎么写。
三个代表
- limit-count:限流——"access 阶段拦截超限请求"的典型,还展示了"本地/Redis 多策略"。
- proxy-rewrite:改写——"rewrite 阶段修改请求(uri/header/host)后转发"的典型。
- ai-proxy:AI 代理——把请求转成各家 LLM 格式,呼应上一站 Higress ai-proxy。
limit-count 结构
plugins/limit-count.lua 是薄封装(priority = 1002),真正逻辑在 plugins/limit-count/ 目录:
plugins/limit-count.lua # 入口(priority/schema/access)
plugins/limit-count/
init.lua # 核心逻辑,按 policy 选实现
limit-count-local.lua # 本地计数(单机,共享内存)
limit-count-redis.lua # Redis 计数(分布式)
limit-count-redis-cluster.lua # Redis 集群
init.lua:26-34 按 policy 配置 require 对应实现(local / redis / redis-cluster)。又是"策略模式"——限流算法固定(滑动窗口计数),存计数的地方可换(本地内存 / Redis)。三种限流策略
limit-count access
limit-count.lua:36 的 access(简化逻辑):
function _M.access(conf, ctx)
-- 按 key(如客户端 IP / consumer 名)取限流器
-- 对这个 key 的计数 +1,检查是否超过 conf.count(在 conf.time_window 窗口内)
local delay, remaining = lim:incoming(key, ...)
if not delay then
-- 超限了
return conf.rejected_code or 503, conf.rejected_msg -- Day 12 的"返回 code 即拦截"
end
-- 没超限:设响应头 X-RateLimit-Remaining 等,放行
core.response.set_header("X-RateLimit-Remaining", remaining)
end
return 429/503(Day 12 的拦截机制)。没超限就放行,还贴心地在响应头告诉客户端"你还剩几次"。key 可以是 IP(每 IP 限)、consumer 名(每用户限,配合 Day 14 的鉴权)、或路由——非常灵活。这是最经典、最能体现"access 阶段拦截"的插件。count=3, time_window=60, key=remote_addr(每 IP 每分钟 3 次)。同一 IP 一分钟内:第 1、2、3 次 → 放行,响应头
X-RateLimit-Remaining: 2 / 1 / 0;第 4 次 →
lim:incoming 返回 nil → return 503 拦下(就像窗口挂出"今日号已放完")。下一分钟窗口重置,又能来 3 次。proxy-rewrite
plugins/proxy-rewrite.lua:在 rewrite 阶段修改"发给上游的请求"。schema(:53+)支持改 uri、regex_uri(正则替换)、host、headers、method 等。
-- rewrite 阶段(简化):
if conf.uri then req_set_uri(conf.uri) end -- 改路径
if conf.regex_uri then ... end -- 正则替换路径
if conf.host then ctx.var.upstream_host = conf.host end -- 改发给上游的 Host
-- 改 headers ...
/api/v1/users、后端 /users)——proxy-rewrite 把 uri 改了再转发。或者给上游加个内部 header、改 Host 做多租户。它在 rewrite 阶段(转发前)改 ctx.var 里的请求属性。注意改 uri/host 可能影响路由——但这已经匹配完路由了(rewrite 在 access 里、路由匹配之后),改的是"发给上游"的,不影响已匹配的路由。register_var 复习
proxy-rewrite.lua:46-48 用了 Day 05 的 register_var:
core.ctx.register_var("proxy_rewrite_regex_uri_captures", function(ctx)
return ctx.proxy_rewrite_regex_uri_captures
end)
$proxy_rewrite_regex_uri_captures(正则改写捕获的分组),之后能在其他配置/日志里用。这展示了插件怎么优雅地"暴露自己算出的值"给 APISIX 变量系统。真实插件里 register_var 用得不少。ai-proxy
plugins/ai-proxy.lua + plugins/ai-proxy/ + plugins/ai-drivers/:把请求代理到各家 LLM(OpenAI/通义/Claude…),统一协议。还有一大批 AI 插件:ai-prompt-guard、ai-prompt-template、ai-rag、ai-proxy-multi、ai-aliyun-content-moderation 等。
ai-drivers/ 里的各家"驱动"把统一请求转成各厂商格式、代理调用、统一响应。APISIX 也全面拥抱了 AI 网关(README 标题就有"AI Gateway")。它可能在 access 阶段直接调 LLM 并返回(不走普通上游转发)——回想上一站 wasm-go 里 "ai-proxy 直接用 http client 调上游、bypass nginx upstream",APISIX 的 bypass_nginx_upstream(init.lua:490)就是干这个。两大网关在 AI 能力上殊途同归。👶 小白:ai-proxy 也是插件,那它转发给 LLM,走的还是 Day 08-10 那套 upstream/负载均衡吗?
👨🏫 老师:不一定。它常在 access 阶段用自己的 http client 直接把请求打给 LLM 厂商、拿到响应就返回,绕过 nginx 的 upstream 转发——源码里 bypass_nginx_upstream(init.lua:490)就是这个开关。因为"调 OpenAI/通义"要做协议转换、流式改写,不是普通的反代,插件自己接管更灵活。这也呼应了上一站 wasm-go 里 ai-proxy 的同款做法。
今日小结 + 动手(第 3 周收官)
🧠 第 3 周你应该能回答
- limit-count 怎么用 access + 返回 code 实现拦截?本地 vs Redis 策略?
- proxy-rewrite 在哪个阶段、改什么?会影响路由吗?
- ai-proxy 和上一站 Higress ai-proxy 的共性?bypass_nginx_upstream 是什么?
- 三类插件行为(拦截/改写/代理)各自的典型阶段?
✋ 动手
cd /Users/bitmart/work/codes/github/apisix
ls apisix/plugins/limit-count/
sed -n '36,60p' apisix/plugins/limit-count.lua
sed -n '44,80p' apisix/plugins/proxy-rewrite.lua
ls apisix/plugins/ | grep -i ai