Kong 插件源码阅读
weak table
先了解下 weak table 的概念,这是我之前忽略的地方。 Programming in Lua : 17
和大多数语言一样,Lua执行自动内存管理。程序仅创建对象(表,函数等);没有删除对象的功能。Lua使用垃圾回收自动删除成为垃圾的对象
垃圾收集器只能收集可以确定的垃圾。它不知道您认为垃圾是什么
就像 JVM 也不知道,只会依赖 GC root 可达性分析,或者是标记清楚算法等
- All you have to do is to insert each new object into the collection. However, once the object is inside the collection, it will never be collected! Even if no one else points to it, the collection does. Lua cannot know that this reference should not prevent the reclamation of the object, unless you tell Lua about that.
我们将每个新对象插入到集合中。但是,一旦对象在集合中,它就永远不会被收集 ! 即使没有其他人指向它,这个集合也会指向它。Lua 不能知道这个引用不应该阻止对象的回收,除非您将这一点告诉Lua。
EG:
print("\n\n\n ================")
-- weak table 弱表
a = {}
b = {}
setmetatable(a, b)
b.__mode = "k" -- now `a' has weak keys
key = {} -- creates first key
a[key] = 1
key = {} -- creates second key
a[key] = 2
print("before GC ")
for k, v in pairs(a) do print(k , v) end
collectgarbage() -- forces a garbage collection cycle
print("frist GC start")
-- 打印已结是两条,因为 print 还是打印了 K,还是有引用
for k, v in pairs(a) do print(k , v) end
print("frist GC end")
a[{}] = 3
a[{}] = 4
-- 只能从弱表手机对象,不能收集非对象: boolean numbers string
a["name"] = "zwd"
for k, v in pairs(a) do print(k , v) end
print("2sd GC start")
collectgarbage()
for k, v in pairs(a) do print(k , v) end
print("2sd GC end")
-- a[{}] = 3
-- for k, v in pairs(a) do print(k , v) end
================
before GC
table: 0x7f80c3d04a20 1
table: 0x7f80c3d04a80 2
frist GC start
table: 0x7f80c3d04a80 2
frist GC end
table: 0x7f80c3d00000 4
table: 0x7f80c3d03e70 3
table: 0x7f80c3d04a80 2
name zwd
2sd GC start
table: 0x7f80c3d04a80 2
name zwd
2sd GC end

Notice that only objects can be collected from a weak table. Values, such as numbers and booleans, are not collectible. For instance, if we insert a numeric key in table a (from our previous example), it will never be removed by the collector. Of course, if the value corresponding to a numeric key is collected, then the whole entry is removed from the weak table.
总结:之前 table 内的 key 没有引用了,就不要了,扔掉了,仅对 Object 有效,确定类型的扔不掉:numbers booleans string
这个也很好理解,Object 需要开辟堆栈空间,记录地址,地址不同,而 string、numbers 等我个人理解是放在了一个类似 JVM 常量池的地方,地址相同,没有回收的必要。
场景
之前学习粗略扫过 weak table,但是并不会使用,当时仅仅一带而过,今天想学习一下下 Kong 内置的 Plugins 大概原理,先看了反机器人/爬虫 模块,看到了这个所以重新学习一下,养成好习惯。
基础插件:bot-detection
先从基础插件入手,这是一个反机器人插件 源码:
参考:base_plugins.lua 以及官方说明
Open-Source API Management and Microservice Management
以及 base_plugins.lua

- 源码
local rules = require "kong.plugins.bot-detection.rules"
local strip = require("kong.tools.utils").strip
local lrucache = require "resty.lrucache"
local ipairs = ipairs
local re_find = ngx.re.find
local BotDetectionHandler = {}
-- 插件的执行顺序,越大越优先,类似 zuul 网关的 order
BotDetectionHandler.PRIORITY = 2500
BotDetectionHandler.VERSION = "2.0.0"
local BAD_REQUEST = 400
local FORBIDDEN = 403
local MATCH_EMPTY = 0
local MATCH_WHITELIST = 1
local MATCH_BLACKLIST = 2
local MATCH_BOT = 3
-- per-worker cache of matched UAs
-- we use a weak table, index by the `conf` parameter, so once the plugin config
-- is GC'ed, the cache follows automatically
local ua_caches = setmetatable({}, { __mode = "k" })
local UA_CACHE_SIZE = 10 ^ 4
-- 获取 ua 方法
local function get_user_agent()
local user_agent = kong.request.get_headers()["user-agent"]
if type(user_agent) == "table" then
return nil, "Only one User-Agent header allowed"
end
return user_agent
end
-- 验证 ua 方法
local function examine_agent(user_agent, conf)
user_agent = strip(user_agent)
if conf.whitelist then
for _, rule in ipairs(conf.whitelist) do
if re_find(user_agent, rule, "jo") then
return MATCH_WHITELIST
end
end
end
if conf.blacklist then
for _, rule in ipairs(conf.blacklist) do
if re_find(user_agent, rule, "jo") then
return MATCH_BLACKLIST
end
end
end
for _, rule in ipairs(rules.bots) do
if re_find(user_agent, rule, "jo") then
return MATCH_BOT
end
end
return MATCH_EMPTY
end
-- 编写 access 主方法
function BotDetectionHandler:access(conf)
local user_agent, err = get_user_agent()
if err then
return kong.response.exit(BAD_REQUEST, { message = err })
end
if not user_agent then
return
end
-- user-agent-cache 中查找是否缓存 conf,若没有 new 个 lru 缓存
local cache = ua_caches[conf]
if not cache then
-- lru 最多 1000 条,多余的按 lru 规则抛弃
cache = lrucache.new(UA_CACHE_SIZE)
ua_caches[conf] = cache
end
-- 查找当前 user-agent,看是否命中当前 request 的 ua
local match = cache:get(user_agent)
if not match then
-- 如果没命中检查是:无效0、白名单1、黑名单2 bot:4 并放进缓存
match = examine_agent(user_agent, conf)
cache:set(user_agent, match)
end
-- 如果看到是黑名单 或 bot,返回 403 错误,否则不处理
if match > 1 then
return kong.response.exit(FORBIDDEN, { message = "Forbidden" })
end
end
return BotDetectionHandler
现在已经十分清晰了,现在除了不知道 access(conf) 这个钩子函数的 conf 不太清楚,但根据 weak table 的特性,我知道 conf 是一个 schema 的 Object,但是具体不知道是那一部分内容,具体内容,如果知道就更好了,看来只好调试一下,好麻烦。
todo