3. 深入理解 Nginx 模块开发:ngx_http_module_t 结构体回调函数详解

nginx源码分析(2)——http模块的初始化过程 前一篇文章介绍了nginx的启动初始化过程,包括了所有模块的初始化过程,比如http模块、事件模块等。这里再详细介绍一下http模块的启动过程,还记得在前一篇文章中提到过ngx_conf_parse函数背后隐藏了大量的细节吗?这里就揭开这层神秘的面纱,去看看几个重要的http模块是如何初始化的。这里依然沿用上一篇文章的结构,首先来看几个重要的数据结构。 1. 重要的数据结构         1 阅读详情

1. 引言

摘要:深入解析 Nginx 模块开发的核心结构体 ngx_http_module_t,系统讲解 8 个回调函数的作用与调用时机,涵盖配置创建、合并、初始化及请求处理全流程。结合可编译运行的完整模块示例,助你从零掌握 Nginx HTTP 模块开发。

Nginx 以其高性能、高并发和模块化架构著称。对于想要扩展 Nginx 功能的开发者来说,编写 HTTP 模块是最常见的需求。而理解 ngx_http_module_t 结构体,正是掌握 Nginx HTTP 模块开发的第一道关卡。

这个结构体定义了模块在 Nginx 配置解析和初始化过程中的 8 个回调函数。本文将逐一剖析它们的作用、调用时机与协作关系,并结合一个可直接编译运行的完整模块示例,帮你彻底吃透这套机制。

2. ngx_http_module_t 结构体定义

首先,我们来看一下 ngx_http_module_t 在 Nginx 源码中的定义(位于 src/http/ngx_http_config.h 或相关头文件中):

typedef struct {
    ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);
    ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);

    void       *(*create_main_conf)(ngx_conf_t *cf);
    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);

    void       *(*create_srv_conf)(ngx_conf_t *cf);
    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);

    void       *(*create_loc_conf)(ngx_conf_t *cf);
    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;

这个结构体包含了 8 个函数指针,它们被分为三组,分别对应 Nginx 配置的三个层级:

  • Main(主配置):create_main_conf 和 init_main_conf
  • Server(虚拟主机配置):create_srv_conf 和 merge_srv_conf
  • Location(请求位置配置):create_loc_conf 和 merge_loc_conf

另外两个是全局的钩子函数:

  • preconfiguration:在解析配置之前调用。
  • postconfiguration:在解析配置之后调用。

3. 配置层级与回调函数分组

Nginx 的配置是分层的,理解这一点至关重要:

  1. Main(http 块):最顶层,影响所有虚拟主机。
  2. Server(server 块):定义虚拟主机,可以继承 Main 层的配置。
  3. Location(location 块):定义 URL 匹配规则,可以继承 Server 层的配置。

ngx_http_module_t 的回调函数正是围绕这三个层级设计的,用于创建和合并各层级的配置结构体。

3.1 三级配置在内存中的布局结构

理解 Nginx 三级配置在内存中的布局,是掌握配置继承机制的关键。Nginx 在解析配置时,会为每个 HTTP 模块在每个层级上分配独立的配置结构体,并通过 ngx_http_conf_ctx_t 将它们串联起来。

核心数据结构:ngx_http_conf_ctx_t

Nginx 使用 ngx_http_conf_ctx_t 来管理每个配置上下文中的模块配置指针数组。其定义如下(位于 src/http/ngx_http_config.h):

typedef struct {
    void        **main_conf;   // 指向所有 HTTP 模块的 main 配置指针数组
    void        **srv_conf;    // 指向所有 HTTP 模块的 srv 配置指针数组
    void        **loc_conf;    // 指向所有 HTTP 模块的 loc 配置指针数组
} ngx_http_conf_ctx_t;

这个结构体是理解内存布局的核心枢纽。每个 http、server、location 块在解析时,都会创建一个独立的 ngx_http_conf_ctx_t 实例。

内存布局全景图

下面通过一张内存布局图来直观展示三级配置的存储结构:

location 块 ngx_http_conf_ctx_t

server 块 ngx_http_conf_ctx_t

http 块 ngx_http_conf_ctx_t

create_main_conf 分配

create_srv_conf 分配

create_loc_conf 分配

create_srv_conf 分配

create_loc_conf 分配

create_loc_conf 分配

指向 http 块

指向 http 块

指向 server 块

main_conf[]
┌─────────────────────┐
│ [0] ngx_http_core_main_conf │
│ [1] ngx_http_hello_main_conf │
│ [2] ... (其他模块) │
└─────────────────────┘

srv_conf[]
┌─────────────────────┐
│ [0] ngx_http_core_srv_conf │
│ [1] ngx_http_hello_srv_conf │
│ [2] ... (其他模块) │
└─────────────────────┘

loc_conf[]
┌─────────────────────┐
│ [0] ngx_http_core_loc_conf │
│ [1] ngx_http_hello_loc_conf │
│ [2] ... (其他模块) │
└─────────────────────┘

main_conf[]
→ 指向 http 块的 main_conf(共享)

