鍍金池/ 教程/ Java/ 數(shù)據(jù)合法性檢測(cè)
定時(shí)任務(wù)
函數(shù)的參數(shù)
超時(shí)
一個(gè) openresty 內(nèi)存“泄漏”問題
獲取 uri 參數(shù)
局部變量
sleep
灰度發(fā)布
TIME_WAIT
代碼覆蓋率
連接池
CentOS 平臺(tái)安裝
稀疏數(shù)組
如何只啟動(dòng)一個(gè) timer 工作?
變量的共享范圍
break,return 關(guān)鍵字
Nginx
SQL 注入
如何引用第三方 resty 庫(kù)
不同階段共享變量
獲取請(qǐng)求 body
動(dòng)態(tài)生成的 lua-resty-redis 模塊方法
動(dòng)態(tài)加載證書和 OCSP stapling
repeat 控制結(jié)構(gòu)
編碼為 array 還是 object
Nginx 靜態(tài)文件服務(wù)
執(zhí)行階段概念
Lua 函數(shù)
日期時(shí)間函數(shù)
健康監(jiān)測(cè)
與其他 location 配合
for 控制結(jié)構(gòu)
函數(shù)定義
HTTPS 時(shí)代
點(diǎn)號(hào)與冒號(hào)操作符的區(qū)別
String 庫(kù)
文件操作
OpenResty 最佳實(shí)踐
<code>ngx.shared.DICT</code> 非隊(duì)列性質(zhì)
使用動(dòng)態(tài) DNS 來完成 HTTP 請(qǐng)求
代碼規(guī)范
什么是 JIT?
Windows 平臺(tái)安裝
正確的記錄日志
LuaNginxModule
不用標(biāo)準(zhǔn)庫(kù)
C10K 編程
控制結(jié)構(gòu)
請(qǐng)求中斷后的處理
Lua 環(huán)境搭建
Test::Nginx 能指定現(xiàn)成的 nginx.conf,而不是自動(dòng)生成一個(gè)嗎
Lua 基礎(chǔ)數(shù)據(jù)類型
動(dòng)態(tài)限速
PostgresNginxModule
簡(jiǎn)單API Server框架
API 測(cè)試
location 匹配規(guī)則
虛變量
單元測(cè)試
防止 SQL 注入
select + set_keepalive 組合操作引起的數(shù)據(jù)讀寫錯(cuò)誤
阻塞操作
全動(dòng)態(tài)函數(shù)調(diào)用
Web 服務(wù)
典型應(yīng)用場(chǎng)景
Nginx 新手起步
TLS session resumption
輸出響應(yīng)體
調(diào)用代碼前先定義函數(shù)
module 是邪惡的
怎樣理解 cosocket
模塊
Socket 編程發(fā)展
如何對(duì) Nginx Lua module 添加新 api
如何在后臺(tái)開啟輕量級(jí)線程完成定時(shí)任務(wù)?
如何定位問題
table 庫(kù)
json 解析的異常捕獲
如何安裝火焰圖生成工具
lua 中如何 continue
if 是邪惡的
為什么我們的域名不能被解析
抵制使用 module() 定義模塊
測(cè)試
body 在 location 中的傳遞
Lua 入門
子查詢
pipeline 壓縮請(qǐng)求數(shù)量
如何發(fā)起新 HTTP 請(qǐng)求
Lua 簡(jiǎn)介
緩存失效風(fēng)暴
Ubuntu 平臺(tái)安裝
日志輸出
緩存
Lua 面向?qū)ο缶幊?/span>
Nginx 陷阱和常見錯(cuò)誤
Redis 接口的二次封裝(發(fā)布訂閱)
日志
訪問有授權(quán)驗(yàn)證的 Redis
正則表達(dá)式
lock
熱裝載代碼
調(diào)用 FFI 出現(xiàn) &quot;table overflow&quot;
數(shù)據(jù)合法性檢測(cè)
禁止某些終端訪問
控制結(jié)構(gòu) if-else
調(diào)試
與 Docker 使用的網(wǎng)絡(luò)瓶頸
PostgresNginxModule 模塊的調(diào)用方式
用 do-end 整理你的代碼
FFI
什么時(shí)候使用
簡(jiǎn)介
環(huán)境搭建
Mac OS X 平臺(tái)安裝
火焰圖
負(fù)載均衡
while 型控制結(jié)構(gòu)
如何定位 openresty 崩潰 bug
使用 Nginx 內(nèi)置綁定變量
判斷數(shù)組大小
請(qǐng)求返回后繼續(xù)執(zhí)行
Redis 接口的二次封裝
KeepAlive
反向代理
協(xié)議無痛升級(jí)
數(shù)學(xué)庫(kù)
元表
Vanilla 介紹
HelloWorld
LuaCjsonLibrary
持續(xù)集成
代碼靜態(tài)分析
網(wǎng)上有大量對(duì) Lua 調(diào)優(yōu)的推薦,我們應(yīng)該如何看待?
script 壓縮復(fù)雜請(qǐng)求
非空判斷
性能測(cè)試
函數(shù)返回值
API 的設(shè)計(jì)
kong 介紹
表達(dá)式
不支持事務(wù)
LuaRestyDNSLibrary 簡(jiǎn)介

