APISIX自定义插件

APISIX自定义插件

插件存放位置

APISIX会默认加载/apisix/plugins下的插件,插件可以直接放在这里

如果是第三方的插件目录,例如目录为/xx,那么APISIX启动时,实际是扫描/xx/apisix/plugins目录

APISIX要求插件父目录必须包含apisix/plugins子目录

并且第三方目录需要再配置文件中config.yaml指定:

apisix:
	#....
	extra_lua_path: "/xx/?.lua"

编写插件

参考示例: 可以参考源码中的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

plugin_name

plugin_name 是插件名

_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" },
}

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/*"
}'

consumer_schema

用于指定创建 consumer 时的参数。

metadata_schema

元数据配置, 可以通过/apisix/admin/plugin_metadata/{pluginName} 接口进行元数据的配置,数据会存放到 apisix 的 etcd 数据库。

生命周期方法

通常来说,校验逻辑,可以写在rewrite阶段或者access阶段

  • plugin
    • init
    • check_schema
    • rewrite
    • access
    • before_proxy
    • header_filter
    • body_filter
    • log

_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() 主要作用是内部访问。

posted @ 2025-07-22 22:50  小郑[努力版]  阅读(66)  评论(0)    收藏  举报