开发指南

介绍

Code 布局

  • auto - Build 脚本

  • src

  • core - 基本类型和函数 - string,array,log,pool 等

  • event - Event 核心

  • modules - Event 通知模块:epollkqueueselect

  • http - 核心 HTTP 模块和 common code

  • modules - 其他 HTTP 模块

  • v2 - HTTP/2

  • mail - 邮件模块

  • os - Platform-specific code

  • unix

  • win32

  • stream - 流模块

包括 files

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

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

除此之外,HTTP code 应该包括

#include <ngx_http.h>

邮件 code 应包括

#include <ngx_mail.h>

流 code 应该包括

#include <ngx_stream.h>

整型

出于一般目的,nginx code 使用两个 integer 类型ngx_int_tngx_uint_t,它们分别是intptr_tuintptr_t的 typedef。

Common return 代码

nginx 中的大多数函数 return 以下代码:

  • NGX_OK - 操作成功。

  • NGX_ERROR - 操作失败。

  • NGX_AGAIN - 操作不完整;再次调用 function。

  • NGX_DECLINED - 对于 example,操作被拒绝,因为它在 configuration 中被禁用。这绝不是错误。

  • NGX_BUSY - 资源不可用。

  • NGX_DONE - 操作在其他地方完成或继续。也用作替代成功 code。

  • NGX_ABORT - Function 被中止。也用作替代错误 code。

错误处理

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

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

示例使用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;
}

Strings

概观

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

nginx string 类型ngx_str_t定义如下:

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

len字段保存 string 长度,data保存 string 数据。保存在ngx_str_t中的 string 在len字节之后可能是 null-terminated,也可能不是 null-terminated。在大多数情况下,它不是。但是,在 code 的某些部分(例如,解析 configuration 时),ngx_str_t objects 已知为 null-terminated,这简化了 string 比较,并且更容易将 strings 传递给系统调用。

nginx 中的 string 操作在src/core/ngx_string.h中声明。其中一些是围绕标准 C 函数的包装器:

  • ngx_strcmp()

  • ngx_strncmp()

  • ngx_strstr()

  • ngx_strlen()

  • ngx_strchr()

  • ngx_memcmp()

  • ngx_memset()

  • ngx_memcpy()

  • ngx_memmove()

其他 string 函数是 nginx-specific

  • ngx_memzero() - 用零填充 memory。

  • ngx_explicit_memzero() - 与ngx_memzero()相同,但编译器的 dead store 消除优化永远不会删除此调用。此 function 可用于清除敏感数据,如密码和密钥。

  • ngx_cpymem() - 与ngx_memcpy()相同,但返回最终目标地址这个可以方便地连续添加多个 strings。

  • ngx_movemem() - 与ngx_memmove()相同,但返回最终目标地址。

  • ngx_strlchr() - 搜索 string 中的字符,由两个指针分隔。

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

  • ngx_tolower()

  • ngx_toupper()

  • ngx_strlow()

  • ngx_strcasecmp()

  • ngx_strncasecmp()

以下宏简化了 string 初始化:

  • ngx_string(text) - 来自 C string 文字的ngx_str_t类型的静态初始化程序text

  • ngx_null_string - ngx_str_t类型的静态空 string 初始值设定项

  • ngx_str_set(str, text) - 使用 C string 文字text初始化ngx_str_t *类型的 string str

  • ngx_str_null(str) - 使用空 string 初始化ngx_str_t *类型的 string str

格式化

以下格式化函数支持 nginx-specific 类型:

  • ngx_sprintf(buf, fmt, ...)

  • ngx_snprintf(buf, max, fmt, ...)

  • ngx_slprintf(buf, last, fmt, ...)

  • ngx_vslprintf(buf, last, fmt, args)

  • ngx_vsnprintf(buf, max, fmt, args)

这些函数支持的格式化选项的完整列表位于src/core/ngx_string.c中。他们之中有一些是:

  • %Ooff_t

  • %Ttime_t

  • %zssize_t

  • %ingx_int_t

  • %pvoid *

  • %Vngx_str_t *

  • %s - u_char *(null-terminated)

  • %*ssize_t + u_char *

您可以在大多数类型上添加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 中实现了几个用于数值转换的函数。前四个每个都将给定长度的 string 转换为指定类型的正 integer。他们_错误地回复NGX_ERROR

  • ngx_atoi(line, n)ngx_int_t

  • ngx_atosz(line, n)ssize_t

  • ngx_atoof(line, n)off_t

  • ngx_atotm(line, n)time_t

还有两个额外的数字转换函数。像前四个一样,他们在错误时回复NGX_ERROR

  • ngx_atofp(line, n, point) - 将给定长度的固定点浮点数转换为ngx_int_t类型的正整数。结果向左移动point decimal 位置。数字的 string 表示应该不超过points小数。对于 example,ngx_atofp("10.5", 4, 2)返回1050

  • ngx_hextoi(line, n) - 将正__ger 的十六进制表示形式转换为ngx_int_t

常用表达

nginx 中的正则表达式接口是PCRE library 周围的 wrapper。相应的头文件是src/core/ngx_regex.h

要使用正则表达式进行 string 匹配,首先需要对其进行编译,这通常在 configuration 阶段完成。请注意,由于 PCRE 支持是可选的,因此使用该接口的所有 code 必须受到周围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 are passed as is to pcre_compile() */

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字段分别包含正则表达式中的所有捕获和命名捕获的计数。

然后可以使用编译的正则表达式来匹配 strings:

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()的 arguments 是已编译的正则表达式re,string 为 match input,一个可选的 array 整数,用于保存找到的任何captures,以及 array 的size。根据PCRE API的要求,captures array 的大小必须是三的倍数。在 example 中,大小是根据捕获的总数加上匹配的 string 本身的一个来计算的。

如果有匹配,则可以按如下方式访问捕获:

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() function 接受ngx_regex_elt_t元素的 array(它们只是带有相关名称的正则表达式),_ctring 到 match 和 log。 function 将 array 中的表达式应用于 string,直到找到 match 或者不再有表达式为止。 ret value 是NGX_OK,当有 match 而NGX_DECLINED否则,或NGX_ERROR如果有错误。

时间

ngx_time_t结构表示 time,具有三种不同的类型,包括秒,毫秒和 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的别名。

要获取当前 time,通常可以访问其中一个可用的 global 变量,表示所需格式的缓存 time value。

可用的 string 表示形式为:

  • ngx_cached_err_log_time - 用于错误 log 条目:"1970/09/28 12:00:00"

  • ngx_cached_http_log_time - 用于 HTTP 访问 log 条目:"28/Sep/1970:12:00:00 +0600"

  • ngx_cached_syslog_time - 用于 syslog 条目:"Sep 28 12:00:00"

  • ngx_cached_http_time - 在 HTTP headers 中使用:"Mon, 28 Sep 1970 06:00:00 GMT"

  • ngx_cached_http_log_iso8601 - ISO 8601 标准格式:"1970-09-28T12:00:00+06:00"

ngx_time()ngx_timeofday()宏_以为单位返回当前 time value,是访问缓存的 time value 的首选方法。

要显式获取 time,请使用ngx_gettimeofday(),它会更新其参数(指向struct timeval的指针)。当 nginx 从 system calls 返回 event 循环时,time 始终会更新。要立即更新 time,请在信号处理程序 context 中更新 time 时调用ngx_time_update()ngx_time_sigsafe_update()

以下函数将time_t转换为指示的 broken-down time 表示。每对中的第一个 function 将time_t转换为ngx_tm_t,将第二个(带有_libc_中缀)转换为struct tm

  • ngx_gmtime(), ngx_libc_gmtime() - Time 表示为 UTC

  • ngx_localtime(), ngx_libc_localtime() - Time 相对于本地 time zone 表示

ngx_http_time(buf, time) function 返回适合在 HTTP headers 中使用的 string 表示(对于 example,"Mon, 28 Sep 1970 06:00:00 GMT")。 ngx_http_cookie_time(buf, time)返回 string 表示 function 返回适合 HTTP cookies("Thu, 31-Dec-37 23:55:55 GMT")的 string 表示。

集装箱

Array

nginx array 类型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;

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

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

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

使用以下函数将元素添加到 array:

  • ngx_array_push(a)添加一个尾部元素并返回指向它的指针

  • ngx_array_push_n(a, n)添加n尾元素并返回指向第一个元素的指针

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

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)创建列表来初始化列表。这两个函数将 arguments 视为单个 item 的大小和每个列表部分的多个项目。要将 item 添加到列表,请使用ngx_list_push(list) function。要迭代项目,直接访问列表字段,如 example 中所示:

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

Lists 主要用于 HTTP 输入和输出 headers。

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

队列

在 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)调用初始化列表头。队列支持以下操作:

  • ngx_queue_insert_head(h, x)ngx_queue_insert_tail(h, x) - 插入一个新节点

  • ngx_queue_remove(x) - 删除队列节点

  • ngx_queue_split(h, q, n) - 在节点处拆分队列,将队列尾部返回到单独的队列中

  • ngx_queue_add(h, n) - 将第二个队列添加到第一个队列

  • ngx_queue_head(h)ngx_queue_last(h) - 获取第一个或最后一个队列节点

  • ngx_queue_sentinel(h) - 获取队列 sentinel object 以结束迭代

  • ngx_queue_data(q, type, link) - 获取队列节点数据结构开头的 reference,考虑其中的队列字段偏移量

一个例子:

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

Red-Black 树

src/core/ngx_rbtree.h头文件提供对 red-black 树的有效 implementation 的访问。

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;

要处理整个树,需要两个节点:root 和 sentinel。通常,它们会添加到自定义结构中,允许您将数据组织到树中,其中树叶包含指向或嵌入数据的链接。

要初始化树:

my_tree_t  root;

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

要遍历树并插入新值,请使用“insert_value”函数。例如,ngx_str_rbtree_insert_value function 处理ngx_str_t类型。它的 arguments 是指向插入的根节点,要添加的新创建的节点以及树的标记的指针。

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

遍历非常简单,可以使用以下 lookup function pattern 进行演示:

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() function 是一个经典的比较器 function,它返回一个小于,等于或大于零的 value。为了加速查找并避免比较可能很大的用户 objects,使用 integer 哈希字段。

要将节点添加到树,请分配新节点,对其进行初始化并调用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() function:

ngx_rbtree_delete(&root->rbtree, node);

哈希

Hash table 函数在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是指向 function 的指针,该函数从 string 创建 hash integer key。有两个通用的 key-creation 函数:ngx_hash_key(data, len)ngx_hash_key_lc(data, len)。后者将 string 转换为所有小写字符,因此传递的 string 必须是可写的。如果那不是 true,则将NGX_HASH_READONLY_KEY flag 传递给 function,初始化 key array(见下文)。

散列键存储在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);

要将键插入哈希键 array,请使用ngx_hash_add_key(keys_array, key, value, flags) function:

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

要 build hash table,请调用ngx_hash_init(hinit, key_names, nelts) function:

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

如果max_sizebucket_size参数不够大,则 function 失败。

构建哈希时,使用ngx_hash_find(hash, key, name, len) function 查找元素:

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。基本 properties 的初始化类似于常规哈希:

ngx_hash_init_t      hash
ngx_hash_combined_t  foo_hash;

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

可以使用NGX_HASH_WILDCARD_KEY flag 添加通配符键:

