Blog icon indicating copy to clipboard operation
Blog copied to clipboard

Kong 插件源码阅读

Open nonacosa opened this issue 5 years ago • 0 comments

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

image

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

先从基础插件入手,这是一个反机器人插件 源码:

Kong/kong

参考:base_plugins.lua 以及官方说明

Open-Source API Management and Microservice Management

以及 base_plugins.lua

image

  • 源码
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

nonacosa avatar May 29 '20 12:05 nonacosa