使用 Lua 创建自定义插件
APISIX 的主要功能之一是其通过 插件 的可扩展性。除了各种现有插件外,APISIX 还允许你构建自定义插件以添加额外功能并使用自定义流 程管理 API 流量。通常,你使用 Lua 编程语言来实现新插件。APISIX 分 阶段 处理请求,相关的插件逻辑在请求路由期间的每个阶段执行。
本指南将引导你完成为 APISIX 开发一个新的自定义 Lua 插件的示例过程。
前置条件
开发文件代理插件
在本节中,你将使用 Lua 为 APISIX 创建一个名为 file-proxy 的新自定义插件。此插件将用于通过 API 公开静态文件(YAML、JSON、JavaScript、CSS 或图像文件)并从指定的 URL 获取文件。
例如,API 用户可以通过指定的 URL http://127.0.0.1:9080/openapi.yaml 访问 openapi.yaml 文件。
创建 Lua 文件
为插件源代码创建一个名为 file-proxy.lua 的新 Lua 文件。
导入模块
为 file-proxy 插件导入必要的模块:
local core = require("apisix.core")
local io = require("io")
local ngx = ngx
定义插件名称
为插件声明一个唯一的名称:
local plugin_name = "file-proxy"
定义插件架构
为插件参数创建一个架构。架构定义了可用参数及其数据类型、属性、默认值、有效值等。
file-proxy 插件需要有一个 文件路径 参数,以便 APISIX 知道在响应中返回其内容之前从哪里读取文件。
local plugin_schema = {
type = "object",
properties = {
path = {
type = "string"
},
},
required = {"path"}
}
❶ 要服务的文件的路径。
❷ 路径设置为必需参数。
定义 Lua 模块表
为插件定义属性 version、priority、name 和 schema。name 和 schema 是之前定义的插件名称和架构。version 和 priority 由 APISIX 用于管理插件。
local _M = {
version = 1.0,
priority = 1000,
name = plugin_name,
schema = plugin_schema
}
❶ version:该字段通常是指当前正在使用的版本。如果你发布并更新你的插件逻辑,它将是 1.1(你可以设置你希望的任何版本)。
❷ priority:该字段用于在执行插件的每个阶段之前对插件进行排序。具有较高优先级的插件首先执行。确保自定义插件的优先级适合现有插件的优先级。现有插件的优先级记录在 config.yaml.example 文件中。
❸ name:插件的名称。
❹ schema:插件的架构。
定义架构检查函数
定义一个架构检查器以根据定义的架构验证用户输入的参数:
function _M.check_schema(conf)
local ok, err = core.schema.check(plugin_schema, conf)
if not ok then
return false, err
end
return true
end
定义自定义逻辑
APISIX 允许你在各种 阶段 中注入自定义逻辑。请参阅 Lua NGINX 模块指令 以了解有关不同阶段的更多信息。
在 access 函数中实现 file-proxy 插件的自定义逻辑,该函数将在 access 阶段执行。逻辑应该打开插件配置中指定的文件,读取其内容,并将内容作为响应返回。如果文件无法打开,它应该记录错误并返回 404 Not Found。
function _M.access(conf, ctx)
local fd = io.open(conf.path, "rb")
if fd then
local content = fd:read("*all")
fd:close()
ngx.header.content_length = #content
ngx.say(content)
ngx.exit(ngx.OK)
else
ngx.exit(ngx.HTTP_NOT_FOUND)
core.log.error("File is not found: ", conf.path, ", error info: ", err)
end
end
定义日志逻辑
log 函数在 log 阶段执行。日志记录是可选的,但有助于调试和检查插件是否正常工作。
实现日志逻辑以记录插件配置、对插件的请求和响应:
function _M.log(conf, ctx)
core.log.warn("conf: ", core.json.encode(conf))
core.log.warn("ctx: ", core.json.encode(ctx, true))
end
组合所有内容
当你组合上面的所有代码时,file-proxy.lua 应该如下所示:
-- Introduce the necessary modules/libraries we need for this plugin
local core = require("apisix.core")
local io = require("io")
local ngx = ngx
-- Declare the plugin's name
local plugin_name = "file-proxy"
-- Define the plugin schema format
local plugin_schema = {
type = "object",
properties = {
path = {
type = "string" -- The path of the file to be served
},
},
required = {"path"} -- The path is a required field
}
-- Define the plugin with its version, priority, name, and schema
local _M = {
version = 1.0,
priority = 1000,
name = plugin_name,
schema = plugin_schema
}
-- Function to check if the plugin configuration is correct
function _M.check_schema(conf)
-- Validate the configuration against the schema
local ok, err = core.schema.check(plugin_schema, conf)
-- If validation fails, return false and the error
if not ok then
return false, err
end
-- If validation succeeds, return true
return true
end
-- Function to be called during the access phase
function _M.access(conf, ctx)
-- Open the file specified in the configuration
local fd = io.open(conf.path, "rb")
-- If the file is opened successfully, read its content and return it as the response
if fd then
local content = fd:read("*all")
fd:close()
ngx.header.content_length = #content
ngx.say(content)
ngx.exit(ngx.OK)
else
-- If the file cannot be opened, log an error and return a 404 Not Found status
ngx.exit(ngx.HTTP_NOT_FOUND)
core.log.error("File is not found: ", conf.path, ", error info: ", err)
end
end
-- Function to be called during the log phase
function _M.log(conf, ctx)
-- Log the plugin configuration and the request context
core.log.warn("conf: ", core.json.encode(conf))
core.log.warn("ctx: ", core.json.encode(ctx, true))
end
-- Return the plugin so it can be used by APISIX
return _M
加载自定义插件
有两种方法可以将自定义插件代码加载到 APISIX 中:
- 将插件源代码放在默认的 APISIX 插件目录
/apisix/plugins中,与其他 APISIX 插件放在一起。 - 将插件源代码放在单独的目录中,并在
config.yaml中的extra_lua_path中指定搜索路径。
以下部分提供了第二种方法的说明,因为建议在单独的目录中管理自定义代码。
Create Custom Plugin File
APISIX 在你的自定义目录中的 /apisix/plugins 中查找 L7 插件,在 /apisix/stream/plugins 中查找 L4 插件。
对于自定义插件,创建一个具有自定义名称的目录,并在该目录中创建 /apisix/plugins:
docker exec apisix-quickstart /bin/sh -c "mkdir -p custom-plugin/apisix/plugins"
将插件文件保存到该目录:
docker exec apisix-quickstart /bin/sh -c "echo '
local core = require(\"apisix.core\")
local io = require(\"io\")
local ngx = ngx
local plugin_name = \"file-proxy\"
local plugin_schema = {
type = \"object\",
properties = {
path = {
type = \"string\"
},
},
required = {\"path\"}
}
local _M = {
version = 1.0,
priority = 1000,
name = plugin_name,
schema = plugin_schema
}
function _M.check_schema(conf)
local ok, err = core.schema.check(plugin_schema, conf)
if not ok then
return false, err
end
return true
end
function _M.access(conf, ctx)
local fd = io.open(conf.path,\"rb\")
if fd then
local content = fd:read(\"*all\")
fd:close()
ngx.header.content_length = #content
ngx.say(content)
ngx.exit(ngx.OK)
else
ngx.exit(ngx.HTTP_NOT_FOUND)
core.log.error(\"File is not found: \", conf.path, \", error info: \", err)
end
end
function _M.log(conf, ctx)
core.log.warn(\"conf: \", core.json.encode(conf))
core.log.warn(\"ctx: \", core.json.encode(ctx, true))
end
return _M
' > /usr/local/apisix/custom-plugin/apisix/plugins/file-proxy.lua"
Update APISIX Configuration File
更新 APISIX 配置文件,在 extra_lua_path 中指定自定义插件的搜索路径,并将插件名称添加到插件列表中:
docker exec apisix-quickstart /bin/sh -c "echo '
apisix:
extra_lua_path: "/usr/local/apisix/custom-plugin/\?.lua"
enable_control: true
control:
ip: 0.0.0.0
port: 9092
deployment:
role: traditional
role_traditional:
config_provider: etcd
admin:
admin_key_required: false
allow_admin:
- 0.0.0.0/0
plugin_attr:
prometheus:
export_addr:
ip: 0.0.0.0
port: 9091
plugins:
- file-proxy
' > /usr/local/apisix/conf/config.yaml"
❶ extra_lua_path:自定义插件的搜索路径。
请注意,仅将 file-proxy 插件添加到插件列表将覆盖所有现有的默认插件。要使 file-proxy 插件成为现有插件的补充,你应该复制现有插件的名称并将它们添加到列表中。
重新加载 APISIX 以使配置更改生效:
docker exec apisix-quickstart apisix reload
测试自定义插件
存储测试文件
在 APISIX 实例中存储一个静态 openapi.yaml 文件:
docker exec apisix-quickstart /bin/sh -c "echo '
openapi: 3.0.1
info:
title: OpenAPI Spec
description: OpenAPI Spec file description.
' > /usr/local/apisix/openapi.yaml"
创建路由
要使用 file-proxy 自定义插件,你需要在 APISIX 中创建一个使用该插件的路由:
- Admin API
- ADC
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d '
{
"id":"openapi-file-proxy",
"uri":"/openapi.yaml",
"plugins":{
"file-proxy":{
"path":"/usr/local/apisix/openapi.yaml"
}
}
}'
services:
- name: OpenAPI File Proxy Service
routes:
- uris:
- /openapi.yaml
name: openapi-file-proxy
plugins:
file-proxy:
path: /usr/local/apisix/openapi.yaml
将配置同步到 APISIX:
adc sync -f adc.yaml
验证插件
向路由发送请求:
curl "http://127.0.0.1:9080/openapi.yaml"
响应应该是 openapi.yaml 文件的内容:
openapi: 3.0.1
info:
title: OpenAPI Spec
description: OpenAPI Spec file description.
下一步
Lua开发自定义插件是扩展APISIX的一种方式. 从这里:
- 如需在不重新部署或重启 APISIX 的情况下检查插件运行时状态,请参阅设置断点。
- 如需使用 Lua 之外的语言编写插件,请参阅创建 WASM 插件。APISIX 还支持通过外部插件运行器执行 Go、Java 和 Python 插件。