/* 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);

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

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

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

需要对键 array 进行排序,并且必须将初始化结果添加到组合哈希中。 dns_wc_tail array 的初始化类似地完成。

组合哈希中的查找由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);

内存管理

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

  • ngx_alloc(size, log) - 从系统堆中分配 memory。这是一个围绕malloc()的 wrapper,有 logging 支持。分配错误和调试信息记录到log

  • ngx_calloc(size, log) - 从系统堆中分配 memory,如ngx_alloc(),但在分配后用零填充 memory。

  • ngx_memalign(alignment, size, log) - 从系统堆中分配对齐的 memory。在提供该功能的平台上,这是一个围绕posix_memalign()的 wrapper。否则 implementation 会回退到ngx_alloc(),从而提供最大对齐。

  • ngx_free(p) - 免费分配 memory。这是一个围绕free()的 wrapper

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

池内部在 memory 的连续块中分配 objects。块已满后,将分配一个新块并将其添加到池 memory 块列表中。当请求的分配太大而无法放入块时,请求将被转发到系统分配器,并且返回的指针将存储在池中以进一步释放。

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

  • ngx_create_pool(size, log) - 创建具有指定块大小的池。返回的池 object 也在池中分配。 size应至少为NGX_MIN_POOL_SIZENGX_POOL_ALIGNMENT的倍数。

  • ngx_destroy_pool(pool) - 释放所有池 memory,包括池 object 本身。

  • ngx_palloc(pool, size) - 从指定的池中分配对齐的 memory。

  • ngx_pcalloc(pool, size) - 从指定的池中分配对齐的 memory 并用零填充它。

  • ngx_pnalloc(pool, size) - 从指定的池中分配未对齐的 memory。主要用于分配 strings。

  • ngx_pfree(pool, p) - 以前在指定池中分配的自由 memory。只能释放由转发到系统分配器的请求产生的分配。

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 池 implementation 提供了重用它们的方法。 ngx_pool_tchain字段保留了先前分配的链接列表,可以重用。要在池中有效分配链接,请使用ngx_alloc_chain_link(pool) function。此 function 在池列表中查找自由链链接,并在池列表为空时分配新的链链接。要释放链接,请调用ngx_free_chain(pool, cl) function。

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

要注册池清理,请调用ngx_pool_cleanup_add(pool, size),它返回一个ngx_pool_cleanup_t指针,由调用者填写。使用size参数为清理处理程序分配 context。

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

共享 memory

nginx 使用共享 memory 在进程之间共享 common 数据。 ngx_shared_memory_add(cf, name, size, tag) function 将一个新的共享 memory 条目ngx_shm_zone_t添加到循环中。 function 接收 zone 的namesize。每个共享 zone 必须具有唯一的 name。如果已存在提供的nametag的共享 zone 条目,则会重用现有的 zone 条目。如果具有相同 name 的现有条目具有不同的标记,则 function 将失败并显示错误。通常,模块结构的地址作为tag传递,从而可以在一个 nginx 模块中重用 name 的共享 zones。

共享 memory 条目结构ngx_shm_zone_t具有以下字段:

  • init - 在共享 zone 映射到实际 memory 之后调用的初始化回调

  • data - Data context,用于将任意数据传递给init回调

  • noreuse - Flag 禁用旧循环中重用的共享 zone

  • tag - 共享 zone 标记

  • shm - Platform-specific object 类型ngx_shm_t,至少包含以下字段:

  • addr - 映射共享 memory 地址,最初为 NULL

  • size - 共享 memory 大小

  • name - 共享 memory name

  • log - 共享 memory log

  • exists - Flag 表示共享 memory 继承自 master process(Windows-specific)

解析 configuration 后,共享 zone 条目将映射到ngx_init_cycle()中的实际 memory。在 POSIX 系统上,mmap()系统调用用于创建共享匿名映射。在 Windows 上,使用CreateFileMapping()/MapViewOfFileEx()对。

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

Slab 池将所有共享 zone 划分为页面。每个页面用于分配相同大小的 objects。指定的大小必须是 2 的幂,并且大于 8 字节的最小大小。不合格的值被四舍五入。每个页面的位掩码跟踪正在使用哪些块以及哪些块可以自由分配。对于大于半页(通常为 2048 字节)的大小,分配在 time 时完成整个页面

要保护共享 memory 中的数据不受并发访问影响,请使用ngx_slab_pool_tmutex字段中提供的 mutex。在分配和释放 memory 时,mutex 最常用于 slab 池,但它可用于保护 shared zone 中分配的任何其他用户数据结构。要锁定或解锁 mutex,请分别调用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;
}

Logging

对于 logging,nginx 使用ngx_log_t objects。 nginx logger 支持几种类型的输出:

  • stderr - Logging to standard error(stderr)

  • file - Logging to a file

  • syslog - Logging to syslog

  • memory - 为了开发目的,Logging 到内部 memory 存储;稍后可以使用调试器访问 memory

logger 实例可以是 loggers 链,使用next字段相互链接。在这种情况下,每条消息都写入链中的所有 logger。

对于每个 logger,severity level 控制将哪些消息写入 log(仅记录分配 level 或更高级别的 events)。支持以下严重性级别:

  • NGX_LOG_EMERG

  • NGX_LOG_ALERT

  • NGX_LOG_CRIT

  • NGX_LOG_ERR

  • NGX_LOG_WARN

  • NGX_LOG_NOTICE

  • NGX_LOG_INFO

  • NGX_LOG_DEBUG

对于 debug logging,也会检查调试掩码。调试掩码是:

  • NGX_LOG_DEBUG_CORE

  • NGX_LOG_DEBUG_ALLOC

  • NGX_LOG_DEBUG_MUTEX

  • NGX_LOG_DEBUG_EVENT

  • NGX_LOG_DEBUG_HTTP

  • NGX_LOG_DEBUG_MAIL

  • NGX_LOG_DEBUG_STREAM

通常,loggers 是由来自error_log指令的现有 nginx code 创建的,几乎在循环,configuration,client 连接和其他 objects 处理的每个阶段都可用。

Nginx 提供以下 logging 宏:

  • ngx_log_error(level, log, err, fmt, ...) - 错误 logging

  • ngx_log_debug0(level, log, err, fmt)ngx_log_debug1(level, log, err, fmt, arg1) etc - 使用最多八个支持的格式化 arguments 调试 logging

log 消息在堆栈上的大小为NGX_MAX_ERROR_STR(当前为 2048 字节)的缓冲区中格式化。该消息前面带有严重性 level,process ID(PID),连接 ID(存储在log->connection中)和系统错误文本。对于 non-debug 消息,也会调用log->handler以在 log 消息中添加更具体的信息。 HTTP 模块 sets ngx_http_log_error() function as log handler to log client and server addresses,current action(存储在log->action), client 请求 line,server name 等。

/* 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);

上面的 example 导致 log 条目如下:

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

周期

循环 object 存储从特定 configuration 创建的 nginx 运行时 context。它的类型是ngx_cycle_t。当前循环由ngx_cycle global 变量引用,并在它们启动时由 nginx workers 继承。每次 time 重新加载 nginx configuration 时,都会从新的 nginx configuration 创建一个新的循环;成功创建新循环后,通常会删除旧循环。

循环由ngx_init_cycle() function 创建,它将前一个循环作为其参数。 function 定位上一个循环的 configuration 文件,并从前一个循环继承尽可能多的资源。称为“init cycle”的占位符循环创建为 nginx start,然后由 configuration 构建的实际循环替换。

该周期的成员包括:

  • pool - 循环池。为每个新周期创建。

  • log - 循环 log。最初从旧循环继承,在读取 configuration 后将其设置为指向new_log

  • new_log - 循环 log,由 configuration 创建。它受 root-scope error_log指令的影响。

  • connectionsconnection_n - Array 类型为ngx_connection_t的连接,由 event 模块在初始化每个 nginx worker 时创建。 nginx configuration 中的worker_connections指令设置连接数connection_n

  • free_connectionsfree_connection_n - 当前可用连接的列表和数量。如果没有可用的连接,则 nginx worker 拒绝接受新的 clients 或连接到上游服务器。

  • filesfiles_n - Array 用于将文件描述符映射到 nginx 连接。这个映射由 event 模块使用,具有NGX_USE_FD_EVENT flag(当前,它是polldevpoll)。

  • conf_ctx - 核心模块配置的阵列。在读取 nginx configuration files 期间创建并填充配置。

  • modulesmodules_n - Array 类型为ngx_module_t的模块,包括静态和动态,由当前 configuration 加载。

  • listening - Array of listen objects of ngx_listening_t。通常通过调用ngx_create_listening() function 的不同模块的listen指令添加侦听 objects。侦听套接字是基于侦听 objects 创建的。

  • paths - Array 类型为ngx_path_t的_path。通过从将在某些目录上运行的模块调用 function ngx_add_path()来添加 Paths。这些目录是在读取 configuration 后由 nginx 创建的,如果缺少的话。此外,可以为每个路径添加两个处理程序:

  • 路径加载器 - 在启动或重新加载 nginx 后,仅在 60 秒内执行一次。通常,加载程序读取 nginx shared memory 中的目录和 stores 数据。处理程序从专用的 nginx process“nginx 缓存加载器”调用。

  • path manager - 定期执行。通常,manager 从目录中删除旧的 files 并更新 nginx memory 以反映更改。处理程序从专用的“nginx cache manager”process 调用。

  • open_files - ngx_open_file_t类型的打开文件 objects 列表,它们是通过调用 function ngx_conf_open_file()创建的。目前,nginx 使用这种 open files 进行 logging。阅读 configuration 后,nginx 打开open_files列表中的所有 files,并 stores object 的fd字段中的每个文件描述符。 files 以追加模式打开,如果缺少则创建。列表中的 files 在收到重新打开信号(最常见的是USR1)后由 nginx workers 重新打开。在这种情况下,fd字段中的描述符将更改为新的 value。

  • shared_memory - 共享的 memory zones 列表,每个都通过调用ngx_shared_memory_add() function 添加。共享 zones 映射到所有 nginx 进程中的相同地址范围,并用于共享 common 数据,例如 HTTP 缓存 in-memory 树。

缓冲

对于 input/output 操作,nginx 提供缓冲区类型ngx_buf_t。通常,它用于保存要写入目标或从源读取的数据。缓冲区可以在 memory 或文件中引用数据,从技术上讲,缓冲区可以在同一 time 时进行 reference。缓冲区的 Memory 是单独分配的,与缓冲区结构ngx_buf_t无关。

ngx_buf_t结构具有以下字段:

  • startend - 为缓冲区分配的 memory 块的边界。

  • poslast - memory 缓冲区的边界;通常是start .. end的子范围。

  • file_posfile_last - 文件缓冲区的边界,表示为距文件开头的偏移量。

  • tag - 用于区分缓冲区的唯一 value;由不同的 nginx 模块创建,通常用于缓冲区重用。

  • file - 文件 object。

  • temporary - Flag 表示缓冲区 references 可写 memory。

  • memory - Flag 表示缓冲区 references read-only memory。

  • in_file - Flag 表示缓冲区 references 文件中的数据。

  • flush - Flag 表示需要刷新缓冲区之前的所有数据。

  • recycled - Flag 表示缓冲区可以重用,需要尽快使用。

  • sync - Flag 表示缓冲区不携带数据或特殊信号,如flushlast_buf。默认情况下,nginx 认为这样的缓冲区是一个错误条件,但是这个 flag 告诉 nginx 跳过错误检查。

  • last_buf - Flag 表示缓冲区是输出中的最后一个。

  • last_in_chain - Flag 表示请求或子请求中没有更多数据缓冲区。

  • shadow - 引用与当前缓冲区相关的另一个(“阴影”)缓冲区,通常是缓冲区使用阴影中的数据。消耗缓冲区时,通常还将阴影缓冲区标记为已消耗。

  • last_shadow - Flag 表示缓冲区是 reference 特定影子缓冲区的最后一个缓冲区。

  • temp_file - Flag 表示缓冲区位于临时文件中。

对于输入和输出操作,缓冲区链接在一起。链是ngx_chain_t类型的链接序列,定义如下:

typedef struct ngx_chain_s  ngx_chain_t;

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

每个链节都保持对其缓冲区的 reference 和对下一个链节的 reference。

使用缓冲区和链的示例:

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是 socket 描述符周围的 wrapper。它包括以下字段:

  • fd - Socket 描述符

  • data - 任意连接 context。通常,它是指向连接顶部构建的 higher-level object 的指针,例如 HTTP 请求或 Stream session。

  • readwrite - 读取和写入连接的 events。

  • recvsendrecv_chainsend_chain - I/O 连接操作。

  • pool - 连接池。

  • log - 连接 log。

  • sockaddrsocklenaddr_text - Remote socket 地址的二进制和文本形式。

  • local_sockaddrlocal_socklen - 二进制形式的本地 socket 地址。最初,这些字段是空的。使用ngx_connection_local_sockaddr() function 获取本地 socket 地址。

  • proxy_protocol_addrproxy_protocol_port - PROXY 协议客户端地址和 port,如果为连接启用了 PROXY 协议。

  • ssl - 连接的 SSL context。

  • reusable - Flag 表示连接位于 state 中,使其有资格重用。

  • close - Flag 表示正在重用连接并需要关闭。

nginx 连接可以透明地封装 SSL 层。在这种情况下,连接的ssl字段包含指向ngx_ssl_connection_t结构的指针,保留连接的所有 SSL-related 数据,包括SSL_CTXSSLrecvsendrecv_chainsend_chain处理程序也设置为 SSL-enabled 函数。

nginx configuration 中的worker_connections指令限制了每个 nginx worker 的连接数。当 worker 启动并存储在循环 object 的connections字段中时,所有连接结构都是预先创建的。要检索连接结构,请使用ngx_get_connection(s, log) function。它将s参数作为 socket 描述符,需要将其包装在连接结构中。

由于每个 worker 的连接数量有限,nginx 提供了一种 grab 当前正在使用的连接的方法。要启用或禁用连接的重用,请调用ngx_reusable_connection(c, reusable) function。在连接结构中调用ngx_reusable_connection(c, 1) sets reuse flag 并将连接插入到循环的reusable_connections_queue中。每当ngx_get_connection()发现循环的free_connections列表中没有可用的连接时,它 calls ngx_drain_connections()以释放特定数量的可重用连接。对于每个这样的连接,设置close flag 并调用其读取处理程序,该处理程序应该通过调用ngx_close_connection(c)释放连接并使其可用于重用。要在可以重用连接时退出 state,将调用ngx_reusable_connection(c, 0)。 HTTP client 连接是 nginx 中可重用连接的示例;它们被标记为可重用,直到从 client 收到第一个请求字节为止。

Events

事件

nginx 中的 Event object ngx_event_t提供了一种通知特定 event 发生的机制。

ngx_event_t中的字段包括:

  • data - event 处理程序中使用的任意 event context,通常作为指向与 event 相关的连接的指针。

  • handler - event 发生时要调用的回调函数。

  • write - Flag 表示写入 event。没有 flag 表示读 event。

  • active - Flag 表示 event 已注册接收 I/O 通知,通常来自通知机制,如epollkqueuepoll

  • ready - Flag 表示 event 已收到 I/O 通知。

  • delayed - Flag 表示由于速率限制导致 I/O 延迟。

  • timer - Red-black 树节点,用于将 event 插入到计时器树中。

  • timer_set - Flag 表示 event 计时器已设置且尚未过期。

  • timedout - Flag 表示 event 计时器已过期。

  • eof - Flag 表示在读取数据时发生了 EOF。

  • pending_eof - Flag 表示 socket 上的 EOF 正在等待,即使在它之前可能有一些数据可用。 flag 通过EPOLLRDHUP epoll event 或EV_EOF kqueue flag 传递。

  • error - Flag 表示在读取(读取 event)或写入(写入 event)期间发生错误。

  • cancelable - Timer event flag 指示在关闭 worker 时应忽略 event。优雅的 worker 关闭被延迟,直到没有安排 non-cancelable timer events。

  • posted - Flag 表示 event 已发布到队列中。

  • queue - 用于将 event 发布到队列的队列节点。

I/O events

通过调用ngx_get_connection() function 获得的每个连接都有两个附加的 events,c->readc->write,它们用于接收 socket 准备好读取或写入的通知。所有这些 events 都以 Edge-Triggered 模式运行,这意味着它们只在 socket 的 state 发生变化时触发通知。例如,对 socket 进行部分读取不会使 nginx 发送重复的读取通知,直到更多数据到达 socket。即使基础 I/O 通知机制基本上是 Level-Triggered(pollselect等),nginx 也会将通知转换为 Edge-Triggered。要使 nginx event 通知在不同平台上的所有通知系统中保持一致,必须在处理 I/O socket 通知或调用该 socket 上的任何 I/O 函数后调用函数ngx_handle_read_event(rev, flags)ngx_handle_write_event(wev, lowat)。通常,在每个读取或写入 event 处理程序结束时调用一次函数。

计时器事件

可以将 event 设置为在超时到期时发送通知。 events 使用的计时器计数毫秒,因为过去某些未指定的点被截断为ngx_msec_t类型。它的当前值可以从ngx_current_msec变量中获得。

function ngx_add_timer(ev, timer) _set 为 event 设置超时,ngx_del_timer(ev)删除先前设置的超时。 global timeout red-black tree ngx_event_timer_rbtree 存储当前设置的所有超时。树中的 key 是ngx_msec_t类型,并且是 event 发生时的 time。树结构支持快速插入和删除操作,以及访问最近的超时,nginx 使用它来查找 long 等待 I/O events 和到期超时 events 的方式。

发表 events

可以发布 event,这意味着稍后将在当前 event 循环迭代中的某个时刻调用其处理程序。发布 events 是简化 code 和转义堆栈溢出的好方法。发布 events 保存在帖子队列中。 ngx_post_event(ev, q)宏将 event ev发布到帖子队列qngx_delete_posted_event(ev)宏从它当前发布的队列中删除 event ev。通常,events 被发布到ngx_posted_events队列,该队列在 event 循环的后期处理 - 在所有 I/O 和计时器 events 已经处理之后。 function ngx_event_process_posted()被调用来处理 event 队列。它 calls event 处理程序,直到队列不为空。这意味着发布的 event 处理程序可以发布更多 events 以在当前 event 循环迭代中处理。

一个例子:

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 */ }
}

