kong – 如何编写插件

本文演示如何编写一个kong的插件,希望帮助大家快速理解插件编写的关键思路。

插件开发参照自2个信息源:

因为lua语法也是很重要的一部分,所以我也会把一些涉及到的lua语法顺便说一下。

初始化plugin源码

随便在哪里创建1个空目录,然后开始准备3个文件:

  • handler.lua:实现kong各个生命期的钩子函数,会被kong加载使用,是我们插件核心逻辑所在。
  • schema.lua:定义plugin支持哪些配置,每一个字段的类型,这样kong可以校验。
  • .rockspec:luarocks包管理工具的描述文件,kong利用luarocks把plugin源码安装到lua的系统查找路径下面。(当然我们也可以不用.rockspec,手动把源码放到lua系统路径下,以便openresty可以加载到)

我要做的插件有2个功能:

  • 为response设置1个http header叫做tag,其值是可以配置的。
  • 把request的来源IP记录到日志中。

schema.lua

从官方模板copy过来,我做了最大程度的简化。

  • schema用于定义插件配置的约束规范,插件配置是向service/route添加plugin时上传的。
  • fields中指定了其中有一个字符串类型的配置项,必传,名字叫做tag
  • self_check是一个更灵活的配置检查回调函数,这里没有做任何事情,仅作演示用。

handler.lua

从官方模板copy过来,进行了最大程序的精简(保留了必要的官方注释),并且实现了插件的2个功能:

handler.lua定义并导出了一个lua模块,看起来像个class,但其实这是lua字典的语法:

  • function plugin:log(plugin_conf)实际就等价于plugin[“log”] = function(plugin_conf),就是字典里定义了一个函数。
  • lua中function plugin:log()和function plugin.log()的区别,其实前者会提供一个隐式的self表示字典自己,调用的时候也只需要plugin:log()即可;后者则需要显示定义和传参字典自身。

再说明一下这些出现的概念:

  • new:nginx master进程启动后的回调。
  • init_worker:nginx worker进程启动后的回调。
  • rewrite:request进来后首先经过rewrite,可以改写URL等。
  • access:request路由完成之后,进一步处理之前,可以修改request。
  • header_filter:response准备返回之前,可以修改header。
  • body_filter:response准备返回之前,可以修改body。

整个流程图奉上:

分析一下我的插件逻辑:

  • access回调:
    • 利用kong.client.get_ip()获取了客户端IP地址字符串;其实kong这个变量是kong框架定义的全局变量,就像代码中ngx.req的ngx全局变量一样;我们也可以知道,其实写kong插件可以使用kong封装的方法kong.xxxx,也可以使用openresty原生方法ngx.xxxx,这块比较灵活。
    • 保存IP地址到kong.ctx.plugin.source_ip;其实它底层就是利用的ngx.ctx,提供了跨hook之间的数据传递能力;plugin是一个请求级生命期的字典,因为我要在log回调时候打印source_ip,所以要在access阶段放到ctx里暂存。
  • log回调:
    • 调用kong.log去打印Nginx日志,其实底层也是利用openresty的能力完成打印。
    • 从kong.ctx.plugin上下文中取出source_ip。

.rockspec

最后要把插件代码安装到lua系统路径下,以便kong框架可以通过lua require加载到。

这个插件的源码最终会被kong框架以require “kong.plugins.my.handler”这样的方式引入,所以其最终会安装到路径:/usr/local/share/lua/5.1/kong/plugins/my/下面,其中/usr/local/share/lua/5.1是lua默认的系统查找路径。

但是我们不采用手动方式copy源码到该路径,而是采用kong官方建议的luarocks包管理工具(虽然我觉得没有啥卵用)。

在代码同目录创建.rockspec文件,注意名字必须长下面这样(后面我会说为什么):

查看rockspec内容:

文件名其实是luarocks要求的,必须是文件里的package-version连起来的样子,不匹配就不合法。

build部分其实就是告诉luarocks把每个文件拷贝到/usr/local/share/lua/5.1下面的什么路径下,luarocks根据kong.plugins.my.handler这样的包名就会把代码handler.lua放到正确位置了。

现在执行luarocks完成插件的安装:

现在看一下lua系统路径下已经存在代码了:

大家感兴趣也可以读读plugins目录下,kong提供的官方插件是怎么实现的。

测试插件

编辑/etc/kong/kong.conf,调整nginx的日志级别(因为我是用debug级别输出的source_ip):

kong的日志是输出到error.log的,其实这是openresty的能力。

然后注册开启插件:

bundled是内置的,my是我们自定义的那个插件,其名字就是包的目录名,kong会去找到它。

然后重启kong:

然后添加一个service叫做my-service(指向baidu):

添加对应的Route叫做my-route(域名为my-baidu.com):

然后验证一下:

可以得到来自百度bfe的应答。

然后添加插件到route对象:

然后再次请求接口:

可以看到tag:owenliang已经出现在response header中。

另外,我们查看一下nginx日志(默认路径:/usr/local/kong/logs/error.log):

成功。

关于kong插件开发的核心概念就说到这里,如果你有openresty的开发经验,那么kong对你来说应该没有什么难于理解的地方。

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~