插件开发最佳实践
本指南汇总了让开发自定义 Lua 插件达到与内置插件相同行为和质量的编码实践,包括可预测的配置、正确的请求和响应处理,以及负载下的良好性能。
最重要的习惯是基于 apisix.core 库构建插件,而不是直接调用原始 OpenResty(ngx.*)API。所有内置插件都采用这种方式。apisix.core 是网关的插件工具箱,它在底层 OpenResty 原语之上提供请求级缓存、一致的错误处理和辅助函数,并已处理许多容易遗漏的边界情况 。
local core = require("apisix.core")
使用 apisix.core,不要直接使用原始 ngx API
原始 ngx.* 调用在快速测试中通常可用,但在生产环境可能产生隐蔽问题。使用 core.* 封装的原因包括:
- 缓存请求范围内的读取结果。
core.request.headers(ctx)和ctx.var.<name>只计算一次并在当前请求中复用,而重复调用ngx.req.get_headers()每次都会重新解析。 - **与网关其他部分保持一致。**插件通过
core.request.set_header设置请求头时,其他插件读取的缓存视图会保持正确;混用原始ngx.req.set_header可能留下过时缓存值。 - **处理 不便的默认行为。**例如,
core.request.get_uri_args读取参数时不设截断限制,而ngx.req.get_uri_args()会静默限制为 100 个参数。 - **集中行为。**日志、JSON 编码和 Schema 校验通过统一入口执行,使级别过滤、错误处理和格式保持一致。
ngx 到 core 的映射
应优先使用对应的 core API。下表列出最常见的替代关系。
原始 ngx API | 推荐的 core API | 说明 |
|---|---|---|
ngx.log(ngx.ERR, ...) | core.log.error(...) (.warn, .info, .debug) | 自动按级别过滤;传入多个参数,不要拼接。 |
ngx.req.get_headers() | core.request.headers(ctx) | 缓存在 ctx 中;键转换为小写,以便不区分大小写地查找。 |
ngx.req.get_headers()[name] | core.request.header(ctx, name) | 不区分大小写;解包多值请求头。 |
ngx.req.set_header(k, v) | core.request.set_header(ctx, k, v) | 同时让缓存的请求头视图失效。 |
ngx.req.get_uri_args() | core.request.get_uri_args(ctx) | 无截断限制;结果会被缓存。 |
ngx.req.set_uri_args(a) | core.request.set_uri_args(ctx, a) | 接受 table 或字符串。 |
ngx.req.read_body() + ngx.req.get_body_data() | core.request.get_body() | 也可读取缓冲到临时文件的请求体;仍可能返回 nil。 |
ngx.req.get_method() | core.request.get_method() | |
ngx.var.<name> | ctx.var.<name> | 延迟加载并缓存;还提供 route_id、consumer_name 等网关变量。 |
ngx.header[k] = v | core.response.set_header(k, v) | 防止在响应头已发送后继续设置。 |
ngx.say / ngx.print / ngx.exit | core.response.exit(code, body) | table 响应体会编码为 JSON;必须返回调用结果。 |
cjson.encode / cjson.decode | core.json.encode / core.json.decode | 输入无效时,decode 返回 nil, err。 |
日志行中的 cjson.encode | core.json.delay_encode(data) | 仅在实际输出日志行时编码。 |
| JSON Schema 校验 | core.schema.check(schema, conf) | 缓存已编译的校验器。 |
table.insert / table.new / table.clear | core.table.insert / core.table.new / core.table.clear | core.table 也代理标准 table 库。 |
ngx.timer.every(周期任务) | core.timer.new(name, cb, opts) | 在 pcall 下运行回调,以隔离错误。 |
核心库概览
core 将辅助函数分组到多个子模块中。插件开发者最常使用以下模块:
| 子模块 | 用途 |
|---|---|
core.request | 读取和修改传入的客户端请求。 |
core.response | 设置响应头并短路请求。 |
core.ctx / ctx.var | 访问请求上下文及 NGINX 或网关变量。 |
core.log | 按日志级别记录。 |
core.json | 编码和解码 JSON,包括日志的延迟编码。 |
core.schema | 根据 JSON Schema 校验插件配置。 |
core.table | table 辅助函数:预分配、清空、计数、深拷贝和合并。 |
core.string | 高效的明文字符串辅助函数(前缀、后缀、查找)。 |
core.utils | 变量替换、DNS、UUID 等通用辅助函数。 |
core.lrucache | 以插件配置为键的 Worker 级 LRU 缓存。 |
core.timer | 托管的后台定时器。 |
读取和修改请求
core.request 中的大多数函数都以 ctx 作为第一个参数。
function _M.access(conf, ctx)
-- 读取请求头(不区分大小写)和查询参数。
local token = core.request.header(ctx, "Authorization")
local args = core.request.get_uri_args(ctx)
-- 在请求代理到上游之前设置请求头。
core.request.set_header(ctx, "X-Consumer-Tier", conf.tier)
end
关键函数:
core.request.header(ctx, name):读取单个请求头值,不区分大小写。core.request.headers(ctx):以table返回所有请求头,键为小写。core.request.set_header(ctx, name, value):设置请求头;传入value = nil可将其删除。core.request.get_uri_args(ctx):获取解析后的查询参数。core.request.get_body():以字符串返回原始请求体。它可能返回nil(例如没有请求体时),使用前必须检查。core.request.get_method():获取 HTTP 方法。core.request.get_remote_client_ip(ctx):获取下游客户端 IP。
读取 JSON 请求体十分常见,因此提供了专用辅助函数:
local body, err = core.request.get_json_request_body_table()
if not body then
core.log.warn("failed to read JSON body: ", core.json.delay_encode(err))
return 400, { message = "invalid request body" }
end