srv_conf[]
┌─────────────────────┐
│ [0] ngx_http_core_srv_conf │
│ [1] ngx_http_hello_srv_conf │
│ [2] ... (其他模块) │
└─────────────────────┘

loc_conf[]
┌─────────────────────┐
│ [0] ngx_http_core_loc_conf │
│ [1] ngx_http_hello_loc_conf │
│ [2] ... (其他模块) │
└─────────────────────┘

main_conf[]
→ 指向 http 块的 main_conf(共享)

srv_conf[]
→ 指向 server 块的 srv_conf(共享)

loc_conf[]
┌─────────────────────┐
│ [0] ngx_http_core_loc_conf │
│ [1] ngx_http_hello_loc_conf │
│ [2] ... (其他模块) │
└─────────────────────┘

关键要点
  1. main_conf 指针共享:所有 server 块和 location 块的 main_conf 指针都指向 http 块创建的 main_conf 数组。这意味着 Main 层级的配置在整个 HTTP 上下文中是全局唯一的。

  2. srv_conf 指针传递:location 块的 srv_conf 指针指向其所属 server 块的 srv_conf 数组。这就是 merge_loc_conf 中 prev 参数的来源——它指向的是所属 server 块的配置结构体。

  3. loc_conf 独立创建:每个 location 块都会通过 create_loc_conf 创建自己独立的 loc_conf 数组。这也是为什么 merge_loc_conf 需要将当前 location 的配置与 server 层的配置进行合并。

  4. 模块索引定位:每个模块在 main_conf、srv_conf、loc_conf 数组中的位置由 ngx_http_modules 数组中的索引决定。模块通过 ngx_http_get_module_main_conf、ngx_http_get_module_srv_conf、ngx_http_get_module_loc_conf 这三个宏来获取自己的配置结构体指针:

// 这三个宏的定义(简化版)
#define ngx_http_get_module_main_conf(r, module)  \
    (r->main_conf[module.ctx_index])
#define ngx_http_get_module_srv_conf(r, module)   \
    (r->srv_conf[module.ctx_index])
#define ngx_http_get_module_loc_conf(r, module)   \
    (r->loc_conf[module.ctx_index])
合并后的最终布局

以我们的 hello_string 指令为例,假设配置如下:

http {
    hello_string "Global Hello";          # Main 层级

    server {
        # 未设置 hello_string,继承 Main 层
        location /api {
            hello_string "API Hello";     # Location 层级
        }
        location /web {
            # 未设置 hello_string,继承 Server 层
        }
    }
}

合并后各层级配置结构体在内存中的最终状态为:

层级配置结构体hello_string 值来源
Mainngx_http_hello_main_conf_t"Global Hello"用户显式配置
Serverngx_http_hello_srv_conf_t"Global Hello"merge_srv_conf 从 Main 继承
Location /apingx_http_hello_loc_conf_t"API Hello"用户显式配置
Location /webngx_http_hello_loc_conf_t"Global Hello"merge_loc_conf 从 Server 继承

这种设计使得 Nginx 的配置既灵活又高效——每个层级只需存储自己真正需要的配置,未设置的字段通过合并函数从上层继承,避免了冗余存储。

下面通过流程图直观展示 hello_string 指令从 Main 到 Server 再到 Location 的继承与合并过程,清晰说明 merge_srv_conf 和 merge_loc_conf 函数的逻辑:

Location 层级(/web 块)

Location 层级(/api 块)

Server 层级(server 块)

Main 层级(http 块)

否

是

是('API Hello')

否

否

是

merge_srv_conf
prev = Main, conf = Server

merge_loc_conf
prev = Server, conf = Location

merge_loc_conf
prev = Server, conf = Location

ngx_http_hello_main_conf_t
hello_string = 'Global Hello'
(用户显式配置)

create_srv_conf 创建
ngx_http_hello_srv_conf_t
hello_string = NGX_CONF_UNSET

是否显式配置了
hello_string?

merge_srv_conf 从 Main 继承
hello_string = 'Global Hello'

最终 Server 配置
hello_string = 'Global Hello'

create_loc_conf 创建
ngx_http_hello_loc_conf_t
hello_string = NGX_CONF_UNSET

是否显式配置了
hello_string?

merge_loc_conf 从 Server 继承
hello_string = 'Global Hello'

最终 Location /api 配置
hello_string = 'API Hello'

create_loc_conf 创建
ngx_http_hello_loc_conf_t
hello_string = NGX_CONF_UNSET

是否显式配置了
hello_string?

merge_loc_conf 从 Server 继承
hello_string = 'Global Hello'

最终 Location /web 配置
hello_string = 'Global Hello'

4. 回调函数详解与代码实例

为了演示,我们假设要开发一个名为 ngx_http_hello_module 的模块,它有一个配置指令 hello_string,可以在不同层级设置。

4.1 配置结构体定义

首先,我们需要定义三个配置结构体,分别对应三个层级:

// 主配置(http 块)
typedef struct {
    ngx_str_t  hello_string; // 默认问候语
} ngx_http_hello_main_conf_t;