Event 循环

除了 nginx master process 之外,所有 nginx 进程都执行 I/O,因此具有 event 循环。 (nginx master process 在sigsuspend()调用中将大部分 time 用于等待 arrive.)的信号。在ngx_process_events_and_timers() function 中实现 nginx event 循环,重复调用,直到 process 退出。

event 循环具有以下阶段:

  • 通过调用ngx_event_find_timer()找到最接近失效的超时。此 function 查找定时器树中最左边的节点,并返回节点到期之前的毫秒数。

  • Process I/O events 通过调用特定于 event 通知机制的处理程序,由 nginx configuration 选择。此处理程序等待至少一个 I/O event 发生,但仅在下一个超时到期之前。当发生读或写 event 时,会设置ready flag 并调用 event 的处理程序。对于 Linux,通常使用ngx_epoll_process_events()处理程序,calls epoll_wait()等待 I/O events。

  • 通过调用ngx_event_expire_timers()来使计时器过期。计时器树从最左边的元素向右迭代,直到找到未到期的超时。对于每个过期的节点,设置timedout event flag,重置timer_set flag,并调用 event 处理程序

  • Process 通过调用ngx_event_process_posted()发布 events。 function 重复从发布的 events 队列中删除第一个元素,并 calls 元素的处理程序,直到队列为空。

所有 nginx 进程也处理信号。信号处理程序仅设置在ngx_process_events_and_timers()调用后检查的 global 变量。

流程

nginx 中有几种类型的进程。 process 的类型保存在ngx_process global 变量中,并且是以下之一:

  • NGX_PROCESS_MASTER - master process,它读取 NGINX configuration,创建循环,并启动和控制子进程。它不执行任何 I/O 并仅响应信号。它的循环功能是ngx_master_process_cycle()

  • NGX_PROCESS_WORKER - worker process,它处理 client 连接。它由 master process 启动,并响应其信号和 channel 命令。它的循环功能是ngx_worker_process_cycle()。可以有多个 worker 进程,由worker_processes指令配置。

  • NGX_PROCESS_SINGLE - 单个 process,仅存在于master_process off模式中,并且是该模式中唯一的 process running。它创建了循环(就像 master process 那样)并处理 client 连接(就像 worker process 那样)。它的循环功能是ngx_single_process_cycle()

  • NGX_PROCESS_HELPER - helper process,目前有两种类型:cache manager 和 cache loader。两者的循环功能是ngx_cache_manager_process_cycle()

nginx 进程处理以下信号:

  • NGX_SHUTDOWN_SIGNAL(大多数系统都是SIGQUIT) - 正常关机。收到此信号后,master process 会向所有子进程发送关闭信号。当没有剩下子进程时,master 会销毁循环池并退出。当 worker process 收到此信号时,它会关闭所有侦听套接字并等待,直到没有安排 non-cancelable events,然后销毁循环池并退出。当 cache manager 或 cache loader process 收到此信号时,它会立即退出。当 process 接收到此信号时,ngx_quit变量设置为1,并在处理后立即重置。 变量设置为1,而 worker process 位于 shutdown state 中。

  • NGX_TERMINATE_SIGNAL(大多数系统都是SIGTERM) - 终止。收到此信号后,master process 会向所有子进程发送终止信号。如果子 process 在 1 秒内没有退出,master process 发送SIGKILL信号来杀死它。当没有剩下子进程时,master process 会销毁循环池并退出。当 worker process,cache manager process 或 cache loader process 收到此信号时,它会销毁循环池并退出。收到此信号时,变量ngx_terminate设置为1

  • NGX_NOACCEPT_SIGNAL(大多数系统都是SIGWINCH) - 关闭所有 worker 和 helper 进程。收到此信号后,master process 将关闭其子进程。如果先前启动的新 nginx 二进制文件退出,则旧 master 的子进程将再次启动。当 worker process 收到此信号时,它会在debug_points指令设置的调试模式下关闭。

  • NGX_RECONFIGURE_SIGNAL(大多数系统上为SIGHUP) - 重新配置。收到此信号后,master process re-reads configuration 并根据它创建一个新的循环。如果成功创建新循环,则删除旧循环并启动新的子进程。同时,旧子进程接收NGX_SHUTDOWN_SIGNAL信号。在 single-process 模式下,nginx 创建一个新循环,但保留旧循环,直到不再有 clients 与 active 连接相关联。 worker 和 helper 进程忽略此信号。

  • NGX_REOPEN_SIGNAL(大多数系统都是SIGUSR1) - 重新打开 files。 master process 将此信号发送给 workers,后者重新打开与循环相关的所有open_files

  • NGX_CHANGEBIN_SIGNAL(在大多数系统上为SIGUSR2) - 更改 nginx 二进制文件。 master process 启动一个新的 nginx 二进制文件并传入所有侦听套接字的列表。在"NGINX"环境变量中传递的 text-format 列表由以分号分隔的 descriptor numbers 组成。新的 nginx 二进制文件读取"NGINX"变量并将套接字添加到其 init 循环中。其他进程忽略此信号。

