开发指南

简介
     代码布局
     包含文件
     整数
     常见返回码
     错误处理
字符串
     概述
     格式化
     数值转换
     正则表达式
时间
容器
     数组
     列表
     队列
     红黑树
     哈希表
内存管理
     
     
     共享内存
日志记录
周期
缓冲区
网络
     连接
事件
     事件
     I/O 事件
     定时器事件
     发布事件
     事件循环
进程
线程
模块
     添加新模块
     核心模块
     配置指令
HTTP
     连接
     请求
     配置
     阶段
     变量
     复杂值
     请求重定向
     子请求
     请求结束
     请求体
     请求体过滤器
     响应
     响应体
     响应体过滤器
     构建过滤器模块
     缓冲区重用
     负载均衡
示例
代码风格
     一般规则
     文件
     注释
     预处理器
     类型
     变量
     函数
     表达式
     条件语句和循环
     标签
调试内存问题
常见陷阱
     编写 C 模块
     C 字符串
     全局变量
     手动内存管理
     线程
     阻塞库
     HTTP 请求到外部服务

简介

代码布局

包含文件

以下两个 #include 语句必须出现在每个 Nginx 文件的开头

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

此外,HTTP 代码应该包含

#include <ngx_http.h>

邮件代码应该包含

#include <ngx_mail.h>

流代码应该包含

#include <ngx_stream.h>

整数

出于通用目的,Nginx 代码使用两种整数类型,ngx_int_tngx_uint_t,它们分别是 intptr_tuintptr_t 的类型定义。

常见返回码

大多数 Nginx 函数返回以下代码

错误处理

ngx_errno 宏返回最后一个系统错误代码。在 POSIX 平台上映射到 errno,在 Windows 上映射到 GetLastError() 调用。ngx_socket_errno 宏返回最后一个套接字错误号。与 ngx_errno 宏一样,它在 POSIX 平台上映射到 errno。在 Windows 上映射到 WSAGetLastError() 调用。连续多次访问 ngx_errnongx_socket_errno 的值可能会导致性能问题。如果错误值可能被多次使用,请将其存储在 ngx_err_t 类型的局部变量中。要设置错误,请使用 ngx_set_errno(errno)ngx_set_socket_errno(errno) 宏。

ngx_errnongx_socket_errno 的值可以传递给日志记录函数 ngx_log_error()ngx_log_debugX(),在这种情况下,系统错误文本将添加到日志消息中。

使用 ngx_errno 的示例

ngx_int_t
ngx_my_kill(ngx_pid_t pid, ngx_log_t *log, int signo)
{
    ngx_err_t  err;

    if (kill(pid, signo) == -1) {
        err = ngx_errno;

        ngx_log_error(NGX_LOG_ALERT, log, err, "kill(%P, %d) failed", pid, signo);

        if (err == NGX_ESRCH) {
            return 2;
        }

        return 1;
    }

    return 0;
}

字符串

概述

对于 C 字符串,Nginx 使用无符号字符类型指针 u_char *

Nginx 字符串类型 ngx_str_t 定义如下

typedef struct {
    size_t      len;
    u_char     *data;
} ngx_str_t;

len 字段保存字符串长度,data 保存字符串数据。保存在 ngx_str_t 中的字符串在 len 字节之后可能以 null 结尾,也可能不以 null 结尾。在大多数情况下,它不以 null 结尾。但是,在代码的某些部分(例如,在解析配置时),已知 ngx_str_t 对象以 null 结尾,这简化了字符串比较,并使将字符串传递给系统调用变得更容易。

Nginx 中的字符串操作声明在 src/core/ngx_string.h 中。其中一些是标准 C 函数的包装器

其他字符串函数是 Nginx 特定的

以下函数执行大小写转换和比较

以下宏简化了字符串初始化

格式化

以下格式化函数支持 Nginx 特定类型

这些函数支持的完整格式选项列表位于 src/core/ngx_string.c 中。其中一些是

您可以在大多数类型前添加 u 以使其无符号。要将输出转换为十六进制,请使用 Xx

例如

u_char      buf[NGX_INT_T_LEN];
size_t      len;
ngx_uint_t  n;

/* set n here */

len = ngx_sprintf(buf, "%ui", n) — buf;

数值转换

Nginx 中实现了多个用于数值转换的函数。前四个函数都将给定长度的字符串转换为指示类型的正整数。它们在出错时返回 NGX_ERROR

还有两个额外的数值转换函数。与前四个函数一样,它们在出错时返回 NGX_ERROR

正则表达式

Nginx 中的正则表达式接口是 PCRE 库的包装器。相应的头文件是 src/core/ngx_regex.h

要将正则表达式用于字符串匹配,首先需要对其进行编译,这通常在配置阶段完成。请注意,由于 PCRE 支持是可选的,因此所有使用该接口的代码都必须受到周围 NGX_PCRE 宏的保护

#if (NGX_PCRE)
ngx_regex_t          *re;
ngx_regex_compile_t   rc;

u_char                errstr[NGX_MAX_CONF_ERRSTR];

ngx_str_t  value = ngx_string("message (\\d\\d\\d).*Codeword is '(?<cw>\\w+)'");

ngx_memzero(&rc, sizeof(ngx_regex_compile_t));

rc.pattern = value;
rc.pool = cf->pool;
rc.err.len = NGX_MAX_CONF_ERRSTR;
rc.err.data = errstr;
/* rc.options can be set to NGX_REGEX_CASELESS */

if (ngx_regex_compile(&rc) != NGX_OK) {
    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err);
    return NGX_CONF_ERROR;
}

re = rc.regex;
#endif

编译成功后,ngx_regex_compile_t 结构中的 capturesnamed_captures 字段分别包含在正则表达式中找到的所有捕获和命名捕获的数量。

然后,编译后的正则表达式可用于与字符串匹配

ngx_int_t  n;
int        captures[(1 + rc.captures) * 3];

ngx_str_t input = ngx_string("This is message 123. Codeword is 'foobar'.");

n = ngx_regex_exec(re, &input, captures, (1 + rc.captures) * 3);
if (n >= 0) {
    /* string matches expression */

} else if (n == NGX_REGEX_NO_MATCHED) {
    /* no match was found */

} else {
    /* some error */
    ngx_log_error(NGX_LOG_ALERT, log, 0, ngx_regex_exec_n " failed: %i", n);
}

ngx_regex_exec() 的参数是已编译的正则表达式 re、要匹配的字符串 input、一个可选的整数数组以保存找到的任何 captures 以及数组的 sizecaptures 数组的大小必须是 3 的倍数,这是 PCRE API 所要求的。在本例中,大小是从捕获总数加上匹配字符串本身计算出来的。

如果存在匹配项,则可以按如下方式访问捕获项

u_char     *p;
size_t      size;
ngx_str_t   name, value;

/* all captures */
for (i = 0; i < n * 2; i += 2) {
    value.data = input.data + captures[i];
    value.len = captures[i + 1] — captures[i];
}

/* accessing named captures */

size = rc.name_size;
p = rc.names;

for (i = 0; i < rc.named_captures; i++, p += size) {

    /* capture name */
    name.data = &p[2];
    name.len = ngx_strlen(name.data);

    n = 2 * ((p[0] << 8) + p[1]);

    /* captured value */
    value.data = &input.data[captures[n]];
    value.len = captures[n + 1] — captures[n];
}

ngx_regex_exec_array() 函数接受一个 ngx_regex_elt_t 元素数组(它们只是编译后的正则表达式,并带有关联的名称),一个要匹配的字符串和一个日志。该函数将数组中的表达式应用于字符串,直到找到匹配项或没有剩余表达式。当有匹配项时,返回值为 NGX_OK,否则为 NGX_DECLINED,或者在发生错误时为 NGX_ERROR

时间

ngx_time_t 结构使用三种不同的类型来表示时间:秒、毫秒和 GMT 偏移量。

typedef struct {
    time_t      sec;
    ngx_uint_t  msec;
    ngx_int_t   gmtoff;
} ngx_time_t;

ngx_tm_t 结构是 UNIX 平台上 struct tm 和 Windows 平台上 SYSTEMTIME 的别名。

要获取当前时间,通常只需访问一个可用的全局变量,该变量以所需的格式表示缓存的时间值。

可用的字符串表示形式如下:

ngx_time()ngx_timeofday() 宏以秒为单位返回当前时间值,是访问缓存时间值的推荐方式。

要显式获取时间,请使用 ngx_gettimeofday(),它会更新其参数(指向 struct timeval 的指针)。当 nginx 从系统调用返回到事件循环时,始终会更新时间。要立即更新时间,请调用 ngx_time_update(),或者如果在信号处理程序上下文中更新时间,则调用 ngx_time_sigsafe_update()

以下函数将 time_t 转换为指示的分解时间表示形式。每对中的第一个函数将 time_t 转换为 ngx_tm_t,第二个函数(带有 _libc_ 中缀)转换为 struct tm

ngx_http_time(buf, time) 函数返回适合在 HTTP 头部中使用的字符串表示形式(例如,"Mon, 28 Sep 1970 06:00:00 GMT")。ngx_http_cookie_time(buf, time) 函数返回适合 HTTP Cookie 的字符串表示形式("Thu, 31-Dec-37 23:55:55 GMT")。

容器

数组

nginx 数组类型 ngx_array_t 定义如下:

typedef struct {
    void        *elts;
    ngx_uint_t   nelts;
    size_t       size;
    ngx_uint_t   nalloc;
    ngx_pool_t  *pool;
} ngx_array_t;

数组的元素在 elts 字段中可用。nelts 字段保存元素的数量。size 字段保存单个元素的大小,并在数组初始化时设置。

使用 ngx_array_create(pool, n, size) 调用在池中创建数组,使用 ngx_array_init(array, pool, n, size) 调用初始化已分配的数组对象。

ngx_array_t  *a, b;

/* create an array of strings with preallocated memory for 10 elements */
a = ngx_array_create(pool, 10, sizeof(ngx_str_t));

/* initialize string array for 10 elements */
ngx_array_init(&b, pool, 10, sizeof(ngx_str_t));

使用以下函数向数组添加元素:

如果当前分配的内存量不足以容纳新元素,则会分配一个新的内存块,并将现有元素复制到其中。新内存块通常是现有内存块的两倍大。

s = ngx_array_push(a);
ss = ngx_array_push_n(&b, 3);

列表

在 nginx 中,列表是一系列数组,针对插入可能大量项目进行了优化。ngx_list_t 列表类型定义如下:

typedef struct {
    ngx_list_part_t  *last;
    ngx_list_part_t   part;
    size_t            size;
    ngx_uint_t        nalloc;
    ngx_pool_t       *pool;
} ngx_list_t;

实际的项目存储在列表部分中,列表部分定义如下:

typedef struct ngx_list_part_s  ngx_list_part_t;

struct ngx_list_part_s {
    void             *elts;
    ngx_uint_t        nelts;
    ngx_list_part_t  *next;
};

在使用之前,必须通过调用 ngx_list_init(list, pool, n, size) 初始化列表,或者通过调用 ngx_list_create(pool, n, size) 创建列表。这两个函数都将单个项目的大小和每个列表部分的项目数量作为参数。要向列表添加项目,请使用 ngx_list_push(list) 函数。要遍历项目,请直接访问列表字段,如示例所示。

ngx_str_t        *v;
ngx_uint_t        i;
ngx_list_t       *list;
ngx_list_part_t  *part;

list = ngx_list_create(pool, 100, sizeof(ngx_str_t));
if (list == NULL) { /* error */ }

/* add items to the list */

v = ngx_list_push(list);
if (v == NULL) { /* error */ }
ngx_str_set(v, "foo");

v = ngx_list_push(list);
if (v == NULL) { /* error */ }
ngx_str_set(v, "bar");

/* iterate over the list */

part = &list->part;
v = part->elts;

for (i = 0; /* void */; i++) {

    if (i >= part->nelts) {
        if (part->next == NULL) {
            break;
        }

        part = part->next;
        v = part->elts;
        i = 0;
    }

    ngx_do_smth(&v[i]);
}

列表主要用于 HTTP 输入和输出头部。

列表不支持删除项目。但是,如果需要,可以在内部将项目标记为丢失,而无需实际从列表中删除。例如,要将 HTTP 输出头部(存储为 ngx_table_elt_t 对象)标记为丢失,请将 ngx_table_elt_t 中的 hash 字段设置为零。以这种方式标记的项目在迭代头部时会显式跳过。

队列

在 nginx 中,队列是侵入式双向链表,每个节点定义如下:

typedef struct ngx_queue_s  ngx_queue_t;

struct ngx_queue_s {
    ngx_queue_t  *prev;
    ngx_queue_t  *next;
};

头部队列节点没有链接任何数据。在使用前,请使用 ngx_queue_init(q) 调用初始化列表头。队列支持以下操作:

示例

typedef struct {
    ngx_str_t    value;
    ngx_queue_t  queue;
} ngx_foo_t;