// 虚拟主机配置(server 块)
typedef struct {
    ngx_str_t  hello_string;
} ngx_http_hello_srv_conf_t;

// 位置配置(location 块)
typedef struct {
    ngx_str_t  hello_string;
} ngx_http_hello_loc_conf_t;

4.2 create_main_conf

  • 作用:为 http 块(Main 层级)创建模块的配置结构体。
  • 调用时机:在解析 http {} 块时,为每个 HTTP 模块调用一次。
  • 返回值:指向新创建的配置结构体的指针。如果失败,返回 NULL。
static void *
ngx_http_hello_create_main_conf(ngx_conf_t *cf)
{
    ngx_http_hello_main_conf_t  *conf;

    // 使用 ngx_pcalloc 在 Nginx 的内存池中分配并清零结构体
    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_hello_main_conf_t));
    if (conf == NULL) {
        return NULL;
    }

    // 设置一个标志,表示该字段尚未被设置
    conf->hello_string.len = NGX_CONF_UNSET_SIZE;
    conf->hello_string.data = NULL;

    return conf;
}

4.3 init_main_conf

  • 作用:初始化 Main 层级的配置。当所有配置指令都解析完毕后,此函数被调用,用于处理那些未在配置文件中显式设置的字段,赋予它们默认值。
  • 调用时机:在 create_main_conf 之后,所有 http 块内的指令解析完成后调用。
  • 返回值:成功返回 NGX_CONF_OK;失败返回描述错误的字符串。
static char *
ngx_http_hello_init_main_conf(ngx_conf_t *cf, void *conf)
{
    ngx_http_hello_main_conf_t  *mconf = conf;

    // 如果用户没有在 http 块中配置 hello_string,则设置默认值
    if (mconf->hello_string.len == NGX_CONF_UNSET_SIZE) {
        ngx_str_set(&mconf->hello_string, "Hello from Main!");
    }

    return NGX_CONF_OK;
}

4.4 create_srv_conf

  • 作用:为 server 块创建配置结构体。
  • 调用时机:在解析每个 server {} 块时调用。
  • 返回值:指向新创建的配置结构体的指针。
static void *
ngx_http_hello_create_srv_conf(ngx_conf_t *cf)
{
    ngx_http_hello_srv_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_hello_srv_conf_t));
    if (conf == NULL) {
        return NULL;
    }

    conf->hello_string.len = NGX_CONF_UNSET_SIZE;
    conf->hello_string.data = NULL;

    return conf;
}

4.5 merge_srv_conf

  • 作用:合并 Server 层级的配置。当一个 server 块没有显式设置某个指令时,它会从 Main 层级(http 块)继承配置。此函数负责实现这个继承逻辑。
  • 调用时机:在 create_srv_conf 之后,所有 server 块内的指令解析完成后调用。
  • 参数:
    • prev:指向 Main 层级配置结构体的指针(即 create_main_conf 创建的)。
    • conf:指向当前 server 块配置结构体的指针(即 create_srv_conf 创建的)。
  • 返回值:成功返回 NGX_CONF_OK;失败返回错误字符串。
static char *
ngx_http_hello_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_hello_srv_conf_t *prev = parent;
    ngx_http_hello_srv_conf_t *conf = child;

    // ngx_conf_merge_str_value 是 Nginx 提供的便捷宏
    // 如果 conf->hello_string 未被设置(值为 NGX_CONF_UNSET),则使用 prev 的值
    ngx_conf_merge_str_value(conf->hello_string, prev->hello_string, "Hello from Server!");

    return NGX_CONF_OK;
}

4.6 create_loc_conf

  • 作用:为 location 块创建配置结构体。
  • 调用时机:在解析每个 location {} 块时调用。
  • 返回值:指向新创建的配置结构体的指针。
static void *
ngx_http_hello_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_hello_loc_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_hello_loc_conf_t));
    if (conf == NULL) {
        return NULL;
    }

    conf->hello_string.len = NGX_CONF_UNSET_SIZE;
    conf->hello_string.data = NULL;

    return conf;
}

4.7 merge_loc_conf

  • 作用:合并 Location 层级的配置。当一个 location 块没有设置某个指令时,它会从所属的 server 块继承配置。
  • 调用时机:在 create_loc_conf 之后,所有 location 块内的指令解析完成后调用。
  • 参数:
    • prev:指向所属 server 块配置结构体的指针。
    • conf:指向当前 location 块配置结构体的指针。
  • 返回值:成功返回 NGX_CONF_OK;失败返回错误字符串。
static char *
ngx_http_hello_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_hello_loc_conf_t *prev = parent;
    ngx_http_hello_loc_conf_t *conf = child;

    // 如果 location 块没有设置 hello_string,则从 server 块继承
    ngx_conf_merge_str_value(conf->hello_string, prev->hello_string, "Hello from Location!");

    return NGX_CONF_OK;
}

4.8 preconfiguration

  • 作用:在 Nginx 开始解析 http 块内的配置指令之前调用。通常用于注册新的配置指令、变量或向其他模块暴露接口。
  • 调用时机:在所有 create_*_conf 函数之前,解析 http 块时调用。
  • 返回值:成功返回 NGX_OK;失败返回 NGX_ERROR。
