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);
}

OpenResty之ngx.var.VARIABLE的更多相关文章

  1. nginx的 ngx.var ngx.ctx ngx.req

    ngx.var 是获取 Nginx 的变量,需要经历字符串 hash.hash 表查找等过程. ngx.ctx 仅仅是一个 Lua table 而已,它的引用存放在 ngx_lua 的模块上下文(ct ...

  2. openresty的ngx.timer.at

    openresty的ngx.timer.at真是个强大的方法. 例如某些函数不可以在一些NGINX的执行阶段使用时,可以ngx.timer.at API 创建一个零延迟的timer,在timer中去处 ...

  3. OpenResty之ngx.ssl

    翻译自: ngx.ssl - Lua API for controlling NGINX downstream SSL handshakes 1. 概要 # 注意:如果你使用的是 OpenResty ...

  4. OpenResty之ngx.shared.DICT

    参考链接: resty.core.shdict ngx_shared.DICT 源码正文: dict.lua 部分源码如下: local ffi = require 'ffi' local base ...

  5. Openresty 与 Tengine

    Openresty 与 Tengine Openresty和Tengine基于 Nginx 的两个衍生版本,某种意义上他们都和淘宝有关系,前者是前淘宝工程师agentzh主导开发的,后者是淘宝的一个开 ...

  6. 《用OpenResty搭建高性能服务端》笔记

    概要 <用OpenResty搭建高性能服务端>是OpenResty系列课程中的入门课程,主讲人:温铭老师.课程分为10个章节,侧重于OpenResty的基本概念和主要特点的介绍,包括它的指 ...

  7. openresty开发系列24--openresty中lua的引入及使用

    openresty开发系列24--openresty中lua的引入及使用 openresty 引入 lua 一)openresty中nginx引入lua方式 1)xxx_by_lua   ---> ...

  8. Openresty使用

    OpenResty是一个全功能的 Web 应用服务器.它打包了标准的 Nginx 核心,常用的第三方模块以及大多数依赖项. 可以把它看成是Nginx附加众多的第三方插件的合集.其主体是嵌入lua脚本的 ...

  9. openresty HTTP status constants nginx api for lua

    https://github.com/openresty/lua-nginx-module context: init_by_lua, set_by_lua, rewrite_by_lua, acce ...

随机推荐

  1. [LeetCode] 76. 最小覆盖子串 ☆☆☆☆☆(滑动窗口)

    https://leetcode-cn.com/problems/minimum-window-substring/solution/hua-dong-chuang-kou-suan-fa-tong- ...

  2. Java Data JPA +hibernate 保存或者是查询遇到的坑

    由于项目需求,接触了Java Data JPA +hibernate,它的调用方式是controller调用service,service有实现的接口serviceimpl,serviceimpl调用 ...

  3. 数组中的reduce

    reduce方法第一次对我的感觉是很鸡肋,但是深入了解,才发现其中的奥妙,是个非常强大且实用的方法 var arr = [1,2,3,4,5,6,7]; var sum = arr.reduce( ( ...

  4. STL的sort函数是使用什么排序算法的?

    先占坑,大概就是主要快速排序+插入排序+堆排序的合体

  5. Mybatis3.0-[tp_28-29]-映射文件-resultMap_自定义结果集映射规则_及关联环境的搭建

    笔记要点出错分析与总结工程组织 1.定义接口  EmployeeMapperPlus.java package com.dao; import com.bean.*; public interface ...

  6. JDBC课程4--使用PreparedStatement进行增删查改--封装进JDBCTools的功能中;模拟SQL注入 ; sql的date()传入参数值格式!

    主要内容: /*SQL 的date()需要传入参数值: preparedStatement().setDate(new java.util.Date().getTime()); 熟悉了使用Prepar ...

  7. Spring如何给静态变量注入值

    Common.java是一个工具类. Spring无法直接给静态变量注入值,因为静态变量不属于对象,只属于类,也就是说在类被加载字节码的时候变量已经初始化了,也就是给该变量分配内存了,导致spring ...

  8. framebufferfetch in vulkan

    framebufferfetch在ogles上是个扩展 到vulkan这里做成一个基本功能了 对应的是subpass(对照metal) 不同renderpass不能stay on chip 只有sub ...

  9. ServletRequest、 HttpServletRequest、Request的联系与区别

    一. servlet理论上可以处理多种形式的请求响应形式 http只是其中之一 所以HttpServletRequest HttpServletResponse分别是ServletRequest和Se ...

  10. Spring核心概念和案例

    一.Spring概念 1.Spring框架概述 轻量级的Java EE开源框架,它是由Rod Johnson为了解决企业应用程序开发的复杂性而创建, Spring框架提供了一个开发平台,用于整合其他技 ...