Apisix插件开发干货总结

本文主要是总结在Apisix插件开发过程中积累的一些经验,插件开发之后如何部署生效网上有很多资料,这里就不再说明。

Apisix的插件逻辑写在插件名称.lua文件中,文件结构如下:

如:example-plugin.lua

local core = require("apisix.core")
-- 插件的名字
local plugin_name = "example-plugin"
-- 插件的基本信息
local _M = {
    version = 0.1,
    priority = 0, -- 优先级
    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"},
}
-- 元数据配置
local metadata_schema = {
    type = "object",
    properties = {
        ikey = {type = "number", minimum = 0},
        skey = {type = "string"},
    },
    required = {"ikey", "skey"},
}
-- 校验配置合法性
function _M.check_schema(conf)
    return core.schema.check(schema, conf)
end
-- 阶段方法
function _M.access(conf, ctx)
    core.log.warn(core.json.encode(conf))
    core.log.warn(core.json.encode(ctx, true))
    ......
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

-- 暴露api的接口
function _M.control_api()
    return {
        {
            methods = {"GET"},
            uris = {"/v1/plugin/example-plugin/hello"},
            handler = hello,
        }
    }
end

 下面结合example-plugin.lua文件对插件的开发逻辑进行说明:

1. 优先级

新插件的优先级(priority 属性)不能与现有插件的优先级相同,您可以使用 control API 的 /v1/schema 方法查看所有插件的优先级。另外,同一个阶段里面,优先级 ( priority ) 值大的插件,会优先执行,比如 example-plugin 的优先级是 0,ip-restriction 的优先级是 3000,所以在每个阶段,会先执行 ip-restriction 插件,再去执行 example-plugin 插件。

对于自定义的插件,优先建议采用 1 到 99 之间的优先级,但是也可以根据实际情况定义更大的优先级,不和现有插件的优先级相同就可以。

2. _M 中还有其他字段会影响到插件的行为。

local _M = {

...

type = 'auth',

run_policy = 'prefer_route',

}

run_policy 字段可以用来控制插件执行。当这个字段设置成 prefer_route 时,且该插件同时配置在全局和路由级别,那么只有路由级别的配置生效。

当一个插件设置 type = 'auth',说明它是个认证插件。认证插件需要在执行后选择对应的 consumer。

举个例子,在 key-auth 插件中,它通过 apikey 请求头获取对应的 consumer,然后通过 consumer.attach_consumer 设置它。

为了跟 consumer 资源一起使用,认证插件需要提供一个 consumer_schema 来检验 consumer 资源的 plugins 属性里面的配置。

下面是 key-auth 插件的 consumer 配置:

{
  "username": "Joe",
  "plugins": {
    "key-auth": {
      "key": "Joe's key"
    }
  }
}

你在创建 Consumer 时会用到它。

为了检验这个配置,这个插件使用了如下的 schema:

local consumer_schema = {
    type = "object",
    properties = {
        key = {type = "string"},
    },
    required = {"key"},
}

注意 key-auth 的 check_schema(conf) 方法和 example-plugin 的同名方法的区别:

-- key-auth
function _M.check_schema(conf, schema_type)
    if schema_type == core.schema.TYPE_CONSUMER then
        return core.schema.check(consumer_schema, conf)
    else
        return core.schema.check(schema, conf)
    end
end
-- example-plugin
function _M.check_schema(conf, schema_type)
    return core.schema.check(schema, conf)
end

 

3. 配置描述与校验

schema定义插件的配置项(即启用插件时需要在插件上配置的变量,通过conf传给插件源码),以及对应的 JSON Schema 描述,并使用check_schema方法完成对 JSON 的校验,这样方便对配置的数据规格进行验证,以确保数据的完整性以及程序的健壮性。

如果插件需要使用一些元数据,可以定义插件的 metadata_schema ,然后就可以通过 Admin API 动态的管理这些元数据了。

上面的示例定义的是字符串和数字类型的对象形式,如果要定义数组类型的对象,可以使用如下方式:

local schema = {
    type = "object",
    properties = {
        -- version_flag为字符串类型
        version_flag = {
            type = "string",
            default = "version",
        },
        -- network为字符串类型
        network = {
            type = "string",
            default = "test",
        },
        -- network_routerlist变量为数组类型
        network_routerlist = {
            type = "array",
            items = {
                type = "object",
                properties = {
                    network_id = {type = "string"},
                    end_point = {type="string"},
                }                
            },
        }
    },
}

