今天第一次在nginx+lua架构下,写了个须要操做Redis的后台接口,该接口的功能主要是接受客户端的json格式的post请求,实现对保存在redis中的任务插入、删除、查询等。虽然nginx,lua等都是刚接触,但这几个接口仍是顺风顺水的坐下来了,不能忘了感谢春哥章亦春。github
在Redis中记录的任务其实很简单,每插入一个任务,就在redis中增长一个HASH结构,每次查询返回该SET的各个Field和对应的Value值,例如md5,filesize等。因为任务类型的不一样,有的Field可能在该任务中不存在,此时在以json格式将查询结果返回时不该显示该Field。redis
以md5域为例,在对当前任务以md5域执行hget后,应该对返回结果作一个判断,若是该HASH结构并无设置md5这个域,则跳过,继续执行后面的逻辑,若是设置了md5域,则把该域的Value取出来,插入到结果table中,后续再做为json格式返回结果的一部分,返回给后台。sql
测试时,却发如今某些域未设置时,查询结果中却仍然会把该域返回给查询调用者,但其Value部分是null。例如,执行下面的测试用例:json
curl -d "{\"queryfile\":[{\"url\":\"/www.baidu.com/img/bdlogo.gif\" }]}" "127.0.0.1/cjson"
尽管对该任务而言,在插入时并无设置md5域,但返回结果包含了md5域:markdown
{"result":[{"url":"\/www.baidu.com\/img\/bdlogo.gif","result":0,"md5":null,"putflag":"remote"}]}
看到这个现象,首先想到的固然是lua脚本中对执行hget md5操做的返回值判断失效了,我第一次是这么写的:架构
local md5,err=red:hget(tasklist,"md5")if md5 and md5 ~= "" then tb.md5=md5end
从后面的结果看,当md5值为空时,该判断条件并无将其过滤掉,依然执行了tb.md5=md5。因为redis模块也是调用春哥的lua-resty-redis,所以猜想是否春哥把redis查询结果中的空值用“null”字符串返回了,因而将上面的几行代码改成:curl
local md5,err=red:hget(tasklist,"md5")if md5 and md5 ~= “null” then tb.md5=md5end
仍然过滤失败,突然眼前一亮,发现查询结果中显示的是"md5":null,而非"md5":"null",上面这种猜想不攻自破。
red:hget(tasklist,"md5")确定是返回了一个跟null相关的结果,但这个结果既不是nil,又不是空字符串,也不是"null"。再次猜想,该值类型可能不是string,虽然这个猜想看上去很奇怪,由于在设置了md5的状况下,其类型的确是string。因而在判断语句前面加了一句打印信息:
ngx.say("type of null is "..type(md5))
果真,这个”空值“并非string类型,而是userdata类型,userdata类型固然跟字符串类型不会相等,因此上面的过滤条件无论设置成什么样子,都不会生效,永远会执行tb.md5=md5。
这样是找到缘由了,但还未最终解决。既然当hget操做返回一个空值时,lua-resty-redis将其设置为一个userdata类型,那咱们在代码里该如何过滤这种状况呢?本质问题就是,red:hget当查询resdis结果为空时,到底返回了什么?(不为空时,是string)
这时候开源的好处就体现出来了,在https://github.com/agentzh/lua-resty-redis里扫了下redis.lua文件,发现返回的是ngx.null。
恩,问题到这就解决了,将上面的过滤代码改成:
local md5,err=red:hget(tasklist,"md5")if md5 and md5 ~= null and md5 ~= ngx.null then tb.md5=md5end
就能保证返回结果里不会包含值为null的域了。
回头看了一下lua-resty-redis的文档,发现关于上面的内容,在Readme里已经写的清清楚楚了,在https://github.com/agentzh/lua-resty-redis/blob/master/README.markdown中,有这么一句:
A non-nil Redis "bulk reply" results in a Lua string as the return value. A nil bulk reply results in a ngx.null return value.
首先不该该是自责,而是再次赞一下agentzh的态度,业界标杆。
那么ngx.null究竟是什么东西呢? 在http://wiki.nginx.org/HttpLuaModule有以下说明:
The ngx.null constant is a NULL light userdata usually used to represent nil values in Lua tables etc and is similar to the lua-cjson library's cjson.null constant. This constant was first introduced in the v0.5.0rc5 release.
ngx.null在print、ngx.print、ngx.log、ngx.say等函数中,有以下特色:
Lua nil arguments are accepted and result in literal "nil" strings while Lua booleans result in literal "true" or "false" strings. And the ngx.null constant will yield the "null" string output.
lua-resty-redis中,为何要把redis查询为空的状况返回一个userdata类型的ngx.null?直接返回nil不行吗?
答案是不行,由于nil在lua中有其特殊意义,若是一个变量被设置为nil,就等于说该变量未定义,与无穷无尽的其余未定义的变量同样。那么,若是把redis查询为空的结果设置为nil,就没法把"查询为空”和“未定义”区分开来了,例如在一个table中,一个key对应一个value,若是将该value设置为nil,则至关让key凭空消失,这显然是不合理的。因此必须用一个userdata类型的独特的值来表示这种查询为空,但又不等同于未定义的变量,例如ngx.null。一样的状况想必在sql的lua模块中也会出现,用来处理记录中键值查询为空的状况。
这就要说道lua中神奇的nil了。nil是一种类型,该类型只有一个值,这个值也叫nil。改值的做用只有一个,表示一个变量不存在。跟C\C++等常规语言不一样,”不存在“跟空、0彻底是两个概念。在C语言中,一个字符串若是为空,那么它就只有一个为0的\nul结束符,若是对齐进行逻辑判断,则是假。但lua中,只要一个变量不是nil类型或者是boolean类型中的false,则对它进行逻辑判断,结果是真,即便该值是一个数字0,或者是一个空字符串。