ngx_foo_t    *f;
ngx_queue_t   values, *q;

ngx_queue_init(&values);

f = ngx_palloc(pool, sizeof(ngx_foo_t));
if (f == NULL) { /* error */ }
ngx_str_set(&f->value, "foo");

ngx_queue_insert_tail(&values, &f->queue);

/* insert more nodes here */

for (q = ngx_queue_head(&values);
     q != ngx_queue_sentinel(&values);
     q = ngx_queue_next(q))
{
    f = ngx_queue_data(q, ngx_foo_t, queue);

    ngx_do_smth(&f->value);
}

红黑树

src/core/ngx_rbtree.h 头文件提供了对红黑树有效实现的访问。

typedef struct {
    ngx_rbtree_t       rbtree;
    ngx_rbtree_node_t  sentinel;

    /* custom per-tree data here */
} my_tree_t;

typedef struct {
    ngx_rbtree_node_t  rbnode;

    /* custom per-node data */
    foo_t              val;
} my_node_t;

要将树作为一个整体处理,您需要两个节点:根节点和哨兵节点。通常,它们被添加到自定义结构中,允许您将数据组织成一棵树,其中叶子包含指向数据的链接或嵌入数据。

要初始化树:

my_tree_t  root;

ngx_rbtree_init(&root.rbtree, &root.sentinel, insert_value_function);

要遍历树并插入新值,请使用“insert_value”函数。例如,ngx_str_rbtree_insert_value 函数处理 ngx_str_t 类型。其参数是指向插入操作的根节点的指针、要添加的新创建的节点以及树哨兵。

void ngx_str_rbtree_insert_value(ngx_rbtree_node_t *temp,
                                 ngx_rbtree_node_t *node,
                                 ngx_rbtree_node_t *sentinel)

遍历非常简单,可以通过以下查找函数模式进行演示:

my_node_t *
my_rbtree_lookup(ngx_rbtree_t *rbtree, foo_t *val, uint32_t hash)
{
    ngx_int_t           rc;
    my_node_t          *n;
    ngx_rbtree_node_t  *node, *sentinel;

    node = rbtree->root;
    sentinel = rbtree->sentinel;

    while (node != sentinel) {

        n = (my_node_t *) node;

        if (hash != node->key) {
            node = (hash < node->key) ? node->left : node->right;
            continue;
        }

        rc = compare(val, node->val);

        if (rc < 0) {
            node = node->left;
            continue;
        }

        if (rc > 0) {
            node = node->right;
            continue;
        }

        return n;
    }

    return NULL;
}

compare() 函数是一个经典的比较器函数,它返回一个小于、等于或大于零的值。为了加快查找速度并避免比较可能很大的用户对象,使用了整数哈希字段。

要向树中添加节点,请分配一个新节点,对其进行初始化,然后调用 ngx_rbtree_insert()

    my_node_t          *my_node;
    ngx_rbtree_node_t  *node;

    my_node = ngx_palloc(...);
    init_custom_data(&my_node->val);

    node = &my_node->rbnode;
    node->key = create_key(my_node->val);

    ngx_rbtree_insert(&root->rbtree, node);

要删除节点,请调用 ngx_rbtree_delete() 函数。

ngx_rbtree_delete(&root->rbtree, node);

哈希表

哈希表函数在 src/core/ngx_hash.h 中声明。支持精确匹配和通配符匹配。后者需要额外的设置,并在下面单独的部分中描述。

在初始化哈希之前,您需要知道它将包含多少个元素,以便 nginx 可以对其进行最佳构建。需要配置的两个参数是 max_sizebucket_size,详情请参阅单独的 文档。它们通常由用户配置。哈希初始化设置存储在 ngx_hash_init_t 类型中,哈希本身是 ngx_hash_t

ngx_hash_t       foo_hash;
ngx_hash_init_t  hash;

hash.hash = &foo_hash;
hash.key = ngx_hash_key;
hash.max_size = 512;
hash.bucket_size = ngx_align(64, ngx_cacheline_size);
hash.name = "foo_hash";
hash.pool = cf->pool;
hash.temp_pool = cf->temp_pool;

key 是一个指向函数的指针,该函数从字符串创建哈希整数键。有两个通用的键创建函数:ngx_hash_key(data, len)ngx_hash_key_lc(data, len)。后者将字符串转换为全小写字符,因此传递的字符串必须是可写的。如果不是,请将 NGX_HASH_READONLY_KEY 标志传递给函数,初始化键数组(见下文)。

哈希键存储在 ngx_hash_keys_arrays_t 中,并使用 ngx_hash_keys_array_init(arr, type) 进行初始化:第二个参数(type)控制为哈希预分配的资源量,可以是 NGX_HASH_SMALLNGX_HASH_LARGE。如果您预计哈希包含数千个元素,则后者更合适。

ngx_hash_keys_arrays_t  foo_keys;

foo_keys.pool = cf->pool;
foo_keys.temp_pool = cf->temp_pool;

ngx_hash_keys_array_init(&foo_keys, NGX_HASH_SMALL);

要将键插入哈希键数组,请使用 ngx_hash_add_key(keys_array, key, value, flags) 函数。

ngx_str_t k1 = ngx_string("key1");
ngx_str_t k2 = ngx_string("key2");

ngx_hash_add_key(&foo_keys, &k1, &my_data_ptr_1, NGX_HASH_READONLY_KEY);
ngx_hash_add_key(&foo_keys, &k2, &my_data_ptr_2, NGX_HASH_READONLY_KEY);

要构建哈希表,请调用 ngx_hash_init(hinit, key_names, nelts) 函数。

ngx_hash_init(&hash, foo_keys.keys.elts, foo_keys.keys.nelts);

如果 max_sizebucket_size 参数不够大,则该函数将失败。

构建哈希后,使用 ngx_hash_find(hash, key, name, len) 函数查找元素。

my_data_t   *data;
ngx_uint_t   key;

key = ngx_hash_key(k1.data, k1.len);

data = ngx_hash_find(&foo_hash, key, k1.data, k1.len);
if (data == NULL) {
    /* key not found */
}

通配符匹配

要创建使用通配符的哈希,请使用 ngx_hash_combined_t 类型。它包含上面描述的哈希类型,并有两个额外的键数组:dns_wc_headdns_wc_tail。基本属性的初始化类似于常规哈希。

ngx_hash_init_t      hash
ngx_hash_combined_t  foo_hash;

hash.hash = &foo_hash.hash;
hash.key = ...;

可以使用 NGX_HASH_WILDCARD_KEY 标志添加通配符键。

/* k1 = ".example.org"; */
/* k2 = "foo.*";        */
ngx_hash_add_key(&foo_keys, &k1, &data1, NGX_HASH_WILDCARD_KEY);
ngx_hash_add_key(&foo_keys, &k2, &data2, NGX_HASH_WILDCARD_KEY);

该函数识别通配符并将键添加到相应的数组中。有关通配符语法和匹配算法的说明,请参阅 map 模块文档。

根据添加的键的内容,您可能需要初始化多达三个键数组:一个用于精确匹配(如上所述),另外两个用于启用从字符串开头或结尾开始的匹配。

if (foo_keys.dns_wc_head.nelts) {

    ngx_qsort(foo_keys.dns_wc_head.elts,
              (size_t) foo_keys.dns_wc_head.nelts,
              sizeof(ngx_hash_key_t),
              cmp_dns_wildcards);

    hash.hash = NULL;
    hash.temp_pool = pool;

    if (ngx_hash_wildcard_init(&hash, foo_keys.dns_wc_head.elts,
                               foo_keys.dns_wc_head.nelts)
        != NGX_OK)
    {
        return NGX_ERROR;
    }

    foo_hash.wc_head = (ngx_hash_wildcard_t *) hash.hash;
}

键数组需要排序,并且必须将初始化结果添加到组合哈希中。dns_wc_tail 数组的初始化方式类似。

组合哈希中的查找由 ngx_hash_find_combined(chash, key, name, len) 处理。

/* key = "bar.example.org"; — will match ".example.org" */
/* key = "foo.example.com"; — will match "foo.*"        */

hkey = ngx_hash_key(key.data, key.len);
res = ngx_hash_find_combined(&foo_hash, hkey, key.data, key.len);

内存管理

要从系统堆分配内存,请使用以下函数:

大多数 nginx 分配都在池中完成。在 nginx 池中分配的内存在池被销毁时会自动释放。这提供了良好的分配性能,并使内存控制变得容易。

池在内部以连续的内存块分配对象。一旦一个块满了,就会分配一个新的块并将其添加到池内存块列表中。当请求的分配过大而无法放入一个块时,请求将转发到系统分配器,并且返回的指针将存储在池中以供进一步释放。

nginx 池的类型为ngx_pool_t。支持以下操作

u_char      *p;
ngx_str_t   *s;
ngx_pool_t  *pool;

pool = ngx_create_pool(1024, log);
if (pool == NULL) { /* error */ }

s = ngx_palloc(pool, sizeof(ngx_str_t));
if (s == NULL) { /* error */ }
ngx_str_set(s, "foo");

p = ngx_pnalloc(pool, 3);
if (p == NULL) { /* error */ }
ngx_memcpy(p, "foo", 3);

链式链接(ngx_chain_t)在 nginx 中被积极使用,因此 nginx 池实现提供了一种重用它们的方法。ngx_pool_tchain字段维护一个先前分配的链接列表,准备用于重用。为了在池中高效地分配链式链接,请使用ngx_alloc_chain_link(pool)函数。此函数在池列表中查找空闲的链式链接,如果池列表为空,则分配一个新的链式链接。要释放链接,请调用ngx_free_chain(pool, cl)函数。

可以在池中注册清理处理程序。清理处理程序是一个带有参数的回调函数,在池销毁时被调用。池通常与特定的 nginx 对象(如 HTTP 请求)绑定,并在对象生命周期结束时被销毁。注册池清理是一种方便的方式来释放资源、关闭文件描述符或对与主对象关联的共享数据进行最终调整。

要注册池清理,请调用ngx_pool_cleanup_add(pool, size),它返回一个ngx_pool_cleanup_t指针,供调用方填充。使用size参数为清理处理程序分配上下文。

ngx_pool_cleanup_t  *cln;

cln = ngx_pool_cleanup_add(pool, 0);
if (cln == NULL) { /* error */ }

cln->handler = ngx_my_cleanup;
cln->data = "foo";

...

static void
ngx_my_cleanup(void *data)
{
    u_char  *msg = data;

    ngx_do_smth(msg);
}

共享内存

nginx 使用共享内存来在进程之间共享公共数据。ngx_shared_memory_add(cf, name, size, tag)函数将一个新的共享内存条目ngx_shm_zone_t添加到一个周期中。该函数接收区域的namesize。每个共享区域必须具有唯一的名称。如果已经存在具有提供的nametag的共享区域条目,则重用现有的区域条目。如果现有条目具有相同的名称但不同的标签,则该函数将失败并报错。通常,模块结构的地址作为tag传递,这使得可以在一个 nginx 模块中按名称重用共享区域成为可能。

共享内存条目结构ngx_shm_zone_t具有以下字段

在解析配置后,共享区域条目在ngx_init_cycle()中映射到实际内存。在 POSIX 系统上,mmap()系统调用用于创建共享匿名映射。在 Windows 上,使用CreateFileMapping()/ MapViewOfFileEx()对。

为了在共享内存中分配内存,nginx 提供了slab 池ngx_slab_pool_t类型。每个 nginx 共享区域都会自动创建一个用于分配内存的slab池。该池位于共享区域的开头,可以通过表达式(ngx_slab_pool_t *) shm_zone->shm.addr访问。要在共享区域中分配内存,请调用ngx_slab_alloc(pool, size)ngx_slab_calloc(pool, size)。要释放内存,请调用ngx_slab_free(pool, p)

Slab 池将所有共享区域划分为页面。每个页面用于分配相同大小的对象。指定的大小必须是 2 的幂,并且大于 8 字节的最小大小。不符合要求的值将向上取整。每个页面的位掩码跟踪哪些块正在使用,哪些块可用于分配。对于大于半页(通常为 2048 字节)的大小,一次分配整个页面。

要保护共享内存中的数据免受并发访问,请使用ngx_slab_pool_tmutex字段中提供的互斥锁。互斥锁最常用于 slab 池在分配和释放内存时,但它也可以用于保护在共享区域中分配的任何其他用户数据结构。要锁定或解锁互斥锁,分别调用ngx_shmtx_lock(&shpool->mutex)ngx_shmtx_unlock(&shpool->mutex)

ngx_str_t        name;
ngx_foo_ctx_t   *ctx;
ngx_shm_zone_t  *shm_zone;

ngx_str_set(&name, "foo");

/* allocate shared zone context */
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_foo_ctx_t));
if (ctx == NULL) {
    /* error */
}

/* add an entry for 64k shared zone */
shm_zone = ngx_shared_memory_add(cf, &name, 65536, &ngx_foo_module);
if (shm_zone == NULL) {
    /* error */
}

