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. rabbitmq笔记(一)rabbitmq简介及基础

    一.消息组件 如果从消息组件来讲主要划分位两类: 1.JMS组件:ActiveMQ(慢): 2.AMQP组件(协议):性能是最高的, 而AMQP有两个主要的开源: 1)RabbitMQ:使用最广泛,速 ...

  2. thefuck安装和使用(ubuntu)

    系统环境(已测试可用): ubuntu 18.04 lts (server或desktop),ubuntu 19.04(server或desktop) sudo apt update sudo apt ...

  3. 记录一下JProfiler的使用

    刚入职实习,第四天了,昨晚老大安排我在公司机器上装个JProfiler看一情况. 然后网上都是什么跟tomcat一起使用的,所以折腾了很久才搞出来. 我这里没用什么服务器,因为公司用的是Play!框架 ...

  4. Go语言中的IO操作、Flag包以及urfave/cli命令行框架

    一.格式化输入和输出 1.从终端获取用户的输入 fmt.Scanf  空格作为分隔符,占位符和格式化输出的一致 fmt.Scan  从终端获取用户的输入,存储在Scanln中的参数里,空格和换行符作为 ...

  5. python学习之flask接口开发,环境变量扩展,网络编程requests

    python基础 flask之mock接口 所谓mock接口,其实就是我们在正式接口还没联调或者是测试接口没有正式使用时,自己创建一个模拟接口,来供项目暂时打通功能或者测试流程梳理的桥梁,而我们这儿使 ...

  6. .Net系列 Transaction 事务

    Transactions 事务(Transaction),一般是指要做的或所做的事情.在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit).在计算机术语中,事务通常就是指数 ...

  7. .NET Core WebAPI IIS 部署问题

    虽然建了 .NET Core 的项目,基本的一些功能也实现了,运行什么的也没有问题,但是一直没有直接发布. 今天就进行了发布测试,结果问题还是来了,只是你不去做自然就不会出现. 一.基本发布 1.先是 ...

  8. JDK源码那些事儿之ArrayBlockingQueue

    线程在JDK中是非常重要的一块内容,所有的应用服务都离不开线程的相关操作,对于大量线程的使用都是推荐使用线程池来进行操作和管理,JDK本身提供的线程池在服务中经常被使用到,以往经常使用Executor ...

  9. VUE编译报错 Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead

    背景: 在使用VUE添加标签的时候编译报错,报错如下: Component template should contain exactly one root element. If you are u ...

  10. 备份MySQL数据库并上传到阿里云OSS存储

    1. 环境配置 要将本地文件上传到阿里云oss中, 必须使用阿里云提供的工具 ossutil, 有32位,也有64位的, Linux和Windows都有.具体可以到阿里云官网下载 官网及文档: htt ...