[TOC]
记录第一次使用golang开发kong网关插件
1.安装kong网关
2.开始开发
2.1 下载kong官方的PDK库
go get github.com/Kong/go-pdk
2.2 编写逻辑代码
2.2.1 定义一个Sturct用于储存配置
type Config struct {
Message string
}
2.2.2 定义插件版本、执行优先级
const Version = "1.0.0"
const Priority = 1
2.2.3 定义构造函数,返回Config对象
func New() interface{} {
return &Config{}
}
2.2.4 开始写自定义逻辑
必须在以下几个阶段定义
Certificate
Rewrite
Access
Response
Preread
Log
比如在响应返回阶段定义自定义逻辑:
func (c Config) Response(kong *pdk.PDK) {
//开始编写逻辑
}
******函数签名中的函数名必须以阶段名命名,否则Kong无法识别
PDK方法文档在go储存库里:PDK文档
2.2.4 编写启动函数
func main() {
err := server.StartServer(New, Version, Priority)
if err != nil {
return
}
}
2.2.5 开始编译
cd [进入插件项目根目录]
go build -buildmode=plugin main.go
以插件形式编译允许kong网关动态加载该插件
编译成功会输出二进制文件main.so
3. 加载插件
3.1 创建插件存放目录
mkdir /etc/kong-plugins //可以自定义位置和目录名
3.2 进入Kong网关容器
或者提前将对应的容器目录映射到主机就不用进入容器了
docker exec -it [容器名/容器ID] /bin/bash
3.3 注册插件
编辑 /usr/local/share/lua/5.1/kong/constants.lua
nano /usr/local/share/lua/5.1/kong/constants.lua
找到 local plugins 字段,写入插件名
local plugins = [
//添加插件名
"xxxx",
"xxxx",
"xxxx",
]
然后编辑Kong配置文件
cd /etc/kong/
mv kong.conf.default kong.conf
nano kong.conf
pluginserver_names = set-token //插件名称,对应go.mod中的module,否则无法识别插件
pluginserver_set_token_socket = /usr/local/kong/set-token.socket //插件启动时会在此目录创建一个sock连接
pluginserver_set_token_start_cmd = /etc/kong-plugins/set-token //插件位置
pluginserver_set_token_query_cmd = /etc/kong-plugins/set-token -dump //插件位置
3.4 重载生效配置
将编译好的main.so插件重命名set-token复制到上面创建的插件目录
然后执行
kong prepare && kong reload
3.5 验证插件加载状态
查看kong网关日志
出现 [kong] process.lua:223 Starting set-token, context: ngx.timer 表示插件加载成功
4.启用插件
使用 Kong Admin API 启用插件 或使用**Kong Manager**可视化控制面板开启插件
//使用Kong Admin API
//这里没有定义Service,Route,Consumer范围,全局生效
curl -X POST http://172.28.14.226:8001/plugins/ \
--data "name=set-token" \
--data "config.message=set-token"
成功启用会输出一下json信息
{"protocols":["grpc","grpcs","http","https"],"id":"d5336e66-1ff3-47ea-9b2a-31f8b52c9b05","consumer":null,"instance_name":null,"config":{"message":"set-token"},"tags":null,"updated_at":1717924637,"name":"set-token","ordering":null,"service":null,"enabled":true,"route":null,"created_at":1717924637}
5. 测试插件
因为这个插件功能是把token信息通过响应头发送给客户端自动设置cookie,所以使用ApiPost向登录服务发起一次请求

type Config struct {
Message string
}
type LoginServiceRes struct {
AccessToken string `json:"AccessToken"`
AccessTokenExp int `json:"AccessTokenExp"`
RefreshToken string `json:"RefreshToken"`
RefreshTokenExp int `json:"RefreshTokenExp"`
}
type LoginServiceTarsRet struct {
Code int `json:"Code"`
Msg string `json:"Msg"`
Status int `json:"Status"`
Desc string `json:"Desc"`
TraceID string `json:"TraceID"`
}
type ServiceLoginBody struct {
Response LoginServiceRes `json:"response"`
TarsRet LoginServiceTarsRet `json:"tars_ret"`
}
const Version = "1.0.0"
const Priority = 1
func New() interface{} {
return &Config{}
}
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
err := server.StartServer(New, Version, Priority)
if err != nil {
return
}
}
func (c Config) Response(kong *pdk.PDK) {
defer func() {
if err := recover(); err != nil {
kong.Log.Err(err)
}
}()
res, err := kong.ServiceResponse.GetRawBody()
if err != nil {
return
}
SLR := ServiceLoginBody{}
err = json.Unmarshal(res, &SLR)
if err != nil {
kong.Log.Err(err.Error())
return
}
if SLR.Response.AccessToken == "" || SLR.Response.RefreshToken == "" {
kong.Log.Err("获取授权信息失败,AccessToken或RefreshToken为空")
return
}
if SLR.TarsRet.TraceID == "" {
kong.Log.Warn("警告:tars服务没有返回traceid!")
}
if err = kong.Response.SetHeader("Set-Cookie", "Authorization="+SLR.Response.AccessToken+"; Path=/; Max-Age="+strconv.Itoa(SLR.Response.AccessTokenExp)+"; HttpOnly"); err != nil {
kong.Log.Err("SetToken插件设置响应头失败")
return
}
if err = kong.Response.AddHeader("Set-Cookie", "RefreshToken="+SLR.Response.RefreshToken+"; Path=/; Max-Age="+strconv.Itoa(SLR.Response.RefreshTokenExp)+"; HttpOnly"); err != nil {
kong.Log.Err("SetToken插件追加响应头失败")
return
}
}