apisix java插件,需要 java 11 的环境,而且 window 没有 netty 的 epoll 模式, 无法在本地进行插件的 deug 调试,这里直接采用 lua 开发。
1 插件存放位置
apisix 会默认加载 /user/local/apisix/plugins 下的插件,插件可以直接放在这里面。
如果是第三方的插件目录,例如目录为/xx,那么 apisix 启动时实际是扫描 /xx/apisix/plugins 目录, APISIX 要求插件父目录必须包含:apisix/plugins 子目录。
并且第三方目录需要在配置文件中指定:
apisix:
# ...
extra_lua_path: "/xx/?.lua"
2 怎么写自定义插件
2.1 代码示例
开发可以参考官方源码中的 example-plugin.lua 这个插件,这是一个示例代码,默认也启用了,直接复制这个进行修改即可。
比如我们创建一个 example-plugin2.lua,代码如下:
– 局部变量
local ngx = ngx
local core = require("apisix.core")
local plugin = require("apisix.plugin")
local upstream = require("apisix.upstream")
local plugin_name = "example-plugin2"
-- 插件基础信息
local _M = {
version = 0.1,
-- 优先级,apisix 要求每个插件的优先级都不同,这里需要注意
priority = 1,
-- type 如果是 auth, 则表明是认证插件,就需要配合consumer一起使用了
-- type = 'auth',
name = plugin_name,
schema = schema,
-- metadata_schema = metadata_schema,
}
-- 插件配置参数
local schema = {
type = "object",
properties = {
i = {type = "number", minimum = 0},
s = {type = "string"},
t = {type = "array", minItems = 1},
ip = {type = "string"},
port = {type = "integer"},
},
required = {"i"},
}
-- consumer 配置,可以参考 jwt-auth.lua
--local consumer_schema = {
-- type = "object",
-- properties = {
-- ak = { type = "string" },
-- sk = { type = "string" },
-- },
-- required = { "ak", "sk" },
--}
-- 元数据配置, 可以通过/apisix/admin/plugin_metadata/{pluginName}进行元数据的配置
--local metadata_schema = {
-- type = "object",
-- properties = {
-- ikey = {type = "number", minimum = 0},
-- skey = {type = "string"},
-- },
-- required = {"ikey", "skey"},
--}
-- 校验配置合法性
function _M.check_schema(conf, schema_type)
if schema_type == core.schema.TYPE_METADATA then
return core.schema.check(metadata_schema, conf)
end
return core.schema.check(schema, conf)
end
function _M.init()
-- call this function when plugin is loaded
local attr = plugin.plugin_attr(plugin_name)
if attr then
core.log.info(plugin_name, " get plugin attr val: ", attr.val)
end
end
-- 销毁方法,一般用于有元数据的情况,在这里面把资源是放掉
function _M.destroy()
-- call this function when plugin is unloaded
end
function _M.rewrite(conf, ctx)
core.log.warn("plugin rewrite phase, conf: ", core.json.encode(conf))
core.log.warn("conf_type: ", ctx.conf_type)
core.log.warn("conf_id: ", ctx.conf_id)
core.log.warn("conf_version: ", ctx.conf_version)
end
--[[
阶段方法:这里写自己的逻辑
conf 参数是插件的相关配置信息
ctx 参数缓存了请求相关的数据信息
]]
function _M.access(conf, ctx)
core.log.warn("plugin access phase, conf: ", core.json.encode(conf))
-- return 200, {message = "hit example plugin"}
if not conf.ip then
return
end
local up_conf = {
type = "roundrobin",
nodes = {
{host = conf.ip, port = conf.port, weight = 1}
}
}
local ok, err = upstream.check_schema(up_conf)
if not ok then
return 500, err
end
local matched_route = ctx.matched_route
upstream.set(ctx, up_conf.type .. "#route_" .. matched_route.value.id,
ctx.conf_version, up_conf)
return
end
function _M.body_filter(conf, ctx)
core.log.warn("plugin body_filter phase, eof: ", ngx.arg[2],
", conf: ", core.json.encode(conf))
end
function _M.delayed_body_filter(conf, ctx)
core.log.warn("plugin delayed_body_filter phase, eof: ", ngx.arg[2],
", conf: ", core.json.encode(conf))
end
local function hello()
local args = ngx.req.get_uri_args()
if args["json"] then
return 200, {msg = "world"}
else
return 200, "world\n"
end
end
-- 这里和官网的略有不同,因为我想暴露端口访问 hello方法,所以这里改成了api
function _M.api()
return {
{
methods = {"GET"},
uri = "/apisix/plugin/example-plugin2/hello",
handler = hello,
}
}
end
return _M
这个插件配置还是比较全的,下面我将逐个讲解每部分的作用。
2.1.1 plugin_name
plugin_name 是插件名
2.1.1 _M
这部分是插件的基本信息,包含了插件版本, 优先级,类型等等.
local _M = {
version = 0.1,
priority = 1,
-- type = 'auth',
name = plugin_name,
schema = schema,
metadata_schema = metadata_schema,
}
需要注意的是,如果type类型为 auth,则表示这是个认证插件,需要和 consumer_schema 连用,以指定consumer 的参数。
例如:
我希望创建一个认证插件, consumer 访问路由的时候,需要带上ak 和sk 作为鉴权,就可以在插件中加入如下配置:
local _M = {
...
type = 'auth',
...
}
local consumer_schema = {
type = "object",
properties = {
ak = { type = "string" },
sk = { type = "string" },
},
required = { "ak", "sk" },
}
2.1.2 schema
schema 是创建插件的时候指定参数。
local schema = {
type = "object",
properties = {
i = {type = "number", minimum = 0},
s = {type = "string"},
t = {type = "array", minItems = 1},
ip = {type = "string"},
port = {type = "integer"},
},
required = {"i"},
}
这段代码中的意思是,在配置这个插件的时候, 可以给定多种参数,但是 i 是必须要的,如果不指定 i ,创建时会报错。
示例:
curl -X PUT 127.0.0.1:9180/apisix/admin/routes/example_plugin2_01 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -d '
{
"plugins": {
"example-plugin2": {
"name": "example2",
"i": 1
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"xx.xx.xx.xx:8200": 1
}
},
"uri": "/weave/*"
}'
2.1.3 consumer_schema
上文已经讲过了,用于指定创建 consumer 时的参数。
2.1.4 metadata_schema
元数据配置, 可以通过**/apisix/admin/plugin_metadata/{pluginName}** 接口进行元数据的配置,数据会存放到 apisix 的 etcd 数据库。
2.1.5 生命周期方法
通常来说,校验逻辑,可以写在 rewrite 阶段或者 access 阶段。
2.1.6 _M.api
local function hello()
local args = ngx.req.get_uri_args()
if args["json"] then
return 200, {msg = "world"}
else
return 200, "world\n"
end
end
function _M.api()
return {
{
methods = {"GET"},
uri = "/apisix/plugin/example-plugin2/hello",
handler = hello,
}
}
end
这里和官网的略有不同,官网中的代码是 _M.control_api()。
_M.api 可以参照官网的 public api 文档,主要作用是对外暴露接口,以供访问,我这里的返回简单写了个 hello 方法,主要用于测试。
_M.control_api() 主要作用是内部访问。
3 部署
我这里是采用 docker-compose 的方式部署。
修改config.yml
将 config-default.yml 中的 plugin 部分拷贝过来, 并加上自己的插件。
配置文件在 /user/local/apisix/conf 目录下
plugins: # plugin list (sorted by priority)
- example-plugin2 # priority: 1
xxx(把config-default.yml 中的 plugin 部分拷贝过来)
修改docker-complse.yml 文件将自己的插件挂载到容器中。
如果你是采用docker部署的,也可以将文件直接拷贝到 /user/local/apisix/apisix/plugins 目录下。
services:
apisix:
image: apache/apisix:${APISIX_IMAGE_TAG:-3.7.0-debian}
restart: always
volumes:
- ./apisix_conf/config.yaml:/usr/local/apisix/conf/config.yaml:ro
- ./apisix_conf/config-default.yaml:/usr/local/apisix/conf/config-default.yaml:ro
- ./apisix_conf/example-plugin2.lua:/usr/local/apisix/apisix/plugins/example-plugin2.lua:ro
部署apisix
启动命令:docker-compose -p docker-apisix up -d
停止命令:docker-compose -p docker-apisix down
查看日志命令:docker-compose -p docker-apisix logs -f
4 测试
创建路由:
curl -X PUT 127.0.0.1:9180/apisix/admin/routes/example_plugin2_01 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -d '
{
"plugins": {
"example-plugin2": {
"name": "example2",
"i": 1
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"10.151.xx.xx:8200": 1
}
},
"uri": "/test/*"
}'
开启路由的 public api:
curl -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/r2' \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \
-H 'Content-Type: application/json' \
-d '{
"uri": "/get_hello",
"plugins": {
"public-api": {
"uri": "/apisix/plugin/example-plugin2/hello"
}
}
}'
访问 pulibc api
curl http://127.0.0.1:9080/get_hello -X GET
输出如下:
5 补充
修改日志级别:
config-default.yml 中可以修改
插件的热加载:
如果只修改了插件内容,可以使用下面方法,无需重启spisix。
curl http://127.0.0.1:9180/apisix/admin/plugins/reload -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT