关于gateway kong的自定义插件

GateWay Kong

简介

Kong网关介绍

Kong Gateway 是一个轻量级、快速、灵活的云原生 API 网关。Kong Gateway 是一个运行在 Nginx 中的 Lua 应用程序

Kong 提供了许多**插件**供在网关部署中使用。您还可以创建自己的自定义插件。

三个组件

  • Kong Server :基于nginx的服务器,用来接收API请求。
  • Cassandra/PostgreSQL :用来存储操作数据**(无DB模式/DB模式,无DB模式在使用上会有所限制)**。
  • Kong dashboard:官方推荐UI管理工具,当然,也可以使用 restful 方式 管理admin api。

管理 admin api

1.通过kong manager界面管理

2.通过restful管理

# 创建服务Service
$ curl -i -X POST --url http://localhost:8001/services/ \
 --data 'name=example-service' \
 --data 'url=http://localhost:8762/test1/'
 
# 创建路由Route 
$ curl -i -X POST --url http://localhost:8001/services/example-service/routes \
 --data 'hosts[]=example.com'

# 通过kong进行api调用
$ curl -i -X GET --url http://localhost:8000/hello \
 --header "Host:example.com"

自定义聚合插件 kong-aggregator

image-20211217141313660

注:所聚合的接口返回内容统一以下格式

{
    "data": {},
    "msg" : "",
    "code": ""
}

插件参数

  • service:插件作用的服务名约束:service和route至少填一个。

  • route:插件作用的路由名约束:service和route至少填一个。

  • urls(必填):所聚合的API地址列表约束:数组类型。

  • result_name(必填):期望每个API返回的结果名称约束:数组类型,必须与url的地址顺序对应。

  • complex_result(非必填):期望返回的数据格式。不填:API返回结果会默认并列显示;填:API结果按照期望结果显示。**注:**此参数要求返回data内容必须是对象,如果不是,例如true,则此参数不生效,此参数一般用于查询接口,增删改的接口不必要使用。

    约束:JSON类型,键名必须和result_name一致,否则会有显示问题,填写时只需填写梗概即可。

# 例子 (不包含service和route的创建过程)
#插件
#url:
http://xxx.xxx.xxx.xxx:xxxx/v1/user,http://xxx.xxx.xxx.xxx:xxxx/v1/userContact

#result_name:
 user,contacts

#complex_result(仅在查询生效):
{
    "user": {
        "contacts": {}
    }
}


# 请求gateway url:
http://xxx.xxx.xxx.xxx:8000/aggregator/add 
# 请求体:之前文档里的params,现直接使用请求体动态传入,要求保持顺序性,json类型,
{
    "user": {
        "name": "ccc",
        "avatar": "321"
    },
    "contacts": {
        "a": "b",
        "name": "333"
    }
}


#返回示例
{
    "msg": "OK",
    "data": {
        "user": true,
        "contacts": true
    },
    "code": 200
}

Docker部署参考

# 拉取Docker 镜像
$ docker pull kong/kong-gateway:2.6.0.1-alpine

# 标记
$ docker tag kong/kong-gateway:2.6.0.1-alpine kong:2.6.0.1

# 创建自定义 Docker 网络以允许容器相互发现和通信:
$ docker network create kong-net

# 启动PostgreSQL容器
$ docker run -d --name kong-database \
           --network=kong-net \
           -p 5432:5432 \
           -e "POSTGRES_USER=postgres" \
           -e "POSTGRES_PASSWORD=postgres" \
           -e "POSTGRES_DB=postgres" \
           postgres:9.6
           
 # 准备kong数据库
 $ docker run --rm \
         --network=kong-net \
         -e "KONG_DATABASE=postgres" \
         -e "KONG_PG_HOST=kong-database" \
         -e "KONG_PG_PORT=5432" \
         -e "KONG_PG_USER=kong" \
         -e "KONG_PG_PASSWORD=kong" \
         -e "KONG_PG_DATABASE=kong" \
         kong:2.6.0.1 kong migrations bootstrap

# 运行以下命令以使用 Kong Gateway 启动容器 *注*:修改{HOSTNAME}
$ docker run -d --name kong  \
          --network=kong-net \
          -e "KONG_DATABASE=postgres" \
          -e "KONG_PG_HOST=kong-database" \
          -e "KONG_PG_USER=kong" \
          -e "KONG_PG_PASSWORD=kong" \
          -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
          -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
          -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
          -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
          -e "KONG_ADMIN_LISTEN=0.0.0.0:8001" \
          -e "KONG_ADMIN_GUI_URL=http://{HOSTNAME}:8002" \
          -p 8000:8000 \
          -p 8443:8443 \
          -p 8001:8001 \
          -p 8444:8444 \
          -p 8002:8002 \
          -p 8445:8445 \
          -p 8003:8003 \
          -p 8004:8004 \
           kong:2.6.0.1
           
# 测试/services使用 Admin API访问端点:

$ curl -i -X GET --url http://{HOSTNAME}:8001/services

# 访问控制界面(kong manager):http://{HOSTNAME}:8002

自定义插件部署

# 进入kong容器
$ docker exec -it kong bash

# 添加自定义插件
# 1.修改constant.lua文件,找到local plugins,添加自定义插件名称,"kong-aggregator",如下:
$ vim /usr/local/share/lua/5.1/kong/constants.lua
------------------------------------
 local plugins = {
   ...
   ...
   "external plugins
   "azure-functions",
   "zipkin",
   "kong-aggregator",
  -- XXX EE
   "application-registration",
}
-----------------------------------
# 2.添加插件源码,需要添加handler.lua(插件核心功能代码)和schema.lua(插件参数配置项)
$ cd /usr/local/share/lua/5.1/kong/plugins
$ mkdir /kong-aggregator  # 将lua文件放置此目录夏
# 3.退出容器并重启容器
$ exit
$ docker restart kong

# 插件使用参照上方简述 创建服务->创建路由->添加插件

相关文件

schema.lua

-- 主要涉及插件的入参配置
local cjson = require "cjson.safe"
local Errors = kong.errors

return {
    fields = {
        urls = { type = "array", required = true },
        --params = { type = "string", required = true, default = '[ {"ssl_verify": false, "headers": {"x-hakuna": "matata", "x-foo": "bar" }, "method": "POST", "body": "a=1&b=2" }, {"ssl_verify": false, "headers": {"content-type": "application/json"  } } ]' },
        --merge_body = { type = "boolean", required = true, default = false },
        result_name = { type = "array", required = true },
        complex_result = { type = "string", required = false }
    },
    self_check = function(schema, plugin_t, dao, is_updating)
        print("----------------开始------------")
        local params = cjson.decode(plugin_t.params)
        local complexResult = cjson.decode(plugin_t.complex_result)
        local resultName = plugin_t.result_name
        if #params == #plugin_t.urls and #params == #resultName then
            return true
        else
            return false, Errors.schema("'complexResult is not required.'params' and 'complex_result' must be a JSON. The number of objects in 'Params' must match the number of objects in 'urls' and 'result_name'")
        end
    end
}

handle.lua

-- 插件内部逻辑代码

local BasePlugin = require "kong.plugins.base_plugin"
local plugin_name = ({ ... })[1]:match("^kong%.plugins%.([^%.]+)")

local plugin = BasePlugin:extend()

local responses = kong.response
local utils = require "kong.tools.utils"
local meta = require "kong.meta"
local http = require "resty.http"
local cjson = require "cjson.safe"
local ngx = ngx
local resty_http = require 'resty.http'
local pp = require 'pl.pretty'
local re_match = ngx.re.match
local string_upper = string.upper
local table_concat = table.concat
local body = {}
-- 判断table中是否存在某个value
function k_include(tab, value)
    for k, v in pairs(tab) do
        if k == value then
            return true
        end
    end
    return false
end
-- 发送http请求
local function do_request(url, params, name, httpc)
    local params_body = {}
    if params ~= nil then
        params_body = params
    end
    kong.log("kong.request.get_query()----------------", kong.request.get_query())
    local send_body = cjson.encode(params_body[name])
    local send_params = {
        headers = kong.request.get_headers(),
        method = kong.request.get_method(),
        body = send_body,
        query = kong.request.get_query(),
        keepalive = false
    }
    local res, err = httpc:request_uri(url, send_params)
    if err ~= nil then
        ngx.log(ngx.ERR, "tem erro")
        return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
    end

    if res == nil then
        ngx.log(ngx.ERR, "nao teve response")
        return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
    end

    --and re_match(string_upper(res.headers["content-type"]), '^APPLICATION/JSON')
    if res.status == 200 then
        body = cjson.decode(res.body)
        return nil, res.status, res.body, res.headers
    end

    return nil, 400, '{"message": "status is not 200 or content-type is not application/json"}', {}
