nil、null與ngx.null

問題概述

今天第一次在nginx+lua架構下,寫了個須要操做Redis的後臺接口,該接口的功能主要是接受客戶端的json格式的post請求,實現對保存在redis中的任務插入、刪除、查詢等。雖然nginx,lua等都是剛接觸,但這幾個接口仍是順風順水的坐下來了,不能忘了感謝春哥章亦春。nginx

在Redis中記錄的任務其實很簡單,每插入一個任務,就在redis中增長一個HASH結構,每次查詢返回該SET的各個Field和對應的Value值,例如md5,filesize等。因爲任務類型的不一樣,有的Field可能在該任務中不存在,此時在以json格式將查詢結果返回時不該顯示該Field。git

以md5域爲例,在對當前任務以md5域執行hget後,應該對返回結果作一個判斷,若是該HASH結構並無設置md5這個域,則跳過,繼續執行後面的邏輯,若是設置了md5域,則把該域的Value取出來,插入到結果table中,後續再做爲json格式返回結果的一部分,返回給後臺。github


測試時,卻發如今某些域未設置時,查詢結果中卻仍然會把該域返回給查詢調用者,但其Value部分是null。例如,執行下面的測試用例:redis

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

儘管對該任務而言,在插入時並無設置md5域,但返回結果包含了md5域:sql

1
{"result":[{"url":"\/www.baidu.com\/img\/bdlogo.gif","result":0,"md5":null,"putflag":"remote"}]}

問題分析

看到這個現象,首先想到的固然是lua腳本中對執行hget md5操做的返回值判斷失效了,我第一次是這麼寫的:json

1234
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」字符串返回了,因而將上面的幾行代碼改成:markdown

1234
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。因而在判斷語句前面加了一句打印信息:curl

1
ngx.say("type of null is "..type(md5))

果真,這個」空值「並非string類型,而是userdata類型,userdata類型固然跟字符串類型不會相等,因此上面的過濾條件無論設置成什麼樣子,都不會生效,永遠會執行tb.md5=md5。ide

這樣是找到緣由了,但還未最終解決。既然當hget操做返回一個空值時,lua-resty-redis將其設置爲一個userdata類型,那咱們在代碼裏該如何過濾這種狀況呢?本質問題就是,red:hget當查詢resdis結果爲空時,到底返回了什麼?(不爲空時,是string)

這時候開源的好處就體現出來了,在https://github.com/agentzh/lua-resty-redis裏掃了下redis.lua文件,發現返回的是ngx.null。

恩,問題到這就解決了,將上面的過濾代碼改成:

1234
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是什麼?

那麼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,或者是一個空字符串。

相關文章
相關標籤/搜索