想在api服務器里實現一個acl的功能,對某些請求(需要登錄,需要檢查appkey,需要限制頻次等)做限制,對某些起始狀態(比如登陸)或者終結狀態(比如報錯)的請求放行。
因為lua里木有switch case,因此通過一個acl_list的table去查找規則,因為需要限制的請求種類比較多,就把rule_check_token當成默認值了,一看是是這麼寫的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
local _M = { ACL_PASS = 0, ACL_DENIED = 1, } function _M.rule_always_pass(self) return _M.ACL_PASS end function _M.rule_check_token(query) if query == nil then return _M.ACL_DENIED end local token = query['token'] or ngx.req.get_headers()['Authorization'] local uid = query['uid'] -- ... end _M.acl_list = setmetatable({ ['error'] = _M.rule_always_pass, ['user/login'] = _M.rule_always_pass, }, { __index = _M.rule_check_token }) return _M |
然而卻會在local token= xxxx那一行報stack overflow,想了半天也發現哪裡有無限遞歸,因為query傳進來的是http請求的query string解析出的鍵值對錶。
把query打印出來一看,發現竟然是這個模塊本身……
仔細看了文檔才知道,原來__index後面的值是一個function時,lua會調用這個function去獲得不存在的鍵,並且第一個參數是模塊本身(即_M,一個table)。在這個例子里:
- 調用一個非默認規則的api
- lua調用了rule_check_token
- 參數query被傳入了_M本身
- 運行到local token= xxxx這一行
- 這個table里(_M)又沒有token這個鍵
- 回到2
所以就死循環了
所以要好好看文檔
解決方法是可以套一個function
1 |
__index = function() return _M.rule_check_token end |
貼一個打印table的工具,方便調試:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function print_r(tbl, pref) for k,v in pairs(tbl) do if type(v) == 'table' then print((pref or "|-"), k, "\t", "<table>") _print_r(v, " "..(pref or "|-")) else if type(v) == 'function' then v = " <function>" end print((pref or "|-"), k, "\t", v) end end end |
可以打印出如下形式:
1 2 3 4 5 6 7 8 |
|-_VERSION 0.01 |-rule_always_pass <function> |-ACL_DENIED 1 |-acl_list <table> |-error <function> |-user/login <function> |-ACL_PASS 0 |-rule_check_token <function> |
使用openresty請自行改寫成local function和ngx.say的形式