/* register init callback and context */
shm_zone->init = ngx_foo_init_zone;
shm_zone->data = ctx;


...


static ngx_int_t
ngx_foo_init_zone(ngx_shm_zone_t *shm_zone, void *data)
{
    ngx_foo_ctx_t  *octx = data;

    size_t            len;
    ngx_foo_ctx_t    *ctx;
    ngx_slab_pool_t  *shpool;

    value = shm_zone->data;

    if (octx) {
        /* reusing a shared zone from old cycle */
        ctx->value = octx->value;
        return NGX_OK;
    }

    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;

    if (shm_zone->shm.exists) {
        /* initialize shared zone context in Windows nginx worker */
        ctx->value = shpool->data;
        return NGX_OK;
    }

    /* initialize shared zone */

    ctx->value = ngx_slab_alloc(shpool, sizeof(ngx_uint_t));
    if (ctx->value == NULL) {
        return NGX_ERROR;
    }

    shpool->data = ctx->value;

    return NGX_OK;
}

日志记录

对于日志记录,nginx 使用ngx_log_t对象。nginx 记录器支持几种类型的输出

记录器实例可以是记录器的链,通过next字段相互链接。在这种情况下,每条消息都写入链中的所有记录器。

对于每个记录器,严重性级别控制写入日志的消息(仅写入分配该级别或更高级别的事件)。支持以下严重性级别

对于调试日志记录,还会检查调试掩码。调试掩码为

通常,记录器是由现有的 nginx 代码从error_log指令创建的,并且在周期的几乎每个处理阶段、配置、客户端连接和其他对象中都可用。

Nginx 提供以下日志记录宏

日志消息在栈上大小为NGX_MAX_ERROR_STR(目前为 2048 字节)的缓冲区中格式化。消息前缀为严重性级别、进程 ID (PID)、连接 ID(存储在log->connection中)和系统错误文本。对于非调试消息,还会调用log->handler以将更具体的信息添加到日志消息的前缀中。HTTP 模块将ngx_http_log_error()函数设置为日志处理程序,以记录客户端和服务器地址、当前操作(存储在log->action中)、客户端请求行、服务器名称等。

/* specify what is currently done */
log->action = "sending mp4 to client";

/* error and debug log */
ngx_log_error(NGX_LOG_INFO, c->log, 0, "client prematurely
              closed connection");

ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
               "mp4 start:%ui, length:%ui", mp4->start, mp4->length);

上面的示例导致如下日志条目

2016/09/16 22:08:52 [info] 17445#0: *1 client prematurely closed connection while
sending mp4 to client, client: 127.0.0.1, server: , request: "GET /file.mp4 HTTP/1.1"
2016/09/16 23:28:33 [debug] 22140#0: *1 mp4 start:0, length:10000

周期

周期对象存储从特定配置创建的 nginx 运行时上下文。其类型为ngx_cycle_t。当前周期由全局变量ngx_cycle引用,并在 nginx 工作进程启动时由它们继承。每次 nginx 配置重新加载时,都会从新的 nginx 配置创建一个新的周期;旧周期通常在新周期成功创建后删除。

周期由ngx_init_cycle()函数创建,该函数以先前的周期作为其参数。该函数定位先前周期的配置文件,并尽可能多地从先前周期继承资源。在 nginx 启动时会创建一个名为“初始化周期”的占位符周期,然后由根据配置构建的实际周期替换。

周期的成员包括

缓冲区

对于输入/输出操作,Nginx 提供了缓冲区类型ngx_buf_t。通常,它用于保存要写入目标或从源读取的数据。缓冲区可以引用内存或文件中的数据,并且从技术上讲,缓冲区可以同时引用两者。缓冲区的内存是单独分配的,与缓冲区结构ngx_buf_t无关。

ngx_buf_t结构包含以下字段

对于输入和输出操作,缓冲区以链的形式链接。链是一系列类型为ngx_chain_t的链链接,定义如下

typedef struct ngx_chain_s  ngx_chain_t;

struct ngx_chain_s {
    ngx_buf_t    *buf;
    ngx_chain_t  *next;
};

每个链链接都保留对其缓冲区的引用以及对下一个链链接的引用。

使用缓冲区和链的示例

ngx_chain_t *
ngx_get_my_chain(ngx_pool_t *pool)
{
    ngx_buf_t    *b;
    ngx_chain_t  *out, *cl, **ll;

    /* first buf */
    cl = ngx_alloc_chain_link(pool);
    if (cl == NULL) { /* error */ }

    b = ngx_calloc_buf(pool);
    if (b == NULL) { /* error */ }

    b->start = (u_char *) "foo";
    b->pos = b->start;
    b->end = b->start + 3;
    b->last = b->end;
    b->memory = 1; /* read-only memory */

    cl->buf = b;
    out = cl;
    ll = &cl->next;

    /* second buf */
    cl = ngx_alloc_chain_link(pool);
    if (cl == NULL) { /* error */ }

    b = ngx_create_temp_buf(pool, 3);
    if (b == NULL) { /* error */ }

    b->last = ngx_cpymem(b->last, "foo", 3);

    cl->buf = b;
    cl->next = NULL;
    *ll = cl;

    return out;
}

网络

连接

连接类型ngx_connection_t是套接字描述符的包装器。它包含以下字段

Nginx 连接可以透明地封装 SSL 层。在这种情况下,连接的ssl字段保存指向ngx_ssl_connection_t结构的指针,该结构保存与连接相关的所有 SSL 数据,包括SSL_CTXSSLrecvsendrecv_chainsend_chain处理程序也设置为启用 SSL 的函数。

Nginx 配置中的worker_connections指令限制每个 Nginx 工作进程的连接数。所有连接结构在工作进程启动时预先创建,并存储在周期对象的connections字段中。要检索连接结构,请使用ngx_get_connection(s, log)函数。它以套接字描述符作为其s参数,该描述符需要包装在连接结构中。

由于每个工作进程的连接数是有限制的,因此 Nginx 提供了一种获取当前正在使用的连接的方法。要启用或禁用连接的重用,请调用ngx_reusable_connection(c, reusable)函数。调用ngx_reusable_connection(c, 1)会在连接结构中设置reuse标志,并将连接插入到周期的reusable_connections_queue中。每当ngx_get_connection()发现周期的free_connections列表中没有可用连接时,它都会调用ngx_drain_connections()释放特定数量的可重用连接。对于每个此类连接,都会设置close标志并调用其读取处理程序,该处理程序应该通过调用ngx_close_connection(c)释放连接并使其可供重用。要退出连接可以重用的状态,请调用ngx_reusable_connection(c, 0)。HTTP 客户端连接是 Nginx 中可重用连接的一个示例;它们被标记为可重用,直到从客户端接收到第一个请求字节。

事件

事件

Nginx 中的事件对象ngx_event_t提供了一种机制,用于通知特定事件已发生。

ngx_event_t中的字段包括

I/O 事件

通过调用ngx_get_connection()函数获得的每个连接都有两个附加事件,c->readc->write,它们用于接收套接字准备好读取或写入的通知。所有此类事件都以边沿触发模式工作,这意味着它们仅在套接字状态发生变化时才触发通知。例如,对套接字执行部分读取不会使 Nginx 传递重复的读取通知,直到套接字上有更多数据到达。即使底层 I/O 通知机制本质上是电平触发(pollselect等),Nginx 也会将通知转换为边沿触发。为了使 Nginx 事件通知在不同平台上的所有通知系统中保持一致,必须在处理 I/O 套接字通知或在该套接字上调用任何 I/O 函数后调用函数ngx_handle_read_event(rev, flags)ngx_handle_write_event(wev, lowat)。通常,这些函数在每个读写事件处理程序的末尾调用一次。

定时器事件

事件可以设置为在超时到期时发送通知。事件使用的计时器计算从过去某个未指定的时间点开始的毫秒数,截断为ngx_msec_t类型。其当前值可以从ngx_current_msec变量中获取。

函数ngx_add_timer(ev, timer)为事件设置超时,ngx_del_timer(ev)删除先前设置的超时。全局超时红黑树ngx_event_timer_rbtree存储当前设置的所有超时。树中的键类型为ngx_msec_t,表示事件发生的时间。树结构使快速插入和删除操作以及访问最近的超时成为可能,Nginx 使用这些功能来确定等待 I/O 事件和超时事件到期的时长。

发布事件

可以发布事件,这意味着它的处理程序将在当前事件循环迭代中的某个时间点之后被调用。发布事件是简化代码和避免堆栈溢出的良好实践。发布的事件保存在发布队列中。ngx_post_event(ev, q)宏将事件ev发布到发布队列qngx_delete_posted_event(ev)宏从其当前发布到的队列中删除事件ev。通常,事件会发布到ngx_posted_events队列,该队列在事件循环的后期处理——在所有 I/O 和计时器事件都已处理之后。调用函数ngx_event_process_posted()来处理事件队列。它会调用事件处理程序,直到队列为空。这意味着发布的事件处理程序可以发布更多事件以在当前事件循环迭代中进行处理。

示例

void
ngx_my_connection_read(ngx_connection_t *c)
{
    ngx_event_t  *rev;

    rev = c->read;

    ngx_add_timer(rev, 1000);

    rev->handler = ngx_my_read_handler;

    ngx_my_read(rev);
}


void
ngx_my_read_handler(ngx_event_t *rev)
{
    ssize_t            n;
    ngx_connection_t  *c;
    u_char             buf[256];

    if (rev->timedout) { /* timeout expired */ }

    c = rev->data;

    while (rev->ready) {
        n = c->recv(c, buf, sizeof(buf));

        if (n == NGX_AGAIN) {
            break;
        }

        if (n == NGX_ERROR) { /* error */ }

        /* process buf */
    }

    if (ngx_handle_read_event(rev, 0) != NGX_OK) { /* error */ }
}

事件循环

除了 Nginx 主进程外,所有 Nginx 进程都执行 I/O,因此都有一个事件循环。(Nginx 主进程则大部分时间都在sigsuspend()调用中等待信号到达。)Nginx 事件循环在ngx_process_events_and_timers()函数中实现,该函数会重复调用,直到进程退出。

事件循环包含以下阶段

所有 Nginx 进程也处理信号。信号处理程序仅设置全局变量,这些变量在 `ngx_process_events_and_timers()` 调用之后进行检查。

进程

Nginx 中有几种类型的进程。进程的类型保存在全局变量 `ngx_process` 中,并且是以下之一:

Nginx 进程处理以下信号:

虽然所有 Nginx 工作进程都能够接收并正确处理 POSIX 信号,但主进程不使用标准的 `kill()` 系统调用来向工作进程和辅助进程传递信号。相反,Nginx 使用进程间套接字对,允许在所有 Nginx 进程之间发送消息。但是,目前消息仅从主进程发送到其子进程。消息携带标准信号。

线程

可以将原本会阻塞 Nginx 工作进程的任务卸载到单独的线程中。例如,可以将 Nginx 配置为使用线程执行 文件 I/O。另一个用例是库没有异步接口,因此无法与 Nginx 正常使用。请记住,线程接口是现有异步处理客户端连接方法的辅助工具,绝不是替代品。

为了处理同步,可以使用以下 `pthreads` 原语的包装器:

Nginx 并没有为每个任务创建一个新线程,而是实现了 线程池 策略。可以为不同的目的配置多个线程池(例如,在不同的磁盘集上执行 I/O)。每个线程池在启动时创建,并包含处理任务队列的有限数量的线程。当任务完成后,会调用预定义的完成处理程序。

src/core/ngx_thread_pool.h 头文件包含相关定义。

struct ngx_thread_task_s {
    ngx_thread_task_t   *next;
    ngx_uint_t           id;
    void                *ctx;
    void               (*handler)(void *data, ngx_log_t *log);
    ngx_event_t          event;
};

typedef struct ngx_thread_pool_s  ngx_thread_pool_t;

ngx_thread_pool_t *ngx_thread_pool_add(ngx_conf_t *cf, ngx_str_t *name);
ngx_thread_pool_t *ngx_thread_pool_get(ngx_cycle_t *cycle, ngx_str_t *name);

ngx_thread_task_t *ngx_thread_task_alloc(ngx_pool_t *pool, size_t size);
ngx_int_t ngx_thread_task_post(ngx_thread_pool_t *tp, ngx_thread_task_t *task);

在配置时,希望使用线程的模块必须通过调用 `ngx_thread_pool_add(cf, name)` 获取对线程池的引用,该函数要么使用给定的 `name` 创建一个新的线程池,要么如果该名称的池已存在则返回对该池的引用。

要在运行时将 `task` 添加到指定线程池 `tp` 的队列中,请使用 `ngx_thread_task_post(tp, task)` 函数。要在线程中执行函数,请传递参数并使用 `ngx_thread_task_t` 结构设置完成处理程序。

typedef struct {
    int    foo;
} my_thread_ctx_t;