static ngx_command_t ngx_http_hello_commands[] = {
    {
        ngx_string("hello_string"),
        NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
        ngx_conf_set_str_slot,
        NGX_HTTP_MAIN_CONF_OFFSET | NGX_HTTP_SRV_CONF_OFFSET | NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_hello_main_conf_t, hello_string), // 注意:这里只是示例,实际需要更复杂的处理
        NULL
    },
    ngx_null_command
};

static ngx_int_t
ngx_http_hello_preconfiguration(ngx_conf_t *cf)
{
    // 在这里可以注册新的变量
    ngx_http_variable_t  *v;

    v = ngx_http_add_variable(cf, &ngx_string("hello_variable"), 0);
    if (v == NULL) {
        return NGX_ERROR;
    }

    v->get_handler = ngx_http_hello_variable_get_handler;
    v->data = 0;

    return NGX_OK;
}

4.9 postconfiguration

  • 作用:在所有配置解析完成、所有 merge_*_conf 函数调用之后调用。这是模块初始化自身处理函数的最后时机,常用于将模块的 handler 挂载到 Nginx 的请求处理阶段。
  • 调用时机:在 init_main_conf 和所有 merge_*_conf 函数之后调用。
  • 返回值:成功返回 NGX_OK;失败返回 NGX_ERROR。
static ngx_int_t
ngx_http_hello_postconfiguration(ngx_conf_t *cf)
{
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;

    // 获取核心模块的 main 配置
    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

    // 在 NGX_HTTP_CONTENT_PHASE 阶段添加我们的处理函数
    h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_http_hello_handler;

    return NGX_OK;
}

5. 实战:完整模块示例

下面提供一个可直接编译运行的 ngx_http_hello_module 完整代码,包含指令解析、请求处理函数和编译配置(config 文件)。将以下文件放入 Nginx 源码的 modules/ngx_http_hello_module/ 目录下即可编译。

5.1 模块源文件:ngx_http_hello_module.c

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

/* ========== 配置结构体定义 ========== */

typedef struct {
    ngx_str_t  hello_string;
} ngx_http_hello_main_conf_t;

typedef struct {
    ngx_str_t  hello_string;
} ngx_http_hello_srv_conf_t;

typedef struct {
    ngx_str_t  hello_string;
} ngx_http_hello_loc_conf_t;

/* ========== 指令解析函数 ========== */

static char *
ngx_http_hello_string(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_str_t  *value = cf->args->elts;
    ngx_str_t  *field;

    /* 根据指令所在层级,获取对应的配置字段 */
    if (cmd->offset == NGX_HTTP_MAIN_CONF_OFFSET) {
        field = &((ngx_http_hello_main_conf_t *) conf)->hello_string;
    } else if (cmd->offset == NGX_HTTP_SRV_CONF_OFFSET) {
        field = &((ngx_http_hello_srv_conf_t *) conf)->hello_string;
    } else {
        field = &((ngx_http_hello_loc_conf_t *) conf)->hello_string;
    }

    *field = value[1];
    return NGX_CONF_OK;
}

/* ========== 指令定义 ========== */

static ngx_command_t ngx_http_hello_commands[] = {
    {
        ngx_string("hello_string"),
        NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
        ngx_http_hello_string,
        NGX_HTTP_MAIN_CONF_OFFSET | NGX_HTTP_SRV_CONF_OFFSET | NGX_HTTP_LOC_CONF_OFFSET,
        0,
        NULL
    },
    ngx_null_command
};

/* ========== 配置创建与合并回调 ========== */

static void *
ngx_http_hello_create_main_conf(ngx_conf_t *cf)
{
    ngx_http_hello_main_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_hello_main_conf_t));
    if (conf == NULL) {
        return NULL;
    }

    conf->hello_string.len = NGX_CONF_UNSET_SIZE;
    conf->hello_string.data = NULL;

    return conf;
}

static char *
ngx_http_hello_init_main_conf(ngx_conf_t *cf, void *conf)
{
    ngx_http_hello_main_conf_t  *mconf = conf;

    if (mconf->hello_string.len == NGX_CONF_UNSET_SIZE) {
        ngx_str_set(&mconf->hello_string, "Hello from Main!");
    }

    return NGX_CONF_OK;
}

static void *
ngx_http_hello_create_srv_conf(ngx_conf_t *cf)
{
    ngx_http_hello_srv_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_hello_srv_conf_t));
    if (conf == NULL) {
        return NULL;
    }

    conf->hello_string.len = NGX_CONF_UNSET_SIZE;
    conf->hello_string.data = NULL;

    return conf;
}

static char *
ngx_http_hello_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_hello_srv_conf_t *prev = parent;
    ngx_http_hello_srv_conf_t *conf = child;

    ngx_conf_merge_str_value(conf->hello_string, prev->hello_string, "Hello from Server!");

    return NGX_CONF_OK;
}

