嘿,伙计们!是不是觉得Nginx的配置文件,除了 proxy_pass 和 location,就是一堆看天书一样的变量,比如 $host, $remote_addr?你可能会用 set $my_variable "hello" 搞点小动作,但有没有那么一个瞬间,你心里会冒出一个大胆的想法:这些变量到底是从哪儿冒出来的?我能不能自己造一个?
当然能!而且这就像是给Nginx装上了“外挂”,能让它做到一些官方能力之外,非常风骚的操作。
今天,咱们不聊那些配置文件里的小打小闹。我们要干一票更大的——直接钻进Nginx的模块里,从源代码层面,创造和使用我们自己的变量。 放心,我不会一上来就甩给你一堆让人头秃的C代码,咱们循序渐进,保证你看得懂,学得会,还能拿去跟同事吹牛。
第一章:变量?不就是给Nginx贴标签嘛!
想象一下,Nginx就像一个无比繁忙的快递分拣中心。每一个请求就是一个包裹。
$host= 收件人地址$request_method= 包裹类型(是空运GET,还是陆运POST?)$remote_addr= 寄件人IP
这些“标签”(变量)让分拣机(Nginx)能智能地处理每个包裹,该去A城市的(location /A),该需要验货的(POST请求),都安排得明明白白。
那么问题来了,这些标签是谁贴上去的?答案就是:Nginx的各个模块。
HTTP模块在处理请求的不同阶段(比如读取请求头、寻找文件、转发请求时),顺手就把这些标签给贴上了。所以,变量是Nginx模块与外部配置文件进行通信的桥梁。
第二章:潜入核心:在模块里“造”一个变量
好了,背景知识普及完毕,现在系好安全带,我们要准备潜入Nginx的C语言内核了。
我们的目标是:编写一个自定义的HTTP模块,这个模块会创建一个名叫 $my_cool_var 的变量,它的值是我们动态生成的。
步骤一:搭个戏台子(模块基础结构)
任何Nginx模块,都得先有个“身份证”,也就是 ngx_module_t 结构。我们给它起个酷炫的名字,比如 ngx_http_my_variable_module。
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
/* 首先,声明我们处理变量的函数 */
static ngx_int_t ngx_http_my_variable_handler(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data);
/* 这是模块的“指令”结构,虽然我们这个模块不处理配置指令,但也要声明一下 */
static ngx_http_module_t ngx_http_my_variable_module_ctx = {
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create location configuration */
NULL /* merge location configuration */
};
/* 隆重登场!模块的“身份证” */
ngx_module_t ngx_http_my_variable_module = {
NGX_MODULE_V1,
&ngx_http_my_variable_module_ctx, /* module context */
NULL, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
看起来有点复杂?别怕,你只需要知道,大部分 NULL 的地方表示我们暂时不关心那个步骤。核心是 ngx_http_my_variable_module 这个结构,它告诉Nginx:“嘿,有我这个模块存在哦!”
步骤二:变量的诞生——在“后配置”阶段注册
Nginx在读取完配置文件后,会有一个 postconfiguration 钩子,这是我们安插变量的最佳时机。
我们来修改一下上面的 ngx_http_my_variable_module_ctx,把 postconfiguration 填上。
/* 后配置函数,在这里注册我们的变量 */
static ngx_int_t ngx_http_my_variable_postconf(ngx_conf_t *cf);
static ngx_http_module_t ngx_http_my_variable_module_ctx = {
NULL, /* preconfiguration */
ngx_http_my_variable_postconf, /* postconfiguration <- 看这里!*/
... // 后面的还是NULL
};
现在,我们来实现这个关键的 ngx_http_my_variable_postconf 函数。
static ngx_int_t
ngx_http_my_variable_postconf(ngx_conf_t *cf)
{
ngx_http_variable_t *var;
/* 创建一个新的变量对象 */
var = ngx_http_add_variable(cf, &ngx_string("my_cool_var"), NGX_HTTP_VAR_NOHASH);
if (var == NULL) {
return NGX_ERROR;
}
/* 设置这个变量的值由哪个函数来生成 */
var->get_handler = ngx_http_my_variable_handler;
var->data = 0; // 可以传递一个自定义的值给handler,这里我们先不用
return NGX_OK;
}
看明白了吗?ngx_http_add_variable 就是那个“造物主”函数。它告诉Nginx:“从现在起,有一个叫 my_cool_var 的变量诞生了!” 而 get_handler 则指定了,每当有人要读取这个变量的值时,就去调用 ngx_http_my_variable_handler 这个函数。
步骤三:注入灵魂——编写变量的“大脑”(Handler)
这是最核心、最有趣的部分了!我们的变量要返回什么值,全由这个函数说了算。
static ngx_int_t
ngx_http_my_variable_handler(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data)
{
u_char *buffer;
size_t len;
ngx_str_t awesome = ngx_string(" is Awesome!");
/* 我们动态生成一个值:比如 "时间戳 is Awesome!" */
len = ngx_time_len + awesome.len; // 计算需要的缓冲区大小
// 从内存池申请一块内存来存放我们的字符串
buffer = ngx_palloc(r->pool, len);
if (buffer == NULL) {
return NGX_ERROR;
}
// 构造字符串:时间戳 + " is Awesome!"
ngx_memcpy(buffer, ngx_cached_http_time.data, ngx_time_len);
ngx_memcpy(buffer + ngx_time_len, awesome.data, awesome.len);
/* 将结果赋值给变量 v */
v->len = len;
v->valid = 1; // 标记这个变量值是有效的
v->no_cacheable = 0; // 标记这个值可以被缓存
v->not_found = 0; // 标记这个变量被成功找到了
v->data = buffer; // 这才是真正的字符串数据
return NGX_OK;
}
这个函数干了啥?
- 计算我们需要多大的内存来存放字符串。我们想让变量返回
"当前时间 is Awesome!"。 - 使用
ngx_palloc从请求的内存池(r->pool)里申请内存。这是Nginx管理内存的优雅方式,请求结束后会自动清理,不用担心内存泄漏。 - 把时间戳字符串和
" is Awesome!"拼接起来。 - 填充
ngx_http_variable_value_t *v这个结构体,这就是最终变量所呈现的值。
编译并加载这个模块(这部分涉及 config 文件和重新编译Nginx,是另一个话题,但相信搞到这一步的你肯定能搞定),然后你就可以在配置文件中为所欲为了!
location /test {
add_header X-My-Variable $my_cool_var; # 在响应头里看到它
return 200 "The value is: $my_cool_var\n"; # 在响应体里看到它
}
访问 /test,你就会看到类似 The value is: Wed, 24 May 2023 10:00:00 GMT is Awesome! 的炫酷内容!恭喜你,你已经在Nginx的宇宙里,创造了一颗属于自己的小星星!
第三章:整点“花活”——一个完整的“马甲”代理示例
光是返回个字符串,太不过瘾了?我们来个更刺激的。
场景:我们写一个模块,创建一个叫 $upstream_with_disguise 的变量。用它来做 proxy_pass 时,它会自动把请求的 User-Agent 头改成 "I'm a Browser from the Future”,伪装成一个来自未来的浏览器!
这个例子会综合运用变量的创建、读取请求头、设置上游请求头。
核心思路:
- 创建一个变量
$upstream_with_disguise。 - 这个变量的handler不直接返回值,而是起到一个“触发器”的作用。当Nginx因为使用这个变量而需要执行上游请求时,会调用我们的handler。
- 在handler里,我们偷偷修改
r->upstream->headers_in中的User-Agent。
简化版代码示例(聚焦核心逻辑):
static ngx_int_t
ngx_http_disguise_variable_handler(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data)
{
ngx_table_elt_t *h;
ngx_str_t new_ua = ngx_string("I'm a Browser from the Future");
// 找到 upstream 的 headers_in 结构
if (r->upstream == NULL || r->upstream->headers_in == NULL) {
v->not_found = 1;
return NGX_OK;
}
// 获取或创建 User-Agent 请求头
h = r->upstream->headers_in->user_agent;
if (h == NULL) {
// 如果上游请求头里本来没有UA,我们就创建一个
h = ngx_list_push(&r->upstream->headers_in->headers);
if (h == NULL) {
return NGX_ERROR;
}
h->key = ngx_string("User-Agent"); // 注意这里要用池化字符串,示例中简化了
r->upstream->headers_in->user_agent = h;
}
// 不管原来是什么,都给它换成我们的“马甲”
h->value = new_ua;
// 对于这个变量本身,我们可以返回一个固定的值,比如原upstream的URI
v->data = r->upstream->uri.data;
v->len = r->upstream->uri.len;
v->valid = 1;
v->not_found = 0;
return NGX_OK;
}
然后在配置文件中:
location /proxy {
# 使用我们自定义的“马甲”变量作为代理目标
# 注意:这需要你自定义一个代理模块指令来配合,这里用set是无效的,仅为示意逻辑
# 假设我们有一个指令叫 `proxy_pass_with_disguise $upstream_with_disguise;`
set $my_backend "http://backend.com/hidden_api";
proxy_pass_with_disguise $my_backend;
}
当请求到达 /proxy 时,Nginx会去获取 $upstream_with_disguise 的值,从而触发我们的handler。我们的handler趁机把发往 http://backend.com/hidden_api 的请求的User-Agent给掉包了!后端服务器看到的就是一个来自“未来浏览器”的请求,而对你真实的UA一无所知。
怎么样,是不是感觉Nginx的世界一下子开阔了很多?从被动的配置使用者,变成了规则的创造者。
结语:从“会用”到“会玩”
通过这两个例子,我们完成了从理解变量本质,到在模块中创造简单变量,再到实现一个复杂、有实际用途的变量功能的跨越。
Nginx模块开发就像是在给它编写“外挂”,而变量系统就是这个外挂与外部世界交互的API。它让你不再局限于 set、map 这些原生命令,而是可以基于复杂的业务逻辑、网络状态、甚至是你的奇思妙想,来动态地控制Nginx的行为。
所以,别再只把Nginx当做一个静态的配置文件了。拿起你的代码编辑器,深入它的内核,去创造,去打破规则。你会发现,这个看似朴素的Web服务器,其内在的灵活性和强大能力,超乎你的想象。
去吧,去创造你的 $super_power_var,让Nginx为你施展真正的魔法!

被折叠的 条评论
为什么被折叠?