虽然所有 nginx worker 进程都能够接收并正确处理 POSIX 信号,但 master process 不使用标准的kill()系统调用来向 workers 和 helper 传递信号。相反,nginx 使用 inter-process socket 对,允许在所有 nginx 进程之间发送消息。但是,目前,消息仅从 master 发送给其子节点。消息携带标准信号。

主题

可以卸载到单独的线程任务中,否则会阻塞 nginx worker process。对于 example,可以将 nginx 配置为使用线程来执行档案 I/O。另一个用例是 library,它没有异步接口,因此通常不能与 nginx 一起使用。请记住,线程接口是处理 client 连接的现有异步方法的帮助程序,绝不是用作替换的。

要处理同步,可以使用以下pthreads primitives 包装:

  • typedef pthread_mutex_t ngx_thread_mutex_t;

  • ngx_int_t ngx_thread_mutex_create(ngx_thread_mutex_t *mtx, ngx_log_t *log);

  • ngx_int_t ngx_thread_mutex_destroy(ngx_thread_mutex_t *mtx, ngx_log_t *log);

  • ngx_int_t ngx_thread_mutex_lock(ngx_thread_mutex_t *mtx, ngx_log_t *log);

  • ngx_int_t ngx_thread_mutex_unlock(ngx_thread_mutex_t *mtx, ngx_log_t *log);

  • typedef pthread_cond_t ngx_thread_cond_t;

  • ngx_int_t ngx_thread_cond_create(ngx_thread_cond_t *cond, ngx_log_t *log);

  • ngx_int_t ngx_thread_cond_destroy(ngx_thread_cond_t *cond, ngx_log_t *log);

  • ngx_int_t ngx_thread_cond_signal(ngx_thread_cond_t *cond, ngx_log_t *log);

  • ngx_int_t ngx_thread_cond_wait(ngx_thread_cond_t *cond, ngx_thread_mutex_t *mtx, ngx_log_t *log);

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

在 configuration time,愿意使用线程的模块必须通过调用ngx_thread_pool_add(cf, name)来获取线程池的 reference,它会创建一个具有给定name的新线程池,或者如果已存在则返回带有该 name 的池的 reference。

要在运行时将task添加到指定线程池tp的队列中,请使用ngx_thread_task_post(tp, task) function。要在线程中执行 function,请使用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 模块都驻留在一个单独的目录中,该目录至少包含两个 files:config和一个包含模块 source code 的文件。 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 脚本,可以设置和访问以下变量:

  • ngx_module_type - build 的模块类型。可能的值为COREHTTPHTTP_FILTERHTTP_INIT_FILTERHTTP_AUX_FILTERMAILSTREAMMISC

  • ngx_module_name - 模块名称。要从一组源 files 中构建多个模块,请指定 whitespace-separated 名称列表。第一个 name 表示动态模块的输出二进制文件的 name。列表中的名称必须匹配 source code 中使用的名称。

  • ngx_addon_name - _配置脚本中 console 输出中显示的模块名称。

  • ngx_module_srcs - Whitespace-separated 用于编译模块的源 files 列表。 $ngx_addon_dir变量可用于表示模块目录的路径。

  • ngx_module_incs - 包括 build 模块所需的 paths

  • ngx_module_deps - Whitespace-separated 模块依赖项列表。通常,它是标头 files 的列表。

  • ngx_module_libs - Whitespace-separated 要与模块链接的 libraries 列表。对于 example,请使用ngx_module_libs=-lpthread链接libpthread library。以下宏可用于链接与 nginx 相同的 libraries:LIBXSLTLIBGDGEOIPPCREOPENSSLMD5SHA1ZLIBPERL

  • ngx_module_link - 由 build 系统设置的变量,动态模块为DYNAMIC,静态模块为ADDON,用于根据链接类型确定要执行的不同操作。

  • ngx_module_order - 为模块加载 order;对HTTP_FILTERHTTP_AUX_FILTER模块类型有用。此选项的格式是 whitespace-separated 模块列表。当前模块的 name 后面的列表中的所有模块最终都在 global 模块列表之后,它为模块初始化设置了 order。对于过滤器模块,以后的初始化意
    以下模块通常用作 references。 ngx_http_copy_filter_module读取其他过滤器模块的数据,并放置在列表底部附近,以便它是最先执行的过滤器模块之一。 ngx_http_write_filter_module将数据写入 client socket 并放置在列表顶部附近,并且是最后执行的。

默认情况下,过滤器模块放在模块列表中的ngx_http_copy_filter之前,以便在复制过滤器处理程序之后执行过滤器处理程序。对于其他模块类型,默认值为空 string。

要将模块静态编译为 nginx,请使用配置脚本的--add-module=/path/to/module参数。要编译模块以便以后动态 loading 到 nginx,请使用--add-dynamic-module=/path/to/module参数。

核心模块

模块是 nginx 的 building 块,其大部分功能都是作为模块实现的。模块源文件必须包含ngx_module_t类型的 global 变量,其定义如下:

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

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

每个模块将其私有数据保存在ctx字段中,识别commands array 中指定的 configuration 指令,并且可以在 nginx 生命周期的某些阶段调用。模块生命周期由以下 events 组成:

  • Configuration 指令处理程序在 master process 的 context 中的 configuration files 中出现时被调用。

  • 成功解析 configuration 后,在 master process 的 context 中调用init_module handler。 处理程序在 master process 中每次加载 configuration 时都会被调用。

  • master process 创建一个或多个 worker 进程,并在每个进程中调用init_process处理程序。

  • 当 worker process 从 master 接收到 shutdown 或 terminate 命令时,它会调用exit_process处理程序。

  • master process 在退出之前调用exit_master处理程序。

由于线程仅在 nginx 中用作具有自己的 API 的补充 I/O 工具,因此当前不会调用init_threadexit_thread处理程序。也没有init_master处理程序,因为这将是不必要的开销。

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

  • NGX_CORE_MODULE

  • NGX_EVENT_MODULE

  • NGX_HTTP_MODULE

  • NGX_MAIL_MODULE

  • NGX_STREAM_MODULE

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

核心模块集包括ngx_core_modulengx_errlog_modulengx_regex_modulengx_thread_pool_modulengx_openssl_module模块。 HTTP 模块,流模块,邮件模块和 event 模块也是核心模块。核心模块的 context 定义为:

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是模块 name string,create_confinit_conf是指向分别创建和初始化模块 configuration 的函数的指针。对于核心模块,在解析新的 configuration 之前 nginx calls create_conf,并且在成功解析所有 configuration 之后init_conf。典型的create_conf function 为 configuration 和 sets 默认值分配 memory。

例如,一个名为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;
}

Configuration 指令

ngx_command_t类型定义单个 configuration 指令。每个支持 configuration 的模块都提供了这种结构的 array,它描述了如何 process arguments 以及要调用的处理程序:

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

使用特殊 value ngx_null_command终止 array。对于 example“workerprocesses”或“listen”,name是 configuration 文件中出现的指令的 name。 type是一个 bit-field 标志,用于指定指令所采用的 arguments 的数量,类型以及它出现的 context。标志是:

  • NGX_CONF_NOARGS - 指令不带 arguments。

  • NGX_CONF_1MORE - 指令需要一个或多个 arguments。

  • NGX_CONF_2MORE - 指令需要两个或更多 arguments。

  • NGX_CONF_TAKE1 .. NGX_CONF_TAKE7 - 指令完全采用指示的 arguments 数量。

  • NGX_CONF_TAKE12NGX_CONF_TAKE13NGX_CONF_TAKE23NGX_CONF_TAKE123NGX_CONF_TAKE1234 - 指令可能会采用不同数量的 arguments。选项仅限于给定的 numbers。对于 example,NGX_CONF_TAKE12表示需要一个或两个 arguments。

指令类型的标志是:

  • NGX_CONF_BLOCK - Directive 是一个块,也就是说,它可以在其开始和结束括号中包含其他指令,甚至可以实现自己的解析器来处理内部的内容。

  • NGX_CONF_FLAG - 指令采用_bo _value,onoff

指令的 context 定义它在 configuration 中的显示位置:

  • NGX_MAIN_CONF - 在顶部 level context 中。

  • NGX_HTTP_MAIN_CONF - 在http块中。

  • NGX_HTTP_SRV_CONF - 在http块内的server块中。

  • NGX_HTTP_LOC_CONF - 在http块内的location块中。

  • NGX_HTTP_UPS_CONF - 在http块内的upstream块中。

  • NGX_HTTP_SIF_CONF - 在http块的server块内的if块中。

  • NGX_HTTP_LIF_CONF - 在http块的location块内的if块中。

  • NGX_HTTP_LMT_CONF - 在http块内的limit_except块中。

  • NGX_STREAM_MAIN_CONF - 在stream块中。

  • NGX_STREAM_SRV_CONF - 在stream块内的server块中。

  • NGX_STREAM_UPS_CONF - 在stream块内的upstream块中。

  • NGX_MAIL_MAIN_CONF - 在mail块中。

  • NGX_MAIL_SRV_CONF - 在mail块内的server块中。

  • NGX_EVENT_CONF - 在event块中。

  • NGX_DIRECT_CONF - 由不创建上下文层次结构且只有一个 global configuration 的模块使用。此 configuration 作为conf参数传递给处理程序。

configuration 解析器使用这些标志在错误的指令和 calls 指令处理程序提供正确的 configuration 指针的情况下抛出错误,以便不同位置的相同指令可以将它们的值存储在不同的位置。

set字段定义了一个处理程序,该处理程序将指令和 stores 解析的值处理到相应的 configuration 中。有许多函数可以执行 common 转换:

  • ngx_conf_set_flag_slot - 将文字 strings onoff分别转换为值为 1 或 0 的ngx_flag_t value。

  • ngx_conf_set_str_slot - 将 string 存储为ngx_str_t类型的 value。

  • ngx_conf_set_str_array_slot - 将 value 追加到 strings ngx_str_t的_ar。如果尚不存在,则创建 array。

  • ngx_conf_set_keyval_slot - 将 key-value 对添加到 key-value 对ngx_keyval_t的_ar。第一个 string 成为 key,第二个成为 value。如果 array 尚不存在,则会创建它。

  • ngx_conf_set_num_slot - 将指令的参数转换为ngx_int_t value。

  • ngx_conf_set_size_slot - 将尺寸转换为以字节表示的size_t value。

  • ngx_conf_set_off_slot - 将抵消转换为以字节表示的off_t value。

  • ngx_conf_set_msec_slot - 将时间转换为以毫秒表示的ngx_msec_t value。

  • ngx_conf_set_sec_slot - 将时间转换为以秒为单位表示的time_t value。

  • ngx_conf_set_bufs_slot - 将两个提供的 arguments 转换为ngx_bufs_t object,其中包含缓冲区的数量和尺寸

  • ngx_conf_set_enum_slot - 将提供的参数转换为ngx_uint_t value。在post字段中传递的ngx_conf_enum_t _ar定义了可接受的 strings 和相应的 integer 值。

  • ngx_conf_set_bitmask_slot - 将提供的 arguments 转换为ngx_uint_t value。每个参数的掩码值都是 ORed 生成结果。在post字段中传递的ngx_conf_bitmask_t _ar定义了可接受的 strings 和相应的掩码值。

  • set_path_slot - 将提供的 arguments 转换为ngx_path_t value 并执行所有必需的初始化。有关详细信息,请参阅proxy_temp_path指令的文档。

  • set_access_slot - 将提供的 arguments 转换为文件权限掩码。有关详细信息,请参阅proxystore_access指令的文档。

