🔬
OpenResty 最佳实践
  • 序
  • 入门篇
  • Lua 入门
    • Lua 简介
    • Lua 环境搭建
    • Lua 编辑器选择
    • 基础数据类型
    • 表达式
    • 控制结构
      • if/else
      • while
      • repeat
      • for
      • break,return 和 goto
    • Lua 函数
      • 函数的定义
      • 函数的参数
      • 函数返回值
      • 全动态函数调用
    • 模块
    • String 库
    • Table 库
    • 日期时间函数
    • 数学库函数
    • 文件操作
  • Lua 高阶
    • 元表
    • 面向对象编程
    • 局部变量
    • 判断数组大小
    • 非空判断
    • 正则表达式
    • 虚变量
    • 抵制使用 module() 定义模块
    • 调用代码前先定义函数
    • 点号与冒号操作符的区别
    • module 是邪恶的
    • FFI
    • 什么是 JIT
  • Nginx
    • Nginx 新手起步
    • location 匹配规则
    • 静态文件服务
    • 日志
    • 反向代理
    • 负载均衡
    • 陷阱和常见错误
  • OpenResty
    • 环境搭建
      • Windows 平台
      • CentOS 平台
      • Ubuntu 平台
      • Mac OS X 平台
    • Hello World
    • 与其他 location 配合
    • 获取 uri 参数
    • 获取请求 body
    • 输出响应体
    • 日志输出
    • 简单 API Server 框架
    • 使用 Nginx 内置绑定变量
    • 子查询
    • 不同阶段共享变量
    • 防止 SQL 注入
    • 如何发起新 HTTP 请求
    • 如何完成 bit 操作
      • 一,复习二进制补码
      • 二,复习位运算
      • 三,LuaJIT 和 Lua BitOp Api
      • 四,位运算算法实例
      • 五,Lua BitOp 的安装
  • LuaRestyRedisLibrary
    • 访问有授权验证的 Redis
    • select+set_keepalive 组合操作引起的数据读写错误
    • redis 接口的二次封装(简化建连、拆连等细节)
    • redis 接口的二次封装(发布订阅)
    • pipeline 压缩请求数量
    • script 压缩复杂请求
    • 动态生成的 lua-resty-redis 模块方法
  • LuaCjsonLibrary
    • json 解析的异常捕获
    • 稀疏数组
    • 空 table 编码为 array 还是 object
  • PostgresNginxModule
    • 调用方式简介
    • 不支持事务
    • 超时
    • 健康监测
    • SQL 注入
  • LuaNginxModule
    • 执行阶段概念
    • 正确的记录日志
    • 热装载代码
    • 阻塞操作
    • 缓存
    • sleep
    • 定时任务
    • 禁止某些终端访问
    • 请求返回后继续执行
    • 调试
    • 请求中断后的处理
    • 我的 lua 代码需要调优么
    • 变量的共享范围
    • 动态限速
    • shared.dict 非队列性质
    • 正确使用长链接
    • 如何引用第三方 resty 库
    • 典型应用场景
    • 怎样理解 cosocket
    • 如何安全启动唯一实例的 timer
    • 如何正确的解析域名
  • LuaRestyDNSLibrary
    • 使用动态 DNS 来完成 HTTP 请求
  • LuaRestyLock
    • 缓存失效风暴
  • OpenResty 与 SSL
    • HTTPS 时代
    • 动态加载证书和 OCSP stapling
    • TLS session resumption
  • 测试
    • 代码静态分析
    • 单元测试
    • 代码覆盖率
    • API 测试
    • 性能测试
    • 持续集成
    • 灰度发布
      • 分流引擎设计
      • 控制台开发
      • 向运维平台发展
  • Web 服务
    • API 的设计
    • 数据合法性检测
    • 协议无痛升级
    • 代码规范
    • 连接池
    • C10K 编程
    • TIME_WAIT 问题
    • 与 Docker 使用的网络瓶颈
  • 火焰图
    • 什么是火焰图
    • 什么时候使用
    • 如何安装火焰图生成工具
    • 如何定位问题
    • 拓展阅读
    • FAQ
Powered by GitBook
On this page
Edit on GitHub
  1. Web 服务

数据合法性检测

PreviousAPI 的设计Next协议无痛升级

Last updated 2 years ago

对用户输入的数据进行合法性检查,避免错误非法的数据进入服务,这是业务系统最常见的需求。很可惜 Lua 目前没有特别好的数据合法性检查库。

坦诚我们自己做的也不够好,这里只能抛砖引玉,看看大家是否有更好办法。

我们有这么几个主要的合法性检查场景:

  • JSON 数据格式

  • 关键字段编码为 HEX(0-9,a-f,A-F),长度不定

  • TABLE 内部字段类型

JSON 数据格式

这里主要是 json decode 时,可能抛出异常的问题。我们已经在 一章中详细说明了问题本身以及解决方法,这里就不再重复。

关键字段编码为 HEX,长度不定

HEX 编码,最常见的存在有 MD5 值等。他们是由 0-9,A-F(或 a-f)组成。笔者把使用过的代码版本逐一罗列,并进行性能测试。通过这个测试,我们不仅仅可以收获参数校验的正确写法,以及可以再次印证一下效率最高的匹配,应该注意什么。

require("resty.core.regex")


-- 纯 lua 版本,优点是兼容性好,可以适用任何 lua 语言环境
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.* 完成,没有使用任何调优参数
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.* 完成,使用调优参数 "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


-- 下面就是测试用例部分代码
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 环境中运行,输出结果如下:

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

不知道这个结果大家是否有些意外,check_hex_default 的运行效率居然比 check_hex_lua 要差。不过所幸的是我们对正则开启了 jo 参数优化后,速度上有明显提升。

引用一下 ngx.re.* 官方 wiki 的原文:

在优化性能时,o 选项非常有用,因为正则表达式模板将仅仅被编译一次,之后缓存在 worker 级的缓存中,并被此 Nginx worker 处理的所有请求共享。缓存数量上限可以通过 lua_regex_cache_max_entries 指令调整。

课后小作业:

  • (1)为什么测试用例中要使用 ngx.update_time() 呢?好好想一想。

  • (2) 课后小作业:在测试用例里面加了一行 require("resty.core.regex")。试试去掉这一行,重新跑下程序。结果怎么样?

TABLE 内部字段类型

当我们接收客户端请求,除了指定字段的特殊校验外,我们最常见的需求就是对指定字段的类型做限制了。比如用户注册接口,我们就要求对方姓名、邮箱等是个字符串,手机号、电话号码等是个数字类型,详细信息可能是个图片又或者是个嵌套的 TABLE。

例如我们接受用户的注册请求,注册接口示例请求 body 如下:

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

这时候可以用一个简单的字段描述格式来表达限制关系,如下:

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

对于有效字段描述格式,数据值是不敏感的,但是数据类型是敏感的,只要数据类型能匹配,就可以让我们轻松不少。

来看下面的参数校验代码以及基本的测试用例:

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=""}))

运行一下上面的代码,结果如下:

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

可以看到,当我们业务层面需要有 email 地址但是请求方没有上送,这时候就能检测出来了。大家看到这里也许会笑,尤其是从其他成熟 Web 框架中过来的同学,我们这里的校验可以说是比较粗糙简陋的,很多开源框架中的参数限制,都可以做到更加精确的限制。

如果你有更好更优雅的解决办案,欢迎与我们联系。

json 解析的异常捕获