static void
my_thread_func(void *data, ngx_log_t *log)
{
    my_thread_ctx_t *ctx = data;

    /* this function is executed in a separate thread */
}


static void
my_thread_completion(ngx_event_t *ev)
{
    my_thread_ctx_t *ctx = ev->data;

    /* executed in nginx event loop */
}


ngx_int_t
my_task_offload(my_conf_t *conf)
{
    my_thread_ctx_t    *ctx;
    ngx_thread_task_t  *task;

    task = ngx_thread_task_alloc(conf->pool, sizeof(my_thread_ctx_t));
    if (task == NULL) {
        return NGX_ERROR;
    }

    ctx = task->ctx;

    ctx->foo = 42;

    task->handler = my_thread_func;
    task->event.handler = my_thread_completion;
    task->event.data = ctx;

    if (ngx_thread_task_post(conf->thread_pool, task) != NGX_OK) {
        return NGX_ERROR;
    }

    return NGX_OK;
}

模块

添加新模块

每个独立的 Nginx 模块都位于一个单独的目录中,该目录至少包含两个文件:`config` 和一个包含模块源代码的文件。`config` 文件包含 Nginx 集成模块所需的所有信息,例如:

ngx_module_type=CORE
ngx_module_name=ngx_foo_module
ngx_module_srcs="$ngx_addon_dir/ngx_foo_module.c"

. auto/module

ngx_addon_name=$ngx_module_name

`config` 文件是一个 POSIX shell 脚本,可以设置和访问以下变量:

要将模块静态编译到 Nginx 中,请使用 `--add-module=/path/to/module` 参数传递给配置脚本。要编译模块以便稍后动态加载到 Nginx 中,请使用 `--add-dynamic-module=/path/to/module` 参数。

核心模块

模块是 Nginx 的构建块,其大部分功能都作为模块实现。模块源文件必须包含类型为 `ngx_module_t` 的全局变量,其定义如下:

struct ngx_module_s {

    /* private part is omitted */

    void                 *ctx;
    ngx_command_t        *commands;
    ngx_uint_t            type;

    ngx_int_t           (*init_master)(ngx_log_t *log);

    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);

    ngx_int_t           (*init_process)(ngx_cycle_t *cycle);
    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);
    void                (*exit_thread)(ngx_cycle_t *cycle);
    void                (*exit_process)(ngx_cycle_t *cycle);

    void                (*exit_master)(ngx_cycle_t *cycle);

    /* stubs for future extensions are omitted */
};

省略的私有部分包含模块版本和签名,并使用预定义宏 `NGX_MODULE_V1` 填充。

每个模块将其私有数据保存在 `ctx` 字段中,识别在 `commands` 数组中指定的配置指令,并且可以在 Nginx 生命周期中的某些阶段被调用。模块生命周期包含以下事件:

由于线程仅在 Nginx 中用作具有其自身 API 的补充 I/O 工具,因此目前不会调用 `init_thread` 和 `exit_thread` 处理程序。也没有 `init_master` 处理程序,因为它是不必要的开销。

模块 `type` 定义了 `ctx` 字段中存储的确切内容。其值是以下类型之一:

NGX_CORE_MODULE 是最基本的,因此也是最通用和最底层的模块类型。其他模块类型在其之上实现,并提供了一种更方便的方式来处理相应的域,例如处理事件或 HTTP 请求。

核心模块集包括ngx_core_modulengx_errlog_modulengx_regex_modulengx_thread_pool_modulengx_openssl_module模块。HTTP模块、stream模块、mail模块和event模块也是核心模块。核心模块的上下文定义为

typedef struct {
    ngx_str_t             name;
    void               *(*create_conf)(ngx_cycle_t *cycle);
    char               *(*init_conf)(ngx_cycle_t *cycle, void *conf);
} ngx_core_module_t;

其中name是模块名称字符串,create_confinit_conf分别是创建和初始化模块配置的函数指针。对于核心模块,nginx会在解析新配置之前调用create_conf,并在所有配置成功解析后调用init_conf。典型的create_conf函数会为配置分配内存并设置默认值。

例如,一个名为ngx_foo_module的简单模块可能如下所示

/*
 * Copyright (C) Author.
 */


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


typedef struct {
    ngx_flag_t  enable;
} ngx_foo_conf_t;


static void *ngx_foo_create_conf(ngx_cycle_t *cycle);
static char *ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf);

static char *ngx_foo_enable(ngx_conf_t *cf, void *post, void *data);
static ngx_conf_post_t  ngx_foo_enable_post = { ngx_foo_enable };


static ngx_command_t  ngx_foo_commands[] = {

    { ngx_string("foo_enabled"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      0,
      offsetof(ngx_foo_conf_t, enable),
      &ngx_foo_enable_post },

      ngx_null_command
};


static ngx_core_module_t  ngx_foo_module_ctx = {
    ngx_string("foo"),
    ngx_foo_create_conf,
    ngx_foo_init_conf
};


ngx_module_t  ngx_foo_module = {
    NGX_MODULE_V1,
    &ngx_foo_module_ctx,                   /* module context */
    ngx_foo_commands,                      /* module directives */
    NGX_CORE_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
};


static void *
ngx_foo_create_conf(ngx_cycle_t *cycle)
{
    ngx_foo_conf_t  *fcf;

    fcf = ngx_pcalloc(cycle->pool, sizeof(ngx_foo_conf_t));
    if (fcf == NULL) {
        return NULL;
    }

    fcf->enable = NGX_CONF_UNSET;

    return fcf;
}


static char *
ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf)
{
    ngx_foo_conf_t *fcf = conf;

    ngx_conf_init_value(fcf->enable, 0);

    return NGX_CONF_OK;
}


static char *
ngx_foo_enable(ngx_conf_t *cf, void *post, void *data)
{
    ngx_flag_t  *fp = data;

    if (*fp == 0) {
        return NGX_CONF_OK;
    }

    ngx_log_error(NGX_LOG_NOTICE, cf->log, 0, "Foo Module is enabled");

    return NGX_CONF_OK;
}

配置指令

ngx_command_t类型定义了一个单独的配置指令。每个支持配置的模块都提供了一个此类结构的数组,用于描述如何处理参数以及调用哪些处理程序

typedef struct ngx_command_s  ngx_command_t;

struct ngx_command_s {
    ngx_str_t             name;
    ngx_uint_t            type;
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    ngx_uint_t            conf;
    ngx_uint_t            offset;
    void                 *post;
};

使用特殊值ngx_null_command终止数组。name是配置指令在配置文件中出现的名称,例如“worker_processes”或“listen”。type是一个标志位字段,指定指令接受的参数数量、类型以及它出现的上下文。标志如下:

指令类型的标志如下:

指令的上下文定义了它可以在配置中的哪个位置出现

配置解析器使用这些标志在指令位置错误的情况下抛出错误,并调用带有正确配置指针的指令处理程序,以便不同位置的相同指令可以将其值存储在不同的位置。

set字段定义了一个处理指令并将解析后的值存储到相应配置中的处理程序。有一些函数执行常见的转换

conf字段定义了哪个配置结构传递给目录处理程序。核心模块仅具有全局配置,并设置NGX_DIRECT_CONF标志以访问它。像HTTP、Stream或Mail这样的模块创建配置层次结构。例如,为serverlocationif范围创建模块的配置。

offset定义了模块配置结构中保存此特定指令值的字段的偏移量。典型用法是使用offsetof()宏。

post字段有两个用途:它可以用来定义在主处理程序完成后调用的处理程序,或者将其他数据传递给主处理程序。在第一种情况下,需要使用处理程序指针初始化ngx_conf_post_t结构,例如

static char *ngx_do_foo(ngx_conf_t *cf, void *post, void *data);
static ngx_conf_post_t  ngx_foo_post = { ngx_do_foo };

post参数是ngx_conf_post_t对象本身,data是指向值的指针,该值由主处理程序使用适当的类型从参数转换而来。

HTTP

连接

每个HTTP客户端连接都经过以下阶段

请求

对于每个客户端HTTP请求,都会创建ngx_http_request_t对象。此对象的一些字段如下:

配置

每个 HTTP 模块可以具有三种类型的配置

配置结构在 nginx 配置阶段通过调用函数创建,这些函数分配结构、初始化结构并合并结构。以下示例显示了如何为模块创建简单的 location 配置。该配置有一个类型为无符号整数的设置 foo

typedef struct {
    ngx_uint_t  foo;
} ngx_http_foo_loc_conf_t;


static ngx_http_module_t  ngx_http_foo_module_ctx = {
    NULL,                                  /* preconfiguration */
    NULL,                                  /* postconfiguration */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    ngx_http_foo_create_loc_conf,          /* create location configuration */
    ngx_http_foo_merge_loc_conf            /* merge location configuration */
};


static void *
ngx_http_foo_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_foo_loc_conf_t  *conf;

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

    conf->foo = NGX_CONF_UNSET_UINT;

    return conf;
}


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

    ngx_conf_merge_uint_value(conf->foo, prev->foo, 1);
}

如示例所示,ngx_http_foo_create_loc_conf() 函数创建一个新的配置结构,ngx_http_foo_merge_loc_conf() 将配置与来自更高级别的配置合并。实际上,服务器和 location 配置不仅存在于服务器和 location 级别,而且也为它们上方的所有级别创建。具体来说,服务器配置也在主级别创建,位置配置在主、服务器和位置级别创建。这些配置使得能够在 nginx 配置文件的任何级别指定服务器和位置特定的设置。最终配置向下合并。许多宏(如 NGX_CONF_UNSETNGX_CONF_UNSET_UINT)用于指示缺少的设置并在合并时忽略它。标准 nginx 合并宏(如 ngx_conf_merge_value()ngx_conf_merge_uint_value())提供了一种方便的方法来合并设置并在没有配置提供显式值的情况下设置默认值。有关不同类型的宏的完整列表,请参见 src/core/ngx_conf_file.h

以下宏可用于在配置时访问 HTTP 模块的配置。它们都将 ngx_conf_t 引用作为第一个参数。

以下示例获取指向标准 nginx 核心模块 ngx_http_core_module 的 location 配置的指针,并替换结构的 handler 字段中保存的位置内容处理器。

static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r);


static ngx_command_t  ngx_http_foo_commands[] = {

    { ngx_string("foo"),
      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
      ngx_http_foo,
      0,
      0,
      NULL },

      ngx_null_command
};


static char *
ngx_http_foo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t  *clcf;

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_bar_handler;

    return NGX_CONF_OK;
}

以下宏可用于在运行时访问 HTTP 模块的配置。

这些宏接收一个指向 HTTP 请求 `ngx_http_request_t` 的引用。请求的主要配置永远不会改变。在为请求选择虚拟服务器后,服务器配置可能会从默认值更改。作为重写操作或内部重定向的结果,为处理请求而选择的 location 配置可能会多次更改。以下示例演示如何在运行时访问模块的 HTTP 配置。

static ngx_int_t
ngx_http_foo_handler(ngx_http_request_t *r)
{
    ngx_http_foo_loc_conf_t  *flcf;

    flcf = ngx_http_get_module_loc_conf(r, ngx_http_foo_module);

    ...
}

阶段

每个 HTTP 请求都经过一系列阶段。在每个阶段,都会对请求执行不同类型的处理。可以在大多数阶段注册模块特定的处理程序,并且许多标准的 nginx 模块注册其阶段处理程序作为在请求处理的特定阶段被调用的方法。阶段依次处理,并且在请求到达该阶段时调用阶段处理程序。以下是 nginx HTTP 阶段的列表。

以下是 preaccess 阶段处理程序的示例。

static ngx_http_module_t  ngx_http_foo_module_ctx = {
    NULL,                                  /* preconfiguration */
    ngx_http_foo_init,                     /* 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 */
};


static ngx_int_t
ngx_http_foo_handler(ngx_http_request_t *r)
{
    ngx_table_elt_t  *ua;

    ua = r->headers_in.user_agent;

    if (ua == NULL) {
        return NGX_DECLINED;
    }

    /* reject requests with "User-Agent: foo" */
    if (ua->value.len == 3 && ngx_strncmp(ua->value.data, "foo", 3) == 0) {
        return NGX_HTTP_FORBIDDEN;
    }

    return NGX_DECLINED;
}


static ngx_int_t
ngx_http_foo_init(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_PREACCESS_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_http_foo_handler;

    return NGX_OK;
}

阶段处理程序预计将返回特定的代码

对于某些阶段,返回代码的处理方式略有不同。在内容阶段,除 NGX_DECLINED 之外的任何返回代码都被视为结束代码。location 内容处理程序返回的任何返回代码都被视为结束代码。在访问阶段,在 satisfy any 模式下,除 NGX_OKNGX_DECLINEDNGX_AGAINNGX_DONE 之外的任何返回代码都被视为拒绝。如果没有后续的访问处理程序使用不同的代码允许或拒绝访问,则拒绝代码将成为结束代码。

变量

访问现有变量

