• OpenResty之ngx.var.VARIABLE


    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 要求的)最后一个字符为零(''),而且它允许在字符串内包含多个这样的零 
         * 因为 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] = '';
        
        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);
    }
    
  • 相关阅读:
    算法基础_递归_求杨辉三角第m行第n个数字
    算法基础_递归_返回字符串的反串
    算法基础_递归_求两个字符串的公共最长子序列长度
    算法基础_递归_全排列问题
    玩转数据结构:第7章 集合和映射
    Java8 新特性_Stream API
    一、VIP课程:互联网工程专题 04-Maven私服使用与插件开发
    Java8 新特性_Lambda 表达式
    一、VIP课程:互联网工程专题 03-Maven基本概念与核心配置
    玩转数据结构:第6章 二分搜索树
  • 原文地址:https://www.cnblogs.com/jimodetiantang/p/9489142.html
Copyright © 2020-2023  润新知