數(shù)據(jù)合法性檢測(cè)

對(duì)用戶輸入的數(shù)據(jù)進(jìn)行合法性檢查,避免錯(cuò)誤非法的數(shù)據(jù)進(jìn)入服務(wù),這是業(yè)務(wù)系統(tǒng)最常見的需求。很可惜 Lua 目前沒有特別好的數(shù)據(jù)合法性檢查庫(kù)。

坦誠(chéng)我們自己做的也不夠好,這里只能拋磚引玉,看看大家是否有更好辦法。

我們有這么幾個(gè)主要的合法性檢查場(chǎng)景:

  • JSON 數(shù)據(jù)格式
  • 關(guān)鍵字段編碼為 HEX(0-9,a-f,A-F),長(zhǎng)度不定
  • TABLE 內(nèi)部字段類型

JSON 數(shù)據(jù)格式

這里主要是 json decode 時(shí),可能拋出異常的問題。我們已經(jīng)在 json 解析的異常捕獲 一章中詳細(xì)說明了問題本身以及解決方法,這里就不再重復(fù)。

關(guān)鍵字段編碼為 HEX,長(zhǎng)度不定

HEX 編碼,最常見的存在有 MD5 值等。他們是由 0-9,A-F(或 a-f)組成。筆者把使用過的代碼版本逐一羅列,并進(jìn)行性能測(cè)試。通過這個(gè)測(cè)試,我們不僅僅可以收獲參數(shù)校驗(yàn)的正確寫法,以及可以再次印證一下效率最高的匹配,應(yīng)該注意什么。

require "resty.core.regex"

-- 純 lua 版本,優(yōu)點(diǎn)是兼容性好,可以適用任何 lua 語(yǔ)言環(huán)境
function check_hex_lua( str )
    if "string" ~= type(str) then
        return false
    end

    for i = 1, #str do
        local ord = str:byte(i)
        if not (
            (48 <= ord and ord <= 57) or
            (65 <= ord and ord <= 70) or
            (97 <= ord and ord <= 102)
        ) then
            return false
        end
    end
    return true
end

-- 使用 ngx.re.* 完成,沒有使用任何調(diào)優(yōu)參數(shù)
function check_hex_default( str )
    if "string" ~= type(str) then
        return false
    end

    return ngx.re.find(str, "[^0-9a-fA-F]") == nil
end

-- 使用 ngx.re.* 完成,使用調(diào)優(yōu)參數(shù) "jo"
function check_hex_jo( str )
    if "string" ~= type(str) then
        return false
    end

    return ngx.re.find(str, "[^0-9a-fA-F]", "jo") == nil
end

-- 下面就是測(cè)試用例部分代碼
function do_test( name, fun )
    ngx.update_time()
    local start = ngx.now()

    local t = "012345678901234567890123456789abcdefABCDEF"
    assert(fun(t))
    for i=1,10000*300 do
        fun(t)
    end

    ngx.update_time()
    print(name, "\ttimes:", ngx.now() - start)
end