启用插件后,在插件的配置中用以下格式设置变量:

 

4. 执行阶段

根据业务功能,确定你的插件需要在哪个阶段执行。key-auth 是一个认证插件,所以需要在 rewrite 阶段执行。在 APISIX,只有认证逻辑可以在 rewrite 阶段里面完成,其他需要在代理到上游之前执行的逻辑都是在 access 阶段完成的。常见的阶段包括:init, rewrite, access, balancer, header filter, body filter 以及 log。

个人理解,插件在哪个阶段执行,就在插件lua文件中在对应阶段方法中编写插件的功能逻辑。如function _M.access(conf, ctx) ... end,function _M.log(conf, ctx)...end

在阶段方法中具有 conf 和 ctx 两个参数,

conf 参数是插件的相关配置信息,即启用插件时需要传递的参数就存在conf中,您可以通过 core.log.warn(core.json.encode(conf)) 将其输出到 error.log 中进行查看。

ctx 参数缓存了请求相关的数据信息,您可以通过 core.log.warn(core.json.encode(ctx, true)) 将其输出到 error.log 中进行查看,示例如下:

function _M.access(conf, ctx)

core.log.warn(core.json.encode(conf))

core.log.warn(core.json.encode(ctx, true))

end

 如果插件需要修改路由的配置或者调整路由匹配的上游,则可以使用api_ctx获取路由对象,然后对路由对象进行修改即可。

如下示例:

function _M.access(conf, ctx)

    local api_ctx = ngx.ctx.api_ctx

    local route = api_ctx.matched_route --获取当前的路由对象

    local upstreamId = "newupstream"

     local originuri = "/newpath"

  

    core.log.warn("before set route============route info is:  "..core.json.encode(route))

    route.value.plugins["proxy-rewrite"].uri = originuri

    route.value.upstream_id = upstreamId

    core.log.warn("after set route=============route info is:  "..core.json.encode(ctx.var._ctx, true))

    core.log.warn("value of route is:============="..core.json.encode(route))

end

注意: local api_ctx = ngx.ctx.api_ctx需要在函数里面定义才会获取到ngx.ctx.api_ctx的值,如果把local api_ctx的声明放在函数外面作为整个插件的局部变量,那获取到的ngx.ctx.api_ctx的值为nil。

5. 注册公共接口

插件可以使用api()注册暴露给公网的接口。以 jwt-auth 插件为例,这个插件为了让客户端能够签名,注册了 GET /apisix/plugin/jwt/sign 这个接口:

local function gen_token()

-- ...

end

function _M.api()

return {

{

methods = {"GET"},

uri = "/apisix/plugin/jwt/sign",

handler = gen_token,

}

}

end

注意,注册的接口将不会默认暴露,需要结合使用public-api 插件来暴露它。

6. 注册控制接口

如果你只想暴露 API 到 localhost 或内网,你可以通过 Control 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.control_api()

return {

{

methods = {"GET"},

uris = {"/v1/plugin/example-plugin/hello"},

handler = hello,

}

}

end

如果你没有改过默认的 control API 配置,这个插件暴露的 GET /v1/plugin/example-plugin/hello API 只有通过 127.0.0.1 才能访问它。通过以下命令进行测试:

curl -i -X GET "http://127.0.0.1:9090/v1/plugin/example-plugin/hello"

 其中,9090是control api的端口,可以在apisix的config.yaml或者config-default.yaml中看到control的port是多少。

7. 注册自定义变量

我们可以在 APISIX 的许多地方使用变量。例如,在 http-logger 中自定义日志格式,用它作为 limit-* 插件的键。在某些情况下,内置的变量是不够的。因此,APISIX 允许开发者在全局范围内注册他们的变量,并将它们作为普通的内置变量使用。

例如,让我们注册一个叫做 a6_labels_zone 的变量来获取路由中 zone 标签的值。

local core = require "apisix.core"

core.ctx.register_var("a6_labels_zone", function(ctx)
    local route = ctx.matched_route and ctx.matched_route.value
    if route and route.labels then
        return route.labels.zone
    end
    return nil
end)

此后,任何对 $a6_labels_zone 的获取操作都会调用注册的获取器来获取数值。

注意,自定义变量不能用于依赖 Nginx 指令的功能,如 access_log_format。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值