1. ngx.var.VARIABLE
syntax: ngx.var.VAR_NAME
context: set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*,
header_filter_by_lua*, body_filter_by_lua*, log_by_lua*
讀或者寫 Nginx 變量值:
value = ngx.var.some_nginx_variable_name
ngx.var.some_nginx_variable_name = value
注:僅僅是已經定義了的 Nginx 變量可以被寫入:
location /foo {
set $my_var ''; # this line is required to create $my_var at config time
content_by_lua_block {
ngx.var.my_var = 123
...
}
}
也就是說,無法動態創建 Nginx 變量。
一些特殊的變量(如 $args 和 $limit_rate)可以被分配一個值,其他許多變量不是,如 $query_string, $arg_PARAMETER, $http_NAME。
Nginx 正則組捕獲變量 $1, $2, $3 等等,也可以通過編寫 ngx.var[1], ngx.var[2], ngx.var[3] 等來讀取此接口。
設置 ngx.var.Foo 為 nil 值將會刪除 $Foo Nginx 變量:
ngx.var.args = nil
警告:當讀取 Nginx 變量時,Nginx 將在每個請求的內存池中分配內存,在請求結束時才釋放。因此當需要在 Lua 代碼中重復讀取 Nginx 變量時,緩存該 Nginx 變量到你自己的 Lua 變量中,如下:
local val = ngx.var.some_var
--- use the val repeatedly later
為了防止在當前請求的生命周期內發生內存泄露,緩存結果的另一種方法是使用 ngx.ctx 表。
未定義的 Nginx 變量值為 nil,而定義了但沒初始化的 Nginx 變量值為 Lua 的空字符串。
該 API 需要相對昂貴的元方法調用,建議避免在 hot 代碼路徑中使用它。
2. 源碼實現
2.1 ngx_http_lua_inject_variable_api
在 OpenResty 中,通過該函數將 ngx.var 相關函數注冊進來。
void
ngx_http_lua_inject_variable_api(lua_State *L)
{
/* {{{ regoster reference maps */
/* 創建一張空表,並將其壓棧。等價於 lua_createtable(L, 0, 0) */
lua_newtable(L); /* ngx.var */
/* 創建一張新的空表並壓棧。
* 第二個參數建議了這張表作為數組使用時會有多少個元素;
* 第三個參數建議了這張表可能擁有多少數組之外的元素,即元方法。
* Lua 會使用這些建議來分配這張表。如果你知道這張表用途的更多信息,預分配
* 可以提高性能。否則,你可以使用函數 lua_newtable */
lua_createtable(L, 0, 2 /* nrec */); /* metatable for .var */
/* 將一個 C 函數壓棧。這個函數接收一個 C 函數指針,並將一個類型為 function 的 Lua 值
* 壓棧。當這個棧頂的值被調用時,將觸發對應的 C 函數。
* 注冊到 Lua 中的任何函數都必須遵循正確的協議來接收參數和返回值(參見 lua_CFunction) */
lua_pushcfunction(L, ngx_http_lua_var_get);
/* 做一個等價於 t[k] = v 的操作,這里的 t 是給出的索引處的值,而 v 是棧頂的那個值。
* 這個函數將把這個值彈出棧。跟在 lua 中一樣,這個函數可能觸發一個 "newindex" 事件的元方法 */
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, ngx_http_lua_var_set);
lua_setfield(L, -2, "__newindex");
/* 把一張表彈出棧,並將其設為給定索引處(這個為-2)的值的元表 */
lua_setmetatable(L, -2);
lua_setfield(L, -2, "var");
}
2.2 ngx_http_lua_var_get
當 ngx.var 表中不存在 key 對應的值時,則會調用該函數。
/**
* Get nginx internal variables content
*
* @retval Always return a string or nil on Lua stack. Return nil when failed
* to get content, and actual content string when found the specified variable.
* @seealso ngx_http_lua_var_set
*/
static int
ngx_http_lua_var_get(lua_State *L)
{
ngx_http_request_t *r;
u_char *p, *lowcase;
size_t len;
ngx_uint_t hash;
ngx_str_t name;
ngx_http_variable_value_t *vv;
#if (NGX_PCRE)
u_char *val;
ngx_uint_t n;
LUA_NUMBER index;
int *cap;
#endif
/* 獲取當前請求上下文結構體 */
r = ngx_http_lua_get_req(L);
if (r == NULL) {
/* 拋出一個錯誤。錯誤消息的格式由 fmt 給出。后面需提供若干參數,這些參數
* 遵循 lua_pushfstring 中的規則。如果能獲得相關信息,它還會在消息前面
* 加上錯誤發生時的文件名及行號。
* 這個函數永遠不會返回。但是在 C 函數中通常遵循慣用法:
* return luaL_error(args) */
return luaL_error(L, "no request object found");
}
/* 檢測當前請求的 socket 套接字是否有效 */
ngx_http_lua_check_fake_request(L, r);
#if (NGX_PCRE)
/* 返回給定有效索引處值的類型,當索引無效(或無法訪問)時返回 LUA_TNONE。
* lua_type 返回的類型被編碼為一些在 lua.h 中定義的常量: LUA_TNIL, LUA_TNUMBER,
* LUA_TBOOLEAN, LUA_TSTRING, LUA_TTABLE, LUA_TFUNCTION, LUA_TUSERDATA,
* LUA_TTHREAD, LUA_TLIGHTUSERDATA */
if (lua_type(L, -1) == LUA_TNUMBER) {
/* it is a regex capturing variable */
/* 把給定索引處的 Lua 值轉換為 lua_Number 這樣一個 C 類型。這個 Lua 值
* 必須是一個數字或是一個可轉換為數字的字符串;否則,lua_tonumber 返回 0 */
index = lua_tonumber(L, -1);
if (index <= 0) {
/* 將空值壓棧 */
lua_pushnil(L);
return 1;
}
n = (ngx_uint_t) index * 2;
dd("n = %d, ncaptures = %d", (int) n, (int) r->ncaptures);
if (r->captures == NULL
|| r->captures_data == NULL
|| n >= r->ncaptures)
{
lua_pushnil(L);
return 1;
}
/* n >= 0 && n < r->ncaptures */
cap = r->captures;
p = r->captures_data;
val = &p[cap[n]];
/* 把指針 val 指向的長度為 len 的字符串壓棧。Lua 對這個字符串做一個內部副本
* (或是復用一個副本),因此 val 處的內存在函數返回后,可以釋放掉或是立刻
* 重用於其他用途。字符串內可以是任意二進制數據,包括零字符。
* 返回內部副本的指針 */
lua_pushlstring(L, (const char *) val, (size_t) (cap[n + 1] - cap[n]));
return 1;
}
#endif
if (lua_type(L, -1) != LUA_TSTRING) {
return luaL_error(L, "bad variable name");
}
/* 把給定索引處的 Lua 值轉換為一個 C 字符串。如果 len 不為 NULL,它還把字符串
* 長度設到 *len 中。這個 Lua 值必須是一個字符串或是一個數字;否則返回 NULL。
* 如果值是一個數字,lua_tolstring 還會把堆棧中的那個值的實際類型轉換為一個
* 字符串。(當遍歷一張表的時候,若把 lua_tolstring 作用在鍵上,這個轉換有可能
* 導致 lua_next 弄錯)
* lua_tolstring 返回一個已對齊指針指向 Lua 狀態機中的字符串。這個字符串總能
* 保證(C 要求的)最后一個字符為零('\0'),而且它允許在字符串內包含多個這樣的零
* 因為 Lua 中可能發生垃圾收集,所以不保證 lua_tolstring 返回的指針,在對應的值
* 從堆棧中移除后依然有效 */
p = (u_char *) lua_tolstring(L, -1, &len);
/* 這個函數分配一塊指定大小的內存塊,把內存塊地址作為一個完全用戶數據壓棧,
* 並返回這個地址。宿主程序可以隨意使用這塊內存 */
lowcase = lua_newuserdata(L, len);
/* 將 p 指向 len 長度字符串拷貝到 lowcase,同時轉為小寫,並返回一個hash值 */
hash = ngx_hash_strlow(lowcase, p, len);
/* 設置該 Nginx 內部變量的長度和名稱 */
name.len = len;
name.data = lowcase;
/* 根據 name 獲取對應的值 */
vv = ngx_http_get_variable(r, &name, hash);
if (vv == NULL || vv->not_found) {
lua_pushnil(L);
return 1;
}
lua_pushlstring(L, (const char *) vv->data, (size_t) vv->len);
return 1;
}
ngx_http_lua_get_req
獲取當前請求上下文結構體。
#define ngx_http_lua_req_key "__ngx_req"
static ngx_inline ngx_http_request_t *
ngx_http_lua_get_req(lua_State *L)
{
ngx_http_request_t *r;
/* 把全局變量 name(這里為 ngx_http_lua_req_key) 里的值壓棧,返回該值的類型 */
lua_getglobal(L, ngx_http_lua_req_key);
/* 如果給定索引處的值是一個完全用戶數據,函數返回其內存塊的地址。如果只是一個
* 輕量用戶數據,那么就返回它表示的指針。否則,返回 NULL */
r = lua_touserdata(L, -1);
/* 從棧中彈出 n 個元素(這里為 1 個) */
lua_pop(L, 1);
return r;
}
ngx_http_lua_check_fake_request
檢測當前連接的 socket 套接字是否有效。
#define ngx_http_lua_check_fake_request(L, r) \
if ((r)->connection->fd == (ngx_socket_t) -1) { \
return luaL_error(L, "API disabled in the current context"); \
}
ngx_http_get_variable
根據變量名獲取該變量對應的值。
ngx_http_variable_value_t *
ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key)
{
size_t len;
ngx_uint_t i, n;
ngx_http_variable_t *v;
ngx_http_variable_value-t *vv;
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
v = ngx_hash_find(&cmcf->variable_hash, key, name->data, name->len);
if (v) {
if (v->flags & NGX_HTTP_VAR_INDEXED) {
return ngx_http_get_flushed_variable(r, v->index);
}
if (ngx_http_variable_depth == 0) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"cycle while evaluating variable \"%V\"", name);
return NULL;
}
ngx_http_variable_depth--;
vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t));
if (vv && v->get_handler(r, vv, v->data) == NGX_OK) {
ngx_http_variable_depth++;
return vv;
}
ngx_http_variable_depth++;
return NULL;
}
vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t));
if (vv == NULL) {
return NULL;
}
len = 0;
v = cmcf->prefix_variables.elts;
n = cmcf->prefix_variables.nelts;
for (i = 0; i < cmcf->prefix_variables.nelts; i++) {
if (name->len >= v[i].name.len && name->len > len
&& ngx_strncmp(name->data, v[i].name.data, v[i].name.len) == 0)
{
len = v[i].name.len;
n = i;
}
}
if (n != cmcf->prefix_variables.nelts) {
if (v[n].get_handler(r, vv, (uintptr_t) name) == NGX_OK) {
return v;
}
return NULL;
}
vv->not_found = 1;
return vv;
}
2.3 ngx_http_lua_var_set
/**
* Set nginx internal variable content
*
* @retval Always return a boolean on Lua stack. Return true when variable
* content was modified successfully, false otherwise.
* @seealso ngx_http_lua_var_get
*/
static int
ngx_http_lua_var_set(lua_State *L)
{
ngx_http_variable_t *v;
ngx_http_variable_value_t *vv;
ngx_http_core_main_conf_t *cmcf;
u_char *p, *lowcase, *val;
size_t len;
ngx_str_t name;
ngx_uint_t hash;
ngx_http_request_t *r;
int value_type;
const char *msg;
r = ngx_http_lua_get_req(L);
if (r == NULL) {
return luaL_error(L, "no request object found");
}
ngx_http_lua_check_fake_request(L, r);
/* we skip the first argument that is the table */
/* we read the variable name */
if (lua_type(L, 2) != LUA_TSTRING) {
/* 拋出一個錯誤,該函數永不返回,以下是通常寫法 */
return luaL_error(L, "bad variable name")
}
/* 將指定索引處的 Lua 值轉換為一個 C 字符串,轉換后的長度通過 len 返回
* 該 Lua 值必須是一個數字或字符串,否則返回 NULL */
p = (u_char *) lua_tolstring(L, 2, &len);
/* 分配一塊指定大小的內存塊,並把內存地址作為一個完全用戶數據壓棧,並返回這個地址 */
lowcase = lua_newuserdata(L, len + 1);
hash = ngx_hash_strlow(lowcase, p, len);
lowcase[len] = '\0';
name.len = len;
name.data = lowcase;
/* we read the variable new value */
/* 返回指定有效索引處的 Lua 值的類型,當索引無效(或無法訪問)時返回 LUA_TNONE */
value_type = lua_type(L, 3);
switch (value_type) {
case LUA_TNUMBER:
case LUA_TSTRING:
/* 檢查指定索引處的 Lua 值是否是一個字符串,並返回該字符串,長度值通過 len 返回 */
p = (u_char *) luaL_checklstring(L, 3, &len);
val = ngx_palloc(r->pool, len);
if (val == NULL) {
return luaL_error(L, "memory allocation error");
}
ngx_memcpy(val, p, len);
break;
case LUA_TNIL:
/* undef the variable */
val = NULL;
len = 0;
break;
default:
/* 將一個格式化后的字符串壓棧,然后返回該字符串的指針。它和 C 函數 printf 比較像,
* 但有一些重要的區別:
* - 不需要為結果分配空間:其結果是一個 Lua 字符串,由 Lua 來關心其內存分配
* (通過 gc 釋放內存)
* - 該轉換非常的受限。不支持符號、寬度、精度。轉換符只支持 '%%'(插入一個字符'%'),
* '%s'(插入一個帶零終止符的字符串,沒有長度限制),'%f'(插入一個 lua_Nubmer),
* '%L'(插入一個 lua_Integer),'%p'(插入一個指針或一個十六進制數),
* '%d'(插入一個 int),'%c'(插入一個用 int 表示的單字節字符),
* '%U'(插入一個用 long int 表示的 UTF-8 字) */
msg = lua_pushfstring(L, "string, number, or nil expected, "
"but got %s", lua_typename(L, value_type));
/* 拋出上一個函數壓棧的錯誤報告。使用下列標准信息並包含了一段 extramsg 作為注解:
* bad argument #arg to 'funcname' (extramsg)
* 該函數永不返回 */
return luaL_argerror(L, 1, msg);
}
/* we fetch the variable itself */
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
v = ngx_hash_find(&cmcf->variable_hash, hash, name.data, name.len);
if (v) {
if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) {
return luaL_error(L, "variable \"%s\" not changeable", lowcase);
}
if (v->set_handler) {
dd("set variable with set_handler");
vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t));
if (vv == NULL) {
return luaL_error(L, "no memory");
}
if (value_type == LUA_TNIL) {
vv->valid = 0;
vv->not_found = 1;
vv->no_cacheable = 0;
vv->data = NULL;
vv->len = 0;
} else {
vv->valid = 1;
vv->not_found = 0;
vv->no_cacheable = 0;
vv->data = val;
vv->len = len;
}
v->set_handler(r, vv, v->data);
return 0;
}
if (v->flags & NGX_HTTP_VAR_INDEXED) {
vv = &r->variables[v->index];
dd("set indexed variable");
if (value_type == LUA_TNIL) {
vv->valid = 0;
vv->not_found = 1;
vv->no_cacheable = 0;
vv->data = NULL;
vv->len = 0;
} else {
vv->valid = 1;
vv->not_found = 0;
vv->no_cacheable = 0;
vv->data = val;
vv->len = len;
}
return 0;
}
return luaL_error(L, "variable \"%s\" cannot be assigned a value",
lowcase);
}
/* variable not found */
return luaL_error(L, "variable \"%s\" not found for writing; "
"maybe it is a built-in variable that is not changeable "
"or you forgot to use \"set $%s '';\" "
"in the config file to define it first",
lowcase, lowcase);
}