可以通过索引(这是最常见的方法)或名称引用变量(请参阅 下方)。索引是在配置阶段创建的,当时变量被添加到配置中。要获取变量索引,请使用 ngx_http_get_variable_index()

ngx_str_t  name;  /* ngx_string("foo") */
ngx_int_t  index;

index = ngx_http_get_variable_index(cf, &name);

这里,`cf` 是指向 nginx 配置的指针,`name` 指向包含变量名称的字符串。该函数在出错时返回 NGX_ERROR,否则返回有效的索引,该索引通常存储在模块的配置中的某个位置以供将来使用。

所有 HTTP 变量都在给定 HTTP 请求的上下文中进行评估,结果特定于该 HTTP 请求并在该请求中缓存。所有评估变量的函数都返回 `ngx_http_variable_value_t` 类型,表示变量值

typedef ngx_variable_value_t  ngx_http_variable_value_t;

typedef struct {
    unsigned    len:28;

    unsigned    valid:1;
    unsigned    no_cacheable:1;
    unsigned    not_found:1;
    unsigned    escape:1;

    u_char     *data;
} ngx_variable_value_t;

其中

ngx_http_get_flushed_variable()ngx_http_get_indexed_variable() 函数用于获取变量的值。它们具有相同的接口 - 接受 HTTP 请求 `r` 作为评估变量的上下文以及标识它的 `index`。典型用法的示例

ngx_http_variable_value_t  *v;

v = ngx_http_get_flushed_variable(r, index);

if (v == NULL || v->not_found) {
    /* we failed to get value or there is no such variable, handle it */
    return NGX_ERROR;
}

/* some meaningful value is found */

这两个函数之间的区别在于,ngx_http_get_indexed_variable() 返回缓存的值,而 ngx_http_get_flushed_variable() 刷新不可缓存变量的缓存。

某些模块(如 SSI 和 Perl)需要处理在配置时未知名称的变量。因此,无法使用索引访问它们,但可以使用 ngx_http_get_variable(r, name, key) 函数。它搜索具有给定 `name` 和从名称派生的哈希 `key` 的变量。

创建变量

要创建变量,请使用 ngx_http_add_variable() 函数。它将配置(在其中注册变量)、变量名称和控制函数行为的标志作为参数

该函数在出错时返回 NULL,否则返回指向 ngx_http_variable_t 的指针

struct ngx_http_variable_s {
    ngx_str_t                     name;
    ngx_http_set_variable_pt      set_handler;
    ngx_http_get_variable_pt      get_handler;
    uintptr_t                     data;
    ngx_uint_t                    flags;
    ngx_uint_t                    index;
};

getset 处理程序分别用于获取或设置变量值,`data` 传递给变量处理程序,`index` 保存分配的变量索引,用于引用变量。

通常,模块会创建 `ngx_http_variable_t` 结构的以 null 结尾的静态数组,并在预配置阶段处理该数组以将变量添加到配置中,例如

static ngx_http_variable_t  ngx_http_foo_vars[] = {

    { ngx_string("foo_v1"), NULL, ngx_http_foo_v1_variable, 0, 0, 0 },

      ngx_http_null_variable
};

static ngx_int_t
ngx_http_foo_add_variables(ngx_conf_t *cf)
{
    ngx_http_variable_t  *var, *v;

    for (v = ngx_http_foo_vars; v->name.len; v++) {
        var = ngx_http_add_variable(cf, &v->name, v->flags);
        if (var == NULL) {
            return NGX_ERROR;
        }

        var->get_handler = v->get_handler;
        var->data = v->data;
    }

    return NGX_OK;
}

此示例中的函数用于初始化 HTTP 模块上下文的 `preconfiguration` 字段,并在 HTTP 配置解析之前调用,以便解析器可以引用这些变量。

get 处理程序负责在特定请求的上下文中评估变量,例如

static ngx_int_t
ngx_http_variable_connection(ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data)
{
    u_char  *p;

    p = ngx_pnalloc(r->pool, NGX_ATOMIC_T_LEN);
    if (p == NULL) {
        return NGX_ERROR;
    }

    v->len = ngx_sprintf(p, "%uA", r->connection->number) - p;
    v->valid = 1;
    v->no_cacheable = 0;
    v->not_found = 0;
    v->data = p;

    return NGX_OK;
}

它在内部错误(例如,内存分配失败)的情况下返回 NGX_ERROR,否则返回 NGX_OK。要了解变量评估的状态,请检查 `ngx_http_variable_value_t` 中的标志(请参阅 上方 的描述)。

set 处理程序允许设置变量引用的属性。例如,`$limit_rate` 变量的 set 处理程序修改请求的 `limit_rate` 字段

...
{ ngx_string("limit_rate"), ngx_http_variable_request_set_size,
  ngx_http_variable_request_get_size,
  offsetof(ngx_http_request_t, limit_rate),
  NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE, 0 },
...

static void
ngx_http_variable_request_set_size(ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data)
{
    ssize_t    s, *sp;
    ngx_str_t  val;

    val.len = v->len;
    val.data = v->data;

    s = ngx_parse_size(&val);

    if (s == NGX_ERROR) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "invalid size \"%V\"", &val);
        return;
    }

    sp = (ssize_t *) ((char *) r + data);

    *sp = s;

    return;
}

复杂值

复杂值,尽管名称如此,但提供了一种简单的方法来评估包含文本、变量及其组合的表达式。

ngx_http_compile_complex_value 中的复杂值描述在配置阶段被编译成 ngx_http_complex_value_t,它在运行时用于获取表达式求值的结果。

ngx_str_t                         *value;
ngx_http_complex_value_t           cv;
ngx_http_compile_complex_value_t   ccv;

value = cf->args->elts; /* directive arguments */

ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

ccv.cf = cf;
ccv.value = &value[1];
ccv.complex_value = &cv;
ccv.zero = 1;
ccv.conf_prefix = 1;

if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
    return NGX_CONF_ERROR;
}

这里,ccv 持有初始化复杂值 cv 所需的所有参数。

当结果需要传递给需要零终止字符串的库时,zero 标志很有用;而当处理文件名时,前缀则非常方便。

编译成功后,cv.lengths 包含有关表达式中变量存在的信息。NULL 值表示表达式仅包含静态文本,因此可以存储在简单字符串中,而不是作为复杂值。

ngx_http_set_complex_value_slot() 是一个方便的函数,用于在指令声明本身中完全初始化复杂值。

在运行时,可以使用 ngx_http_complex_value() 函数计算复杂值。

ngx_str_t  res;

if (ngx_http_complex_value(r, &cv, &res) != NGX_OK) {
    return NGX_ERROR;
}

给定请求 r 和先前编译的值 cv,该函数会评估表达式并将结果写入 res

请求重定向

HTTP 请求始终通过 ngx_http_request_t 结构的 loc_conf 字段连接到一个 location。这意味着,在任何时候,都可以通过调用 ngx_http_get_module_loc_conf(r, module) 从请求中检索任何模块的 location 配置。请求 location 在请求的生命周期中可能会发生多次变化。最初,默认服务器的默认服务器 location 会分配给请求。如果请求切换到不同的服务器(由 HTTP“Host”标头或 SSL SNI 扩展选择),请求也会切换到该服务器的默认 location。location 的下一次更改发生在 NGX_HTTP_FIND_CONFIG_PHASE 请求阶段。在此阶段,通过请求 URI 从为服务器配置的所有未命名的 location 中选择一个 location。 ngx_http_rewrite_module 可以在 NGX_HTTP_REWRITE_PHASE 请求阶段更改请求 URI,这是 rewrite 指令的结果,并将请求发送回 NGX_HTTP_FIND_CONFIG_PHASE 阶段,以根据新的 URI 选择新的 location。

也可以通过调用 ngx_http_internal_redirect(r, uri, args)ngx_http_named_location(r, name) 在任何时候将请求重定向到新的 location。

ngx_http_internal_redirect(r, uri, args) 函数更改请求 URI 并将请求返回到 NGX_HTTP_SERVER_REWRITE_PHASE 阶段。请求将继续使用服务器默认 location。稍后在 NGX_HTTP_FIND_CONFIG_PHASE 阶段,将根据新的请求 URI 选择新的 location。

以下示例执行具有新请求参数的内部重定向。

ngx_int_t
ngx_http_foo_redirect(ngx_http_request_t *r)
{
    ngx_str_t  uri, args;

    ngx_str_set(&uri, "/foo");
    ngx_str_set(&args, "bar=1");

    return ngx_http_internal_redirect(r, &uri, &args);
}

函数 ngx_http_named_location(r, name) 将请求重定向到命名 location。location 的名称作为参数传递。在当前服务器的所有命名 location 中查找 location,然后请求切换到 NGX_HTTP_REWRITE_PHASE 阶段。

以下示例执行重定向到命名 location @foo。

ngx_int_t
ngx_http_foo_named_redirect(ngx_http_request_t *r)
{
    ngx_str_t  name;

    ngx_str_set(&name, "foo");

    return ngx_http_named_location(r, &name);
}

这两个函数 - ngx_http_internal_redirect(r, uri, args)ngx_http_named_location(r, name) 都可以在 nginx 模块已在请求的 ctx 字段中存储一些上下文时调用。这些上下文可能与新的 location 配置不一致。为了防止不一致,这两个重定向函数都会擦除所有请求上下文。

调用 ngx_http_internal_redirect(r, uri, args)ngx_http_named_location(r, name) 会增加请求 count。为了保证请求引用计数的一致性,在重定向请求后调用 ngx_http_finalize_request(r, NGX_DONE)。这将结束当前请求代码路径并减少计数器。

重定向和重写的请求变为内部请求,并且可以访问 internal location。内部请求已设置 internal 标志。

子请求

子请求主要用于将一个请求的输出插入到另一个请求中,可能与其他数据混合在一起。子请求看起来像一个普通请求,但与它的父请求共享一些数据。特别是,与客户端输入相关的所有字段都是共享的,因为子请求不会从客户端接收任何其他输入。子请求的请求字段 parent 包含到其父请求的链接,对于主请求则为 NULL。字段 main 在一组请求中包含到主请求的链接。

子请求从 NGX_HTTP_SERVER_REWRITE_PHASE 阶段开始。它经过与普通请求相同的后续阶段,并根据其自己的 URI 分配 location。

子请求中的输出标头始终被忽略。ngx_http_postpone_filter 将子请求的输出主体放置在相对于父请求产生的其他数据的正确位置。

子请求与活动请求的概念相关。如果 c->data == r,则请求 r 被认为是活动的,其中 c 是客户端连接对象。在任何给定时间,仅允许请求组中的活动请求将其缓冲区输出到客户端。非活动请求仍然可以将其输出发送到过滤器链,但它不会超过 ngx_http_postpone_filter 并由该过滤器缓冲,直到请求变为活动状态。以下是请求激活的一些规则

通过调用函数 ngx_http_subrequest(r, uri, args, psr, ps, flags) 创建子请求,其中 r 是父请求,uriargs 是子请求的 URI 和参数,psr 是输出参数,它接收新创建的子请求引用,ps 是一个回调对象,用于通知父请求子请求正在完成,而 flags 是标志的位掩码。以下标志可用

以下示例创建一个 URI 为 /foo 的子请求。

ngx_int_t            rc;
ngx_str_t            uri;
ngx_http_request_t  *sr;

...

ngx_str_set(&uri, "/foo");

rc = ngx_http_subrequest(r, &uri, NULL, &sr, NULL, 0);
if (rc == NGX_ERROR) {
    /* error */
}

此示例克隆当前请求并为子请求设置完成回调。

ngx_int_t
ngx_http_foo_clone(ngx_http_request_t *r)
{
    ngx_http_request_t          *sr;
    ngx_http_post_subrequest_t  *ps;

    ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
    if (ps == NULL) {
        return NGX_ERROR;
    }

    ps->handler = ngx_http_foo_subrequest_done;
    ps->data = "foo";

    return ngx_http_subrequest(r, &r->uri, &r->args, &sr, ps,
                               NGX_HTTP_SUBREQUEST_CLONE);
}


ngx_int_t
ngx_http_foo_subrequest_done(ngx_http_request_t *r, void *data, ngx_int_t rc)
{
    char  *msg = (char *) data;

    ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                  "done subrequest r:%p msg:%s rc:%i", r, msg, rc);

    return rc;
}

子请求通常在主体过滤器中创建,在这种情况下,它们的输出可以像来自任何显式请求的输出一样对待。这意味着,最终子请求的输出会被发送到客户端,在所有在创建子请求之前传递的显式缓冲区和在创建之后传递的任何缓冲区之后。即使对于大型的子请求层次结构,也会保留此顺序。以下示例在所有请求数据缓冲区之后插入来自子请求的输出,但在带有 last_buf 标志的最终缓冲区之前。