conf字段定义将哪个 configuration 结构传递给目录处理程序。核心模块只有 global configuration 并设置NGX_DIRECT_CONF flag 来访问它。 HTTP,Stream 或 Mail 等模块创建配置层次结构。例如,为serverlocationif范围创建模块的 configuration。

  • NGX_HTTP_MAIN_CONF_OFFSET - http块的 Configuration。

  • NGX_HTTP_SRV_CONF_OFFSET - 配置http块中的server块。

  • NGX_HTTP_LOC_CONF_OFFSET - 配置http中的location块。

  • NGX_STREAM_MAIN_CONF_OFFSET - stream块的 Configuration。

  • NGX_STREAM_SRV_CONF_OFFSET - 配置stream块中的server块。

  • NGX_MAIL_MAIN_CONF_OFFSET - mail块的 Configuration。

  • NGX_MAIL_SRV_CONF_OFFSET - 配置mail块中的server块。

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

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

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 object 本身,data是指向 value 的指针,由主处理程序使用适当的类型从 arguments 转换。

HTTP

连接

每个 HTTP client 连接都运行以下阶段:

  • ngx_event_accept()接受 client TCP 连接。调用此处理程序以响应 listen socket 上的读取通知。在此阶段创建一个新的ngx_connection_t object 来包装新接受的 client socket。每个 nginx listener 都提供了一个处理程序来传递新的连接 object。对于 HTTP 连接,它是ngx_http_init_connection(c)

  • ngx_http_init_connection()执行 HTTP 连接的早期初始化。在此阶段,为连接创建ngx_http_connection_t object,其 reference 存储在连接的data字段中。稍后它将被 HTTP 请求 object 替换。 PROXY 协议解析器和 SSL 握手也在此阶段启动。

  • ngx_http_wait_request_handler() read event 处理程序在 client socket 上有数据时被调用。在此阶段,将创建 HTTP 请求 object ngx_http_request_t并将其设置为连接的data字段。

  • ngx_http_process_request_line() read event 处理程序读取 client 请求 line。处理程序由ngx_http_wait_request_handler()设置。数据被读入连接buffer。缓冲区的大小最初由指令client_header_buffer_size设置。整个 client 标头应该适合缓冲区。如果初始大小不足,则分配更大的缓冲区,其容量由large_client_header_buffers指令设置。

  • ngx_http_process_request_headers()读取 event 处理程序,在ngx_http_process_request_line()之后设置读取 client 请求标头。

  • 完全读取和解析请求标头时调用ngx_http_core_run_phases()。此 function 运行从NGX_HTTP_POST_READ_PHASENGX_HTTP_CONTENT_PHASE的请求阶段。最后一个阶段旨在生成响应并将其传递给过滤器链。在此阶段,响应不一定发送到 client。它可能会保持缓冲状态并在完成阶段发送。

  • 当请求生成所有输出或产生错误时,通常会调用ngx_http_finalize_request()。在后一种情况下,查找适当的错误页面并将其用作响应。如果此时响应未完全发送到 client,则会激活 HTTP writer ngx_http_writer()以完成发送未完成的数据。

  • 当完整响应已发送到 client 时,将调用ngx_http_finalize_connection(),并且可以销毁请求。如果启用了 client 连接 keepalive feature,则会调用ngx_http_set_keepalive(),这会破坏当前请求并等待连接上的下一个请求。否则,ngx_http_close_request()会破坏请求和连接。

请求

对于每个 client HTTP 请求,都会创建ngx_http_request_t object。该 object 的一些领域是:

  • connection - 指向ngx_connection_t client 连接 object 的指针。有几个请求可以在同一个 time 上引用相同的连接 object - 一个主要请求及其子请求。删除请求后,可以在同一连接上创建新请求。

请注意,对于 HTTP 连接,ngx_connection_tdata字段指向请求。此类请求称为 active,而不是与连接相关的其他请求。 active 请求用于处理 client 连接 events,并允许将其响应输出到 client。通常,每个请求在某个时刻变为 active,以便它可以发送其输出。

  • ctx - _Apray 的 HTTP 模块上下文。 NGX_HTTP_MODULE类型的每个模块都可以在请求中存储任何 value(通常是指向结构的指针)。 value 存储在模块ctx_index位置的ctx array 中。以下宏提供了获取和设置请求上下文的便捷方法:

  • ngx_http_get_module_ctx(r, module) - 返回module的 context

  • ngx_http_set_ctx(r, c, module) - Sets c作为module的 context

  • main_confsrv_confloc_conf - 当前请求配置的数组。配置存储在模块的ctx_index位置。

  • read_event_handlerwrite_event_handler - 为请求读取和写入 event 处理程序。通常,HTTP 连接的 read 和 write event 处理程序都设置为ngx_http_request_handler()。此 function 为当前 active 请求调用read_event_handlerwrite_event_handler处理程序。

  • cache - 请求缓存 object 以缓存上游响应。

  • upstream - 请求上游 object 进行代理。

  • pool - 请求池。请求 object 本身在此池中分配,在删除请求时会将其销毁。对于需要在整个 client 连接的生命周期中可用的分配,请改用ngx_connection_t的池。

  • header_in - 读取 client HTTP 请求标头的缓冲区。

  • headers_inheaders_out - 输入和输出 HTTP headers objects。两个 objects 都包含ngx_list_t类型的headers字段,用于保存 headers 的原始列表。除此之外,特定的_header 可用于获取和设置为单独的字段,用于 example content_length_nstatus等。

  • request_body - Client 请求正文 object。

  • start_secstart_msec - 创建请求时的时间点,用于跟踪请求持续时间。

  • methodmethod_name - client HTTP 请求方法的数字和文本表示形式。方法的数值在src/http/ngx_http_request.h中定义,宏NGX_HTTP_GETNGX_HTTP_HEADNGX_HTTP_POST等。

  • http_protocol - Client HTTP 协议 version 的原始文本格式(“HTTP/1.0”,“HTTP/1.1”等)。

  • http_version - Client HTTP 协议 version 以数字形式(NGX_HTTP_VERSION_10NGX_HTTP_VERSION_11,etc.)。

  • http_majorhttp_minor - Client HTTP 协议 version 以数字形式分为主要和次要部分。

  • request_lineunparsed_uri - 在原始 client 请求中请求 line 和 URI。

  • uriargsexten - 当前请求的 URI,arguments 和文件扩展名。此处的 URI value 可能与 client 由于规范化而发送的原始 URI 不同。在整个请求处理过程中,这些值可以在执行内部重定向时发生变化。

  • main - 指向主请求 object 的指针。创建此 object 以处理 client HTTP 请求,而不是子请求,这些子请求是为在主请求中执行特定子任务而创建的。

  • parent - 指向子请求的 parent 请求的指针。

  • postponed - 发送和创建它们的 order 中的输出缓冲区和子请求列表。当推迟过滤器的一部分由子请求创建时,该列表由推迟过滤器使用以提供一致的请求输出。

  • post_subrequest - 指向处理程序,当子请求完成时,要调用 context。未用于主要请求。

  • posted_requests - 要启动或恢复的请求列表,通过调用请求的write_event_handler来完成。通常,此处理程序保存请求 main function,它首先运行请求阶段然后生成输出。

请求通常由ngx_http_post_request(r, NULL)调用发布。它始终发布到主请求posted_requests列表中。 function ngx_http_run_posted_requests(c)运行在传递的连接的 active 请求的主请求中发布的所有请求。所有 event 处理程序调用ngx_http_run_posted_requests,这可能导致新发布的请求。通常,在调用请求的读取或写入处理程序之后调用它。

  • phase_handler - 当前请求阶段的索引。

  • ncapturescapturescaptures_data - 请求的最后一个正则表达式 match 生成的正则表达式捕获。在请求处理期间,可以在许多位置发生正则表达式匹配:map 查找,SNI 或 HTTP Host,rewrite,proxy_redirect 等服务器查找。查找生成的捕获存储在上述字段中。字段ncaptures保存捕获的数量,captures保持捕获边界,captures_data保存与正则表达式匹配的 string,用于提取捕获。在每个新的正则表达式 match 之后,请求捕获被重置以保存新值。

  • count - 请求参考计数器。该字段仅对主要请求有意义。增加计数器是通过简单的r->main->count++完成的。要减少计数器,请调用ngx_http_finalize_request(r, rc)。 创建子请求并运行请求体读取 process 都会增加计数器。

  • subrequests - 当前子请求嵌套 level。每个子请求都会继承其 parent 的嵌套 level,减少一个。如果 value 达到零,则会生成错误。主请求的 value 由NGX_HTTP_MAX_SUBREQUESTS常量定义。

  • uri_changes - 请求剩余的 URI 更改数。请求可以更改其 URI 的总次数受NGX_HTTP_MAX_URI_CHANGES常量的限制。每次更改时,value 都会递减,直到达到零,此时 time 会生成错误。重写和内部重定向到普通或命名位置被视为 URI 更改。

  • blocked - 请求中保留的块计数器。虽然此 value 为 non-zero,但请求无法终止。目前,挂起的 AIO 操作(POSIX AIO 和线程操作)和 active 缓存锁定会增加此 value。

  • buffered - 显示哪些模块缓冲了请求产生的输出的位掩码。许多过滤器可以缓冲输出;对于 example,sub_filter 可以缓冲数据,因为部分 string match,复制过滤器可以缓冲数据,因为缺少空闲输出缓冲区等。由于这个 value 是 non-zero,因此请求未在冲洗之前完成。

  • header_only - Flag 表示输出不需要正文。对于 example,此 HTTP 标记由 HTTP HEAD 请求使用。

  • keepalive - Flag 指示是否支持 client 连接 keepalive。 value 是从 HTTP version 和“Connection”头的 value 推断出来的。

  • header_sent - Flag 表示请求已发送输出标头。

  • internal - Flag 表示当前请求是内部的。要输入内部 state,请求必须通过内部重定向或作为子请求。允许内部请求进入内部位置。

  • allow_ranges - Flag 表示可以根据 HTTP Range 标头的请求将部分响应发送到 client。

  • subrequest_ranges - Flag 表示在处理子请求时可以发送部分响应。

  • single_range - Flag 表示只能将一个连续范围的输出数据发送到 client。此 flag 通常在发送数据流时设置,例如从代理服务器发送,并且整个响应在一个缓冲区中不可用。

  • main_filter_need_in_memoryfilter_need_in_memory - 请求在 memory 缓冲区而不是 files 中生成输出的标志。即使启用了 sendfile,这也是复制过滤器从文件缓冲区读取数据的信号。两个标志之间的区别是设置它们的过滤器模块的位置。在过滤器链中的推迟过滤器之前调用的过滤器设置filter_need_in_memory,请求只有当前请求输出进入 memory 缓冲区。稍后在过滤器链中调用的过滤器集main_filter_need_in_memory,请求发送输出时主要请求和所有子请求都在 memory 中读取 files。

  • filter_need_temporary - Flag 请求在临时缓冲区中生成请求输出,但不在 readonly memory 缓冲区或文件缓冲区中生成。过滤器使用它可以直接在发送它的缓冲区中更改输出。

组态

每个 HTTP 模块可以有三种类型的 configuration:

  • 主 configuration - 适用于整个http块。用作模块的 global 设置。

  • 服务器 configuration - 适用于单个server块。用作模块的 server-specific 设置。

  • 位置 configuration - 适用于单个locationiflimit_except块。用作模块的 location-specific 设置。

Configuration 结构是在 nginx configuration 阶段通过调用函数创建的,函数分配结构,初始化它们并合并它们。以下 example 显示了如何为模块创建简单的位置 configuration。 configuration 有一个设置,foo,类型为 unsigned integer。

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

