开发指南

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

介绍

代码布局

包含文件

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

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

除此之外,HTTP 代码应该包含

#include <ngx_http.h>

Mail 代码应该包含

#include <ngx_mail.h>

Stream 代码应该包含

#include <ngx_stream.h>

整型

通常,nginx 代码使用两种整型:ngx_int_tngx_uint_t,它们分别是 intptr_tuintptr_t 的 typedef。

常见返回码

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 结尾。在大多数情况下不会。然而,在代码的某些部分(例如,解析配置时),已知 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 数组的大小必须是三的倍数,这是 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,函数将失败并报错。通常,模块结构的地址作为 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 启动时会创建一个名为“init cycle”的占位符周期,然后被根据配置构建的实际周期替换。

周期的成员包括

缓冲区

对于输入/输出操作,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_CTXSSL。`recv`、`send`、`recv_chain` 和 `send_chain` 处理程序也会被设置为支持 SSL 的函数。

nginx 配置中的 worker_connections 指令限制了每个 nginx worker 的连接数。所有连接结构在 worker 启动时预先创建,并存储在 cycle 对象的 connections 字段中。要检索连接结构,请使用 `ngx_get_connection(s, log)` 函数。它将套接字描述符 `s` 作为参数,该描述符需要被封装在一个连接结构中。

由于每个 worker 的连接数是有限的,nginx 提供了一种方式来获取当前正在使用的连接。要启用或禁用连接的复用,请调用 `ngx_reusable_connection(c, reusable)` 函数。调用 `ngx_reusable_connection(c, 1)` 会在连接结构中设置 `reuse` 标志,并将连接插入 cycle 的 `reusable_connections_queue` 中。当 `ngx_get_connection()` 发现 cycle 的 `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->read` 和 `c->write`,用于接收套接字准备好进行读或写操作的通知。所有这些事件都工作在 Edge-Triggered (边缘触发)模式下,这意味着它们仅在套接字状态发生变化时触发通知。例如,对套接字进行部分读取不会使 nginx 再次发送读取通知,直到套接字上有更多数据到达。即使底层 I/O 通知机制本质上是 Level-Triggered (水平触发)(例如 `poll`、`select` 等),nginx 也会将通知转换为 Edge-Triggered。为了使 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 事件以及处理到期超时事件需要等待多久。

投递事件

事件可以被 post (发布),这意味着其处理程序将在当前事件循环迭代中的稍后某个时间被调用。发布事件是简化代码和避免堆栈溢出的良好实践。已发布的事件保存在 post 队列中。宏 `ngx_post_event(ev, q)` 将事件 `ev` 发布到 post 队列 `q`。宏 `ngx_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 master 进程外,所有 nginx 进程都执行 I/O 操作,因此都有一个事件循环。(nginx master 进程则大部分时间花在 `sigsuspend()` 调用中,等待信号到达。)nginx 事件循环实现在 `ngx_process_events_and_timers()` 函数中,该函数会重复调用,直到进程退出。

事件循环包含以下阶段

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

进程

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

nginx 进程处理以下信号

尽管所有 nginx worker 进程都能够接收并正确处理 POSIX 信号,但 master 进程并不使用标准的 kill() 系统调用将信号传递给 worker 和 helper 进程。相反,nginx 使用进程间套接字对,它允许在所有 nginx 进程之间发送消息。然而,目前消息仅从 master 发送到其子进程。这些消息携带标准信号。

线程

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

为了处理同步,可以使用以下对 pthreads 原语的封装

nginx 并未为每个任务创建一个新线程,而是实现了线程池 (thread_pool)策略。可以为不同的目的配置多个线程池(例如,对不同的磁盘组执行 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 中,请使用 configure 脚本的 --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_threadexit_thread 处理程序。也没有 init_master 处理程序,因为这会带来不必要的开销。

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

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

核心模块集包括 ngx_core_module, ngx_errlog_module, ngx_regex_module, ngx_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 级别,还为它们以上的所有级别创建。具体来说,服务器配置也在主级别创建,而 location 配置在主、服务器和 location 级别创建。这些配置使得可以在 nginx 配置文件的任何级别指定特定于服务器和 location 的设置。最终配置会向下合并。提供了许多宏,如 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 字段中保存的 location 内容处理器。

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;
}

阶段处理器应返回特定的代码

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

变量

访问现有变量

变量可以通过索引(这是最常用的方法)或名称(参见下文)引用。索引在配置阶段创建,当变量添加到配置中时。要获取变量索引,使用 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 存储分配的变量索引,用于引用变量。

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

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 请求阶段通过 rewrite 指令更改请求 URI,并将请求发送回 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;
}

子请求通常在 body 过滤器中创建,在这种情况下,它们的输出可以像任何显式请求的输出一样处理。这意味着最终,子请求的输出会在创建子请求之前传递的所有显式缓冲区之后,以及创建之后传递的任何缓冲区之前发送给客户端。即使对于大量嵌套的子请求,这种顺序也会保留。以下示例将子请求的输出插入到所有请求数据缓冲区之后,但在带有 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);
}

子请求(subrequest)也可以用于数据输出以外的其他目的。例如,ngx_http_auth_request_module 模块在 NGX_HTTP_ACCESS_PHASE 阶段创建一个子请求。为了在该阶段禁用输出,子请求会设置 header_only 标志。这会阻止子请求的正文发送给客户端。请注意,子请求的头部永远不会发送给客户端。子请求的结果可以在回调处理程序中进行分析。

请求终结

HTTP 请求通过调用函数 ngx_http_finalize_request(r, rc) 来结束。通常,内容处理程序在所有输出缓冲区发送到过滤链后结束请求。此时,并非所有输出都已发送到客户端,其中一部分可能仍然缓冲在过滤链的某个位置。如果是这样,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 中。先前的处理程序地址通常存储在模块的静态变量中,并在新添加的处理程序退出之前由新处理程序调用。

以下是一个头部过滤器模块示例,它会向所有状态为 200 的响应添加 HTTP 头部“X-Foo: foo”。

#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 模块实现了替代的负载均衡方法,但它们实际上是作为 upstream 轮询模块的扩展实现的,并与其共享大量代码,例如服务器组的表示。 keepalive 模块是一个独立的模块,扩展了 upstream 功能。

ngx_http_upstream_module 可以通过在配置文件中放置相应的 upstream 块来显式配置,也可以通过使用接受 URL(该 URL 在某个时刻会被解析为服务器列表)的指令(如 proxy_pass)来隐式配置。替代的负载均衡方法仅适用于显式 upstream 配置。upstream 模块配置有自己的指令上下文 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_peer_connection_t 类型的 ngx_http_upstream_t.peer 对象中获取

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;

调试内存问题

要调试内存问题,例如缓冲区溢出或 use-after-free 错误,可以使用某些现代编译器支持的 AddressSanitizer (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 worker 都会被阻塞,从而降低性能。只使用提供异步接口且不阻塞整个进程的库。

对外部服务的 HTTP 请求

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

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

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

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