ngx_int_t
ngx_http_foo_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_int_t                   rc;
    ngx_buf_t                  *b;
    ngx_uint_t                  last;
    ngx_chain_t                *cl, out;
    ngx_http_request_t         *sr;
    ngx_http_foo_filter_ctx_t  *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_http_foo_filter_module);
    if (ctx == NULL) {
        return ngx_http_next_body_filter(r, in);
    }

    last = 0;

    for (cl = in; cl; cl = cl->next) {
        if (cl->buf->last_buf) {
            cl->buf->last_buf = 0;
            cl->buf->last_in_chain = 1;
            cl->buf->sync = 1;
            last = 1;
        }
    }

    /* Output explicit output buffers */

    rc = ngx_http_next_body_filter(r, in);

    if (rc == NGX_ERROR || !last) {
        return rc;
    }

    /*
     * Create the subrequest.  The output of the subrequest
     * will automatically be sent after all preceding buffers,
     * but before the last_buf buffer passed later in this function.
     */

    if (ngx_http_subrequest(r, ctx->uri, NULL, &sr, NULL, 0) != NGX_OK) {
        return NGX_ERROR;
    }

    ngx_http_set_ctx(r, NULL, ngx_http_foo_filter_module);

    /* Output the final buffer with the last_buf flag */

    b = ngx_calloc_buf(r->pool);
    if (b == NULL) {
        return NGX_ERROR;
    }

    b->last_buf = 1;

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

    return ngx_http_output_filter(r, &out);
}

子请求也可以用于数据输出以外的其他目的。例如, ngx_http_auth_request_module 模块在 NGX_HTTP_ACCESS_PHASE 阶段创建子请求。为了在此处禁用输出,会在子请求上设置 header_only 标志。这可以防止子请求主体发送到客户端。请注意,子请求的标头永远不会发送到客户端。可以在回调处理程序中分析子请求的结果。

请求结束

通过调用函数 ngx_http_finalize_request(r, rc) 完成 HTTP 请求。通常由内容处理程序在所有输出缓冲区发送到过滤器链后完成。此时,所有输出可能尚未发送到客户端,其中一些可能仍缓存在过滤器链中的某个位置。如果是这种情况,ngx_http_finalize_request(r, rc) 函数会自动安装一个特殊的处理程序 ngx_http_writer(r) 以完成发送输出。如果发生错误或需要向客户端返回标准 HTTP 响应代码,也会完成请求。

函数 ngx_http_finalize_request(r, rc) 期望以下 rc

请求体

为了处理客户端请求的主体,nginx 提供了 ngx_http_read_client_request_body(r, post_handler)ngx_http_discard_request_body(r) 函数。第一个函数读取请求主体并使其通过 request_body 请求字段可用。第二个函数指示 nginx 丢弃(读取并忽略)请求主体。必须为每个请求调用这两个函数之一。通常,内容处理程序会进行调用。

不允许从子请求中读取或丢弃客户端请求主体。它必须始终在主请求中完成。创建子请求时,它会继承父请求的 request_body 对象,如果主请求之前已读取请求主体,则子请求可以使用该对象。

函数 ngx_http_read_client_request_body(r, post_handler) 启动读取请求主体过程。主体完全读取后,将调用 post_handler 回调以继续处理请求。如果请求主体丢失或已读取,则会立即调用回调。函数 ngx_http_read_client_request_body(r, post_handler) 分配类型为 ngx_http_request_body_trequest_body 请求字段。此对象的 bufs 字段将结果保留为缓冲区链。主体可以保存在内存缓冲区或文件缓冲区中,如果由 client_body_buffer_size 指令指定的容量不足以将整个主体放入内存中。

以下示例读取客户端请求主体并返回其大小。

ngx_int_t
ngx_http_foo_content_handler(ngx_http_request_t *r)
{
    ngx_int_t  rc;

    rc = ngx_http_read_client_request_body(r, ngx_http_foo_init);

    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
        /* error */
        return rc;
    }

    return NGX_DONE;
}


void
ngx_http_foo_init(ngx_http_request_t *r)
{
    off_t         len;
    ngx_buf_t    *b;
    ngx_int_t     rc;
    ngx_chain_t  *in, out;

    if (r->request_body == NULL) {
        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return;
    }

    len = 0;

    for (in = r->request_body->bufs; in; in = in->next) {
        len += ngx_buf_size(in->buf);
    }

    b = ngx_create_temp_buf(r->pool, NGX_OFF_T_LEN);
    if (b == NULL) {
        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return;
    }

    b->last = ngx_sprintf(b->pos, "%O", len);
    b->last_buf = (r == r->main) ? 1 : 0;
    b->last_in_chain = 1;

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = b->last - b->pos;

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        ngx_http_finalize_request(r, rc);
        return;
    }

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

    rc = ngx_http_output_filter(r, &out);

    ngx_http_finalize_request(r, rc);
}

请求的以下字段决定了如何读取请求体

request_body_no_buffering 标志启用请求体的无缓冲读取模式。在此模式下,在调用 ngx_http_read_client_request_body() 后,bufs 链可能只保留请求体的一部分。要读取下一部分,请调用 ngx_http_read_unbuffered_request_body(r) 函数。返回值 NGX_AGAIN 和请求标志 reading_body 表示有更多数据可用。如果在调用此函数后 bufs 为 NULL,则目前没有可读取的数据。当请求体的下一部分可用时,将调用请求回调 read_event_handler

请求体过滤器

读取请求体的一部分后,它将通过调用存储在 ngx_http_top_request_body_filter 变量中的第一个请求体过滤器处理程序传递给请求体过滤器链。假设每个请求体处理程序都会调用链中的下一个处理程序,直到调用最终处理程序 ngx_http_request_body_save_filter(r, cl)。此处理程序收集 r->request_body->bufs 中的缓冲区,并在必要时将其写入文件。最后一个请求体缓冲区具有非零的 last_buf 标志。

如果过滤器计划延迟数据缓冲区,则在第一次调用时应将标志 r->request_body->filter_need_buffering 设置为 1

以下是一个延迟请求体一秒钟的简单请求体过滤器的示例。

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


#define NGX_HTTP_DELAY_BODY  1000


typedef struct {
    ngx_event_t   event;
    ngx_chain_t  *out;
} ngx_http_delay_body_ctx_t;


static ngx_int_t ngx_http_delay_body_filter(ngx_http_request_t *r,
    ngx_chain_t *in);
static void ngx_http_delay_body_cleanup(void *data);
static void ngx_http_delay_body_event_handler(ngx_event_t *ev);
static ngx_int_t ngx_http_delay_body_init(ngx_conf_t *cf);


static ngx_http_module_t  ngx_http_delay_body_module_ctx = {
    NULL,                          /* preconfiguration */
    ngx_http_delay_body_init,      /* 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_delay_body_filter_module = {
    NGX_MODULE_V1,
    &ngx_http_delay_body_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
};


static ngx_http_request_body_filter_pt   ngx_http_next_request_body_filter;


static ngx_int_t
ngx_http_delay_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_int_t                   rc;
    ngx_chain_t                *cl, *ln;
    ngx_http_cleanup_t         *cln;
    ngx_http_delay_body_ctx_t  *ctx;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "delay request body filter");

    ctx = ngx_http_get_module_ctx(r, ngx_http_delay_body_filter_module);

    if (ctx == NULL) {
        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_delay_body_ctx_t));
        if (ctx == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        ngx_http_set_ctx(r, ctx, ngx_http_delay_body_filter_module);

        r->request_body->filter_need_buffering = 1;
    }

    if (ngx_chain_add_copy(r->pool, &ctx->out, in) != NGX_OK) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    if (!ctx->event.timedout) {
        if (!ctx->event.timer_set) {

            /* cleanup to remove the timer in case of abnormal termination */

            cln = ngx_http_cleanup_add(r, 0);
            if (cln == NULL) {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
            }

            cln->handler = ngx_http_delay_body_cleanup;
            cln->data = ctx;

            /* add timer */

            ctx->event.handler = ngx_http_delay_body_event_handler;
            ctx->event.data = r;
            ctx->event.log = r->connection->log;

            ngx_add_timer(&ctx->event, NGX_HTTP_DELAY_BODY);
        }

        return ngx_http_next_request_body_filter(r, NULL);
    }

    rc = ngx_http_next_request_body_filter(r, ctx->out);

    for (cl = ctx->out; cl; /* void */) {
        ln = cl;
        cl = cl->next;
        ngx_free_chain(r->pool, ln);
    }

    ctx->out = NULL;

    return rc;
}


static void
ngx_http_delay_body_cleanup(void *data)
{
    ngx_http_delay_body_ctx_t *ctx = data;

    if (ctx->event.timer_set) {
        ngx_del_timer(&ctx->event);
    }
}


static void
ngx_http_delay_body_event_handler(ngx_event_t *ev)
{
    ngx_connection_t    *c;
    ngx_http_request_t  *r;

    r = ev->data;
    c = r->connection;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "delay request body event");

    ngx_post_event(c->read, &ngx_posted_events);
}


static ngx_int_t
ngx_http_delay_body_init(ngx_conf_t *cf)
{
    ngx_http_next_request_body_filter = ngx_http_top_request_body_filter;
    ngx_http_top_request_body_filter = ngx_http_delay_body_filter;

    return NGX_OK;
}

响应

在 nginx 中,HTTP 响应是通过发送响应头后跟可选的响应体来生成的。头和体都通过一系列过滤器传递,最终写入客户端套接字。nginx 模块可以将其处理程序安装到头或体过滤器链中,并处理来自先前处理程序的输出。

响应头

ngx_http_send_header(r) 函数发送输出头。在 r->headers_out 包含生成 HTTP 响应头所需的所有数据之前,不要调用此函数。r->headers_out 中的 status 字段必须始终设置。如果响应状态指示响应体位于头的后面,则也可以设置 content_length_n。此字段的默认值为 -1,这意味着主体大小未知。在这种情况下,使用分块传输编码。要输出任意头,请追加 headers 列表。

