Nginx基础教程(63)Nginx在模块里使用变量:别愣着了!Nginx变量在模块里这么玩才够劲:从入门到“作弊”指南

嘿,伙计们!是不是觉得Nginx的配置文件,除了 proxy_passlocation,就是一堆看天书一样的变量,比如 $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;
}

这个函数干了啥?

  1. 计算我们需要多大的内存来存放字符串。我们想让变量返回 "当前时间 is Awesome!"
  2. 使用 ngx_palloc 从请求的内存池(r->pool)里申请内存。这是Nginx管理内存的优雅方式,请求结束后会自动清理,不用担心内存泄漏。
  3. 把时间戳字符串和 " is Awesome!" 拼接起来。
  4. 填充 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”,伪装成一个来自未来的浏览器!

这个例子会综合运用变量的创建、读取请求头、设置上游请求头。

核心思路

  1. 创建一个变量 $upstream_with_disguise
  2. 这个变量的handler不直接返回值,而是起到一个“触发器”的作用。当Nginx因为使用这个变量而需要执行上游请求时,会调用我们的handler。
  3. 在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。它让你不再局限于 setmap 这些原生命令,而是可以基于复杂的业务逻辑、网络状态、甚至是你的奇思妙想,来动态地控制Nginx的行为。

所以,别再只把Nginx当做一个静态的配置文件了。拿起你的代码编辑器,深入它的内核,去创造,去打破规则。你会发现,这个看似朴素的Web服务器,其内在的灵活性和强大能力,超乎你的想象。

去吧,去创造你的 $super_power_var,让Nginx为你施展真正的魔法!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

值引力

持续创作,多谢支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值