do_test("check_hex_lua", check_hex_lua)
do_test("check_hex_default", check_hex_default)
do_test("check_hex_jo", check_hex_jo)

把上面的源碼在 OpenResty 環(huán)境中運(yùn)行,輸出結(jié)果如下:

?  resty test.lua
check_hex_lua   times:1.0390000343323
check_hex_default   times:5.1929998397827
check_hex_jo    times:0.4539999961853

不知道這個(gè)結(jié)果大家是否有些意外,check_hex_default 的運(yùn)行效率居然比 check_hex_lua 要差。不過所幸的是我們對(duì)正則開啟了 jo 參數(shù)優(yōu)化后,速度上有明顯提升。

引用一下 ngx.re.* 官方 wiki 的原文:在優(yōu)化性能時(shí),o 選項(xiàng)非常有用,因?yàn)檎齽t表達(dá)式模板將僅僅被編譯一次,之后緩存在 worker 級(jí)的緩存中,并被此 Nginx worker 處理的所有請(qǐng)求共享。緩存數(shù)量上限可以通過 lua_regex_cache_max_entries 指令調(diào)整。

課后小作業(yè):為什么測(cè)試用例中要使用 ngx.update_time() 呢?好好想一想。 課后小作業(yè):在測(cè)試用例里面加了一行 require "resty.core.regex"。試試去掉這一行,重新跑下程序。結(jié)果怎么樣?

TABLE 內(nèi)部字段類型

當(dāng)我們接收客戶端請(qǐng)求,除了指定字段的特殊校驗(yàn)外,我們最常見的需求就是對(duì)指定字段的類型做限制了。比如用戶注冊(cè)接口,我們就要求對(duì)方姓名、郵箱等是個(gè)字符串,手機(jī)號(hào)、電話號(hào)碼等是個(gè)數(shù)字類型,詳細(xì)信息可能是個(gè)圖片又或者是個(gè)嵌套的 TABLE。

例如我們接受用戶的注冊(cè)請(qǐng)求,注冊(cè)接口示例請(qǐng)求 body 如下:

{
    "username":"myname",
    "age":8,
    "tel":88888888,
    "mobile_no":13888888888,
    "email":"***@**.com",
    "love_things":["football", "music"]
}

這時(shí)候可以用一個(gè)簡(jiǎn)單的字段描述格式來表達(dá)限制關(guān)系,如下:

{
    "username":"",
    "age":0,
    "tel":0,
    "mobile_no":0,
    "email":"",
    "love_things":[]
}

對(duì)于有效字段描述格式,數(shù)據(jù)值是不敏感的,但是數(shù)據(jù)類型是敏感的,只要數(shù)據(jù)類型能匹配,就可以讓我們輕松不少。

來看下面的參數(shù)校驗(yàn)代碼以及基本的測(cè)試用例:

function check_args_template(args, template)
    if type(args) ~= type(template) then
      return false
    elseif "table" ~= type(args) then
      return true
    end

    for k,v in pairs(template) do
      if type(v) ~= type(args[k]) then
        return false
      elseif "table" == type(v) then
        if not check_args_template(args[k], v) then
          return false
        end
      end
    end

    return true
end

local args = {name="myname", tel=888888, age=18,
    mobile_no=13888888888, love_things = {"football", "music"}}

print("valid   check: ", check_args_template(args, {name="", tel=0, love_things={}}))
print("unvalid check: ", check_args_template(args, {name="", tel=0, love_things={}, email=""}))

運(yùn)行一下上面的代碼,結(jié)果如下:

?  resty test.lua
valid   check: true
unvalid check: false

可以看到,當(dāng)我們業(yè)務(wù)層面需要有 email 地址但是請(qǐng)求方?jīng)]有上送,這時(shí)候就能檢測(cè)出來了。大家看到這里也許會(huì)笑,尤其是從其他成熟 web 框架中過來的同學(xué),我們這里的校驗(yàn)可以說是比較粗糙簡(jiǎn)陋的,很多開源框架中的參數(shù)限制,都可以做到更加精確的限制。

如果你有更好更優(yōu)雅的解決辦法,歡迎與我們聯(lián)系。