static ngx_int_t
ngx_http_foo_content_handler(ngx_http_request_t *r)
{
    ngx_int_t         rc;
    ngx_table_elt_t  *h;

    /* send header */

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = 3;

    /* X-Foo: foo */

    h = ngx_list_push(&r->headers_out.headers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    h->hash = 1;
    ngx_str_set(&h->key, "X-Foo");
    ngx_str_set(&h->value, "foo");

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

    /* send body */

    ...
}

头过滤器

ngx_http_send_header(r) 函数通过调用存储在 ngx_http_top_header_filter 变量中的第一个头过滤器处理程序来调用头过滤器链。假设每个头处理程序都会调用链中的下一个处理程序,直到调用最终处理程序 ngx_http_header_filter(r)。最终的头处理程序根据 r->headers_out 构造 HTTP 响应,并将其传递给 ngx_http_writer_filter 以进行输出。

要向头过滤器链添加处理程序,请在配置时将其地址存储在全局变量 ngx_http_top_header_filter 中。先前的处理程序地址通常存储在模块中的静态变量中,并在新添加的处理程序退出之前由其调用。

以下头过滤器模块示例将 HTTP 头“X-Foo: foo”添加到状态为 200 的每个响应中。

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


static ngx_int_t ngx_http_foo_header_filter(ngx_http_request_t *r);
static ngx_int_t ngx_http_foo_header_filter_init(ngx_conf_t *cf);


static ngx_http_module_t  ngx_http_foo_header_filter_module_ctx = {
    NULL,                                   /* preconfiguration */
    ngx_http_foo_header_filter_init,        /* 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_foo_header_filter_module = {
    NGX_MODULE_V1,
    &ngx_http_foo_header_filter_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
};


static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;


static ngx_int_t
ngx_http_foo_header_filter(ngx_http_request_t *r)
{
    ngx_table_elt_t  *h;

    /*
     * The filter handler adds "X-Foo: foo" header
     * to every HTTP 200 response
     */

    if (r->headers_out.status != NGX_HTTP_OK) {
        return ngx_http_next_header_filter(r);
    }

    h = ngx_list_push(&r->headers_out.headers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    h->hash = 1;
    ngx_str_set(&h->key, "X-Foo");
    ngx_str_set(&h->value, "foo");

    return ngx_http_next_header_filter(r);
}


static ngx_int_t
ngx_http_foo_header_filter_init(ngx_conf_t *cf)
{
    ngx_http_next_header_filter = ngx_http_top_header_filter;
    ngx_http_top_header_filter = ngx_http_foo_header_filter;

    return NGX_OK;
}

响应体

要发送响应体,请调用 ngx_http_output_filter(r, cl) 函数。该函数可以多次调用。每次调用时,它都会以缓冲区链的形式发送响应体的一部分。在最后一个主体缓冲区中设置 last_buf 标志。

以下示例生成一个完整的 HTTP 响应,其主体为“foo”。为了使该示例能够作为子请求以及主请求工作,在输出的最后一个缓冲区中设置了 last_in_chain 标志。last_buf 标志仅为主请求设置,因为子请求的最后一个缓冲区不会结束整个输出。

static ngx_int_t
ngx_http_bar_content_handler(ngx_http_request_t *r)
{
    ngx_int_t     rc;
    ngx_buf_t    *b;
    ngx_chain_t   out;

    /* send header */

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = 3;

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

    /* send body */

    b = ngx_calloc_buf(r->pool);
    if (b == NULL) {
        return NGX_ERROR;
    }

    b->last_buf = (r == r->main) ? 1 : 0;
    b->last_in_chain = 1;

    b->memory = 1;

    b->pos = (u_char *) "foo";
    b->last = b->pos + 3;

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

    return ngx_http_output_filter(r, &out);
}

响应体过滤器

ngx_http_output_filter(r, cl) 函数通过调用存储在 ngx_http_top_body_filter 变量中的第一个主体过滤器处理程序来调用主体过滤器链。假设每个主体处理程序都会调用链中的下一个处理程序,直到调用最终处理程序 ngx_http_write_filter(r, cl)

主体过滤器处理程序接收一个缓冲区链。处理程序应该处理这些缓冲区并将一个可能新的链传递给下一个处理程序。值得注意的是,传入链的链链接 ngx_chain_t 属于调用者,并且不得重用或更改。在处理程序完成后,调用者可以使用其输出链链接来跟踪它发送的缓冲区。要保存缓冲区链或在传递给下一个过滤器之前替换一些缓冲区,处理程序需要分配自己的链链接。

以下是一个简单的主体过滤器示例,它计算主体中的字节数。结果可作为 $counter 变量使用,该变量可以在访问日志中使用。

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


typedef struct {
    off_t  count;
} ngx_http_counter_filter_ctx_t;


static ngx_int_t ngx_http_counter_body_filter(ngx_http_request_t *r,
    ngx_chain_t *in);
static ngx_int_t ngx_http_counter_variable(ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data);
static ngx_int_t ngx_http_counter_add_variables(ngx_conf_t *cf);
static ngx_int_t ngx_http_counter_filter_init(ngx_conf_t *cf);


static ngx_http_module_t  ngx_http_counter_filter_module_ctx = {
    ngx_http_counter_add_variables,        /* preconfiguration */
    ngx_http_counter_filter_init,          /* 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_counter_filter_module = {
    NGX_MODULE_V1,
    &ngx_http_counter_filter_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
};


static ngx_http_output_body_filter_pt  ngx_http_next_body_filter;

static ngx_str_t  ngx_http_counter_name = ngx_string("counter");


static ngx_int_t
ngx_http_counter_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_chain_t                    *cl;
    ngx_http_counter_filter_ctx_t  *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_http_counter_filter_module);
    if (ctx == NULL) {
        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_counter_filter_ctx_t));
        if (ctx == NULL) {
            return NGX_ERROR;
        }

        ngx_http_set_ctx(r, ctx, ngx_http_counter_filter_module);
    }

    for (cl = in; cl; cl = cl->next) {
        ctx->count += ngx_buf_size(cl->buf);
    }

    return ngx_http_next_body_filter(r, in);
}


static ngx_int_t
ngx_http_counter_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
    uintptr_t data)
{
    u_char                         *p;
    ngx_http_counter_filter_ctx_t  *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_http_counter_filter_module);
    if (ctx == NULL) {
        v->not_found = 1;
        return NGX_OK;
    }

    p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN);
    if (p == NULL) {
        return NGX_ERROR;
    }

    v->data = p;
    v->len = ngx_sprintf(p, "%O", ctx->count) - p;
    v->valid = 1;
    v->no_cacheable = 0;
    v->not_found = 0;

    return NGX_OK;
}


static ngx_int_t
ngx_http_counter_add_variables(ngx_conf_t *cf)
{
    ngx_http_variable_t  *var;

    var = ngx_http_add_variable(cf, &ngx_http_counter_name, 0);
    if (var == NULL) {
        return NGX_ERROR;
    }

    var->get_handler = ngx_http_counter_variable;

    return NGX_OK;
}


static ngx_int_t
ngx_http_counter_filter_init(ngx_conf_t *cf)
{
    ngx_http_next_body_filter = ngx_http_top_body_filter;
    ngx_http_top_body_filter = ngx_http_counter_body_filter;

    return NGX_OK;
}

构建过滤器模块

编写主体或头过滤器时,请特别注意过滤器在过滤器顺序中的位置。nginx 标准模块注册了许多头和主体过滤器。nginx 标准模块注册了许多头和主体过滤器,并且相对于它们在正确的位置注册新的过滤器模块非常重要。通常,模块在它们的后期配置处理程序中注册过滤器。处理过程中调用过滤器的顺序显然与它们注册的顺序相反。

对于第三方过滤器模块,nginx 提供了一个特殊的插槽 HTTP_AUX_FILTER_MODULES。要在此插槽中注册过滤器模块,请在模块的配置中将 ngx_module_type 变量设置为 HTTP_AUX_FILTER

以下示例显示了一个过滤器模块配置文件,假设该模块只有一个源文件 ngx_http_foo_filter_module.c

ngx_module_type=HTTP_AUX_FILTER
ngx_module_name=ngx_http_foo_filter_module
ngx_module_srcs="$ngx_addon_dir/ngx_http_foo_filter_module.c"

. auto/module

缓冲区重用

在发出或更改缓冲区流时,通常希望重用已分配的缓冲区。nginx 代码中一种标准且广泛采用的方法是为此目的保留两个缓冲区链:freebusyfree 链保留所有可重用的空闲缓冲区。busy 链保留当前模块发送的所有仍在其他过滤器处理程序中使用的缓冲区。如果缓冲区的大小大于零,则认为该缓冲区正在使用中。通常,当过滤器使用缓冲区时,它的 pos(或文件缓冲区的 file_pos)会向 last(文件缓冲区的 file_last)移动。缓冲区完全使用后,即可重用。要将新释放的缓冲区添加到 free 链中,只需遍历 busy 链并将其中最顶部的零大小缓冲区移动到 free 即可。此操作非常常见,因此有一个特殊的函数 ngx_chain_update_chains(free, busy, out, tag) 用于执行此操作。该函数将输出链 out 附加到 busy 并将空闲缓冲区从 busy 的顶部移动到 free。仅重用具有指定 tag 的缓冲区。这允许模块仅重用它自己分配的缓冲区。

以下示例是一个主体过滤器,它在每个传入缓冲区之前插入字符串“foo”。如果可能,模块分配的新缓冲区将被重用。请注意,为了使此示例正常工作,还需要设置一个 头过滤器 并将 content_length_n 重置为 -1,但此处未提供相关代码。

typedef struct {
    ngx_chain_t  *free;
    ngx_chain_t  *busy;
}  ngx_http_foo_filter_ctx_t;


ngx_int_t
ngx_http_foo_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_int_t                   rc;
    ngx_buf_t                  *b;
    ngx_chain_t                *cl, *tl, *out, **ll;
    ngx_http_foo_filter_ctx_t  *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_http_foo_filter_module);
    if (ctx == NULL) {
        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_foo_filter_ctx_t));
        if (ctx == NULL) {
            return NGX_ERROR;
        }

        ngx_http_set_ctx(r, ctx, ngx_http_foo_filter_module);
    }

    /* create a new chain "out" from "in" with all the changes */

    ll = &out;

    for (cl = in; cl; cl = cl->next) {

        /* append "foo" in a reused buffer if possible */

        tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
        if (tl == NULL) {
            return NGX_ERROR;
        }

        b = tl->buf;
        b->tag = (ngx_buf_tag_t) &ngx_http_foo_filter_module;
        b->memory = 1;
        b->pos = (u_char *) "foo";
        b->last = b->pos + 3;

        *ll = tl;
        ll = &tl->next;

        /* append the next incoming buffer */

        tl = ngx_alloc_chain_link(r->pool);
        if (tl == NULL) {
            return NGX_ERROR;
        }

        tl->buf = cl->buf;
        *ll = tl;
        ll = &tl->next;
    }

    *ll = NULL;

    /* send the new chain */

    rc = ngx_http_next_body_filter(r, out);

    /* update "busy" and "free" chains for reuse */

    ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out,
                            (ngx_buf_tag_t) &ngx_http_foo_filter_module);

    return rc;
}

负载均衡

ngx_http_upstream_module 提供了将请求传递到远程服务器所需的基本功能。实现特定协议(例如 HTTP 或 FastCGI)的模块使用此功能。该模块还提供了一个用于创建自定义负载均衡模块的接口,并实现了一个默认的轮循方法。

least_connhash 模块实现了替代的负载均衡方法,但实际上是作为上游轮循模块的扩展来实现的,并与其共享大量代码,例如服务器组的表示。 keepalive 模块是一个独立的模块,扩展了上游功能。

ngx_http_upstream_module 可以通过将相应的 upstream 块放置到配置文件中来显式配置,或者通过使用诸如 proxy_pass 之类的指令来隐式配置,这些指令接受在某个时间点被评估为服务器列表的 URL。替代负载均衡方法仅在显式上游配置中可用。上游模块配置有其自己的指令上下文 NGX_HTTP_UPS_CONF。结构定义如下

struct ngx_http_upstream_srv_conf_s {
    ngx_http_upstream_peer_t         peer;
    void                           **srv_conf;

    ngx_array_t                     *servers;  /* ngx_http_upstream_server_t */

    ngx_uint_t                       flags;
    ngx_str_t                        host;
    u_char                          *file_name;
    ngx_uint_t                       line;
    in_port_t                        port;
    ngx_uint_t                       no_port;  /* unsigned no_port:1 */

#if (NGX_HTTP_UPSTREAM_ZONE)
    ngx_shm_zone_t                  *shm_zone;
#endif
};

当 Nginx 需要将请求传递给另一个主机进行处理时,它会使用配置的负载均衡方法获取要连接的地址。该方法是从 ngx_http_upstream_t.peer 对象(类型为 ngx_peer_connection_t)中获取的。

struct ngx_peer_connection_s {
    ...

    struct sockaddr                 *sockaddr;
    socklen_t                        socklen;
    ngx_str_t                       *name;

    ngx_uint_t                       tries;

    ngx_event_get_peer_pt            get;
    ngx_event_free_peer_pt           free;
    ngx_event_notify_peer_pt         notify;
    void                            *data;

#if (NGX_SSL || NGX_COMPAT)
    ngx_event_set_peer_session_pt    set_session;
    ngx_event_save_peer_session_pt   save_session;
#endif

    ...
};

该结构体包含以下字段:

所有方法至少接受两个参数:一个对等连接对象 pc 和由 ngx_http_upstream_srv_conf_t.peer.init() 创建的 data。请注意,由于负载均衡模块的“链式”关系,它可能与 pc.data 不同。

示例

nginx-dev-examples 存储库提供了 Nginx 模块示例。

代码风格

一般规则

size_t
ngx_utf8_length(u_char *p, size_t n)
{
    u_char  c, *last;
    size_t  len;

    last = p + n;

    for (len = 0; p < last; len++) {

        c = *p;

        if (c < 0x80) {
            p++;
            continue;
        }

        if (ngx_utf8_decode(&p, last - p) > 0x10ffff) {
            /* invalid UTF-8 */
            return n;
        }
    }

    return len;
}

文件

一个典型的源文件可能包含以下部分,部分之间用两个空行隔开

版权声明如下所示

/*
 * Copyright (C) Author Name
 * Copyright (C) Organization, Inc.
 */

如果文件被大幅修改,则应更新作者列表,将新作者添加到顶部。

ngx_config.hngx_core.h 文件始终首先包含,然后是 ngx_http.hngx_stream.hngx_mail.h 之一。然后是可选的外部头文件

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

#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxslt/xslt.h>

#if (NGX_HAVE_EXSLT)
#include <libexslt/exslt.h>
#endif

头文件应包含所谓的“头文件保护”

#ifndef _NGX_PROCESS_CYCLE_H_INCLUDED_
#define _NGX_PROCESS_CYCLE_H_INCLUDED_
...
#endif /* _NGX_PROCESS_CYCLE_H_INCLUDED_ */

注释

预处理器

宏名称以 ngx_NGX_(或更具体)前缀开头。常量宏名称为大写。参数化宏和初始化宏为小写。宏名称和值之间至少用两个空格分隔

#define NGX_CONF_BUFFER  4096

#define ngx_buf_in_memory(b)  (b->temporary || b->memory || b->mmap)

#define ngx_buf_size(b)                                                      \
    (ngx_buf_in_memory(b) ? (off_t) (b->last - b->pos):                      \
                            (b->file_last - b->file_pos))

#define ngx_null_string  { 0, NULL }

条件在括号内,否定在括号外

#if (NGX_HAVE_KQUEUE)
...
#elif ((NGX_HAVE_DEVPOLL && !(NGX_TEST_BUILD_DEVPOLL)) \
       || (NGX_HAVE_EVENTPORT && !(NGX_TEST_BUILD_EVENTPORT)))
...
#elif (NGX_HAVE_EPOLL && !(NGX_TEST_BUILD_EPOLL))
...
#elif (NGX_HAVE_POLL)
...
#else /* select */
...
#endif /* NGX_HAVE_KQUEUE */

