跳到主要内容
版本:3.10.x

开发自定义 Lua 插件

自定义 Lua 插件可以在请求/响应生命周期的不同阶段执行自定义逻辑,从而扩展 API7 网关功能。本指南将带你创建一个简单的 Lua 插件,在 API7 网关中注册该插件,并将其应用到路由。

前提条件

  • 具备 Lua 编程语言基础知识。
  • 熟悉 OpenResty 和 NGINX 请求生命周期。
  • 能够访问 API7 网关实例及其 Dashboard。
  • 从 Dashboard 创建的 Token

插件结构

Lua 插件是一个导出 Table 的模块,其中包含配置 Schema、优先级和阶段处理函数。

标准字段

每个插件都应定义以下字段:

  • name:插件的唯一名称。
  • version:插件版本。
  • priority:决定执行顺序,优先级越高越先执行。
  • schema:定义插件配置参数的 JSON Schema。

执行阶段

API7 网关按以下顺序执行插件:

  1. rewrite:在路由匹配前修改请求,例如更改 URI。
  2. access:主要处理阶段,例如认证和限流。
  3. before_proxy:在 access 阶段之后、请求代理到上游之前执行。
  4. header_filter:修改响应头。
  5. body_filter:修改响应体。
  6. log:响应发送给客户端后记录日志。

分步开发

1. 创建插件文件

为插件新建 Lua 文件。本示例使用名为 custom-header 的插件。

2. 定义 Schema 和优先级

使用 apisix.core 根据 JSON Schema 校验插件配置。

local core = require("apisix.core")

local schema = {
type = "object",
properties = {
header_name = {
type = "string",
default = "X-Custom-Lua"
},
header_value = {
type = "string",
default = "loaded"
}
}
}

local plugin_name = "custom-header"

local _M = {
version = 0.1,
priority = 1500,
name = plugin_name,
schema = schema,
}

function _M.check_schema(conf)
return core.schema.check(schema, conf)
end

3. 实现阶段处理函数

在适当阶段实现逻辑。若要在请求到达上游前添加请求头,请使用 access 阶段。

function _M.access(conf, ctx)
core.request.set_header(ctx, conf.header_name, conf.header_value)
end

4. 完整插件示例

以下是 custom-header插件的完整代码:

local core = require("apisix.core")

local schema = {
type = "object",
properties = {
header_name = {
type = "string",
default = "X-Custom-Lua"
},
header_value = {
type = "string",
default = "loaded"
}
}
}

local plugin_name = "custom-header"

local _M = {
version = 0.1,
priority = 1500,
name = plugin_name,
schema = schema,
}

function _M.check_schema(conf)
return core.schema.check(schema, conf)
end

function _M.access(conf, ctx)
core.request.set_header(ctx, conf.header_name, conf.header_value)
end

return _M

在 API7 网关中注册插件

在 API7 网关中,自定义插件由控制面管理。准备好 Lua 源文件后,通过 Dashboard 上传并部署,使其出现在路由、服务、消费者或全局规则的插件选择器中。

1. 在 Dashboard 中添加插件

在 Dashboard 中进入 Gateway Settings,打开 Custom Plugin 标签页,然后点击 Add Custom Plugin

上传 Lua 源文件并提供所需插件元数据:

  • Plugin Source Code File:上传 custom-header.lua
  • Plugin Description:简要说明插件功能。
  • Plugin Documentation Link:可选,填写插件文档链接。
  • Plugin Author:标明插件所有者。
  • Deployed Gateway Group:选择可使用该插件的网关组。为安全发布,最初只选择测试网关组,详见跨网关组发布

文件上传成功后,API7 网关会解析插件,并在添加前于对话框显示检测到的 nameversion

添加插件后,可以在已部署网关组的路由和服务 Add Plugin 对话框中选择它。

有关产品操作流程,请参阅自定义插件

2. 确认插件可用

在已部署网关组中打开路由或服务,确认 Add Plugin 对话框中出现 custom-header

添加依赖库

从 Dashboard 上传的自定义插件会注册为独立、完整的插件模块。控制面将源代码分发到网关,并直接加载到运行时。该文件不会写入网关的 Lua 模块搜索路径,因此其他插件无法可靠地通过 require() 解析它。请把共享库作为自定义插件上传供其他插件require。这种做法最初可能有效,但网关重启后会失效,因为无法保证依赖先于引用它的插件加载。

有些插件会共享辅助代码,或依赖第三方 Lua 模块或原生 C 库。请将依赖安装到网关的包路径,并像往常一样在插件中 require

将依赖放到 Lua 或 C 路径

网关通过两类搜索路径解析模块:

  • Lua 模块通过 lua_package_path 解析。可使用 require("myorg.utils") 加载 <dir>/myorg/utils.lua
  • 原生 Lua/C 模块通过 lua_package_cpath 解析。可使用 require("myorg.native") 加载 <dir>/myorg/native.so