如 example 中所示,ngx_http_foo_create_loc_conf() function 创建一个新的 configuration 结构,ngx_http_foo_merge_loc_conf()将 configuration 与来自更高 level 的 configuration 合并。实际上,服务器和位置 configuration 不仅存在于服务器和位置级别,而是也为它们之上的所有级别创建。具体来说,还在主 level 创建服务器 configuration,并在主,服务器和位置级别创建位置配置。这些配置可以在 nginx configuration 文件的任何 level 上指定 server-and 和 location-specific 设置。最终将配置合并。提供了许多宏,如NGX_CONF_UNSETNGX_CONF_UNSET_UINT,用于指示缺少的设置,并在合并时忽略它。标准的 nginx 合并宏如ngx_conf_merge_value()ngx_conf_merge_uint_value()提供了一种合并设置的便捷方式,如果 none 配置提供了显式 value,则设置默认的 value。有关不同类型的宏的完整列表,请参阅src/core/ngx_conf_file.h

以下宏可用。用于在 configuration time 访问 HTTP 模块的 configuration。他们都把ngx_conf_t reference 作为第一个参数。

  • ngx_http_conf_get_module_main_conf(cf, module)

  • ngx_http_conf_get_module_srv_conf(cf, module)

  • ngx_http_conf_get_module_loc_conf(cf, module)

以下 example 获取指向标准 nginx 核心模块ngx_http_core_module的位置 configuration 的指针,并替换保留在结构的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 模块的 configuration。

  • ngx_http_get_module_main_conf(r, module)

  • ngx_http_get_module_srv_conf(r, module)

  • ngx_http_get_module_loc_conf(r, module)

这些宏接收 HTTP 请求ngx_http_request_t的 reference。请求的主要配置永远不会改变。在选择了请求的虚拟服务器之后,服务器 configuration 可以从默认值更改。选择用于处理请求的位置 configuration 可能会因 rewrite 操作或内部重定向而多次更改。以下 example 显示了如何在运行时访问模块的 HTTP configuration。

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 请求都通过一系列阶段。在每个阶段中,对请求执行不同类型的处理。 Module-specific 处理程序可以在大多数阶段注册,并且许多标准 nginx 模块将其阶段处理程序注册为在请求处理的特定阶段调用的方法。连续处理阶段,并在请求到达阶段后调用阶段处理程序。以下是 nginx HTTP 阶段列表。

  • NGX_HTTP_POST_READ_PHASE - 第一阶段。 ngx_http_realip_module在此阶段注册其处理程序,以便在调用任何其他模块之前替换 client 地址。

  • NGX_HTTP_SERVER_REWRITE_PHASE - 处理server块(但在location块之外)中定义的 rewrite 指令的阶段。 ngx_httprewrite_module在此阶段安装其处理程序。

  • NGX_HTTP_FIND_CONFIG_PHASE - 根据请求 URI 选择位置的特殊阶段。在此阶段之前,相关虚拟服务器的默认位置将分配给请求,并且任何请求位置 configuration 的模块都会收到默认服务器位置的 configuration。此阶段为请求分配新位置。在此阶段无法注册其他处理程序。

  • NGX_HTTP_REWRITE_PHASE - 与NGX_HTTP_SERVER_REWRITE_PHASE相同,但适用于在上一阶段选择的位置中定义的 rewrite 规则。

  • NGX_HTTP_POST_REWRITE_PHASE - 如果在 rewrite 期间 URI 发生更改,请求将重定向到新位置的特殊阶段。这是通过再次通过NGX_HTTP_FIND_CONFIG_PHASE的请求实现的。在此阶段无法注册其他处理程序。

  • NGX_HTTP_PREACCESS_PHASE - 不同类型处理程序的 common 阶段,与访问控制无关。标准 nginx 模块ngx_http_limit_conn_modulengx_http_limit_req_module在此阶段注册其处理程序。

  • NGX_HTTP_ACCESS_PHASE - 验证 client 是否有权发出请求的阶段。标准 nginx 模块(如ngx_http_access_modulengx_http_auth_basic_module)在此阶段注册其处理程序。默认情况下,client 必须将在此阶段注册的所有处理程序的授权检查传递给继续下一阶段的请求。如果任何阶段处理程序授权 client,满足指令可用于允许处理_继续。

  • NGX_HTTP_POST_ACCESS_PHASE - 处理满足任何指令的特殊阶段。如果某些访问阶段处理程序拒绝访问且 none 明确允许访问,则该请求将被最终确定。在此阶段无法注册其他处理程序。

  • NGX_HTTP_PRECONTENT_PHASE - 在生成内容之前调用处理程序的阶段。标准模块(如ngx_http_tryfiles_modulengx_http_mirror_module)在此阶段注册其处理程序。

  • NGX_HTTP_CONTENT_PHASE - 通常生成响应的阶段。多个 nginx 标准模块在此阶段注册其处理程序,包括ngx_http_index_modulengx_http_static_module。它们被顺序调用,直到其中一个产生输出。也可以在 per-location 的基础上设置内容处理程序。如果ngx_http_core_module的位置 configuration 设置为handler,则将其作为内容处理程序调用,并忽略此阶段安装的处理程序。

  • NGX_HTTP_LOG_PHASE - 执行请求 logging 的阶段。目前,只有ngx_httplog_module在此阶段注册其处理程序以进行访问 logging。在释放请求之前,在请求处理的最后调用 Log 阶段处理程序。

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

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

阶段处理程序应该_return 特定代码:

  • NGX_OK - 进入下一阶段。

  • NGX_DECLINED - 继续执行当前阶段的下一个处理程序。如果当前处理程序是当前阶段的最后一个,则转到下一阶段。

  • NGX_AGAINNGX_DONE - 暂停阶段处理直到某个未来 event,它可以是异步 I/O 操作,或者只是延迟,对于 example。假设稍后通过调用ngx_http_core_run_phases()来恢复阶段处理。

  • 阶段处理程序返回的任何其他 value 都被视为请求终结 code,特别是 HTTP 响应 code。使用提供的 code 完成请求。

对于某些阶段,return 代码的处理方式略有不同。在内容阶段,任何 return code 其他NGX_DECLINED都被视为 finalization code。来自位置内容处理程序的任何 return code 都被视为 finalization code。在访问阶段,在满足任何模式下,NGX_OKNGX_DECLINEDNGX_AGAINNGX_DONE以外的任何 return code 都被视为拒绝。如果没有后续访问处理程序允许或拒绝使用不同的 code 访问,则拒绝 code 将成为 finalization code。

变量

访问现有变量

变量可以通过索引(这是最常见的方法)或 name(请参阅下面)来引用。在 configuration 阶段创建索引,此时将变量添加到 configuration。要获取变量索引,请使用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 configuration 的指针,name指向包含变量 name 的 string。 function 会在出错时返回NGX_ERROR,否则返回有效索引,通常将其存储在模块的 configuration 中的某个位置以备将来使用。

所有 HTTP 变量都在给定 HTTP 请求的 context 中进行计算,结果特定于该 HTTP 请求并缓存在该 HTTP 请求中。评估变量的所有函数返回ngx_http_variable_value_t类型,表示变量 value:

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;

哪里:

  • len - value 的长度

  • data - value 本身

  • valid - value 有效

  • not_found - 找不到变量,因此datalen字段无关紧要;例如,当在请求中未传递相应的参数时,会发生$arg_foo等变量

  • no_cacheable - 不要缓存结果

  • escape - 由 logging 模块在内部使用,以标记需要在输出时转义的值。

ngx_http_get_flushed_variable()ngx_http_get_indexed_variable()函数用于获取变量的 value。它们具有相同的接口 - 接受 HTTP 请求r作为用于评估变量的 context 和用于标识变量的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()返回缓存的 value,ngx_http_get_flushed_variable()为 non-cacheable 变量刷新缓存。

某些模块(如 SSI 和 Perl)需要处理 configuration time 时未知 name 的变量。因此,索引不能用于访问它们,但ngx_http_get_variable(r, name, key) 功能可用。它搜索具有给定name的变量及其从 name 派生的哈希key

创建变量

要创建变量,请使用ngx_http_add_variable() function。它需要 arguments configuration(注册变量),变量 name 和控制 function 行为的标志:

  • NGX_HTTP_VAR_CHANGEABLE - 允许重新定义变量:如果另一个模块定义具有相同 name 的变量,则不会发生冲突。这允许指令覆盖变量。

  • NGX_HTTP_VAR_NOCACHEABLE - 禁用缓存,这对于$time_local等变量很有用。

  • NGX_HTTP_VAR_NOHASH - 表示此变量只能通过索引访问,而不能通过 name 访问。当已知在 SSI 或 Perl 等模块中不需要变量时,这是一个小的优化。

  • NGX_HTTP_VAR_PREFIX - 变量的 name 是前缀。在这种情况下,处理程序必须实现其他逻辑以获取特定变量的 value。例如,所有“arg_”变量都由同一个处理程序处理,该处理程序在请求 arguments 中执行查找并返回特定参数的 value。

如果出现错误,function 将返回 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处理程序来获取或设置变量 value,data传递给变量处理程序,index保存赋值变量索引用于 reference 变量。

通常,ngx_http_variable_t结构的 null-terminated static array 由模块创建,并在预配置阶段处理,以将变量添加到 configuration 中,对于 example:

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

example 中的此 function 用于初始化 HTTP 模块 context 的preconfiguration字段,并在解析 HTTP configuration 之前调用,以便解析器可以引用这些变量。

对于 example,get处理程序负责评估特定请求的 context 中的变量:

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

如果出现内部错误(对于 example,失败的 memory 分配),则返回NGX_ERROR,否则返回NGX_OK。要了解变量 evaluation 的状态,请检查ngx_http_variable_value_t中的标志(请参阅说明以上)。

set处理程序允许设置变量引用的 property。对于 example,$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;
}

复杂的价值观

复杂的 value 尽管有 name,但它提供了一种简单的方法来计算可以包含文本,变量及其组合的表达式。

ngx_http_compile_complex_value中的复杂 value 描述在 configuration 阶段编译为ngx_http_complex_value_t,在运行时用于获取表达式 evaluation 的结果。

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包含初始化 complex value cv所需的所有参数:

  • cf - Configuration 指针

  • value - 要解析的 String(输入)

  • complex_value - 编译 value(输出)

  • zero - 启用 zero-terminating value 的 Flag

  • conf_prefix - 使用 configuration 前缀(nginx 当前正在查找 configuration 的目录)前缀结果

  • root_prefix - 使用根前缀(正常的 nginx 安装前缀)前缀结果

当结果要传递给需要 zero-terminated strings 的 libraries 时,zero flag 很有用,并且在处理文件名时前缀很方便。

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

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

在运行时,可以使用ngx_http_complex_value() function 计算复杂的 value:

ngx_str_t  res;

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

给定请求r和先前编译的 value cv,function 计算表达式并将结果写入res

请求重定向

HTTP 请求始终通过ngx_http_request_t结构的loc_conf字段连接到某个位置。这意味着在任何时候都可以通过调用ngx_http_get_module_loc_conf(r, module)从请求中检索任何模块的位置 configuration。在请求的生命周期内,请求位置可能会多次更改。最初,将默认服务器的默认服务器位置分配给请求。如果请求切换到不同的服务器(由 HTTP“Host”标头或 SSL SNI 扩展选择),请求也会切换到该服务器的默认位置。位置的下一个更改发生在NGX_HTTP_FIND_CONFIG_PHASE请求阶段。在此阶段,通过为服务器配置的所有 non-named 位置中的请求 URI 选择位置。作为改写指令的结果,ngx_httprewrite_module可以在NGX_HTTP_REWRITE_PHASE请求阶段更改请求 URI,并将请求发送回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) function 更改请求 URI 并将请求返回到NGX_HTTP_SERVER_REWRITE_PHASE阶段。请求继续使用服务器默认位置。稍后在NGX_HTTP_FIND_CONFIG_PHASE,基于新请求 URI 选择新位置。

