nil、null与ngx.null

问题概述

今天第一次在nginx+lua架构下,写了个需要操作Redis的后台接口,该接口的功能主要是接受客户端的json格式的post请求,实现对保存在redis中的任务插入、删除、查询等。虽然nginx,lua等都是刚接触,但这几个接口还是顺风顺水的坐下来了,不能忘了感谢春哥章亦春。

在Redis中记录的任务其实很简单,每插入一个任务,就在redis中增加一个HASH结构,每次查询返回该SET的各个Field和对应的Value值,例如md5,filesize等。由于任务类型的不同,有的Field可能在该任务中不存在,此时在以json格式将查询结果返回时不应显示该Field。

以md5域为例,在对当前任务以md5域执行hget后,应该对返回结果做一个判断,如果该HASH结构并没有设置md5这个域,则跳过,继续执行后面的逻辑,如果设置了md5域,则把该域的Value取出来,插入到结果table中,后续再作为json格式返回结果的一部分,返回给后台。

测试时,却发现在某些域未设置时,查询结果中却仍然会把该域返回给查询调用者,但其Value部分是null。例如,执行下面的测试用例:

curl -d "{\"queryfile\":[{\"url\":\"/www.baidu.com/img/bdlogo.gif\" }]}" "127.0.0.1/cjson"

尽管对该任务而言,在插入时并没有设置md5域,但返回结果包含了md5域:

{"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=md5 end 

从后面的结果看,当md5值为空时,该判断条件并没有将其过滤掉,依然执行了tb.md5=md5。由于redis模块也是调用春哥的lua-resty-redis,因此猜测是否春哥把redis查询结果中的空值用“null”字符串返回了,于是将上面的几行代码改为:

local md5,err=red:hget(tasklist,"md5") if md5 and md5 ~= “null” then tb.md5=md5 end 

仍然过滤失败,忽然眼前一亮,发现查询结果中显示的是"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=md5 end 

就能保证返回结果里不会包含值为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是什么?

那么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模块中也会出现,用来处理记录中键值查询为空的情况。

幽灵般的nil

这就要说道lua中神奇的nil了。nil是一种类型,该类型只有一个值,这个值也叫nil。改值的作用只有一个,表示一个变量不存在。跟C\C++等常规语言不同,”不存在“跟空、0完全是两个概念。在C语言中,一个字符串如果为空,那么它就只有一个为0的\nul结束符,如果对齐进行逻辑判断,则是假。
但lua中,只要一个变量不是nil类型或者是boolean类型中的false,则对它进行逻辑判断,结果是真,即使该值是一个数字0,或者是一个空字符串。

时间: 2024-10-11 02:52:18

nil、null与ngx.null的相关文章

iOS开发中(null)与<null>的处理

转自:http://blog.csdn.net/shenshen123jun/article/details/38315755   收藏起来方便自己查看 不小心在开发过程中,得到了(null)以及<null>的返回值,找了好长时间只找到了一个关于<null>的. 由于要根据返回值进行判断,做出必要反应,因此必须知道返回值所代表的具体字符,在得到(null)后利用isEqual:和@“”,NULL,@“(null)”,nil,Nil比较后均得不到正确结果,弄得不知所措了,但是还是感觉

2&gt;&1 &gt;/dev/null & 与/dev/null 2&gt;&1含义

/dev/null 代表空设备文件 > 代表重定向到哪里,例如:echo "123" > /home/123.txt 1 表示stdout标准输出,系统默认值是1,所以">/dev/null"等同于"1>/dev/null" 2 表示stderr标准错误 & 表示等同于的意思,2>&1,表示2的输出重定向等同于1 1>/dev/null 首先表示标准输出重定向到空设备文件,也就是不输出任何信息到

原!! java直接打印一个对象时,并不是直接调用该类的toString方法 ,而是会先判断是否为null,非null才会调用toString方法

网上看了好多java直接打印一个对象时,直接调用该类的toString方法 . 但是: Object obj=null; System.out.println(obj);//没有报错 System.out.println(obj.toString());//报空指针异常 System.out.println(obj);既然也是直接调用toString方法,为什么不报错??? 原因总结如下: 1.调用Object类的toString方法,必须保证object不是null值,否则将抛出NullPoi

【MySQL】探究之null与not null

相信很多用了mysql很久的人,对这两个字段属性的概念还不是很清楚,一般会有以下疑问: 我字段类型是not null,为什么我可以插入空值 为毛not null的效率比null高 判断字段不为空的时候,到底要 select * from table where column <> '' 还是要用 select * from table where column is not null 呢. 带着上面几个疑问,我们来深入研究一下null 和 not null 到底有什么不一样. 首先,我们要搞清

MySQL中的字段约束 null、not null、default、auto_increment

在MySQL中,每个字段定义都包含附加约束或者修饰符,这些可以用来增加对所输入数据的约束.今天我们来看一下MySQL的字段约束:NULL和NOT NULL修饰符.DEFAULT修饰符,AUTO_INCREMENT修饰符. NULL 和 NOT NULL 修饰符: 可以在每个字段后面都加上这NULL 或 NOT NULL 修饰符来指定该字段是否可以为空(NULL),还是说必须填上数据(NOT NULL).MySQL默认情况下指定字段为NULL修饰符,如果一个字段指定为NOT NULL,MySQL则

数据库表字段,DEFAULT NULL与NOT NULL DEFAULT

为什么要把字段设置成not null 呢? 1.空值是不占用空间的 2.mysql中的NULL其实是占用空间的,下面是来自于MYSQL官方的解释 "NULL columns require additional space in the row to record whether their values are NULL. For MyISAM tables, each NULL column takes one bit extra, rounded up to the nearest byt

SQL语句中=null和is null

平时经常会遇到这两种写法:IS NOT NULL与!=NULL.也经常会遇到数据库有符合条件!=NULL的数据,但是返回为空集合.实际上,是由于对二者使用区别理解不透彻. 默认情况下,推荐使用 IS NOT NULL去做条件判断,因为SQL默认情况下对WHERE XX!= Null的判断会永远返回0行,却不会提示语法错误. 这是为什么呢? SQL Server文档中对Null值的比较运算定义了两种规则,如在SQL Server 2000中: 规则一是是ANSISQL(SQL-92)规定的Null

org.dom4j.DocumentException: null Nested exception: null

1.错误描述 org.dom4j.DocumentException: null Nested exception: null at org.dom4j.io.SAXReader.read(SAXReader.java:484) at org.dom4j.io.SAXReader.read(SAXReader.java:343) at com.you.dom.utils.Reader.main(Reader.java:50) Nested exception: java.net.Malforme

关于ognl.OgnlException: target is null for setProperty(null的解决方案

在跑struts2的时候有时候会出现上面的错,特别是新手, 这种情况是在struts2高级的POJO访问时候出现的s 警告: Error setting expression 'user.password' with value '[Ljava.lang.String;@1cb2dd1' ognl.OgnlException: target is null for setProperty(null, "password", [Ljava.lang.String;@1cb2dd1) at