可以使用网关内置路径或自定义目录:

  • 内置路径:将文件放到网关内置搜索路径(例如 /usr/local/apisix/),并与 require 的模块名保持一致。

  • 额外路径(推荐):将代码与运行时分离,并在数据面config.yaml 中添加自定义目录:

    apisix:
    extra_lua_path: "/data/lua/?.lua" # 扩展 lua_package_path
    extra_lua_cpath: "/data/clib/?.so" # 扩展 lua_package_cpath

    详情请参阅自定义插件路径配置参考

备注

通过 LuaJIT FFI 的 ffi.load() 加载的原生库由系统动态链接器解析,而不是 lua_package_cpath。请将这类库放到系统库路径(例如 /usr/local/lib),或在网关上设置 LD_LIBRARY_PATH

让网关能够访问文件

网关启动前,每个网关节点上都必须存在依赖文件:

  • Kubernetes / Helm:使用卷(如 ConfigMap 或持久卷)把文件挂载到网关 Pod,并让 extra_lua_path / extra_lua_cpath 指向挂载路径。
  • Docker / Docker Compose:将目录绑定挂载到网关容器。

上传的自定义插件由控制面动态分发,而包路径上的库在网关 Worker 启动时加载。添加或修改后,请重新加载或重启网关使变更生效。

示例

将共享辅助函数放入网关路径上的普通 Lua 模块,例如 /data/lua/myorg/base.lua

local _M = {}

function _M.verify(token)
-- 多个插件共用的逻辑
return token ~= nil
end

return _M

将目录添加到数据面配置:

apisix:
extra_lua_path: "/data/lua/?.lua"

然后在任意自定义插件中 require

local core = require("apisix.core")
local base = require("myorg.base")

-- ...

function _M.access(conf, ctx)
if not base.verify(core.request.header(ctx, "Authorization")) then
return 401
end
end

由于模块位于包路径,require("myorg.base") 在每个 Worker 上的解析方式相同,网关重启后仍然有效。

测试插件

插件源代码已在网关节点上可用且插件已注册到 API7 网关后,可以使用 Admin API 或 ADC 在特定路由上启用它。

  1. 创建启用该插件的路由:
curl -k "https://localhost:7443/apisix/admin/services/custom-header-service?gateway_group_id={gateway_group_id}" -X PUT \
-H "X-API-KEY: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "custom-header-service",
"upstream": {
"type": "roundrobin",
"scheme": "http",
"nodes": [
{
"host": "httpbin.org",
"port": 80,
"weight": 100
}
]
}
}'

curl -k "https://localhost:7443/apisix/admin/routes/custom-header-route?gateway_group_id={gateway_group_id}" -X PUT \
-H "X-API-KEY: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "custom-header-route",
"methods": ["GET"],
"paths": ["/get"],
"service_id": "custom-header-service",
"plugins": {
"custom-header": {
"header_name": "X-Trace-ID",
"header_value": "docs-test"
}
}
}'
  1. 发送请求并验证上游响应体中的请求头:
curl -i "http://localhost:9080/get"

httpbin 响应的 headers 中应包含 X-Trace-ID: docs-test,这表明插件在代理前修改了请求。

如果路由创建后代理立即返回 404 Route Not Found,请等待几秒后重试。

跨网关组发布

自定义插件按网关组部署。每个网关组都是隔离的配置范围:部署到一个组的插件无法供其他组的路由或服务使用。借助这种隔离,可在插件影响生产流量前先在非生产组中验证。

推荐的发布流程如下:

  1. **部署到测试网关组。**添加插件时,仅在 Deployed Gateway Group 下选择测试或预发布网关组。插件只对该组可用。
  2. **验证。**按照测试插件在测试组的路由上启用插件并确认行为符合预期。生产组尚无该插件,因此迭代期间不受影响。
  3. **发布到生产环境。**验证完成后,编辑自定义插件并将生产网关组添加到 Deployed Gateway Group。控制面会立即把插件分发到新选中组的网关。

要更改部署范围,请进入 Gateway Settings,打开 Custom Plugin 标签页和目标插件,更新 Deployed Gateway Group 并保存。

请注意:

  • **更新会同时应用到所有已部署组。**上传新版本会同时更新所有网关组中的插件,不支持按组维护版本。因此测试优先流程非常适合新插件。若要验证已在生产环境运行的插件变更,请将它作为名称唯一的独立插件上传,并仅部署到测试组;验证后再将变更应用到生产插件。
  • **移除网关组会撤回插件。**如果该组中的路由仍引用插件,可能会出现故障。只有确认不存在依赖后才能移除。
  • **插件名称是全局的。**自定义插件是带有一组部署网关组的单一命名对象,而不是每个组各有一份副本。发布过程中请保持名称唯一且稳定。

调试提示

  • 检查错误日志:日志默认位于 /usr/local/apisix/logs/error.log
  • 语法错误:上传前使用 luac -p custom-header.lua 检查 Lua 语法。
  • 插件可见性:如果 Dashboard 插件列表中未出现该插件,请确认它已部署到正确的网关组。
  • Admin API 载荷结构:API7 网关创建路由时使用 pathsservice_id,而不是 APISIX 风格的顶层 uri 和内联 upstream
  • 名称冲突:确保插件名称唯一且不与内置插件冲突。

后续步骤