end
-- 自定义返回参数
local function generate_complex_data(table, data, vtable)
    for k, v in pairs(vtable) do
        table[k] = data[k]
        if v ~= nil then
            generate_complex_data(table[k], data, v)
        end
    end
    return table
end

local function set_header(header, value)
    if not ngx.header[header] then
        ngx.header[header] = value
    end
end

local function override_header(header, value)
    if ngx.header[header] then
        ngx.header[header] = nil
    end
    ngx.header[header] = value
end
-- 返回内容生成
local function generate_body(content, conf)
    local bodyJson = {}
    local data = {}
    local msg = "OK"
    local code = 200
    local resultName = conf.result_name
    for i = 1, #conf.urls do
        local resultJson = cjson.decode(content[i])
        local currentName = resultName[i]
        local resultTable = {}
        if resultJson["data"] ~= nil then
            resultTable[currentName] = resultJson["data"]
        end
        if resultJson["code"] ~= 200 then
            code = resultJson["code"]
            if msg then
                msg = currentName .. ":" .. resultJson["msg"]
            end
        end
        data = utils.table_merge(data, resultTable)
    end
    bodyJson["msg"] = msg
    bodyJson["code"] = code
    local complexData = conf.complex_result
    if complexData ~= nil then
        local jsonTable = cjson.decode(complexData)
        local flag = true
        for k, v in pairs(data) do
            if type(v) ~= "table" then
                flag = false
            end
        end
        if flag then
            data = generate_complex_data(jsonTable, data, jsonTable)
        end
    end
    bodyJson["data"] = data
    bodyJson = cjson.encode(bodyJson)
    return bodyJson
end

local function send(content, conf)
    ngx.status = 200
    body = generate_body(content, conf)
    set_header('Content-Length', #body)
    override_header('Content-Type', 'application/json')

    ngx.say(body)

    return ngx.exit(ngx.status)
end
-- 打印table内容,debug用
key = ""
function PrintTable(table, level)
    level = level or 1
    local indent = ""
    for i = 1, level do
        indent = indent .. "  "
    end

    if key ~= "" then
        print(indent .. key .. " " .. "=" .. " " .. "{")
    else
        print(indent .. "{")
    end

    key = ""
    for k, v in pairs(table) do
        if type(v) == "table" then
            key = k
            PrintTable(v, level + 1)
        else
            local content = string.format("%s%s = %s", indent .. "  ", tostring(k), tostring(v))
            print(content)
        end
    end
    print(indent .. "}")

end
function plugin:new()
    plugin.super.new(self, plugin_name)
end

function split(inputString, sep)
    if sep == nil then
        sep = "%s"
    end
    local t = {};
    i = 1
    for str in string.gmatch(inputString, "([^" .. sep .. "]+)") do
        t[i] = str
        i = i + 1
    end
    return t
end
--方法集成
function plugin:access(conf)
    plugin.super.access(self)

    local upstream_uri = ngx.var.upstream_uri == "/" and "" or ngx.var.upstream_uri
    local params = cjson.decode(kong.request.get_raw_body())
    if params == nil then
        params = cjson.decode("[]")
    end
    local err = {}
    local status = {}
    local content = {}
    local headers = {}

    local resultName = conf.result_name
    local httpc = resty_http:new()

    for i = 1, #conf.urls do
        err[i], status[i], content[i], headers[i] = do_request(conf.urls[i] .. upstream_uri, params, resultName[i], httpc)
    end

    return send(content, conf)
end

plugin.PRIORITY = 750
plugin.VERSION = "0.1-1"

return plugin
)
    end
    local err = {}
    local status = {}
    local content = {}
    local headers = {}

    local resultName = conf.result_name
    local httpc = resty_http:new()

    for i = 1, #conf.urls do
        err[i], status[i], content[i], headers[i] = do_request(conf.urls[i] .. upstream_uri, params, resultName[i], httpc)
    end

    return send(content, conf)
end

plugin.PRIORITY = 750
plugin.VERSION = "0.1-1"

return plugin
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值