有些时候,我们需要通过 Lua 代码操作 Nginx 里面的某些状态,但是想要的 API 并不存在于 OpenResty 之内。这时候,可以选择编写一个 Nginx C 模块,然后暴露出可供 Lua 调用的接口。
本文中,我们会分别探讨,如何通过 Nginx 变量或 FFI 的方式去提供 Lua 调用得到的接口。
文中的示例代码可以在 ngx_http_example_or_module 找到。
通过 Nginx 变量提供接口
ngx.var.variable=
在调用的时候,会先查找变量 variable
对应的 handler(一个在 Nginx 内注册的 C 函数),如果 handler 存在,会去调用该 handler。
这意味着,如果我们定义了一个 Nginx 变量和对应的 handler,我们就可以通过在 Lua 代码里调用 ngx.var.variable=
来触发该 handler。
空说无益,先上示例。
在 Nginx 里面我们可以通过 limit_rate
和 limit_rate_after
两个指令来限制响应给客户端的速率。前者决定了限速的多少,后者决定了从什么时候开始限速。当然更多的时候我们需要动态去调整这两个指标。
limit_rate
对应有一个 Nginx 内置的变量, $limit_rate
,我们可以修改该变量来达到动态调整的目的。相关的 Lua 代码是 ngx.var.limit_rate = limit_rate
。但是并不存在 $limit_rate_after
这样一个变量。
不用担心。因为我们可以自己加上。
// ngx_http_example_or_module.c
// 定义变量和它的 getter/setter
static ngx_http_variable_t ngx_http_example_or_variables[] = {
{ ngx_string("limit_rate_after"), ngx_http_variable_request_set_size,
ngx_http_variable_request_get_limit_rate_after,
offsetof(ngx_http_request_t, limit_rate_after),
NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE, 0 },
{ ngx_null_string, NULL, NULL, 0, 0, 0 }
};
// getter 和 setter 的实现在 GitHub 上的示例代码里有,这里就不贴上了。
通过 FFI 提供接口
不过在大多数情况下,我们并不需要借助变量来间接调用 Nginx C 函数。我们完全可以借助 LuaJIT 的 FFI,直接调用 Nginx C 函数。
lua-resty-murmurhash2 就是一个现成的例子。
下面让我们再看另外一个例子,通过 Lua 代码来获取当前的 Nginx 错误日志等级。
在开发中,我们有时需要在测试环境中通过日志来记录某个 table 的值,比如 ngx.log(ngx.INFO, cjson.encode(res))
。
在生产环境里,我们会设置日志等级为 error,这样就不会输出 table 的值。但是日志等级无论是多少,cjson.encode
都是必然会被调用的。
不幸的是,这行代码所在的路径非常热,我们需要避免无谓的 json encode 操作。如果能获取实际的日志等级,判断是否为 error,来决定是否调用cjson.encode
,就能省下这一笔开销。
要实现这一功能,仅需加个获取当前配置的日志等级的 Nginx C 函数和对应的 Lua 接口。
我们可以像这样提供一个 Lua 接口:
-- lib/example_or.lua
...
if not pcall(ffi.typeof, "ngx_http_request_t") then
ffi.cdef[[
struct ngx_http_request_s;
typedef struct ngx_http_request_s ngx_http_request_t;
]]
end
ffi.cdef[[
int ngx_http_example_or_ffi_get_error_log_level(ngx_http_request_t *r);
]]
function _M.get_error_log_level()
local r = getfenv(0).__ngx_req
return tonumber(C.ngx_http_example_or_ffi_get_error_log_level(r))
end
对应的 Nginx C 函数很简单:
int
ngx_http_example_or_ffi_get_error_log_level(ngx_http_request_t *r)
{
ngx_log_t *log;
int log_level;
if (r && r->connection && r->connection->log) {
log = r->connection->log;
} else {
log = ngx_cycle->log;
}
log_level = log->log_level;
if (log_level == NGX_LOG_DEBUG_ALL) {
log_level = NGX_LOG_DEBUG;
}
return log_level;
}
使用时直接拿它跟特定的 Nginx 日志等级常量比较即可:
-- config.lua
-- 目前 Nginx 不支持动态变更日志等级,所以可以把日志等级缓存起来
local example_or = require "lib.example_or"
_M.log_leve = example_or.get_error_log_level()
-- in other file
local config = require "common.config"
local log_level = config.log_level
if log_level >= ngx.WARN then
-- 错误日志等级是 warn 或者 info 一类
ngx.log(ngx.WARN, "log a warning event")
else
-- 错误日志等级是 error 一类
ngx.log(ngx.WARN, "do not log another warning event")
end
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。