static void *
ngx_http_hello_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_hello_loc_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_hello_loc_conf_t));
    if (conf == NULL) {
        return NULL;
    }

    conf->hello_string.len = NGX_CONF_UNSET_SIZE;
    conf->hello_string.data = NULL;

    return conf;
}

static char *
ngx_http_hello_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_hello_loc_conf_t *prev = parent;
    ngx_http_hello_loc_conf_t *conf = child;

    ngx_conf_merge_str_value(conf->hello_string, prev->hello_string, "Hello from Location!");

    return NGX_CONF_OK;
}

/* ========== 请求处理函数 ========== */

static ngx_int_t
ngx_http_hello_handler(ngx_http_request_t *r)
{
    ngx_int_t                    rc;
    ngx_buf_t                   *b;
    ngx_chain_t                  out;
    ngx_http_hello_loc_conf_t   *hlcf;
    u_char                      *response;
    size_t                       len;

    /* 只处理 GET 和 HEAD 请求 */
    if (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD))) {
        return NGX_HTTP_NOT_ALLOWED;
    }

    /* 丢弃请求体 */
    rc = ngx_http_discard_request_body(r);
    if (rc != NGX_OK) {
        return rc;
    }

    /* 获取当前 location 的配置 */
    hlcf = ngx_http_get_module_loc_conf(r, ngx_http_hello_module);

    /* 构造响应内容 */
    len = hlcf->hello_string.len + 1; /* +1 for newline */
    response = ngx_pnalloc(r->pool, len);
    if (response == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    ngx_memcpy(response, hlcf->hello_string.data, hlcf->hello_string.len);
    response[hlcf->hello_string.len] = '\n';

    /* 设置响应头 */
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = len;
    r->headers_out.content_type.len = sizeof("text/plain") - 1;
    r->headers_out.content_type.data = (u_char *) "text/plain";

    /* 构造输出 buffer */
    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if (b == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    b->pos = response;
    b->last = response + len;
    b->memory = 1;     /* 内容在内存中 */
    b->last_buf = 1;   /* 这是最后一个 buffer */

    out.buf = b;
    out.next = NULL;

    /* 发送响应头 */
    rc = ngx_http_send_header(r);
    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

    /* 发送响应体 */
    return ngx_http_output_filter(r, &out);
}

/* ========== preconfiguration:注册变量 ========== */

static ngx_int_t
ngx_http_hello_variable_get_handler(ngx_http_request_t *r,
    ngx_http_variable_t *v, uintptr_t data)
{
    ngx_http_hello_loc_conf_t  *hlcf;

    hlcf = ngx_http_get_module_loc_conf(r, ngx_http_hello_module);
    v->valid = 1;
    v->no_cacheable = 0;
    v->not_found = 0;
    v->data = hlcf->hello_string;

    return NGX_OK;
}

static ngx_int_t
ngx_http_hello_preconfiguration(ngx_conf_t *cf)
{
    ngx_http_variable_t  *v;

    v = ngx_http_add_variable(cf, &ngx_string("hello_variable"), 0);
    if (v == NULL) {
        return NGX_ERROR;
    }

    v->get_handler = ngx_http_hello_variable_get_handler;
    v->data = 0;

    return NGX_OK;
}

/* ========== postconfiguration:挂载 handler ========== */

static ngx_int_t
ngx_http_hello_postconfiguration(ngx_conf_t *cf)
{
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

    h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_http_hello_handler;

    return NGX_OK;
}

/* ========== 模块上下文与模块定义 ========== */

static ngx_http_module_t ngx_http_hello_module_ctx = {
    ngx_http_hello_preconfiguration,      /* preconfiguration */
    ngx_http_hello_postconfiguration,     /* postconfiguration */

    ngx_http_hello_create_main_conf,      /* create main configuration */
    ngx_http_hello_init_main_conf,        /* init main configuration */

    ngx_http_hello_create_srv_conf,       /* create server configuration */
    ngx_http_hello_merge_srv_conf,        /* merge server configuration */

    ngx_http_hello_create_loc_conf,       /* create location configuration */
    ngx_http_hello_merge_loc_conf         /* merge location configuration */
};

ngx_module_t ngx_http_hello_module = {
    NGX_MODULE_V1,
    &ngx_http_hello_module_ctx,           /* module context */
    ngx_http_hello_commands,              /* 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
};

5.2 编译配置文件:config

在模块目录下创建 config 文件,内容如下:

ngx_addon_name=ngx_http_hello_module
HTTP_MODULES="$HTTP_MODULES ngx_http_hello_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_hello_module.c"

5.3 编译与测试

将上述两个文件放入 Nginx 源码目录下的 modules/ngx_http_hello_module/ 目录,然后执行以下命令编译:

# 在 Nginx 源码根目录下执行
./configure --add-module=modules/ngx_http_hello_module
make -j$(nproc)
sudo make install

编译完成后,在 nginx.conf 中添加如下配置:

http {
    hello_string "Hello, Nginx Module World!";

    server {
        listen       80;
        server_name  localhost;

        location /hello {
            hello_string "Hello from /hello location!";
        }

        location / {
            # 使用默认的 hello_string
        }
    }
}

启动 Nginx 后,测试效果:

# 访问 / 路径,将输出 Main 层级的默认问候语
curl http://localhost/
# 输出: Hello, Nginx Module World!

# 访问 /hello 路径,将输出 location 层级的问候语
curl http://localhost/hello
# 输出: Hello from /hello location!

这个完整的模块示例涵盖了指令解析、配置继承、请求处理函数、变量注册和 handler 挂载等核心环节,你可以直接编译运行来验证 Nginx 模块开发的全流程。

5.1 完整的模块定义

最后,我们将所有回调函数组合起来,定义我们的模块:

static ngx_http_module_t ngx_http_hello_module_ctx = {
    ngx_http_hello_preconfiguration,      /* preconfiguration */
    ngx_http_hello_postconfiguration,     /* postconfiguration */

    ngx_http_hello_create_main_conf,      /* create main configuration */
    ngx_http_hello_init_main_conf,        /* init main configuration */

    ngx_http_hello_create_srv_conf,       /* create server configuration */
    ngx_http_hello_merge_srv_conf,        /* merge server configuration */

    ngx_http_hello_create_loc_conf,       /* create location configuration */
    ngx_http_hello_merge_loc_conf         /* merge location configuration */
};

ngx_module_t ngx_http_hello_module = {
    NGX_MODULE_V1,
    &ngx_http_hello_module_ctx,           /* module context */
    ngx_http_hello_commands,              /* 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
};

6. 总结

ngx_http_module_t 结构体是 Nginx HTTP 模块开发的核心。通过本文的讲解和代码实例,你应该已经掌握了这 8 个回调函数的作用和调用时机:

  • create_*_conf:负责为各层级分配配置结构体内存。
  • init_main_conf:初始化 Main 层级的默认值。
  • merge_*_conf:实现配置的继承机制,是 Nginx 配置灵活性的关键。
  • preconfiguration:在配置解析前注册指令和变量。
  • postconfiguration:在配置解析后挂载请求处理函数。

理解并正确实现这些回调,是编写健壮、可配置的 Nginx 模块的第一步。希望这篇教程能帮助你开启 Nginx 模块开发之旅。

Nginx核心结构体ngx_http_conf_addr_t解析与应用 在Web服务器架构中,监听地址管理是实现高性能网络服务的关键技术。Nginx通过ngx_http_conf_addr_t结构体封装了服务器地址配置的核心参数,包括IP绑定、端口监听、SSL/TLS配置等关键信息。该结构体采用哈希表优化虚拟主机路由效率,支持HTTP/2等现代协议特性。理解其内存管理机制和配置合并原理,能帮助开发者优化Nginx的多虚拟主机场景性能,特别是在高并发环境下处理HTTPS请求时。通过分析结构体成员如ngx_http_listen_opt_t,可以深入掌握TCP参数调优技巧,实现连接 阅读详情

相关推荐

Nginx C模块开发实战:从echo模块深入HTTP处理机制

Nginx C模块是实现高性能、低延迟、高确定性网络逻辑的核心技术路径。其本质是将业务逻辑深度嵌入Nginx事件驱动引擎,依托内存池管理、缓冲区链表(ngx_chain_t)、配置阶段化(preconf/postconf)和HTTP请求生命周期钩子等底层机制,规避解释器开销、进程创建与GC抖动。相比Lua、Python或反向代理方案,C模块在API鉴权、边缘动态注入、安全审计日志等场景中可达成微秒级延迟与零拷贝I/O。本文以可复现的echo模块为入口,解析ngx_module_t结构体、handler执行流

diaozhiwa5526的博客 461

nginx的http模块配置接口以及http框架配置内存布局

http模块是nginx中数量最多,也是配置最复杂的模块。http模块的配置最多可以分为三级,分别是http级别,server级别和location级别。nginx为http模块定义了一组接口,用于模块创建,设置,合并各级别的配置项。在配置读取阶段一共有9个接口,在不同阶段,不同位置被调用。其中8个在ngx_http_module_t中定义,这8个回调是模块级别的,还有1个是指令级别的,由模块的指

qiuhui00的专栏 794

《深入理解Nginx》笔记之 ngx_mail相关结构体

Nginx mail相关模块结构体ngx_mail_module_t      这是mail模块的抽象接口,即ngx_module_t中的ctx成员所指向的mail模块特有接口。每一个mail模块都会实现自己的ngx_mail_module_t结构体。 typedef struct { // POP3 STMP IMAP邮件模块提取出的通用接口 ngx_mail_protocol

猫和鱼爪的鸟窝 1478

Nginx的http启动流程分析

Nginx 启动http Server流程:ngx_module_t、ngx_http_commands、ngx_http_block、ngx_http_optimize_servers、ngx_http_init_listening、ngx_http_add_listening。

Lion_Long的博客 4983

Nginx升级后模块报错?手把手教你修复ngx_http_xslt_filter_module.so版本问题

本文详细解析了Nginx升级后`ngx_http_xslt_filter_module.so`版本不匹配的报错问题,提供了从诊断到修复的完整指南。包括版本冲突机制解析、三种修复方案(完整重编译、动态模块热加载、版本伪装)及预防措施,帮助运维人员快速解决模块兼容性问题,确保服务稳定运行。

weixin_30897233的博客 446

MuJoCo新手必看:5分钟搞定你的第一个机器人仿真(附Python代码)

本文为零基础新手提供了一份详尽的MuJoCo物理仿真引擎入门指南。从环境搭建、MJCF模型文件解析,到编写Python控制代码,手把手教你5分钟内创建并运行第一个机器人仿真。内容涵盖双摆模型控制与自定义四轮小车构建,帮助你快速掌握这一高性能开源工具,迈出机器人学与强化学习仿真的第一步。

weixin_29032489的博客 655

ngx_http_module_t

【代码】ngx_http_module_t。

Edidaughter的博客 361

反激开关电源变压器设计实例讲解(定明芳)

内容来自《反激变压器的设计》(定明芳主讲)。 定名芳老师主要讲了10步,最后应该还有一步,线径的选择已经绕制方式。这里进行了记录整理,把一些公式来源进行了推导。所有的设计都是基于理论计算。 1.确定电源规格 2.确定工作频率和最大占空比 3.计算匝比(利用伏秒平衡原则) 此处插入解释一下伏秒平衡,根源是磁通量平衡。该原则可以用来检查MOS管耐压是否够。 4.计算初级线圈峰值电流 基于CCM模式计算的。ip1是开关管要关闭时的峰值电流,ip2是开关管开始导通时的起始电流。因为时CCM模式,所以,ip

ydgd118的博客 5万+

Nginx模块开发系列1--预备知识

Nginx模块开发:ngx_command_t、ngx_module_t、ngx_http_module_t

weixin_49406295的博客 3723

Ai数字人直播系统源码部署

虚拟形象定制,直播内容智能生成,多语言支持,智能互动,声音克隆,24 小时不间断直播,多平台直播,智能数据分析,实时情感识别,视频合成,Ai数字人直播系统源码出售,Ai数字人直播系统源码部署实现 AI 数字人 24 小时不间断直播功能是一个较为复杂的任务,涉及到多个方面,如直播流的推送、内容的持续生成、系统的稳定性保障等。以下为你提供一个简化的实现思路和示例代码,借助 Python 结合 FFmpeg 工具来模拟 24 小时不间断直播。

weixin_ggwwsscc的博客 1256

【学习笔记-QGIS】 QGIS从零快速上手

安装文章参考:https://zhuanlan.zhihu.com/p/370633306。

Xcong_Zhu的博客 1万+

nginx-gridfs源码解析:核心模块ngx_http_gridfs_module.c详解

nginx-gridfs是一个强大的Nginx模块,它允许直接从MongoDB的GridFS存储系统提供文件服务,实现了高效的文件存储与分发解决方案。本文将深入解析其核心模块`ngx_http_gridfs_module.c`的实现原理,帮助开发者理解其工作机制和关键技术点。 ## 模块整体架构与核心功能 `ngx_http_gridfs_module.c`作为nginx-gridfs项目的核

gitblog_00110的博客 977

YOLOv8改进 | 主干网络 | 增加网络结构增强小目标检测能力【独家创新——附结构图】

YOLOv8改进,小目标检测,yolov8

kay_545 6020

Traefik 插件机制详解:experimental.plugins 安装配置的启用、校验与源码级实现原理

本文围绕 Traefik 的实验性安装配置项 `plugins`(及其配套的 `localPlugins`)展开,完整覆盖插件的启用方式(YAML/TOML/CLI 三种配置形态)、全部配置字段及取值约束、本地插件的使用规则,并基于开源仓库源码深入解析插件的下载、哈希校验、解包、运行时(Yaegi 与 Wasm)分发等底层流程。读完本文,你可以安全地在 Traefik 实例上启用远程或本地插件,并

gitblog_00002的博客 469

PINN驱动的3D不可压缩流场重建——从稀疏2D2C观测到完整物理场反演——含源码实现

多尺度融合策略在此发挥作用:高分辨率的2D2C数据用于约束平面内的精细结构,而低分辨率的3D3C数据提供第三维度的粗尺度信息。迁移学习架构在此发挥关键作用。这种策略在湍流封闭模型构建中尤为有效,其中RANS提供的平均流场与DNS的雷诺应力数据融合,显著提升了分离流的预测精度。在稀疏数据场景下,注意力权重根据观测数据的分布动态调整,强化数据缺失区域的物理约束,同时避免过拟合于观测点。这种机制与自适应采样的结合,实现了计算效率与重建精度的平衡,在仅保留约百分之十关键节点的情况下,仍能保持高物理一致性。

u012133341的博客 136

Amesim应用篇-电芯等效电路模型标定

为了使计算模型更加准确,在有电芯实验测试数据的情况下,依据现有的实验数据对Amesim中的电池等效电路模型进行标定。标定的目的是为了获得更加符合项目实际情况的电芯等效电路模型,标定完的电芯可以用于搭建PACK模型,也可以用于其他虚拟实验。本文以充电标定为例,进行介绍。放电标定与充电一致。

2201_75375391的博客 1213

headers-more-nginx-module:Nginx HTTP头管理的终极解决方案

在构建现代Web应用时,Nginx作为高性能的反向代理服务器,其HTTP头管理能力直接影响着应用的安全性、性能和用户体验。headers-more-nginx-module是一个功能强大的Nginx扩展模块,它彻底改变了传统的头管理方式,让你能够灵活地设置、修改和清除HTTP请求和响应头。 ## 🚀 快速入门:5分钟掌握核心功能 headers-more-nginx-module提供了比标准

gitblog_00233的博客 887

《从零学会 CST 仿真全流程》专栏简介

本专栏系统讲解CSTPCBStudio与MWSStudio在高速PCB仿真中的应用,涵盖SI/PI仿真全流程。重点介绍CSTPCBStudio用于板级信号完整性分析,CSTMWS用于3D高频电磁仿真,以及电缆、低频、多物理场等配套工具。课程以实战为导向,通过USB3.1、LPDDR4等案例,手把手教学软件操作与工程应用,适合硬件工程师、EMC工程师及电子专业学生学习。内容注重实操,弱化理论,帮助快速掌握仿真核心技能。

一只豌豆象的博客 687

nginx框架---模块结构体成员说明

nginx模块结构体成员说明

小小东西的博客 446

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Nginx 模块自主开发七:源码剖析整个Nginx框架

在博客提到的Nginx 模块自主开发四: 模块数据结构一个很重要的数据结构ngx_module_s,所有的模块都是用这个结构体来表示。ngx_module_t结构体 模块又分为ngx_core_module_t和ngx_xxx_module_t,而在ngx_module_t中会包含这两个结构,只不过不同类的模块包含不同的结构,一般会 用ctx表示,ngx_module_t又用type这个域用来表

youbingchen的博客 1万+

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

nginx:让nginx运行自己的编写第三方模块

缘由 今天阅读 深入理解ngnix 的第三章,需要完成一个第三方模块,然后添加到nginx里面去,然后我在对应的网页访问的时候就可以直接调用我写的第三方模块,这个第三方模块只是发浏览器发了一个hello world而已。 流程 1、P72:建一个ngx_http_mytest_module.c的文件,写我的新添加的模块的代码,因为还是比较简单的,所以用一个文件就可以了。2、P79

zy825316的专栏 1701

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

深入理解nginx chap3 开发一个简单的HTTP模块

1. 如何调用HTTP模块本质:worker 进程会在一个for 循环里面反复调用事件模块检测网络事件。 2. 基本数据结构 命名规则: ngx_http_xxx_module.c 2.1 整形封装 ngx_int_t 封装了有符号整形, 而 ngx_uint_t 封装了无符号整形 2.2 ngx_str_t ngx_str_t 封装了字符串typedef struct { size_t

zhyh1435589631的专栏 5465

深入理解nginx-auth-ldap原理:从代码实现看Nginx模块开发

nginx-auth-ldap是一个功能强大的Nginx模块,它允许通过LDAP协议对用户进行身份验证。本文将深入剖析其实现原理,带您了解Nginx模块开发的核心概念和关键技术点。 ## Nginx模块基础架构 Nginx模块开发遵循特定的架构模式,nginx-auth-ldap也不例外。从[ngx_http_auth_ldap_module.c](https://link.gitcode.c

gitblog_00240的博客 344

nginx模块开发0(hello world)

nginx模块开发

sxc1989的博客 525

深入理解Nginx:(4) 解析 HTTP 配置的流程

文章目录解析 HTTP 配置的流程参考 解析 HTTP 配置的流程 上图是 HTTP 框架解析配置项的示意流程图(图中包含 HTTP 框架的两个主要模块:ngx_http_module 和 ngx_http_core_module 模块)具体解析流程如下: 上图中的主循环是指 Nginx 进程的主循环,主循环只有调用配置文件解析器才能解析 nginx.conf 文件(这里的 “主循环” 是解析全部配置文件的循环代码); 当发现配置文件中包含 http{} 关键字时,HTTP 框架开始启动; HTTP 框

wozaizhe55 347

headers-more-nginx-module终极指南:完全掌控Nginx HTTP头的高级技巧

headers-more-nginx-module是Nginx服务器中功能最强大的HTTP头管理扩展模块,它突破了标准headers模块的限制,让你能够完全掌控请求和响应头。作为Nginx HTTP头管理的终极解决方案,这个模块提供了比标准headers模块更强大、更灵活的HTTP头管理能力。无论你是想要隐藏服务器信息、优化缓存策略,还是实现复杂的跨域请求处理,headers-more-nginx

gitblog_00476的博客 474
上一篇: LM358经典电路设计全解析
下一篇: 4. Nginx 模块开发:深入理解 ngx_command_t 结构体与配置指令解析
chexus
博客等级 码龄8年 3粉丝 33原创
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

chexus

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值