以下 example 使用新请求 arguments 执行内部重定向。

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

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

以下 example 执行重定向到命名位置@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);
}

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

调用ngx_http_internal_redirect(r, uri, args)ngx_http_named_location(r, name)会增加请求count。对于一致的请求 reference 计数,请在重定向请求后调用ngx_http_finalize_request(r, NGX_DONE)。这将最终确定当前请求 code 路径并减少计数器。

重定向和重写的请求变为内部请求并可以访问内部位置。内部请求设置了internal flag。

子请求

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

子请求在NGX_HTTP_SERVER_REWRITE_PHASE阶段开始。它通过与正常请求相同的后续阶段,并根据自己的 URI 分配位置。

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

子请求与 active 请求的概念有关。如果c->data == r,请求r被视为 active,其中c是 client 连接 object。在任何给定点,只允许请求 group 中的 active 请求将其缓冲区输出到 client。非活动请求仍然可以将其输出发送到过滤器链,但它不会超过ngx_http_postpone_filter并由该过滤器保持缓冲,直到请求变为 active。以下是一些请求激活规则:

  • 最初,主要请求是 active。

  • @active 请求的第一个子请求在创建后立即变为 active。

  • 一旦发送了该请求之前的所有数据,ngx_http_postpone_filter就会激活 active 请求的子请求列表中的下一个请求。

  • 请求完成后,其 parent 将被激活。

通过调用 function ngx_http_subrequest(r, uri, args, psr, ps, flags)创建子请求,其中r是 parent 请求,uriargs是子请求的 URI 和 arguments,psr是输出参数,它接收新创建的子请求 reference,ps是回调 object 用于通知 parent 请求最终确定子请求,flags是标志的位掩码。可以使用以下标志:

  • NGX_HTTP_SUBREQUEST_IN_MEMORY - 输出不会发送到 client,而是存储在 memory 中。 flag 仅影响由其中一个代理模块处理的子请求。子请求完成后,其输出可在ngx_buf_t类型ngx_buf_t中获得。

  • NGX_HTTP_SUBREQUEST_WAITED - 即使子请求在最终确定时不是 active,也会设置子请求的done flag。该子请求 flag 由 SSI 过滤器使用。

  • NGX_HTTP_SUBREQUEST_CLONE - 子请求创建为其 parent 的克隆。它在同一位置启动,并从与 parent 请求相同的阶段开始。

以下 example 创建 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 */
}

此 example 克隆当前请求并设置子请求的终结回调。

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

子请求通常在主体过滤器中创建,在这种情况下,它们的输出可以被视为来自任何显式请求的输出。这意味着,在创建子请求之前和创建之后传递的任何缓冲区之前传递的所有显式缓冲区之后,最终将子请求的输出发送到 client。即使对于子请求的大型层次结构,也会保留此 ordering。以下 example 在所有请求数据缓冲区之后但在具有last_buf flag 的最终缓冲区之前插入子请求的输出。

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

还可以为数据输出之外的其他目的创建子请求。对于 example,ngx_http_auth_request_module模块在NGX_HTTP_ACCESS_PHASE阶段创建子请求。要在此时禁用输出,请在子请求上设置header_only flag。这可以防止子请求主体被发送到 client。请注意,子请求的标头永远不会发送到 client。可以在回调处理程序中分析子请求的结果。

请求最终确定

通过调用 function ngx_http_finalize_request(r, rc)来完成 HTTP 请求。在将所有输出缓冲区发送到过滤器链之后,通常由内容处理程序完成。此时,所有输出可能都不会被发送到 client,其中一些输出在过滤器链的某处保持缓冲。如果是,ngx_http_finalize_request(r, rc) function 会自动安装一个特殊的处理程序ngx_http_writer(r)来完成发送输出。如果出现错误或者需要将标准 HTTP 响应 code 返回给 client,也会终止请求。

function ngx_http_finalize_request(r, rc)需要以下rc值:

  • NGX_DONE - 快速完成。递减请求count并在请求达到零时销毁请求。在销毁当前请求后,client 连接可用于更多请求。

  • NGX_ERRORNGX_HTTP_REQUEST_TIME_OUT(408),NGX_HTTP_CLIENT_CLOSED_REQUEST(499) - 错误定型。尽快终止请求并关闭 client 连接。

  • NGX_HTTP_CREATED(201),NGX_HTTP_NO_CONTENT(204),代码大于或等于NGX_HTTP_SPECIAL_RESPONSE(300) - 特殊响应定稿。对于这些值,nginx 会向 client 发送 code 的默认响应页面,或者如果为 code 配置了内部重定向,则执行内部重定向到error_page位置。

  • 其他代码被认为是成功的终结代码,可能会激活请求 writer 以完成发送响应正文。一旦完全发送了主体,请求count就会递减。如果它达到零,则请求被销毁,但 client 连接仍可用于其他请求。如果count为正,则请求中有未完成的活动,这些活动将在稍后确定。

请求正文

为了处理 client 请求的主体,nginx 提供了ngx_http_read_client_request_body(r, post_handler)ngx_http_discard_request_body(r)函数。第一个 function 读取请求正文并通过request_body request 字段使其可用。第二个 function 指示 nginx 丢弃(读取和忽略)请求主体。必须为每个请求调用其中一个函数。通常,内容处理程序进行调用。

不允许从子请求中读取或丢弃 client 请求正文。必须始终在主要请求中完成。创建子请求时,它会继承 parent 的request_body object,如果主请求先前已读取请求正文,则子请求可以使用该对象。

function ngx_http_read_client_request_body(r, post_handler)启动读取请求主体的 process。当完全读取主体时,将调用post_handler回调以继续处理请求。如果请求正文丢失或已被读取,则立即调用回调。 function ngx_http_read_client_request_body(r, post_handler)分配ngx_http_request_body_t类型的request_body请求字段。该 object 的字段bufs将结果保存为缓冲区链。如果client_body_buffer_size指令指定的容量不足以适合 memory 中的整个主体,则可以将主体保存在 memory 缓冲区或文件缓冲区中。

以下 example 读取 client 请求正文并返回其大小。

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_in_single_buf - 将主体读取到单个 memory 缓冲区。

  • request_body_in_file_only - 即使适合 memory 缓冲区,也要将正文读取到文件中。

  • request_body_in_persistent_file - 创建后不要立即取消链接文件。具有此 flag 的文件可以移动到另一个目录。

  • request_body_in_clean_file - 在请求完成时取消链接文件。当文件应该移动到另一个目录但由于某种原因未被移动时,这可能很有用。

  • request_body_file_group_access - 通过将默认的 0600 访问掩码替换为 0660,启用 group 访问该文件。

  • request_body_file_log_level - log 文件错误的严重性 level。

  • request_body_no_buffering - 无需缓冲即可读取请求正文。

request_body_no_buffering flag 启用无缓冲的读取请求体的模式。在此模式下,在调用ngx_http_read_client_request_body()之后,bufs链可能只保留正文的一部分。要阅读下一部分,请调用ngx_http_read_unbuffered_request_body(r) function。 return value NGX_AGAIN和请求 flag reading_body表示有更多数据可用。如果在调用此 function 后bufs为 NULL,则 moment 上没有任何内容可读。当请求体的下一部分可用时,将调用请求回调read_event_handler

响应

在 nginx 中,通过发送响应头和随后的可选响应主体来生成 HTTP 响应。标题和正文都通过一系列过滤器传递,最终写入 client socket。 nginx 模块可以将其处理程序安装到头或主体过滤器链中,并处理来自前一个处理程序的输出。

响应头

ngx_http_send_header(r) function 发送输出头。在r->headers_out包含生成 HTTP 响应头所需的所有数据之前,请不要调用此函数。必须始终设置r->headers_out中的status字段。如果响应状态指示响应主体跟随标题,则也可以设置content_length_n。此字段的默认 value 为-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) function 通过调用存储在ngx_http_top_header_filter变量中的第一个头过滤器处理程序来调用头过滤器链。假设每个头处理程序 calls 链中的下一个处理程序,直到调用最终的处理程序ngx_http_header_filter(r)。最终的头处理程序根据r->headers_out构造 HTTP 响应,并将其传递给ngx_http_writer_filter输出。

要向头过滤器链添加处理程序,请在 configuration time 中的 global 变量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) function。 function 可以多次调用。每个 time,它以缓冲链的形式发送响应体的一部分。在最后一个体缓冲区中设置last_buf flag。

以下 example 生成一个完整的 HTTP 响应,其中包含“foo”。要使 example 作为子请求以及主请求工作,last_in_chain flag 将在输出的最后一个缓冲区中设置。 last_buf flag 仅为主请求设置,因为子请求的最后一个缓冲区不会结束整个输出。

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

身体过滤器

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

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

以下是一个简单的主体过滤器的示例,它计算正文中的字节数。结果以$counter变量的形式提供,可以在 access log 中使用。

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

Building 过滤器模块

在编写正文或标题过滤器时,请特别注意过滤器在过滤器 order 中的位置。 nginx 标准模块注册了许多头和主体过滤器。 nginx 标准模块注册了许多头部和主体滤波器,在相应的位置注册新的滤波器模块非常重要。通常,模块在其配置后处理程序中注册过滤器。在处理过程中调用过滤器的 order 显然与它们注册的 order 相反。

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

以下 example 显示了一个过滤器模块配置文件,假设模块只有一个源文件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 code 中标准且广泛采用的方法是为此目的保留两个缓冲区链:freebusyfree链保留所有可以重用的空闲缓冲区。 busy链保留当前模块发送的所有缓冲区,这些缓冲区仍由其他一些过滤器处理程序使用。如果缓冲区的大小大于零,则考虑使用缓冲区。通常,当过滤器使用缓冲区时,其pos(或文件缓冲区的file_pos)将移向last(file_last表示文件缓冲区)。缓冲区完全耗尽后,就可以重复使用了。要将新释放的缓冲区添加到free链,它足以迭代busy链并将其头部的零大小缓冲区移动到free。这个操作非常常见,它有一个特殊的功能,ngx_chain_update_chains(free, busy, out, tag)。 function 将输出链out附加到busy并将空闲缓冲区从busy的顶部移动到free。仅重用具有指定tag的缓冲区。这使模块只重用它自己分配的缓冲区。

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

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提供将请求传递给 remote 服务器所需的基本功能。实现特定协议的模块(如 HTTP 或 FastCGI)使用此功能。该模块还为 creating 自定义 load-balancing 模块提供了一个接口,并实现了一个默认的 round-robin 方法。

least_conn哈希模块实现了替代的 load-balancing 方法,但实际上是作为上游 round-robin 模块的 extensions 实现的,并与它共享大量 code,例如服务器 group 的表示。 活着模块是一个扩展上游功能的独立模块。

