ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Apache APISIX echo 插件深度解析:从示例插件到自研插件开发实战

Apache APISIX echo 插件深度解析:从示例插件到自研插件开发实战 Apache APISIX echo 插件深度解析从示例插件到自研插件开发实战【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisixecho 插件是 Apache APISIX 官方为帮助开发者理解插件开发流程而设计的示例插件它在 body_filter 与 header_filter 阶段对响应进行加工支持在响应体前/后追加内容、整体替换响应体以及注入自定义响应头。读完本文你将完整掌握 echo 插件的全部配置属性与启用方式并通过源码与测试用例理解 APISIX 插件的阶段执行机制、响应体改写原理最终具备独立开发 APISIX 插件的实战能力。插件定位一个用于学习插件开发的活教材在 Apache APISIX 中插件Plugin是扩展网关能力的核心机制它可以在 HTTP 请求生命周期的多个阶段被挂载执行例如init、rewrite、access、balancer、header filter、body filter和log等。echo 插件正是为了演示如何开发一个 APISIX 插件而诞生的教学示例。不过需要特别强调的是echo 插件在官方文档中带有明确的警告标注echo 插件仅作为示例构建存在缺失的场景处理不应在生产环境中使用参考 docs/en/latest/plugins/echo.md。它的价值在于教学而非生产——通过阅读其源码可以最直观地掌握 APISIX 插件的标准骨架schema 校验、check_schema、阶段回调函数body_filter、header_filter的编写方式。插件本体位于 apisix/plugins/echo.lua同时仓库中还提供了覆盖更完整阶段的教学示例 apisix/plugins/example-plugin.lua可作为进阶参考。属性Attributes详解echo 插件共支持 4 个配置属性官方文档给出的属性表如下名称类型必选项默认值合法值说明before_bodystring可选在过滤阶段之前响应体前追加的内容bodystring可选用于替换上游Upstream响应的内容after_bodystring可选在修改阶段之后响应体末尾追加的内容headersobject可选要写入响应的新请求头约束条件before_body、body、after_body三者中至少必须指定一个否则配置校验不通过。这个约束在源码中有明确的 schema 实现apisix/plugins/echo.lualocal schema { type object, properties { before_body { description body before the filter phase., type string }, body { description body to replace upstream response., type string }, after_body { description body after the modification of filter phase., type string }, headers { description new headers for response, type object, minProperties 1, }, }, anyOf { {required {before_body}}, {required {body}}, {required {after_body}} }, minProperties 1, }从源码中可以读出几个容易被文档忽略的细节anyOf结构实现了三者至少其一的校验逻辑——只要before_body、body、after_body中任意一个字段出现即通过校验minProperties 1意味着配置对象不能为空headers的minProperties 1约束了 headers 至少包含 1 个键值对每个字段都限定了type string如果传入数字等其他类型校验会直接失败对应测试t/plugin/echo.t的 TEST 2 验证了传入after_body 10时会报错property after_body validation failed: wrong type: expected string, got number。工作原理阶段回调与响应改写机制插件声明与优先级插件模块返回的_M表中声明了版本、优先级、名称与 schemalocal _M { version 0.1, priority 412, name plugin_name, schema schema, }其中priority 412表示该插件的执行优先级数值越大越先执行这决定了多个插件同时挂载在同一路由时的执行顺序。check_schema函数负责在配置写入时进行合法性校验它直接调用core.schema.check(schema, conf)完成校验apisix/plugins/echo.lua。body_filter响应体三段式改写echo 插件的核心逻辑集中在body_filter阶段。在 Nginx 中body_filter回调通过ngx.arg[1]当前响应体 chunk和ngx.arg[2]是否为最后一个 chunk操作响应体echo 插件的实现如下apisix/plugins/echo.luafunction _M.body_filter(conf, ctx) if conf.body then ngx.arg[1] conf.body ngx.arg[2] true end if conf.before_body and not ctx.plugin_echo_body_set then ngx.arg[1] conf.before_body .. ngx.arg[1] ctx.plugin_echo_body_set true end if ngx.arg[2] and conf.after_body then ngx.arg[1] ngx.arg[1] .. conf.after_body end end三段逻辑对应三种属性执行顺序为整体替换 → 前缀追加 → 后缀追加body整体替换将ngx.arg[1]直接替换为conf.body并设置ngx.arg[2] true标记当前为最后一个 chunk从而丢弃上游返回的原始响应体before_body前缀追加将配置内容拼接到当前 chunk 前面形成before_body 原始内容的效果。这里通过ctx.plugin_echo_body_set标志位保证前缀只在第一个 chunk 上追加一次避免分块传输chunked时每个 chunk 都被重复加前缀after_body后缀追加仅在最后一个 chunkngx.arg[2]为 true时将配置内容追加到响应体末尾。header_filter清理失效响应头并注入新头修改响应体后上游返回的Content-Length、Content-Encoding、Last-Modified、ETag等头部信息已与实际响应体不再匹配必须清除否则会导致客户端解析异常。echo 插件在header_filter阶段做了两件事apisix/plugins/echo.lua第一件事清除失效的响应头。只要配置了body、before_body、after_body中的任意一个就调用core.response.clear_header_as_body_modified()清理头部function _M.header_filter(conf, ctx) if conf.body or conf.before_body or conf.after_body then core.response.clear_header_as_body_modified() end ...该函数的实现在 apisix/core/response.lua它会依次清除四个关键响应头function _M.clear_header_as_body_modified() ngx.header.content_length nil -- in case of upstream content is compressed content ngx.header.content_encoding nil -- clear cache identifier ngx.header.last_modified nil ngx.header.etag nil endcontent_length响应体被改写后长度已变化必须清空否则客户端会按旧长度截断或等待content_encoding如果上游返回的是 gzip 等压缩内容改写响应体后编码信息不再成立必须清空last_modified与etag响应内容已变化缓存标识必须清除避免客户端/缓存服务器拿到过期的缓存校验信息。第二件事注入自定义响应头。当配置了headers属性时插件会把 headers 逐项写入响应if not conf.headers then return end if not conf.headers_arr then conf.headers_arr {} for field, value in pairs(conf.headers) do if type(field) string and (type(value) string or type(value) number) then if #field 0 then return false, invalid field length in header end core.table.insert(conf.headers_arr, field) core.table.insert(conf.headers_arr, value) else return false, invalid type as header value end end end local field_cnt #conf.headers_arr for i 1, field_cnt, 2 do ngx.header[conf.headers_arr[i]] conf.headers_arr[i1] end这里有两个值得注意的实现细节一是对 header 的 field 和 value 做了类型检查field 必须是非空字符串value 必须是字符串或数字非法类型会被拒绝二是使用conf.headers_arr缓存扁平化后的键值对数组由于 header_filter 阶段可能被多次调用例如开启 subrequest 时缓存可以避免重复做同样的校验与扁平化工作。启用插件为指定路由挂载 echoecho 插件需要通过 Admin API 挂载到具体的 Route路由上。官方文档给出的启用示例如下。前置准备从conf/config.yaml中读取admin_key并保存为环境变量需要系统已安装yq工具admin_key$(yq .deployment.admin.admin_key[0].key conf/config.yaml | sed s///g)创建带 echo 插件的路由curl http://127.0.0.1:9180/apisix/admin/routes/1 -H X-API-KEY: $admin_key -X PUT -d { plugins: { echo: { before_body: before the body modification } }, upstream: { nodes: { 127.0.0.1:1980: 1 }, type: roundrobin }, uri: /hello }该命令的含义PUT /apisix/admin/routes/1创建或更新 id 为1的路由Admin API 默认监听9180端口X-API-KEY头携带 Admin API 的认证密钥plugins.echo.before_body配置 echo 插件在所有响应体前追加before the body modification前缀upstream将请求转发到127.0.0.1:1980节点测试环境通常由 Test::Nginx 框架启动的 mock 上游承担负载均衡算法为roundrobin加权轮询uri: /hello路由匹配的请求路径。配置写入后 APISIX 会从 etcd 自动热加载新配置无需重启网关进程。示例运行观察响应体的实际变化配置完成后向网关默认监听9080端口发起请求curl -i http://127.0.0.1:9080/hello预期响应如下HTTP/1.1 200 OK ... before the body modification hello world可以看到上游mock 服务返回的响应体hello world前被追加了before the body modification前缀最终响应体变为before the body modification hello world。这正是before_body属性作用的直观体现。综合实战同时使用全部四个属性官方文档的示例只展示了before_body的用法但插件实际支持四种属性的任意组合受三者至少其一约束。测试用例 t/plugin/echo.t 的 TEST 3 与 TEST 4 展示了一个同时配置全部属性的完整场景curl http://127.0.0.1:9180/apisix/admin/routes/1 -H X-API-KEY: $admin_key -X PUT -d { plugins: { echo: { before_body: before the body modification , body: hello upstream, after_body: after the body modification., headers: { Location: https://www.iresty.com, Authorization: userpass } } }, upstream: { nodes: { 127.0.0.1:1980: 1 }, type: roundrobin }, uri: /hello }此时访问GET /hello响应体由于配置了body上游原始响应体被整体替换为hello upstream然后按顺序拼接前缀和后缀最终为before the body modification hello upstream after the body modification.响应头Location: https://www.iresty.com与Authorization: userpass会被写入响应失效头清理Content-Length、Content-Encoding、ETag等头部被自动清除。对应测试期望t/plugin/echo.t--- response_body chomp before the body modification hello upstream after the body modification. --- response_headers Location: https://www.iresty.com Authorization: userpass关键细节chunked 编码上游的兼容处理测试用例中还有一个值得注意的场景——上游返回分块传输chunked编码时的兼容性t/plugin/echo.t。当仅配置body时TEST 8/9无论上游响应是否分块响应体都会被整体替换为配置值最终输出hello upstream--- response_body chomp hello upstream当同时配置before_body与after_body时TEST 10/11前缀只会在第一个 chunk 上追加一次后缀只在最后一个 chunk 上追加最终输出--- response_body chomp before the body modification hello world after the body modification.这里ctx.plugin_echo_body_set标志位的作用就体现出来了它确保before_body前缀不会在每个 chunk 上重复出现而after_body则借助ngx.arg[2]EOF 标志精准地在最后一个 chunk 追加。这正是前文分段响应改写逻辑的意义所在。删除插件移除配置即自动生效要移除 echo 插件只需将路由配置中plugins里的 echo 配置删除置为空对象。APISIX 会自动热加载新配置无需重启网关进程curl http://127.0.0.1:9180/apisix/admin/routes/1 \ -H X-API-KEY: $admin_key -X PUT -d { methods: [GET], uri: /hello, plugins: {}, upstream: { type: roundrobin, nodes: { 127.0.0.1:1980: 1 } } }删除后GET /hello将恢复返回上游原始响应不再有任何前缀、替换或自定义头注入。测试验证schema 校验与热更新保障仓库的测试套件 t/plugin/echo.t 覆盖了 echo 插件的核心行为可作为验证配置正确性的参考测试项验证内容TEST 1check_schema对合法配置同时含三个 body 属性校验通过TEST 2after_body传入数字类型时校验失败报类型错误TEST 3/4通过 Admin API 写入含全部属性的插件配置访问后响应体与响应头均符合预期TEST 5/6热更新插件配置仅保留before_body与headers后立即生效响应体仅保留前缀TEST 7从 etcd 读取配置确认无脏数据headers等字段原样存储TEST 8-11对 chunked 编码的上游响应body整体替换与before_body/after_body追加均正确其中 TEST 7 特别验证了配置存储的纯净性写入的插件配置在 etcd 中被原样保存{echo:{before_body:before the body modification ,headers:{Location:https://www.iresty.com}}}说明插件的conf.headers_arr缓存只存在于运行期内存不会污染持久化配置。延伸从 echo 到自研插件——标准开发骨架echo 插件虽小却完整展示了 APISIX 插件开发的最小骨架可以归纳为三步定义 schema 并声明插件元信息在_M中声明version、priority、name、schema其中priority决定插件在多个插件间的执行顺序实现check_schema调用core.schema.check(schema, conf)校验配置合法性Admin API 在写入配置时即触发该校验按需实现阶段回调函数APISIX 会在对应阶段自动调用插件中同名的回调函数例如rewrite、access、body_filter、header_filter、log等。阶段回调的调度由插件框架统一完成——apisix/plugin.lua 中的run_plugin(phase, plugins, api_ctx)会遍历当前路由挂载的所有插件找到与阶段名匹配的回调函数并依次执行。需要理解的是rewrite、access等阻塞型阶段按插件优先级顺序同步执行而header_filter、body_filter、log属于响应阶段处理的是ngx.arg与ngx.header等 Nginx 阶段上下文。如果希望看到覆盖更完整阶段的示例可以研读 apisix/plugins/example-plugin.lua它额外演示了init、destroy、control_api控制面 API 注册、delayed_body_filter以及metadata_schema插件元数据 schema等进阶能力并展示了通过ctx上下文读取conf_type、conf_id、conf_version等运行时信息的方法是继 echo 之后进阶学习 APISIX 插件开发的理想范本。总结echo 插件虽然只是一个教学示例但它浓缩了 APISIX 插件开发的核心知识schema 驱动的配置校验、anyOf组合约束、按优先级排序的阶段回调、body_filter中基于ngx.arg的响应体改写技巧、header_filter中失效响应头清理与自定义头注入以及 chunked 编码下的兼容处理。无论是想快速验证 APISIX 的插件机制还是准备开发自己的第一个 APISIX 插件从 echo 插件的源码apisix/plugins/echo.lua与测试t/plugin/echo.t入手都是最快的路径。使用时请牢记echo 是教学玩具生产环境请勿直接使用。【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表