开发指南

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

介绍

代码布局

包含文件

每个 nginx 文件的开头必须包含以下两个 #include 语句:

#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 的 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字节后可能是空终止的,也可能不是。在大多数情况下,它们不是。但是,在代码的某些部分(例如,解析配置时),已知ngx_str_t对象是空终止的,这简化了字符串比较并使得更容易将字符串传递给系统调用。

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`,以及数组的`size`。`captures`数组的大小必须是三的倍数,符合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提供了用于分配内存的板块池ngx_slab_pool_t类型。在每个nginx共享区域中,自动创建一个用于分配内存的板块池。该池位于共享区域的开头,并且可以通过表达式(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()函数创建的,该函数以前一个循环作为其参数。该函数定位前一个循环的配置文件,并尽可能多地从前一个循环继承资源。一个名为"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_CTXSSLrecv, send, recv_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)。nginx中的HTTP客户端连接是可重用连接的一个示例;它们被标记为可重用,直到从客户端收到第一个请求字节为止。

事件

事件

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发布到发布队列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主进程外,所有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 不是为每个任务创建一个新线程,而是实现了 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中,使用配置脚本的--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_modulengx_errlog_modulengx_regex_modulengx_thread_pool_modulengx_openssl_module模块。HTTP模块、流模块、邮件模块和事件模块也是核心模块。核心模块的上下文定义为:

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之类的模块创建配置的层次结构。例如,为`server`、`location`和`if`范围创建模块的配置。

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配置阶段通过调用函数创建,这些函数分配结构,初始化它们并合并它们。以下示例显示了如何为模块创建一个简单的位置配置。配置有一个名为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()将一个配置与更高级别的配置合并。实际上,服务器和位置配置不仅存在于服务器和位置级别,还会为它们上面的所有级别创建。具体来说,服务器配置也会在主级别创建,位置配置会在主、服务器和位置级别创建。这些配置使得可以在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的位置配置的指针,并替换结构的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的引用。请求的主配置永远不会改变。服务器配置可以在选择请求的虚拟服务器后从默认值更改。为处理请求选择的位置配置可以多次更改,因为重写操作或内部重定向的结果。以下示例显示了如何在运行时访问模块的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阶段的列表。

以下是预访问阶段处理程序的示例。

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被视为最终化代码。从位置内容处理程序返回的任何代码都被视为最终化代码。在访问阶段,在满足任何模式下,除了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保存分配的变量索引,用于引用变量。

通常,模块会创建一个以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中的标志(请参阅上面的描述above)。

set处理程序允许设置由变量引用的属性。例如,$limit_rate变量的设置处理程序修改请求的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字段连接到位置。这意味着在任何时候都可以通过调用ngx_http_get_module_loc_conf(r, module)从请求中检索任何模块的位置配置。请求位置在请求的生命周期中可以多次更改。最初,将请求分配给默认服务器位置的默认服务器。如果请求切换到另一个服务器(由HTTP“Host”标头或SSL SNI扩展选择),则请求也切换到该服务器的默认位置。位置的下一个更改发生在NGX_HTTP_FIND_CONFIG_PHASE请求阶段。在此阶段,根据请求URI从为服务器配置的所有非命名位置中选择一个位置。 ngx_http_rewrite_module可以在NGX_HTTP_REWRITE_PHASE请求阶段更改请求URI,结果是rewrite指令将请求发送回NGX_HTTP_FIND_CONFIG_PHASE阶段,以根据新URI选择新位置。

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

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

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

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)函数将请求重定向到命名位置。位置的名称作为参数传递。在当前服务器的所有命名位置中查找位置,之后请求切换到NGX_HTTP_REWRITE_PHASE阶段。

以下示例执行到命名位置@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 字段中存储了一些上下文时被调用。这些上下文可能与新的位置配置不一致。为了防止不一致,这两个重定向函数都会擦除所有请求上下文。

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

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

子请求

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

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

子请求中的输出头总是被忽略。 ngx_http_postpone_filter 将子请求的输出主体放置在与父请求产生的其他数据相对应的正确位置。

子请求与活动请求的概念相关联。如果 c->data == r,其中 c 是客户端连接对象,则请求 r 被视为活动请求。在任何给定时间点,请求组中只有活动请求被允许将其缓冲区输出到客户端。非活动请求仍然可以将其输出发送到过滤器链,但它不会通过 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标志。

以下示例生成了一个以"foo"作为其主体的完整HTTP响应。为了使示例作为子请求和主请求工作,必须在输出的最后一个缓冲区中设置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模块是一个独立的模块,扩展了上游功能。

可以通过将相应的upstream块放入配置文件中来明确配置ngx_http_upstream_module,或者通过使用诸如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_peer_connection_tngx_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 不同。

示例

The nginx-dev-examples repository provides nginx module examples.

代码风格

一般规则

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;

调试内存问题

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

由于nginx中的大多数分配都来自nginx内部的pool,启用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配置中声明一个位置,并将您的子请求定向到该位置。这个位置不仅限于代理请求,还可以包含其他nginx指令。这种方法的一个例子是在ngx_http_auth_request模块中实现的auth_request指令。

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