Apache APISIX ext-plugin-post-resp 插件详解:在响应阶段运行外部插件
【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix
ext-plugin-post-resp是 Apache APISIX 内置的ext-plugin-*系列插件之一,用于在请求已从上游获取响应之后,将响应交给 Plugin Runner 中运行的 External Plugin 处理,实现用任意语言(Go、Java、Python 等)改写响应状态码、响应头和响应体。本文基于当前仓库的官方文档、插件源码与测试用例,系统讲解该插件的工作机制、属性配置、启用与测试方法,以及它与其他内置插件之间的兼容性边界,帮助你在实际路由中安全、正确地落地响应阶段的外部插件能力。
插件定位:在响应阶段执行 External Plugin
根据官方文档(docs/zh/latest/plugins/ext-plugin-post-resp.md)的描述,ext-plugin-post-resp插件用于"在执行内置 Lua 插件之前和在 Plugin Runner 内运行特定的 External Plugin",并且在请求获取到上游的响应之后执行。这意味着它的核心职责是:当 APISIX 已经拿到上游(Upstream)返回的 HTTP 响应时,把这条响应的状态码、头部等信息通过 RPC 发送给 Plugin Runner,由 External Plugin 对响应进行二次处理,再把处理结果(可能被修改过的状态码、响应头、响应体)返回给客户端。
External Plugin 执行的结果会直接影响当前请求的响应——这也是使用本插件时必须牢记的前提。
在ext-plugin-*系列中,三个插件的分工与执行时机互补:
| 插件 | 优先级 | 执行时机 | 处理对象 |
|---|---|---|---|
ext-plugin-pre-req | 12000 | 请求进入 APISIX 后、请求被转发到上游前 | 请求本身(URI、请求头、请求体等) |
ext-plugin-post-req | -3000 | 请求阶段(access 阶段) | 请求转发前的后续处理 |
ext-plugin-post-resp | -4000 | 获取上游响应之后 | 上游响应(状态码、响应头、响应体) |
优先级数值可在 conf/config.yaml.example 的plugins配置段中直接看到:ext-plugin-post-req的优先级为-3000,ext-plugin-post-resp的优先级为-4000。优先级越低,表示越晚执行,因此ext-plugin-post-resp是 APISIX 内置插件执行链上最靠后的插件之一。
注意:
ext-plugin-post-resp处理的是上游响应;而ext-plugin-pre-req(docs/zh/latest/plugins/ext-plugin-pre-req.md)处理的是客户端请求。两者配合可以分别实现"请求改写"与"响应改写"。
工作原理:直接向上游发起请求,再把响应交给 Plugin Runner
要理解ext-plugin-post-resp为什么能"在获取上游响应之后"执行,需要看它的核心实现 apisix/plugins/ext-plugin-post-resp.lua。该插件在before_proxy阶段(位于 access 阶段之后、正式代理转发之前)通过lua-resty-http库主动向上游发起一次请求,而不是走 APISIX 常规的代理转发管线。
从源码看,其处理流程大致如下:
- 建立到上游的连接:
get_response函数使用ctx.picked_server中的host与port、ctx.upstream_scheme中的协议,通过http_obj:connect连接上游; - 构造并发送请求:URI 优先取
ctx.var.upstream_uri,若为空则回退到原始ctx.var.uri;请求头来自core.request.headers(ctx);请求方法来自core.request.get_method();若存在请求体则一并携带; - 拿到上游响应:
http_obj:request(params)返回响应对象res,存入ctx.runner_ext_response; - 通知 Plugin Runner 处理响应:调用
ext.communicate(conf, ctx, name, constants.RPC_HTTP_RESP_CALL),即通过 unix socket 与 Plugin Runner 进行RPC_HTTP_RESP_CALL类型的 RPC 交互,把响应状态码和响应头发给 Runner,并接收 Runner 可能改写后的状态码、响应头与响应体; - 把结果写回客户端:
send_response函数根据 Runner 返回的结果(ctx.runner_ext_response_body中的响应体分片,或原始响应的body_reader)通过ngx.print/ngx.flush直接向客户端输出。
整个交互中,如果向上游请求失败,插件会关闭连接并返回502;如果响应写出失败且响应头尚未发送,也会返回502。
External Plugin 与 Plugin Runner 的整体协作架构可参考官方文档 docs/zh/latest/external-plugin.md 中的架构图:
简单来说:APISIX 以子进程方式拉起 Plugin Runner,Runner 与 APISIX 通过 unix socket 通信;当某个路由启用了ext-plugin-*插件时,命中该路由的请求会触发 APISIX 到 Runner 的 RPC 调用,Runner 在其内部运行 External Plugin 并把结果返回给 APISIX。
属性说明
根据官方文档,ext-plugin-post-resp支持以下两个属性:
| 名称 | 类型 | 必选项 | 默认值 | 有效值 | 描述 |
|---|---|---|---|---|---|
conf | array | 否 | 无 | [{"name": "ext-plugin-A", "value": "{\"enable\":\"feature\"}"}] | 在 Plugin Runner 内执行的插件列表配置 |
allow_degradation | boolean | 否 | false | [false, true] | 当 Plugin Runner 临时不可用时是否允许请求继续,设置为true时自动允许请求继续 |
这两个属性的校验逻辑定义在 apisix/plugins/ext-plugin/init.lua 中,ext-plugin-post-resp直接复用该共享 schema(schema = ext.schema),其中:
conf:array类型,至少包含 1 个元素(minItems = 1);每个元素是一个对象,包含两个必填字段——name(字符串,长度 1~128 个字符)和value(字符串)。value通常是一个 JSON 字符串,用于向 External Plugin 传递其自身配置;allow_degradation:boolean类型,默认值为false。
从communicate的实现(apisix/plugins/ext-plugin/init.lua)可以看出allow_degradation的实际行为:当 Plugin Runner 不可用时,若allow_degradation为true,则允许请求继续(相当于降级为不处理响应);否则直接返回503 Service Unavailable。该函数默认最多重试 3 次,其中"conf token not found"错误会触发缓存刷新后重试,其余错误直接终止并降级或返回 503。
此外,conf配置会被缓存在 APISIX 的共享内存(ext-pluginshared dict)与进程内 LRU 缓存中,缓存有效期由 apisix/plugins/ext-plugin/helper.lua 中的get_conf_token_cache_time()决定(当前为 3600 秒,即 1 小时),用于避免每个请求都向 Runner 重复发送RPC_PREPARE_CONF配置同步请求。
使用限制:与部分内置插件的兼容性边界
官方文档明确提示:启用本插件后,APISIX 将使用lua-resty-http库向上游发起请求,这会导致以下内置功能不可用:
- proxy-control 插件不可用;
- proxy-mirror 插件不可用;
- proxy-cache 插件不可用;
- APISIX 与上游间的双向认证(mTLS)功能尚不可用。
这一限制的根因可以从源码结构推断:ext-plugin-post-resp在before_proxy阶段用lua-resty-http直接向上游发起了独立请求,并自行把响应写回客户端,绕过了 APISIX 常规的代理(proxy)响应处理管线。因此那些挂接在代理管线上的插件(如 proxy-cache 的缓存读写、proxy-mirror 的流量镜像、proxy-control 的响应控制)以及依赖 APISIX 与上游之间 TLS 连接管理的 mTLS 能力,都无法对这条"旁路"请求生效。在实际规划插件组合时,应避免将上述插件与ext-plugin-post-resp同时用于同一条路由。
前置准备:配置 Plugin Runner
在启用插件之前,需要先让 APISIX 知道如何启动 Plugin Runner。在conf/config.yaml(参考 conf/config.yaml.example)中配置:
ext-plugin: cmd: ["blah"] # 替换为所选 Runner 的真实可执行命令,如 Go/Java/Python RunnerAPISIX 会以子进程方式托管该 Plugin Runner:当 APISIX 重启或重新加载时,Runner 也会随之重启;Runner 异常退出后,APISIX 会在 3 秒后自动重新拉起(见 apisix/plugins/ext-plugin/init.lua 中setup_runner的实现与runner_exit事件处理)。
开发调试场景下,也可以让 Runner 单独运行并监听固定地址:
APISIX_LISTEN_ADDRESS=unix:/tmp/x.sock同时在config.yaml中配置 APISIX 连接到该固定地址(此时不要配置cmd):
ext-plugin: path_for_test: "/tmp/x.sock" # 不带 unix: 前缀关于 Plugin Runner 的更多细节(支持的 Runner 语言、环境变量传递、进程管理等),请阅读 External Plugin 文档。
启用插件
以下示例展示了如何在指定路由中启用ext-plugin-post-resp插件(来自官方文档)。
首先,可以从config.yaml中获取admin_key并存入环境变量:
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')然后通过 Admin API 创建路由并启用插件:
curl -i http://127.0.0.1:9180/apisix/admin/routes/1 \ -H "X-API-KEY: $admin_key" -X PUT -d ' { "uri": "/index.html", "plugins": { "ext-plugin-post-resp": { "conf" : [ {"name": "ext-plugin-A", "value": "{\"enable\":\"feature\"}"} ] } }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } } }'其中:
conf数组中的每一项对应一个要执行的 External Plugin:name是插件名,value是传给该插件的配置(JSON 字符串);upstream指向实际后端服务(此处示例为127.0.0.1:1980)。
同时确保plugins配置中已经启用ext-plugin-post-resp(在 conf/config.yaml.example 中可见ext-plugin-post-resp位于插件列表且注释为priority: -4000)。
测试插件
通过上述命令启用插件后,可以使用如下命令测试插件是否生效:
curl -i http://127.0.0.1:9080/index.html在返回结果中可以看到刚刚配置的 Plugin Runner 已经被触发,同时ext-plugin-A插件也已经被执行(例如它可以在响应上追加自定义响应头或改写响应体)。
修改响应:External Plugin 能对响应做什么
从仓库测试用例 t/plugin/ext-plugin/response.t 可以验证ext-plugin-post-resp对上游响应的处理能力:
- 修改响应体:测试
modify_body场景下,上游返回hello world,经过 Runner 处理后客户端收到cat; - 修改响应头:测试
modify_header场景下,Runner 设置X-Runner: Test-Runner,且可同时保留/过滤上游响应头;modify same response headers场景验证了同名响应头的追加行为(X-Same: one, two); - 修改状态码:测试
modify_status场景下,上游 200 响应被 Runner 改写为304; - 容错行为:
default allow_degradation测试验证了默认配置下conf的传递与 Runner 不可用时的降级路径。
这些能力的底层实现在 apisix/plugins/ext-plugin/init.lua 的RPC_HTTP_RESP_CALL处理器中:
- APISIX 会把上游响应状态码和全部响应头发送给 Runner;
- Runner 返回的响应头会覆盖或追加到最终响应上(同名头第一次出现用
set_header,重复出现用add_header); - 若 Runner 未返回任何响应头,则 APISIX 会保留上游响应头,但会过滤掉一组"禁止透传"的头部,包括
connection、content-length、transfer-encoding、location、server、www-authenticate、content-encoding、content-type、content-location、content-language等(见源码中的exclude_resp_header表); - 若 Runner 返回了新的状态码(非 0),则用它覆盖上游状态码;若 Runner 只改写了响应体而未指定状态码,则沿用上游状态码。
删除插件
当你需要禁用ext-plugin-post-resp插件时,可通过以下命令删除相应的 JSON 配置,APISIX 会自动重新加载相关配置,无需重启服务:
curl http://127.0.0.1:9180/apisix/admin/routes/1 \ -H "X-API-KEY: $admin_key" -X PUT -d ' { "uri": "/index.html", "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } } }'即:PUT 一个新的路由配置,其中不再包含plugins.ext-plugin-post-resp字段。该操作与其他插件的删除方式一致,均为声明式覆盖,配置下发后立即生效。
源码与测试指引
如果你希望深入了解ext-plugin-post-resp的实现细节,可以按以下路径继续阅读当前仓库:
- apisix/plugins/ext-plugin-post-resp.lua:插件主体,包含
before_proxy阶段的请求构造、RPC 交互与响应写出逻辑; - apisix/plugins/ext-plugin/init.lua:共享 schema、unix socket RPC 收发、
RPC_HTTP_RESP_CALL响应处理、communicate重试与降级逻辑; - apisix/plugins/ext-plugin/helper.lua:unix socket 路径解析与 conf token 缓存时间;
- apisix/plugins/ext-plugin-post-req.lua:同系列
ext-plugin-post-req插件实现,便于对比执行时机; - t/plugin/ext-plugin/response.t:响应改写(体/头/状态码)与降级的完整测试用例;
- t/plugin/ext-plugin/extra-info.t:包含
ext-plugin-post-resp路由配置的扩展信息(Extra Info)交互测试; - t/plugin/ext-plugin/sanity.t:插件 schema 校验的冒烟测试;
- docs/zh/latest/external-plugin.md:External Plugin 与 Plugin Runner 的概念、部署与常见问题。
综上所述,ext-plugin-post-resp为 APISIX 提供了一种在响应阶段接入任意语言插件的能力。在规划架构时,建议先明确其"旁路直连上游、自行回写响应"的实现特性,妥善规避与 proxy-cache、proxy-mirror、proxy-control 等插件的冲突,再结合allow_degradation设计好 Runner 异常时的降级策略,即可稳定地将外部生态的复杂响应处理逻辑纳入 APISIX 的请求链路。
【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考