ngx_http_upstream_module可以通过将相应的上游块放入 configuration 文件中显式配置,或者通过使用proxy_pass等指令隐式配置,这些指令接受在某个时刻被评估到服务器列表中的 URL。备用 load-balancing 方法仅可用于显式上游 configuration。上游模块 configuration 有自己的指令 context 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
};
  • srv_conf - 上游模块的 Configuration context。

  • servers - Array of ngx_http_upstream_server_t,解析upstream块中的一组服务器指令的结果。

  • flags - 主要标记 load-balancing 方法支持哪些 features 的标志。 features 被配置为服务器指令的参数:

  • NGX_HTTP_UPSTREAM_CREATE - 区分明确定义的上游与proxy_pass指令和“朋友”自动创建的上游(FastCGI,SCGI,etc.)

  • NGX_HTTP_UPSTREAM_WEIGHT - 支持“weight”参数

  • NGX_HTTP_UPSTREAM_MAX_FAILS - 支持“max_fails”参数

  • NGX_HTTP_UPSTREAM_FAIL_TIMEOUT - 支持“fail_timeout”参数

  • NGX_HTTP_UPSTREAM_DOWN - 支持“down”参数

  • NGX_HTTP_UPSTREAM_BACKUP - 支持“backup”参数

  • NGX_HTTP_UPSTREAM_MAX_CONNS - 支持“max_conns”参数

  • host - 上游的名称。

  • file_name, line - 配置文件的 Name 和upstream块所在的 line。

  • portno_port - 不用于明确定义的上游组。

  • shm_zone - 此上游 group 使用的共享 memory zone(如果有)。

  • peer - 包含初始化上游 configuration 的通用方法的 object:

typedef struct {
    ngx_http_upstream_init_pt        init_upstream;
    ngx_http_upstream_init_peer_pt   init;
    void                            *data;
} ngx_http_upstream_peer_t;

实现 load-balancing 算法的模块必须设置这些方法并初始化 private data。如果在 configuration 解析期间未初始化init_upstreamngx_http_upstream_module 将其设置为默认的ngx_http_upstream_init_round_robin算法。

  • init_upstream(cf, us) - Configuration-time 方法负责初始化 group 服务器并在成功的情况下初始化init()方法。典型的 load-balancing 模块使用upstream块中的服务器列表来创建它使用的有效数据结构,并将自己的 configuration 保存到data字段。

  • init(r, us) - 初始化用于负载平衡的 per-request ngx_http_upstream_peer_t.peer结构(不要与上述的ngx_http_upstream_srv_conf_t.peer混淆,这是 per-upstream)。它作为data参数传递给处理服务器选择的所有回调。

当 nginx 必须将请求传递给另一个 host 进行处理时,它使用配置的 load-balancing 方法获取要连接的地址。该方法是从ngx_peer_connection_t类型的ngx_http_upstream_t.peer object 获得的:

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

    ...
};

该结构具有以下字段:

  • sockaddrsocklenname - 要连接的上游服务器的地址;这是 load-balancing 方法的输出参数。

  • data - load-balancing 方法的 per-request 数据;保留选择算法的 state,通常包括上游 configuration 的链接。它作为参数传递给处理服务器选择的所有方法(参见下面)。

  • tries - 允许尝试连接到上游服务器。

  • getfreenotifyset_sessionsave_session - load-balancing 模块的方法,如下所述。

所有方法都接受至少两个 arguments:对等连接 object pcngx_http_upstream_srv_conf_t.peer.init()创建的data。请注意,由于 load-balancing 模块的“链接”,它可能与pc.data不同。

  • get(pc, data) - 上游模块准备好将请求传递给上游服务器并需要知道其地址时调用的方法。该方法必须填充ngx_peer_connection_t结构的sockaddrsocklenname字段。 return 是以下之一:

  • NGX_OK - 服务器已被选中。

  • NGX_ERROR - 发生内部错误。

  • NGX_BUSY - 目前没有服务器可用。这可能由于多种原因而发生,包括:动态服务器 group 为空,group 中的所有服务器都在失败的 state 中,或者 group 中的所有服务器都已处理最大连接数。

  • NGX_DONE - 重用了底层连接,无需创建与上游服务器的新连接。此 value 由keepalive模块设置。

  • free(pc, data, state) - 上游模块完成特定服务器工作时调用的方法。 state参数是上游连接的完成状态,是具有以下可能值的位掩码:

  • NGX_PEER_FAILED - 尝试是不成功

  • NGX_PEER_NEXT - 上游服务器返回代码403404的特殊情况,这些代码不被视为失败

  • NGX_PEER_KEEPALIVE - 目前未使用

此方法还会递减tries计数器。

  • notify(pc, data, type) - 目前在 OSS version 中未使用。

  • set_session(pc, data)save_session(pc, data) - SSL-specific 方法,用于启用到上游服务器的缓存会话。 implementation 由 round-robin balance 方法提供。

例子

nginx-dev-examples repository 提供了 nginx 模块示例。

Code 风格

通用规则

  • 最大文字宽度为 80 个字符

  • 缩进是 4 个空格

  • 没有标签,没有尾随空格

  • 同一 line 上的列表元素用空格分隔

  • 十六进制 literals 是小写

  • 文件名,function 和类型名称以及 global 变量具有ngx_或更多特定前缀,例如ngx_http_ngx_mail_

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, n) > 0x10ffff) {
            /* invalid UTF-8 */
            return n;
        }
    }

    return len;
}

Files

典型的源文件可能包含以下两个空行分隔的部分:

  • 版权陈述

  • 包括

  • 预处理器定义

  • 类型定义

  • function 原型

  • 变量定义

  • function 定义

版权所有陈述如下:

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

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

始终包含ngx_config.hngx_core.h files,然后是ngx_http.hngx_stream.hngx_mail.h之一。然后按照可选的外部头 files:

#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

标题 files 应该包含所谓的“标题保护”:

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

评论

  • //”comments 未使用

  • 文字是用英文写的,美国拼写是首选

  • multi-line comments 的格式如下:

/*
 * The red-black tree code is based on the algorithm described in
 * the "Introduction to Algorithms" by Cormen, Leiserson and Rivest.
 */
/* find the server configuration for the address:port */

预处理器

宏名称从ngx_NGX_(或更具体)前缀开始。常量的宏名称是大写的。初始值设定项的参数化宏和宏是小写的。宏 name 和 value 之间至少有两个空格:

#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”后缀结尾。定义的类型 name 由至少两个空格分隔:

typedef ngx_uint_t  ngx_rbtree_key_t;

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

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

保持文件中不同结构之间的对齐方式相同。指向自身的结构具有 name,以“_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;

每个结构成员都在自己的 line 上声明:

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;

变量

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

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

静态和 global 变量可以在声明时初始化:

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

有一堆常用的 type/name 组合:

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;

功能

所有函数(甚至是静态函数)都应该有原型。原型包括参数名称。 Long 原型在 continuation lines 上用单个缩进包装:

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

定义中的 function name 以新的 line 开头。 function body 打开和关闭括号位于单独的 lines 上。 function 的主体是缩进的。函数之间有两个空行:

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)
{
    ...
}

function name 和左括号后没有空格。 Long function calls 被包装,使得 continuation lines 从第一个 function 参数的位置开始。如果这是不可能的,请格式化第一个延续 line,使其在位置 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);

表达式

除“.”和“->”之外的二进制运算符应与其操作数分隔一个空格。一元 operators 和下标不是用空格分隔它们的操作数:

width = width * 10 + (*fmt++ - '0');
ch = (u_char) ((decoded << 4) + (ch - '0'));
r->exten.data = &r->uri.data[i + 1];

类型转换由一个空格与转换表达式分隔。类型转换中的星号用 name 类型的空格分隔:

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

如果表达式不适合单个 line,则将其换行。 break line 的首选点是二进制 operator。延续 line 与表达式的开头对齐:

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

作为最后的手段,可以包装一个表达式,以便继续 line ends 在位置 79:

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

以上规则也适用于 sub-expressions,其中每个 sub-expression 都有自己的缩进 level:

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

有时,在转换后包装表达式很方便。在这种情况下,延续 line 是缩进的:

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

指针明确与NULL(不是0)进行比较:

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

条件和循环

if”关键字与条件分隔一个空格。如果条件需要多个 lines,则开括号位于同一 line 上,或位于专用 line 上。闭合大括号位于专用 line 上,可选地后跟“else if/else”。通常,在“else if/else”部分之前有一个空的 line:

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”关键字与条件分隔一个空格。开口支架位于同一 line 上。闭合支撑位于专用的 line 上。 “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 */”comment 指示:

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

带有空体的循环也由“/* void */”comment 表示,它可以放在同一个 line 上:

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

无限循环看起来像这样:

for ( ;; ) {
    ...
}

标签

标签被空 lines 包围,并在前一个 level 缩进:

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;

共同陷阱

编写 C 模块

最常见的陷阱是在可以避免的情况下尝试编写 full-fledged C 模块。在大多数情况下,您的任务可以通过创建正确的 configuration 来完成。如果编写模块是不可避免的,请尽量使其尽可能小巧。对于 example,模块只能 export 一些变量

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

  • 是否可以使用已实现所需的 feature?

  • 是否可以使用 built-in 脚本语言解决问题,例如PerlNJS

C Strings

nginx 中最常用的 string 类型,ngx_str_t不是 C-Style zero-terminated string。您无法将数据传递给标准 C library 函数,例如strlen()strstr()。相反,应该使用接受ngx_str_t的 nginx 同行或指向数据和长度的指针。但是,有一种情况是ngx_str_t持有指向 zero-terminated string 的指针:由于 configuration 文件解析而产生的 strings 是 zero-terminated。

全局变量

避免在模块中使用 global 变量。有一个 global 变量,这很可能是一个错误。任何 global 数据都应绑定到configuration 循环并从相应的memory 池中分配。这允许 nginx 执行优雅的 configuration 重新加载。试图使用 global 变量可能会破坏这个 feature,因为在同一个 time 上不可能有两个配置并且摆脱它们。有时 global 变量是必需的。在这种情况下,需要特别注意正确管理重新配置。另外,检查 code 使用的 libraries 是否具有可能在重新加载时被破坏的隐式 global state。

手册记忆管理

而不是处理容易出错的 malloc/free 方法,学习如何使用 nginx 。创建一个池并绑定到 object - 组态周期连接HTTP 请求。当 object 被销毁时,关联的池也会被销毁。因此,在使用 object 时,可以从相应的池中分配所需的金额,即使出现错误也不关心释放 memory。

主题

建议避免在 nginx 中使用线程,因为它肯定会发生错误:大多数 nginx 函数都不是 thread-safe。预计线程将仅执行 system calls 和 thread-safe library 函数。如果你需要运行一些与 client 请求处理无关的 code,正确的方法是在init_process模块处理程序中安排一个计时器,并在计时器处理程序中执行所需的操作。在内部,nginx 使用线程来增强 IO-related 操作,但这是一个特殊情况,有很多限制。

阻止 Libraries

common 错误是使用内部阻塞的 libraries。大多数 libraries 本质上都是同步和阻塞的。换句话说,他们在 time 执行一个操作并且浪费 time 等待来自其他同伴的响应。因此,当使用这样的 library 处理请求时,整个 nginx worker 被阻止,从而破坏了 performance。仅使用提供异步接口的 libraries 而不阻止整个 process。

对外部服务的 HTTP 请求

模块通常需要对某些外部服务执行 HTTP 调用。一个常见的错误是使用一些外部 library(例如 libcurl)来执行 HTTP 请求。绝对没有必要为 nginx 本身完成的任务带来大量的外部(可能闭塞!) code。

需要外部请求时有两种基本使用方案:

  • 在处理 client 请求的 context 中(对于 example,在内容处理程序中)

  • 在 worker process 的 context 中(对于 example,计时器处理程序)

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

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

Updated at: 7 months ago
贡献变化Table of content模块参考