有些时候,我们需要通过 Lua 代码操作 Nginx 里面的某些状态,但是想要的 API 并不存在于 OpenResty 之内。这时候,可以选择编写一个 Nginx C 模块,然后暴露出可供 Lua 调用的接口。
本文中,我们会分别探讨,如何通过 Nginx 变量或 FFI 的方式去提供 Lua 调用得到的接口。文中的示例代码可以在 找到。
通过 Nginx 变量提供接口
ngx.var.variable=
在调用的时候,会先查找变量 variable
对应的 handler(一个在 Nginx 内注册的 C 函数),如果 handler 存在,会去调用该 handler。
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/setterstatic 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 代码来获取当前的 Nginx 错误日志等级。
在开发中,我们有时需要在测试环境中通过日志来记录某个 table 的值,比如 ngx.log(ngx.INFO, cjson.encode(res))
。
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; ]]endffi.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 函数很简单:
intngx_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 filelocal config = require "common.config"local log_level = config.log_levelif 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