Leafo/Lapis 框架中的输入验证机制详解
为什么需要输入验证?
在Web应用开发中,任何来自外部的参数都应被视为不可信输入。开发者有责任在使用这些参数前验证它们是否符合预期格式。常见的需要验证的输入问题包括:
- 超长输入(如预期几个字符却收到MB级文本)
- 无效的Unicode序列或不可打印字符
- 过多的空白字符或不可见字符
- 类型不匹配(如预期字符串却收到对象)
- 超出预期的数字范围(如巨大的页码或负数)
- 不在预设选项范围内的值(如枚举值)
- 错误地将空字符串视为有效值
Lapis验证模块概述
Lapis框架提供了强大的验证模块,帮助开发者在正式使用输入前确保其正确性和安全性。该模块不仅能验证输入,还能对格式不正确的输入进行转换处理(如去除字符串首尾空白)。
Tableshape验证系统
Lapis目前基于Tableshape库构建了新的验证系统。Tableshape是一个Lua库,用于验证值或对象结构,并具备转换值的能力,可以修复不符合要求的输入。
安装Tableshape
Tableshape不是Lapis的默认依赖,需要单独安装:
luarocks install tableshape
核心验证类型
Lapis在lapis.validate.types
模块中提供了一系列Tableshape兼容的类型和类型构造器。
基本使用模式
local types = require "lapis.validate.types"
lapis.validate.types
模块的__index
元方法已设置为Tableshape的types
对象,因此可以直接访问Tableshape提供的所有类型。
关键验证功能
with_params辅助函数
with_params
是一个包装器函数,只有当参数通过验证时才会执行后续操作:
local with_params = require("lapis.validate").with_params
app:post("/user/:id", with_params({
{"id", types.db_id},
{"action", types.one_of {"delete", "update"}}
}, function(self, params)
-- 只有参数验证通过才会执行这里
end))
类型构造器
- params_shape - 创建适合从参数对象中提取验证值的类型检查器
- 按指定顺序检查字段
- 忽略未明确指定的额外字段
- 返回新的验证后对象
local test_params = types.params_shape {
{"user_id", types.db_id},
{"bio", types.empty + types.limited_text(256)},
{"confirm", types.literal("yes"), error = "请确认"}
}
-
params_array - 创建从表中提取数组组件的类型检查器
- 可指定长度范围
- 可自定义错误前缀
-
params_map - 创建验证键值对的类型检查器
- 可自定义错误格式
- 支持有序迭代
错误处理
- assert_error - 包装类型检查器,验证失败时抛出错误
- flatten_errors - 将数组错误转换为单一字符串错误消息
内置验证类型
-
empty - 匹配nil、空字符串或空白字符串
types.empty("") --> true types.empty(" ") --> true
-
valid_text - 匹配有效的UTF8字符串
types.valid_text("hello") --> true types.valid_text("hel\0o") --> false
-
cleaned_text - 清理无效UTF8序列和非打印字符
types.cleaned_text:transform("hel\0o") --> "helo"
-
trimmed_text - 去除字符串两端的空白
types.trimmed_text:transform(" wor ld \t ") --> "wor ld"
-
truncated_text - 截断超长字符串(UTF8感知)
types.truncated_text(5):transform("hi world") --> "hi wo"
-
limited_text - 限制字符串长度范围
local limit5 = types.limited_text(5) limit5("hi world") --> 超出范围
-
db_id - 匹配适合PostgreSQL序列类型的整数
types.db_id:transform("2392") --> 2392 types.db_id:transform("-5") --> 验证失败
-
db_enum - 匹配数据库枚举值
local statuses = enum { default = 1, banned = 2 } local check_status = types.db_enum(statuses) check_status:transform("default") --> 1
最佳实践建议
- 始终验证输入:即使是内部API也应验证所有输入
- 使用类型转换:利用验证系统的转换能力规范化输入
- 组合验证规则:可以组合多个验证器创建复杂规则
- 明确错误消息:为用户提供清晰的验证失败反馈
- 考虑性能:对于高频API,验证逻辑应保持高效
通过合理使用Lapis的验证系统,开发者可以大幅提高应用的安全性和健壮性,同时减少因无效输入导致的意外错误。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考