类型

类型名称以“_t”后缀结尾。定义的类型名称之间至少用两个空格分隔

typedef ngx_uint_t  ngx_rbtree_key_t;

结构体类型使用 typedef 定义。在结构体内部,成员类型和名称对齐

typedef struct {
    size_t      len;
    u_char     *data;
} ngx_str_t;

保持文件中不同结构体之间的对齐方式一致。指向自身的结构体名称以“_s”结尾。相邻的结构体定义之间用两个空行隔开

typedef struct ngx_list_part_s  ngx_list_part_t;

struct ngx_list_part_s {
    void             *elts;
    ngx_uint_t        nelts;
    ngx_list_part_t  *next;
};


typedef struct {
    ngx_list_part_t  *last;
    ngx_list_part_t   part;
    size_t            size;
    ngx_uint_t        nalloc;
    ngx_pool_t       *pool;
} ngx_list_t;

每个结构体成员都单独声明一行

typedef struct {
    ngx_uint_t        hash;
    ngx_str_t         key;
    ngx_str_t         value;
    u_char           *lowcase_key;
} ngx_table_elt_t;

结构体内的函数指针具有以“_pt”结尾的定义类型

typedef ssize_t (*ngx_recv_pt)(ngx_connection_t *c, u_char *buf, size_t size);
typedef ssize_t (*ngx_recv_chain_pt)(ngx_connection_t *c, ngx_chain_t *in,
    off_t limit);
typedef ssize_t (*ngx_send_pt)(ngx_connection_t *c, u_char *buf, size_t size);
typedef ngx_chain_t *(*ngx_send_chain_pt)(ngx_connection_t *c, ngx_chain_t *in,
    off_t limit);

typedef struct {
    ngx_recv_pt        recv;
    ngx_recv_chain_pt  recv_chain;
    ngx_recv_pt        udp_recv;
    ngx_send_pt        send;
    ngx_send_pt        udp_send;
    ngx_send_chain_pt  udp_send_chain;
    ngx_send_chain_pt  send_chain;
    ngx_uint_t         flags;
} ngx_os_io_t;

枚举类型以“_e”结尾

typedef enum {
    ngx_http_fastcgi_st_version = 0,
    ngx_http_fastcgi_st_type,
    ...
    ngx_http_fastcgi_st_padding
} ngx_http_fastcgi_state_e;

变量

变量按基本类型的长度排序,然后按字母顺序排序。类型名称和变量名称对齐。类型和名称“列”之间用两个空格分隔。大型数组放在声明块的末尾

u_char                      |  | *rv, *p;
ngx_conf_t                  |  | *cf;
ngx_uint_t                  |  |  i, j, k;
unsigned int                |  |  len;
struct sockaddr             |  | *sa;
const unsigned char         |  | *data;
ngx_peer_connection_t       |  | *pc;
ngx_http_core_srv_conf_t    |  |**cscfp;
ngx_http_upstream_srv_conf_t|  | *us, *uscf;
u_char                      |  |  text[NGX_SOCKADDR_STRLEN];

静态和全局变量可以在声明时初始化

static ngx_str_t  ngx_http_memcached_key = ngx_string("memcached_key");

static ngx_uint_t  mday[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

static uint32_t  ngx_crc32_table16[] = {
    0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
    ...
    0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
};

有一组常用的类型/名称组合

u_char                        *rv;
ngx_int_t                      rc;
ngx_conf_t                    *cf;
ngx_connection_t              *c;
ngx_http_request_t            *r;
ngx_peer_connection_t         *pc;
ngx_http_upstream_srv_conf_t  *us, *uscf;

函数

所有函数(即使是静态函数)都应该有原型。原型包括参数名称。长原型在续行上用单个缩进换行

static char *ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_http_init_phases(ngx_conf_t *cf,
    ngx_http_core_main_conf_t *cmcf);

static char *ngx_http_merge_servers(ngx_conf_t *cf,
    ngx_http_core_main_conf_t *cmcf, ngx_http_module_t *module,
    ngx_uint_t ctx_index);

定义中的函数名称以新行开头。函数体开始和结束花括号在单独的行上。函数体缩进。函数之间有两个空行

static ngx_int_t
ngx_http_find_virtual_server(ngx_http_request_t *r, u_char *host, size_t len)
{
    ...
}


static ngx_int_t
ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
    ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)
{
    ...
}

函数名称和开始括号之间没有空格。长函数调用换行,续行从第一个函数参数的位置开始。如果这不可能,则格式化第一行续行,使其在第 79 位结束

ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
               "http header: \"%V: %V\"",
               &h->key, &h->value);

hc->busy = ngx_palloc(r->connection->pool,
                  cscf->large_client_header_buffers.num * sizeof(ngx_buf_t *));

应使用 ngx_inline 宏代替 inline

static ngx_inline void ngx_cpuid(uint32_t i, uint32_t *buf);

表达式

除了“.”和“−>”之外的二元运算符应与其操作数之间用一个空格分隔。一元运算符和下标不与其操作数之间用空格分隔

width = width * 10 + (*fmt++ - '0');

ch = (u_char) ((decoded << 4) + (ch - '0'));

r->exten.data = &r->uri.data[i + 1];

类型转换与转换表达式之间用一个空格分隔。类型转换内部的星号与类型名称之间用空格分隔

len = ngx_sock_ntop((struct sockaddr *) sin6, p, len, 1);

如果表达式不适合单行,则将其换行。首选换行点是二元运算符。续行与表达式的开头对齐

if (status == NGX_HTTP_MOVED_PERMANENTLY
    || status == NGX_HTTP_MOVED_TEMPORARILY
    || status == NGX_HTTP_SEE_OTHER
    || status == NGX_HTTP_TEMPORARY_REDIRECT
    || status == NGX_HTTP_PERMANENT_REDIRECT)
{
    ...
}

p->temp_file->warn = "an upstream response is buffered "
                     "to a temporary file";

作为最后的手段,可以将表达式换行,使其续行在第 79 位结束

hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t)
                                     + size * sizeof(ngx_hash_elt_t *));

上述规则也适用于子表达式,其中每个子表达式都有自己的缩进级别

if (((u->conf->cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING)
     || c->stale_updating) && !r->background
    && u->conf->cache_background_update)
{
    ...
}

有时,在类型转换后换行表达式很方便。在这种情况下,续行缩进

node = (ngx_rbtree_node_t *)
           ((u_char *) lr - offsetof(ngx_rbtree_node_t, color));

指针显式地与 NULL(而不是 0)进行比较

if (ptr != NULL) {
    ...
}

条件语句和循环

if”关键字与条件之间用一个空格分隔。开始花括号位于同一行,或者如果条件占用多行,则位于专用行。结束花括号位于专用行,之后可以选择跟“else if / else”。通常,“else if / else”部分之前有一个空行

if (node->left == sentinel) {
    temp = node->right;
    subst = node;

} else if (node->right == sentinel) {
    temp = node->left;
    subst = node;

} else {
    subst = ngx_rbtree_min(node->right, sentinel);

    if (subst->left != sentinel) {
        temp = subst->left;

    } else {
        temp = subst->right;
    }
}

类似的格式规则应用于“do”和“while”循环

while (p < last && *p == ' ') {
    p++;
}

do {
    ctx->node = rn;
    ctx = ctx->next;
} while (ctx);

switch”关键字与条件之间用一个空格分隔。开始花括号位于同一行。结束花括号位于专用行。“case”关键字与“switch”对齐

switch (ch) {
case '!':
    looked = 2;
    state = ssi_comment0_state;
    break;

case '<':
    copy_end = p;
    break;

default:
    copy_end = p;
    looked = 0;
    state = ssi_start_state;
    break;
}

大多数“for”循环格式如下

for (i = 0; i < ccf->env.nelts; i++) {
    ...
}

for (q = ngx_queue_head(locations);
     q != ngx_queue_sentinel(locations);
     q = ngx_queue_next(q))
{
    ...
}

如果“for”语句的某些部分被省略,则用“/* void */”注释表示

for (i = 0; /* void */ ; i++) {
    ...
}

带有空语句体的循环也用“/* void */”注释表示,该注释可以放在同一行

for (cl = *busy; cl->next; cl = cl->next) { /* void */ }

无限循环如下所示

for ( ;; ) {
    ...
}

标签

标签用空行包围,并在上一级缩进

    if (i == 0) {
        u->err = "host not found";
        goto failed;
    }

    u->addrs = ngx_pcalloc(pool, i * sizeof(ngx_addr_t));
    if (u->addrs == NULL) {
        goto failed;
    }

    u->naddrs = i;

    ...

    return NGX_OK;

failed:

    freeaddrinfo(res);
    return NGX_ERROR;

调试内存问题

要调试内存问题(例如缓冲区溢出或释放后使用错误),您可以使用某些现代编译器支持的 地址消毒器 (ASan)。要使用 gccclang 启用 ASan,请使用 -fsanitize=address 编译器和链接器选项。在构建 Nginx 时,可以通过将选项添加到 configure 脚本的 --with-cc-opt--with-ld-opt 参数来完成。

由于 Nginx 中的大多数分配都是从 Nginx 内部 内存池 中进行的,因此启用 ASan 并不总是足以调试内存问题。内部内存池从系统分配一大块内存,并从中切出较小的分配。但是,可以通过将 NGX_DEBUG_PALLOC 宏设置为 1 来禁用此机制。在这种情况下,分配直接传递给系统分配器,使其完全控制缓冲区的边界。

以下配置行总结了上述信息。建议在开发第三方模块并在不同平台上测试 Nginx 时使用。

auto/configure --with-cc-opt='-fsanitize=address -DNGX_DEBUG_PALLOC=1'
               --with-ld-opt=-fsanitize=address

常见陷阱

编写 C 模块

最常见的陷阱是尝试编写一个完整的 C 模块,而实际上可以避免。在大多数情况下,您的任务可以通过创建正确的配置来完成。如果编写模块不可避免,请尽量使其尽可能小且简单。例如,模块只能导出一些 变量

在开始编写模块之前,请考虑以下问题:

C 字符串

Nginx 中最常用的字符串类型 ngx_str_t 不是 C 样式的以零结尾的字符串。您不能将数据传递给标准 C 库函数(例如 strlen()strstr())。相反,应使用接受 ngx_str_t 的 Nginx 对应函数 或数据指针和长度。但是,在某些情况下,ngx_str_t 包含指向以零结尾的字符串的指针:作为配置文件解析结果的字符串是以零结尾的。

全局变量

避免在模块中使用全局变量。很有可能使用全局变量是一个错误。任何全局数据都应与 配置周期 相关联,并从相应的 内存池 中分配。这允许 Nginx 执行优雅的配置重新加载。尝试使用全局变量可能会破坏此功能,因为它不可能同时拥有两个配置并摆脱它们。有时需要全局变量。在这种情况下,需要特别注意正确管理重新配置。此外,请检查代码使用的库是否具有在重新加载时可能被破坏的隐式全局状态。

手动内存管理

与其使用容易出错的 malloc/free 方法,不如学习如何使用 nginx 内存池。内存池会被创建并绑定到一个对象上 - 配置周期连接HTTP 请求。当对象被销毁时,与其关联的内存池也会被销毁。因此,在处理对象时,可以从相应的内存池中分配所需的数量,并且无需担心内存释放,即使在发生错误的情况下。

线程

建议避免在 nginx 中使用线程,因为这肯定会导致问题:大多数 nginx 函数都不是线程安全的。预期线程只会执行系统调用和线程安全的库函数。如果您需要运行一些与客户端请求处理无关的代码,正确的方法是在 init_process 模块处理程序中调度一个定时器,并在定时器处理程序中执行所需的操作。nginx 在内部使用 线程 来提升与 IO 相关的操作,但这是一种有很多限制的特殊情况。

阻塞库

一个常见的错误是使用内部阻塞的库。大多数现有的库本质上都是同步和阻塞的。换句话说,它们一次执行一个操作,并浪费时间等待来自其他对等方的响应。结果,当使用此类库处理请求时,整个 nginx 工作进程会被阻塞,从而降低性能。仅使用提供异步接口且不会阻塞整个进程的库。

HTTP 请求到外部服务

模块通常需要对某些外部服务执行 HTTP 调用。一个常见的错误是使用一些外部库,例如 libcurl,来执行 HTTP 请求。完全没有必要为了完成 nginx 本身可以完成的任务而引入大量外部(可能 阻塞!)代码。

当需要外部请求时,有两种基本的使用场景。

在第一种情况下,最好使用 子请求 API。与其直接访问外部服务,不如在 nginx 配置中声明一个 location 并将您的子请求定向到此 location。此 location 不限于 代理 请求,还可以包含其他 nginx 指令。此类方法的一个示例是 auth_request 指令,它在 ngx_http_auth_request 模块 中实现。

对于第二种情况,可以使用 nginx 中提供的基本 HTTP 客户端功能。例如,OCSP 模块 实现了一个简单的 HTTP 客户端。