Development guide

Introduction

Code layout

  • auto —构建脚本

  • src

  • core —基本类型和函数-字符串,数组,日志,池等。

    • event —活动核心
  • modules —事件通知模块:epollkqueueselect

    • http —核心 HTTP 模块和通用代码
  • modules —其他 HTTP 模块

    • v2-HTTP/2

    • mail —邮件模块

    • os —特定于平台的代码

  • unix

    • win32

    • stream —流模块

Include files

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

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

除此之外,HTTP 代码应包括

#include <ngx_http.h>

邮件代码应包括

#include <ngx_mail.h>

流代码应包括

#include <ngx_stream.h>

Integers

出于一般目的,nginx 代码使用两种整数类型ngx_int_tngx_uint_t,它们分别是intptr_tuintptr_t的 typedef。

通用返回码

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

  • NGX_OK —操作成功。

  • NGX_ERROR —操作失败。

  • NGX_AGAIN —操作未完成;再次调用该函数。

  • NGX_DECLINED-拒绝操作,例如,因为在配置中已将其禁用。这绝不是错误。

  • NGX_BUSY —资源不可用。

  • NGX_DONE-操作已完成或在其他位置 continue。也用作替代成功代码。

  • NGX_ABORT —功能被中止。也用作替代错误代码。

Error handling

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

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

使用ngx_errno的示例:

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

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

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

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

        return 1;
    }

    return 0;
}

Strings

Overview

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

nginx 字符串类型ngx_str_t的定义如下:

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

len字段保留字符串长度,而data字段保留字符串数据。保留在ngx_str_t中的字符串在len字节之后可以为空,也可以不为空。在大多数情况下不是。但是,在代码的某些部分(例如,在解析配置时),已知ngx_str_t对象是以空字符终止的,这简化了字符串比较,并使将字符串传递给 syscall 更容易。

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

  • ngx_strcmp()

  • ngx_strncmp()

  • ngx_strstr()

  • ngx_strlen()

  • ngx_strchr()

  • ngx_memcmp()

  • ngx_memset()

  • ngx_memcpy()

  • ngx_memmove()

其他字符串函数特定于 Nginx

  • ngx_memzero() —用零填充内存。

  • ngx_explicit_memzero() —与ngx_memzero()相同,但是此调用永远不会被编译器的无效存储消除优化删除。此功能可用于清除敏感数据,例如密码和密钥。

  • ngx_cpymem() —与ngx_memcpy()相同,但返回最终的目标地址。该地址很方便用于连续添加多个字符串。

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

  • ngx_strlchr() —搜索由两个指针分隔的字符串中的字符。

以下功能执行大小写转换和比较:

  • ngx_tolower()

  • ngx_toupper()

  • ngx_strlow()

  • ngx_strcasecmp()

  • ngx_strncasecmp()

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

  • ngx_string(text) —来自 C 字符串文字textngx_str_t类型的静态初始化程序

  • ngx_null_stringngx_str_t类型的静态空字符串初始值设定项

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

  • ngx_str_null(str) —用空字符串初始化ngx_str_t *类型的字符串str

Formatting

以下格式函数支持特定于 nginx 的类型:

  • 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 *

  • %su_char *(以零结尾)

  • %*ssize_t + u_char *

您可以在大多数类型前面加上u,以使它们未签名。要将输出转换为十六进制,请使用Xx

For example:

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

/* set n here */

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

Numeric conversion

nginx 中实现了一些用于数字转换的函数。前四个分别将给定长度的字符串转换为指示类型的正整数。他们返回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个小数位。该数字的字符串表示形式应不超过points个小数位数。例如,ngx_atofp("10.5", 4, 2)返回1050

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

Regular expressions

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

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

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

u_char                errstr[NGX_MAX_CONF_ERRSTR];

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

ngx_memzero(&rc, sizeof(ngx_regex_compile_t));

rc.pattern = value;
rc.pool = cf->pool;
rc.err.len = NGX_MAX_CONF_ERRSTR;
rc.err.data = errstr;
/* rc.options 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字段分别包含在正则表达式中找到的所有捕获和命名捕获的计数。

然后可以将编译后的正则表达式用于匹配字符串:

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

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

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

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

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

ngx_regex_exec()的参数是已编译的正则表达式re,与input匹配的字符串,一个可选的整数数组,用于保存找到的所有captures以及该数组的sizecaptures数组的大小必须为PCRE API的三倍。在示例中,大小是从捕获的总数加上匹配的字符串本身的数目计算得出的。

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

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

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

/* accessing named captures */

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

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

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

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

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

ngx_regex_exec_array()函数接受ngx_regex_elt_t元素的数组(它们只是带有相关名称的已编译正则表达式),要匹配的字符串和日志。该函数将数组中的表达式应用于字符串,直到找到匹配项或不再有其他表达式为止。如果存在匹配项,则返回值为NGX_OK,否则为NGX_DECLINED,如果出错则返回NGX_ERROR

Time

ngx_time_t结构用秒,毫秒和 GMT 偏移三种不同的类型表示时间:

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

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

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

可用的字符串表示形式是:

  • ngx_cached_err_log_time —用于错误日志条目:"1970/09/28 12:00:00"

  • ngx_cached_http_log_time —用于 HTTP 访问日志条目:"28/Sep/1970:12:00:00 +0600"

  • ngx_cached_syslog_time —用于系统日志条目:"Sep 28 12:00:00"

  • ngx_cached_http_time —用于 HTTPHeaders:"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()宏以秒为单位返回当前时间值,是访问缓存的时间值的首选方式。

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

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

  • ngx_gmtime(), ngx_libc_gmtime() —以 UTC 表示的时间

  • ngx_localtime(), ngx_libc_localtime() —相对于当地时区表示的时间

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

Containers

Array

nginx 数组类型ngx_array_t定义如下

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

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

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

ngx_array_t  *a, b;

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

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

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

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

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

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

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

List

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

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

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

typedef struct ngx_list_part_s  ngx_list_part_t;

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

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

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

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

/* add items to the list */

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

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

/* iterate over the list */

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

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

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

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

    ngx_do_smth(&v[i]);
}

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

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

Queue

在 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)-获取队列哨兵对象以在以下位置结束迭代

  • ngx_queue_data(q, type, link) —考虑到队列节点数据结构中的偏移量,以获取对队列节点数据结构开头的引用

An example:

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 tree

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

typedef struct {
    ngx_rbtree_t       rbtree;
    ngx_rbtree_node_t  sentinel;

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

typedef struct {
    ngx_rbtree_node_t  rbnode;

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

要处理整个树,您需要两个节点:root 和 sentinel。通常,它们被添加到自定义结构中,从而使您可以将数据组织到一棵树中,其中的叶子包含指向或嵌入数据的链接。

初始化树:

my_tree_t  root;

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

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

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

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

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

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

    while (node != sentinel) {

        n = (my_node_t *) node;

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

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

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

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

        return n;
    }

    return NULL;
}

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

要将节点添加到树,请分配一个新节点,对其进行初始化并调用ngx_rbtree_insert()

my_node_t          *my_node;
    ngx_rbtree_node_t  *node;

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

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

    ngx_rbtree_insert(&root->rbtree, node);

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

ngx_rbtree_delete(&root->rbtree, node);

Hash

哈希表函数在src/core/ngx_hash.h中声明。完全匹配和通配符匹配均受支持。后者需要额外的设置,下面将在单独的部分中进行介绍。

在初始化哈希之前,您需要知道它将保留的元素数量,以便 nginx 可以最佳地构建它。需要配置的两个参数是max_sizebucket_size,分别在document中进行了详细说明。它们通常可由用户配置。哈希初始化设置以ngx_hash_init_t类型存储,哈希本身为ngx_hash_t

ngx_hash_t       foo_hash;
ngx_hash_init_t  hash;

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

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

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

ngx_hash_keys_arrays_t  foo_keys;

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

ngx_hash_keys_array_init(&foo_keys, NGX_HASH_SMALL);

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

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

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

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

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

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

构建散列后,请使用ngx_hash_find(hash, key, name, len)函数查找元素:

my_data_t   *data;
ngx_uint_t   key;

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

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

Wildcard matching

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

ngx_hash_init_t      hash
ngx_hash_combined_t  foo_hash;

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

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

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

该函数识别通配符,并将密钥添加到相应的数组中。请参阅map模块文档以获取通配符语法和匹配算法的描述。

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

if (foo_keys.dns_wc_head.nelts) {

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

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

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

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

keys 数组需要排序,并且初始化结果必须添加到组合的哈希中。 dns_wc_tail数组的初始化类似地完成。

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

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

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

Memory management

Heap

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

  • ngx_alloc(size, log) —从系统堆中分配内存。这是malloc()的包装,并支持日志记录。分配错误和调试信息记录到log

  • ngx_calloc(size, log) —像ngx_alloc()一样从系统堆中分配内存,但分配后用零填充内存。

  • ngx_memalign(alignment, size, log) —从系统堆中分配对齐的内存。在提供该功能的平台上,这是posix_memalign()的包装。否则,实现会退回到ngx_alloc(),这会提供最大的对齐方式。

  • ngx_free(p) —可用的已分配内存。这是free()的包装

Pool

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

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

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

  • ngx_create_pool(size, log) —创建具有指定块大小的池。返回的池对象也将在池中分配。 size应该至少为NGX_MIN_POOL_SIZE并且是NGX_POOL_ALIGNMENT的倍数。

  • ngx_destroy_pool(pool) —释放所有池内存,包括池对象本身。

  • ngx_palloc(pool, size) —从指定的池中分配对齐的内存。

  • ngx_pcalloc(pool, size) —从指定的池中分配对齐的内存,并用零填充。

  • ngx_pnalloc(pool, size) —从指定的池中分配未对齐的内存。通常用于分配字符串。

  • ngx_pfree(pool, p) —先前在指定池中分配的可用内存。仅释放由转发到系统分配器的请求所导致的分配。

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

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

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

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

链链接(ngx_chain_t)在 nginx 中得到积极使用,因此 nginx 池实现提供了一种重用它们的方法。 ngx_pool_tchain字段保留了可供重新使用的先前分配的链接的列表。为了在池中有效分配链链接,请使用ngx_alloc_chain_link(pool)函数。此功能在池列表中查找自由链链接,如果池列表为空,则分配一个新的链链接。要释放链接,请调用ngx_free_chain(pool, cl)函数。

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

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

ngx_pool_cleanup_t  *cln;

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

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

...

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

    ngx_do_smth(msg);
}

Shared memory

Nginx 使用共享内存在进程之间共享公共数据。 ngx_shared_memory_add(cf, name, size, tag)函数将新的共享内存条目ngx_shm_zone_t添加到循环中。该函数接收区域的namesize。每个共享区域必须具有唯一的名称。如果已经存在带有提供的nametag的共享区域条目,则现有的区域条目将被重用。如果具有相同名称的现有条目具有不同的标记,则该函数将失败并显示错误。通常,模块结构的地址以tag的形式传递,从而可以在一个 nginx 模块中按名称重用共享区域。

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

  • init —初始化回调,在共享区域映射到实际内存后调用

  • data —数据上下文,用于将任意数据传递到init回调

  • noreuse —禁止从旧周期重用共享区域的标志

  • tag —共享区域标签

  • shm —类型ngx_shm_t的特定于平台的对象,至少具有以下字段:

  • addr —映射的共享内存地址,最初为 NULL

    • size —共享内存大小

    • name —共享内存名称

    • log —共享内存日志

    • exists-表示共享内存是从主进程继承的标志(特定于 Windows)

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

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

平板池将所有共享区域划分为页面。每个页面用于分配相同大小的对象。指定的大小必须为 2 的幂,并且大于 8 个字节的最小大小。不合格的值被四舍五入。每个页面的位掩码跟踪哪些块正在使用中,哪些块可供分配。对于大于一半页面(通常为 2048 字节)的大小,一次分配整个页面

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

ngx_str_t        name;
ngx_foo_ctx_t   *ctx;
ngx_shm_zone_t  *shm_zone;

ngx_str_set(&name, "foo");

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

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

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

...

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

    size_t            len;
    ngx_foo_ctx_t    *ctx;
    ngx_slab_pool_t  *shpool;

    value = shm_zone->data;

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

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

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

    /* initialize shared zone */

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

    shpool->data = ctx->value;

    return NGX_OK;
}

Logging

为了记录 nginx,使用ngx_log_t个对象。 NginxLogger 支持几种类型的输出:

  • stderr —记录到标准错误(stderr)

  • 文件—登录到文件

  • syslog —登录到 syslog

  • 内存—出于开发目的而登录到内部内存存储;以后可以使用调试器访问内存

Logger 实例可以是由next字段彼此链接的 Logger 链。在这种情况下,每条消息都会写入链中的所有 Logger。

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

  • NGX_LOG_EMERG

  • NGX_LOG_ALERT

  • NGX_LOG_CRIT

  • NGX_LOG_ERR

  • NGX_LOG_WARN

  • NGX_LOG_NOTICE

  • NGX_LOG_INFO

  • NGX_LOG_DEBUG

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

  • 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

通常,Logger 是由error_log指令中的现有 nginx 代码创建的,几乎可以在周期,配置,客户端连接和其他对象的每个处理阶段使用 Logger。

Nginx 提供以下日志记录宏:

  • ngx_log_error(level, log, err, fmt, ...) —错误记录

  • ngx_log_debug0(level, log, err, fmt)ngx_log_debug1(level, log, err, fmt, arg1)等-调试日志记录,最多支持 8 个格式参数

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

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

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

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

上面的示例生成如下日志条目:

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

Cycle

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

ngx_init_cycle()函数创建一个循环,该循环以前一个循环为参数。该函数查找前一个周期的配置文件,并从前一个周期继承尽可能多的资源。在 nginx 开始时会创建一个称为“初始循环”的占位符循环,然后将其替换为根据配置构建的实际循环。

该周期的成员包括:

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

  • log —循环日志。最初继承自旧周期,在读取配置后将其设置为指向new_log

  • new_log —循环日志,由配置创建。它受 root-scope error_log指令的影响。

  • connectionsconnection_n —类型为ngx_connection_t的连接数组,由事件模块在初始化每个 nginx worker 时创建。 nginx 配置中的worker_connections指令设置连接数connection_n

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

  • filesfiles_n —用于将文件 Descriptors 映射到 nginx 连接的数组。事件模块使用具有NGX_USE_FD_EVENT标志(当前为polldevpoll)的映射。

  • conf_ctx —核心模块配置的数组。在读取 nginx 配置文件期间将创建并填充配置。

  • modulesmodules_n-当前配置加载的ngx_module_t类型的模块(静态和动态)的数组。

  • listening —类型为ngx_listening_t的侦听对象的数组。侦听对象通常由调用ngx_create_listening()函数的不同模块的listen指令添加。侦听套接字是基于侦听对象创建的。

  • paths —类型为ngx_path_t的路径数组。通过从将在某些目录上运行的模块中调用函数ngx_add_path()来添加路径。这些目录由 nginx 在读取配置后创建(如果缺少)。此外,可以为每个路径添加两个处理程序:

  • 路径加载器-在启动或重新加载 nginx 之后的 60 秒内仅执行一次。通常,加载器读取目录并将数据存储在 nginx 共享内存中。从专用的 nginx 进程“ nginx 缓存加载器”中调用该处理程序。

    • 路径管理器-定期执行。通常,管理器会从目录中删除旧文件,并更新 nginx 内存以反映更改。从专用的“ nginx 缓存管理器”进程中调用该处理程序。
  • open_files —通过调用函数ngx_conf_open_file()创建的ngx_open_file_t类型的打开文件对象的列表。当前,nginx 使用这种打开文件进行日志记录。读取配置后,nginx 将打开open_files列表中的所有文件,并将每个文件 Descriptors 存储在对象的fd字段中。这些文件以追加模式打开,如果缺少则创建。收到重新打开 signal(最常见的是USR1)后,nginx worker 重新打开列表中的文件。在这种情况下,fd字段中的 Descriptors 将更改为新值。

  • shared_memory —共享内存区域的列表,每个区域都通过调用ngx_shared_memory_add()函数添加。共享区域在所有 nginx 进程中都映射到相同的地址范围,并用于共享公共数据,例如 HTTP 缓存内存树。

Buffer

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

ngx_buf_t结构具有以下字段:

  • startend —为缓冲区分配的存储块的边界。

  • poslast —内存缓冲区的边界;通常是start .. end的子范围。

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

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

  • file —文件对象。

  • temporary —指示缓冲区引用可写内存的标志。

  • memory —指示缓冲区引用只读存储器的标志。

  • in_file —指示缓冲区引用文件中数据的标志。

  • flush —标志,指示需要清除缓冲区之前的所有数据。

  • recycled —指示缓冲区可以重用并且需要尽快消耗的标志。

  • sync —指示缓冲区不携带数据或flushlast_buf之类的特殊 signal 的标志。默认情况下,nginx 认为此类缓冲区是错误情况,但是此标志告诉 nginx 跳过错误检查。

  • last_buf —标志,指示缓冲区是最后一个输出。

  • last_in_chain —指示请求或子请求中没有数据缓冲区的标志。

  • shadow —引用与当前缓冲区相关的另一个(“影子”)缓冲区,通常是指该缓冲区使用了来自影子的数据。当缓冲区被消耗时,影子缓冲区通常也被标记为已消耗。

  • last_shadow —标志,指示该缓冲区是最后一个引用特定 shade 缓冲区的缓冲区。

  • temp_file —标志,指示缓冲区在临时文件中。

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

typedef struct ngx_chain_s  ngx_chain_t;

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

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

使用缓冲区和链的示例:

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

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

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

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

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

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

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

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

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

    return out;
}

Networking

Connection

连接类型ngx_connection_t是套接字 Descriptors 的包装。它包括以下字段:

  • fd —套接字 Descriptors

  • data —任意连接上下文。通常,它是指向构建在连接之上的更高级别对象的指针,例如 HTTP 请求或 Stream 会话。

  • readwrite —读取和写入连接事件。

  • recvsendrecv_chainsend_chain —连接的 I/O 操作。

  • pool —连接池。

  • log —连接日志。

  • sockaddrsocklenaddr_text —二进制和文本形式的远程套接字地址。

  • local_sockaddrlocal_socklen —二进制格式的本地套接字地址。最初,这些字段为空。使用ngx_connection_local_sockaddr()函数获取本地套接字地址。

  • proxy_protocol_addrproxy_protocol_port-PROXY 协议客户端地址和端口(如果已为连接启用 PROXY 协议)。

  • ssl —连接的 SSL 上下文。

  • reusable —标志,指示连接处于使其可以重复使用的状态。

  • close —指示连接正在重用并且需要关闭的标志。

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

nginx 配置中的worker_connections指令限制了每个 nginx worker 的连接数。所有连接结构都是在工作人员启动时预先创建的,并存储在循环对象的connections字段中。要检索连接结构,请使用ngx_get_connection(s, log)函数。它以套接字 Descriptors 作为其s参数,该 Descriptors 需要包装在连接结构中。

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

Events

Event

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

ngx_event_t中的字段包括:

  • data —事件处理程序中使用的任意事件上下文,通常用作指向与事件相关的连接的指针。

  • handler —事件发生时要调用的回调函数。

  • write —指示写事件的标志。缺少标志表示读取事件。

  • active —标志,指示该事件已注册用于接收 I/O 通知,通常是从诸如epollkqueuepoll之类的通知机制接收的。

  • ready —标记,指示事件已收到 I/O 通知。

  • delayed —表示由于速率限制而导致 I/O 延迟的标志。

  • timer —红黑树节点,用于将事件插入计时器树。

  • timer_set —指示已设置事件计时器且尚未到期的标志。

  • timedout —表示事件计时器已过期的标志。

  • eof —指示在读取数据时发生 EOF 的标志。

  • pending_eof —标志,指示 EOF 尚未在套接字上挂起,即使之前可能有一些可用数据。该标志是通过EPOLLRDHUP epoll事件或EV_EOF kqueue标志传递的。

  • error —标记,指示在读取(对于读取事件)或写入(对于写入事件)期间发生错误。

  • cancelable —计时器事件标志,指示在关闭工作进程时应忽略该事件。正常的工作程序关闭将延迟,直到没有计划的不可取消的计时器事件为止。

  • posted —标记,指示事件已发布到队列中。

  • queue —用于将事件发布到队列的队列节点。

I/O events

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

Timer events

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

函数ngx_add_timer(ev, timer)为事件设置超时,函数ngx_del_timer(ev)删除先前设置的超时。全局超时红黑树ngx_event_timer_rbtree存储当前设置的所有超时。树中的键的类型为ngx_msec_t,是事件发生的时间。树结构使快速插入和删除操作以及对最近的超时的访问成为可能,nginx 使用该结构来查找 awaitI/O 事件和超时事件过期的时间。

Posted events

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

An example:

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 loop

除了 nginx 主进程外,所有 nginx 进程都执行 I/O,因此具有事件循环。 (相反,nginx 主进程将大部分时间都花在sigsuspend()调用中 awaitsignal 到达.)ng_x 事件循环是在ngx_process_events_and_timers()函数中实现的,该函数被重复调用直到该进程退出。

事件循环具有以下阶段:

  • 通过调用ngx_event_find_timer()查找最接近超时的超时。此函数在计时器树中找到最左边的节点,并返回直到该节点到期为止的毫秒数。

  • 通过调用特定于事件通知机制的处理程序来处理 I/O 事件,该处理程序由 nginx 配置选择。该处理程序 await 至少一个 I/O 事件发生,但仅等到下一个超时到期为止。发生读取或写入事件时,会设置ready标志并调用事件的处理程序。对于 Linux,通常使用ngx_epoll_process_events()处理函数,该处理函数调用epoll_wait()awaitI/O 事件。

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

  • 通过调用ngx_event_process_posted()处理发布的事件。该函数反复从发布的事件队列中删除第一个元素,并调用元素的处理程序,直到队列为空。

所有 Nginx 进程也都处理 signal。signal 处理程序仅设置在ngx_process_events_and_timers()调用后检查的全局变量。

Processes

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

  • NGX_PROCESS_MASTER —主进程读取 NGINX 配置,创建循环,并启动和控制子进程。它不执行任何 I/O,仅响应 signal。其循环函数为ngx_master_process_cycle()

  • NGX_PROCESS_WORKER —工作进程,用于处理客户端连接。它由主进程启动,并且也响应其 signal 和通道命令。其循环函数为ngx_worker_process_cycle()worker_processes指令可以配置多个工作进程。

  • NGX_PROCESS_SINGLE —单个进程,仅在master_process off模式下存在,并且是唯一在该模式下运行的进程。它创建周期(就像主进程一样)并处理客户端连接(就像工作进程一样)。其循环函数为ngx_single_process_cycle()

  • NGX_PROCESS_HELPER —帮助程序进程,当前有两种类型:缓存管理器和缓存加载器。两者的循环函数均为ngx_cache_manager_process_cycle()

Nginx 进程处理以下 signal:

  • NGX_SHUTDOWN_SIGNAL(在大多数系统上为SIGQUIT)-正常关机。收到此 signal 后,主进程将关闭 signal 发送给所有子进程。如果没有子进程,则主服务器破坏循环池并退出。当工作进程接收到该 signal 时,它将关闭所有侦听套接字,并 await 直到没有调度的不可取消事件,然后销毁循环池并退出。当缓存管理器或缓存加载器进程收到此 signal 时,它将立即退出。 ngx_quit变量在进程接收到此 signal 时设置为1,并在处理后立即复位。当工作进程处于关闭状态时,ngx_exiting变量设置为1

  • NGX_TERMINATE_SIGNAL(在大多数系统上为SIGTERM)—终止。接收到此 signal 后,主进程将向所有子进程发送一个终止 signal。如果子进程未在 1 秒内退出,则主进程发送SIGKILLsignal 将其杀死。如果没有子进程,则主进程将破坏循环池并退出。当工作进程,缓存管理器进程或缓存加载器进程接收到此 signal 时,它将破坏循环池并退出。接收到该 signal 后,变量ngx_terminate设置为1

  • NGX_NOACCEPT_SIGNAL(在大多数系统上为SIGWINCH)-关闭所有辅助进程。收到此 signal 后,主进程将关闭其子进程。如果先前启动的新的 nginx 二进制文件退出,则旧的 master 的子进程将再次启动。当工作进程接收到该 signal 时,它将在debug_points指令设置的调试模式下关闭。

  • NGX_RECONFIGURE_SIGNAL(在大多数系统上为SIGHUP)-重新配置。接收到此 signal 后,主进程将重新读取配置并基于此创建新的周期。如果成功创建了新循环,则删除旧循环并启动新的子进程。同时,旧的子进程接收NGX_SHUTDOWN_SIGNALsignal。在单进程模式下,nginx 创建一个新的周期,但保留旧的周期,直到不再有绑定了活动连接的客户端为止。Worker 和助手过程会忽略此 signal。

  • NGX_REOPEN_SIGNAL(在大多数系统上为SIGUSR1)-重新打开文件。主进程将此 signal 发送给工作程序,工作程序重新打开与周期有关的所有open_files

  • NGX_CHANGEBIN_SIGNAL(在大多数系统上为SIGUSR2)-更改 nginx 二进制文件。主进程启动一个新的 nginx 二进制文件,并传入所有侦听套接字的列表。在"NGINX"环境变量中传递的文本格式列表由用分号分隔的 Descriptors 号组成。新的 nginx 二进制文件读取"NGINX"变量,并将套接字添加到其初始化周期。其他进程忽略此 signal。

虽然所有 nginx 工作进程都能够接收并正确处理 POSIXsignal,但主进程不会使用标准的kill() syscall 将 signal 传递给工作进程和助手。相反,nginx 使用进程间套接字对,允许在所有 nginx 进程之间发送消息。但是,当前,消息仅从主服务器发送到其子服务器。消息带有标准 signal。

Threads

可以将任务转移到单独的线程中,这些任务原本会阻止 nginx worker 进程。例如,可以将 nginx 配置为使用线程执行file I/O。另一个用例是没有异步接口的库,因此通常无法与 nginx 一起使用。请记住,线程接口是用于处理客户端连接的现有异步方法的帮助者,绝不打算替代它。

为了处理同步,可以使用以下pthreadsPrimitives 的包装器:

  • 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)配置多个线程池。每个线程池都是在启动时创建的,并且包含有限数量的线程来处理任务队列。完成任务后,将调用 sched 义的完成处理程序。

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

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

typedef struct ngx_thread_pool_s  ngx_thread_pool_t;

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

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

在配置时,愿意使用线程的模块必须通过调用ngx_thread_pool_add(cf, name)来获得对线程池的引用,后者可以使用给定的name创建一个新的线程池,或者如果已经存在则返回对该名称的池的引用。

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

typedef struct {
    int    foo;
} my_thread_ctx_t;

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

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

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

    /* executed in nginx event loop */
}

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

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

    ctx = task->ctx;

    ctx->foo = 42;

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

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

    return NGX_OK;
}

Modules

添加新模块

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

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

. auto/module

ngx_addon_name=$ngx_module_name

config文件是 POSIX Shell 脚本,可以设置和访问以下变量:

  • ngx_module_type —要构建的模块类型。可能的值为COREHTTPHTTP_FILTERHTTP_INIT_FILTERHTTP_AUX_FILTERMAILSTREAMMISC

  • ngx_module_name —模块名称。要从一组源文件构建多个模块,请指定以空格分隔的名称列表。名字表示动态模块的输出二进制文件的名称。列表中的名称必须与源代码中使用的名称匹配。

  • ngx_addon_name —模块名称,显示在配置脚本中控制台上的输出中。

  • ngx_module_srcs —用空格分隔的用于编译模块的源文件列表。 $ngx_addon_dir变量可用于表示模块目录的路径。

  • ngx_module_incs —包括构建模块所需的路径

  • ngx_module_deps —用空格分隔的模块依赖关系列表。通常,它是头文件列表。

  • ngx_module_libs —与模块链接的用空格分隔的库列表。例如,使用ngx_module_libs=-lpthread链接libpthread库。以下宏可用于链接到与 nginx 相同的库:LIBXSLTLIBGDGEOIPPCREOPENSSLMD5SHA1ZLIBPERL

  • ngx_module_link —构建系统将变量设置为DYNAMIC(对于动态模块)或ADDON(对于静态模块),并用于根据链接类型确定要执行的不同操作。

  • ngx_module_order —模块的加载顺序;对于HTTP_FILTERHTTP_AUX_FILTER模块类型很有用。此选项的格式是空格分隔的模块列表。当前模块名称之后的列表中的所有模块都将在模块的全局列表中排在其后,从而设置了模块初始化的顺序。对于过滤器模块,以后的初始化意味着更早的执行。

以下模块通常用作参考。 ngx_http_copy_filter_module读取其他过滤器模块的数据,并将其放在列表的底部附近,因此它是最早执行的数据之一。 ngx_http_write_filter_module将数据写入客户端套接字,并放置在列表顶部附近,并且是最后执行的位置。

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

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

Core Modules

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

struct ngx_module_s {

    /* private part is omitted */

    void                 *ctx;
    ngx_command_t        *commands;
    ngx_uint_t            type;

    ngx_int_t           (*init_master)(ngx_log_t *log);

    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);

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

    void                (*exit_master)(ngx_cycle_t *cycle);

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

省略的私有部分包括模块版本和签名,并使用 sched 义的宏NGX_MODULE_V1进行填充。

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

  • 当配置指令处理程序出现在主进程的上下文中的配置文件中时,将调用它们。

  • 成功解析配置后,将在主进程的上下文中调用init_module处理程序。每次加载配置时,都会在主进程中调用init_module处理程序。

  • 主进程创建一个或多个工作进程,并在每个工作进程中调用init_process处理程序。

  • 当工作进程从主服务器收到关闭或终止命令时,它将调用exit_process处理程序。

  • 主进程在退出之前会调用exit_master处理程序。

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

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

  • NGX_CORE_MODULE

  • NGX_EVENT_MODULE

  • NGX_HTTP_MODULE

  • NGX_MAIL_MODULE

  • NGX_STREAM_MODULE

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

核心模块集包括ngx_core_modulengx_errlog_modulengx_regex_modulengx_thread_pool_modulengx_openssl_module模块。 HTTP 模块,流模块,邮件模块和事件模块也是核心模块。核心模块的上下文定义为:

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

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

例如,一个名为ngx_foo_module的简单模块可能看起来像这样:

/*
 * Copyright (C) Author.
 */

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

typedef struct {
    ngx_flag_t  enable;
} ngx_foo_conf_t;

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

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

static ngx_command_t  ngx_foo_commands[] = {

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

      ngx_null_command
};

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

ngx_module_t  ngx_foo_module = {
    NGX_MODULE_V1,
    &ngx_foo_module_ctx,                   /* module context */
    ngx_foo_commands,                      /* module directives */
    NGX_CORE_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};

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

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

    fcf->enable = NGX_CONF_UNSET;

    return fcf;
}

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

    ngx_conf_init_value(fcf->enable, 0);

    return NGX_CONF_OK;
}

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

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

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

    return NGX_CONF_OK;
}

Configuration Directives

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

typedef struct ngx_command_s  ngx_command_t;

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

用特殊值ngx_null_command终止数组。 name是配置文件中显示的指令名称,例如“ worker_processes”或“ listen”。 type是标志的位域,用于指定该指令采用的参数数量,其类型以及其出现的上下文。标志是:

  • NGX_CONF_NOARGS-指令不带参数。

  • NGX_CONF_1MORE —指令接受一个或多个参数。

  • NGX_CONF_2MORE —指令接受两个或多个参数。

  • NGX_CONF_TAKE1 .. NGX_CONF_TAKE7 —指令正好采用指定数量的参数。

  • NGX_CONF_TAKE12NGX_CONF_TAKE13NGX_CONF_TAKE23NGX_CONF_TAKE123NGX_CONF_TAKE1234 —指令可以使用不同数量的参数。选项仅限于给定的数字。例如,NGX_CONF_TAKE12表示需要一个或两个参数。

指令类型的标志是:

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

  • NGX_CONF_FLAG —指令采用布尔值onoff

指令的上下文定义了它在配置中可能出现的位置:

  • NGX_MAIN_CONF —在顶级上下文中。

  • 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 —由不创建上下文层次结构且仅具有一个全局配置的模块使用。此配置作为conf参数传递给处理程序。

配置分析器使用这些标志在指令放置错误的情况下引发错误,并调用适当的配置指针提供的指令处理程序,以便位于不同位置的相同指令可以将其值存储在不同的位置。

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

  • ngx_conf_set_flag_slot —将文字字符串onoff分别转换为值为 1 或 0 的ngx_flag_t值。

  • ngx_conf_set_str_slot —将字符串存储为ngx_str_t类型的值。

  • ngx_conf_set_str_array_slot —向字符串ngx_str_t的数组ngx_array_t附加值。如果还不存在,则创建该数组。

  • ngx_conf_set_keyval_slot —将键值对附加到键值对ngx_keyval_t的数组ngx_array_t中。第一个字符串成为键,第二个字符串成为值。如果数组不存在,则创建该数组。

  • ngx_conf_set_num_slot —将指令的参数转换为ngx_int_t值。

  • ngx_conf_set_size_slot —将size转换为以字节表示的size_t值。

  • ngx_conf_set_off_slot —将offset转换为以字节表示的off_t值。

  • ngx_conf_set_msec_slot —将time转换为ngx_msec_t值(以毫秒为单位)。

  • ngx_conf_set_sec_slot —将time转换为以秒表示的time_t值。

  • ngx_conf_set_bufs_slot —将提供的两个参数转换为ngx_bufs_t对象,该对象包含缓冲区的数量和size

  • ngx_conf_set_enum_slot —将提供的参数转换为ngx_uint_t值。在post字段中传递的ngx_conf_enum_t的空终止数组定义了可接受的字符串和相应的整数值。

  • ngx_conf_set_bitmask_slot —将提供的参数转换为ngx_uint_t值。对每个自变量的掩码值进行“或”运算以生成结果。在post字段中传递的以ngx_conf_bitmask_t终止的空终止数组定义了可接受的字符串和相应的掩码值。

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

  • set_access_slot —将提供的参数转换为文件权限掩码。有关详细信息,请参见proxy_store_access指令的文档。

conf字段定义将哪种配置结构传递到目录处理程序。核心模块仅具有全局配置,并设置NGX_DIRECT_CONF标志来访问它。 HTTP,Stream 或 Mail 之类的模块会创建配置层次结构。例如,将为serverlocationif范围创建模块的配置。

  • NGX_HTTP_MAIN_CONF_OFFSEThttp块的配置。

  • NGX_HTTP_SRV_CONF_OFFSEThttp块中的server块的配置。

  • NGX_HTTP_LOC_CONF_OFFSET —在http内配置location块。

  • NGX_STREAM_MAIN_CONF_OFFSETstream块的配置。

  • NGX_STREAM_SRV_CONF_OFFSETstream块中的server块的配置。

  • NGX_MAIL_MAIN_CONF_OFFSETmail块的配置。

  • NGX_MAIL_SRV_CONF_OFFSETmail块中的server块的配置。

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

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

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

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

HTTP

Connection

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

  • ngx_event_accept()接受客户端 TCP 连接。响应于侦听套接字上的读取通知而调用此处理程序。在此阶段创建一个新的ngx_connection_t对象,以包装新接受的客户端套接字。每个 nginx 侦听器都提供一个处理程序,以将新的连接对象传递给该处理程序。对于 HTTP 连接,它是ngx_http_init_connection(c)

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

  • 当客户端套接字上有可用数据时,将调用ngx_http_wait_request_handler() read 事件处理程序。在此阶段,将创建 HTTP 请求对象ngx_http_request_t并将其设置为连接的data字段。

  • ngx_http_process_request_line()读取事件处理程序读取客户端请求行。处理程序由ngx_http_wait_request_handler()设置。数据被读取到连接的buffer中。缓冲区的大小最初由指令client_header_buffer_size设置。整个客户端 Headers 应该适合缓冲区。如果初始大小不足,则会分配更大的缓冲区,其容量由large_client_header_buffers指令设置。

  • ngx_http_process_request_line()之后设置ngx_http_process_request_headers()读取事件处理程序,以读取客户端请求 Headers。

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

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

  • 当完整的响应已发送到客户端并且可以销毁请求时,将调用ngx_http_finalize_connection()。如果启用了客户端连接保持活动功能,则调用ngx_http_set_keepalive(),它将破坏当前请求并 await 连接上的下一个请求。否则,ngx_http_close_request()破坏请求和连接。

Request

对于每个客户端 HTTP 请求,都会创建ngx_http_request_t对象。该对象的某些字段是:

  • connection —指向ngx_connection_t客户端连接对象的指针。多个请求可以同时引用同一连接对象-一个主请求及其子请求。删除请求后,可以在同一连接上创建一个新请求。

请注意,对于 HTTP 连接,ngx_connection_tdata字段指向该请求。与绑定到该连接的其他请求相反,此类请求被称为活动请求。活动请求用于处理客户端连接事件,并允许将其响应输出到客户端。通常,每个请求都会在某个时候处于活动状态,以便可以发送其输出。

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

  • ngx_http_get_module_ctx(r, module) —返回module的上下文

    • ngx_http_set_ctx(r, c, module) —将c设置为module的上下文
  • main_confsrv_confloc_conf —当前请求配置的数组。配置存储在模块的ctx_index位置。

  • read_event_handlerwrite_event_handler-读写请求的事件处理程序。通常,HTTP 连接的读取和写入事件处理程序都设置为ngx_http_request_handler()。该函数为当前活动的请求调用read_event_handlerwrite_event_handler处理程序。

  • cache —请求缓存对象,用于缓存上游响应。

  • upstream —请求上游对象进行代理。

  • pool —请求池。请求对象本身在此池中分配,删除请求后销毁。对于需要在客户端连接的整个生命周期中可用的分配,请改用ngx_connection_t的池。

  • header_in —客户端 HTTP 请求 Headers 被读入的缓冲区。

  • headers_inheaders_out-输入和输出 HTTPHeaders 对象。这两个对象都包含类型为ngx_list_theaders字段,用于保留 Headers 的原始列表。除此之外,特定的 Headers 可用于获取和设置为单独的字段,例如content_length_nstatus等。

  • request_body —客户请求正文对象。

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

  • methodmethod_name —客户端 HTTP 请求方法的数字和文本表示形式。在src/http/ngx_http_request.h中使用宏NGX_HTTP_GETNGX_HTTP_HEADNGX_HTTP_POST等定义方法的数值。

  • http_protocol —客户端 HTTP 协议版本,其原始文本格式(“ HTTP/1.0”,“ HTTP/1.1”等)。

  • http_version —数字形式的客户端 HTTP 协议版本(NGX_HTTP_VERSION_10NGX_HTTP_VERSION_11等)。

  • http_majorhttp_minor-数字形式的客户端 HTTP 协议版本,分为主要部分和次要部分。

  • request_lineunparsed_uri-原始客户请求中的请求行和 URI。

  • uriargsexten —当前请求的 URI,自变量和文件 extensions。由于规范化,此处的 URI 值可能与客户端发送的原始 URI 不同。在整个请求处理过程中,这些值可以随着内部重定向的执行而改变。

  • main —指向主请求对象的指针。创建此对象是为了处理客户端 HTTP 请求,而不是子请求,而子请求是为了在主请求中执行特定的子任务而创建的。

  • parent —指向子请求的父请求的指针。

  • postponed —输出缓冲区和子请求的列表,按其发送和创建的顺序。当子请求创建列表后,过滤器使用该列表提供一致的请求输出。

  • post_subrequest —指向处理程序的指针,该处理程序具有在子请求完成时要调用的上下文。未用于主要请求。

  • posted_requests-要启动或恢复的请求列表,可以通过调用请求的write_event_handler完成。通常,此处理程序保留请求主函数,该函数首先运行请求阶段,然后生成输出。

通常通过ngx_http_post_request(r, NULL)呼叫发布请求。它总是发布到主要请求posted_requests列表中。函数ngx_http_run_posted_requests(c)运行发布在传递的连接的活动请求的主请求中的所有请求。所有事件处理程序都调用ngx_http_run_posted_requests,这可能导致新发布的请求。通常,它是在调用请求的读取或写入处理程序之后调用的。

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

  • ncapturescapturescaptures_data —由请求的最后一个正则表达式匹配产生的正则表达式捕获。在请求处理期间,可以在许多地方进行正则表达式匹配:映射查找,SNI 或 HTTP 主机进行的服务器查找,重写,proxy_redirect 等。由查找产生的捕获存储在上述字段中。字段ncaptures保存捕获数,captures保存捕获边界,captures_data保存与正则表达式匹配的字符串,该字符串用于提取捕获。在每个新的正则表达式匹配之后,将重置请求捕获以保留新值。

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

  • subrequests —当前的子请求嵌套级别。每个子请求都继承其父级的嵌套级别,并降低一级。如果该值达到零,则会生成错误。主请求的值由NGX_HTTP_MAX_SUBREQUESTS常量定义。

  • uri_changes —为请求剩余的 URI 更改数。请求可以更改其 URI 的总次数受NGX_HTTP_MAX_URI_CHANGES常量限制。每次更改时,该值都会递减,直到达到零为止,这时会产生错误。重写和内部重定向到正常位置或命名位置被视为 URI 更改。

  • blocked —请求中保留的阻止计数器。该值不为零时,无法终止请求。当前,此值通过挂起的 AIO 操作(POSIX AIO 和线程操作)和活动的缓存锁定来增加。

  • buffered —位掩码,显示哪些模块已缓冲请求所产生的输出。许多过滤器可以缓冲输出。例如,由于部分字符串匹配,sub_filter 可以缓冲数据,由于缺少可用的输出缓冲区,因此复制过滤器可以缓冲数据等。只要此值不为零,就不会在刷新之前完成请求。

  • header_only —标记,指示输出不需要主体。例如,HTTP HEAD 请求使用此标志。

  • keepalive —指示是否支持客户端连接 keepalive 的标志。该值是从 HTTP 版本和“ Connection”Headers 的值推断出来的。

  • header_sent —指示请求已发送输出头的标志。

  • internal —标记,指示当前请求是内部请求。要进入内部状态,请求必须通过内部重定向或成为子请求。内部请求被允许进入内部位置。

  • allow_ranges —标志,指示可以按照 HTTP RangeHeaders 的要求将部分响应发送到客户端。

  • subrequest_ranges —指示在处理子请求时可以发送部分响应的标志。

  • single_range —标志,指示只能将单个连续范围的输出数据发送到客户端。通常在从代理服务器发送数据流时设置此标志,并且整个响应在一个缓冲区中不可用。

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

  • filter_need_temporary —标记请求在临时缓冲区中产生请求输出,但不在只读内存缓冲区或文件缓冲区中产生。过滤器使用它,这些过滤器可以直接在发送它的缓冲区中更改输出。

Configuration

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

  • 主要配置-适用于整个http块。用作模块的全局设置。

  • 服务器配置-适用于单个server块。用作模块的服务器特定设置。

  • 位置配置-适用于单个locationiflimit_except块。用作模块的位置特定设置。

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

typedef struct {
    ngx_uint_t  foo;
} ngx_http_foo_loc_conf_t;

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

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

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

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

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

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

    conf->foo = NGX_CONF_UNSET_UINT;

    return conf;
}

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

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

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

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

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

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

static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r);

static ngx_command_t  ngx_http_foo_commands[] = {

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

      ngx_null_command
};

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

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

    return NGX_CONF_OK;
}

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

  • 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的引用。请求的主要配置不会改变。选择请求的虚拟服务器后,服务器配置可以从默认值更改。由于重写操作或内部重定向,选择用于处理请求的位置配置可能会多次更改。以下示例显示了如何在运行时访问模块的 HTTP 配置。

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

    flcf = ngx_http_get_module_loc_conf(r, ngx_http_foo_module);

    ...
}

Phases

每个 HTTP 请求都经过一系列阶段。在每个阶段,对请求执行不同类型的处理。特定于模块的处理程序可以在大多数阶段中进行注册,许多标准的 nginx 模块将其阶段处理程序注册为一种在请求处理的特定阶段被调用的方式。阶段将连续处理,一旦请求到达阶段,阶段处理程序将被调用。以下是 Nginx HTTP 阶段的列表。

  • NGX_HTTP_POST_READ_PHASE —第一阶段。 ngx_http_realip_module在此阶段注册其处理程序,以允许在调用任何其他模块之前替换客户端地址。

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

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

  • NGX_HTTP_REWRITE_PHASE —与NGX_HTTP_SERVER_REWRITE_PHASE相同,但对于在上一阶段中选择的位置中定义的重写规则。

  • NGX_HTTP_POST_REWRITE_PHASE —特殊阶段,如果请求的 URI 在重写期间被更改,则将请求重定向到新位置。这是通过再次通过NGX_HTTP_FIND_CONFIG_PHASE的请求来实现的。此阶段不能注册其他处理程序。

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

  • NGX_HTTP_ACCESS_PHASE —验证客户端被授权发出请求的阶段。标准 nginx 模块(例如ngx_http_access_modulengx_http_auth_basic_module)在此阶段注册其处理程序。默认情况下,客户端必须通过对在此阶段注册的所有处理程序的授权检查,才能 continue 进行下一阶段的请求。如果任何阶段处理程序授权客户端,则satisfy指令可用于允许处理 continue。

  • NGX_HTTP_POST_ACCESS_PHASE —处理satisfy any指令的特殊阶段。如果某些访问阶段处理程序拒绝了访问并且没有显式允许访问,则该请求将最终确定。此阶段不能注册其他处理程序。

  • NGX_HTTP_PRECONTENT_PHASE —在生成内容之前调用处理程序的阶段。诸如ngx_http_try_files_modulengx_http_mirror_module之类的标准模块在此阶段注册其处理程序。

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

  • NGX_HTTP_LOG_PHASE —执行请求日志记录的阶段。当前,只有ngx_http_log_module在此阶段注册其处理程序以进行访问日志记录。在释放请求之前,在请求处理的最后阶段调用日志阶段处理程序。

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

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

阶段处理程序应返回特定的代码:

  • NGX_OK —进入下一个阶段。

  • NGX_DECLINED —转到当前阶段的下一个处理程序。如果当前处理程序是当前阶段的最后一个,则移至下一个阶段。

  • NGX_AGAINNGX_DONE —暂停阶段处理,直到某个将来的事件为止,例如,事件可能是异步 I/O 操作或只是延迟。假定稍后将通过调用ngx_http_core_run_phases()恢复阶段处理。

  • 阶段处理程序返回的任何其他值都将被视为请求完成代码,尤其是 HTTP 响应代码。该请求将使用提供的代码完成。

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

Variables

访问现有变量

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

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

index = ngx_http_get_variable_index(cf, &name);

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

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

typedef ngx_variable_value_t  ngx_http_variable_value_t;

typedef struct {
    unsigned    len:28;

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

    u_char     *data;
} ngx_variable_value_t;

where:

  • len —值的长度

  • data —值本身

  • valid —该值有效

  • not_found-找不到变量,因此datalen字段无关。例如,当未在请求中传递相应的参数时,例如$arg_foo这样的变量就可能发生这种情况

  • no_cacheable —不缓存结果

  • escape —由日志记录模块在内部使用,以标记需要在输出上转义的值。

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

ngx_http_variable_value_t  *v;

v = ngx_http_get_flushed_variable(r, index);

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

/* some meaningful value is found */

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

一些模块,例如 SSI 和 Perl,需要处理在配置时名称未知的变量。因此,不能使用索引来访问它们,但是ngx_http_get_variable(r, name, key)功能可用。它搜索具有给定name及其从名称派生的哈希key的变量。

Creating variables

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

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

  • NGX_HTTP_VAR_NOCACHEABLE —禁用缓存,这对诸如$time_local之类的变量很有用。

  • NGX_HTTP_VAR_NOHASH —表示只能按索引访问此变量,不能按名称访问。当已知在诸如 SSI 或 Perl 的模块中不需要该变量时,这是使用时的一个小优化。

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

如果出现错误,该函数将返回 NULL;否则返回ngx_http_variable_t的指针:

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

调用getset处理程序以获取或设置变量值,将data传递给变量处理程序,并且index保留用于引用变量的已分配变量索引。

通常,由模块创建以ngx_http_variable_t结构终止的空终止静态数组,并在预配置阶段进行处理,以将变量添加到配置中,例如:

static ngx_http_variable_t  ngx_http_foo_vars[] = {

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

      ngx_http_null_variable
};

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

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

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

    return NGX_OK;
}

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

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

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

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

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

    return NGX_OK;
}

如果发生内部错误(例如,内存分配失败),它将返回NGX_ERROR,否则返回NGX_OK。要了解变量评估的状态,请检查ngx_http_variable_value_t中的标记(请参见说明above)。

set处理程序允许设置变量引用的属性。例如,$limit_rate变量的 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;
}

Complex values

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

ngx_http_compile_complex_value中的复数值描述在配置阶段被编译为ngx_http_complex_value_t,该ngx_http_complex_value_t在运行时用于获得表达式求值的结果。

ngx_str_t                         *value;
ngx_http_complex_value_t           cv;
ngx_http_compile_complex_value_t   ccv;

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

ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

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

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

在这里,ccv拥有初始化复数值cv所需的所有参数:

  • cf —配置指针

  • value —要解析的字符串(输入)

  • complex_value —编译值(输出)

  • zero —启用零终止值的标志

  • conf_prefix —使用配置前缀(nginx 当前正在寻找配置的目录)作为结果的前缀

  • root_prefix —使用根前缀(正常的 nginx 安装前缀)为结果添加前缀

当将结果传递到需要以零结尾的字符串的库时,zero标志很有用;在处理文件名时,前缀很方便。

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

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

在运行时,可以使用ngx_http_complex_value()函数计算一个复数值:

ngx_str_t  res;

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

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

Request redirection

HTTP 请求始终通过ngx_http_request_t结构的loc_conf字段连接到某个位置。这意味着在任何时候都可以通过调用ngx_http_get_module_loc_conf(r, module)从请求中检索任何模块的位置配置。在请求的生存期内,请求位置可能会更改几次。最初,将默认服务器的默认服务器位置分配给请求。如果请求切换到其他服务器(由 HTTP“主机”Headers 或 SSL SNIextensions 选择),则请求也将切换到该服务器的默认位置。位置的下一次更改发生在NGX_HTTP_FIND_CONFIG_PHASE请求阶段。在此阶段,将通过请求 URI 在为服务器配置的所有未命名位置中选择一个位置。 ngx_http_rewrite_module可以根据rewrite指令在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)函数更改请求 URI 并将请求返回到NGX_HTTP_SERVER_REWRITE_PHASE阶段。该请求将 continue 使用服务器的默认位置。稍后在NGX_HTTP_FIND_CONFIG_PHASE处,根据新的请求 URI 选择新的位置。

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

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

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

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

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

以下示例将重定向到命名位置@foo。

ngx_int_t
ngx_http_foo_named_redirect(ngx_http_request_t *r)
{
    ngx_str_t  name;

    ngx_str_set(&name, "foo");

    return ngx_http_named_location(r, &name);
}

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

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

重定向和重写的请求成为内部请求,可以访问internal个位置。内部请求设置了internal标志。

Subrequests

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

子请求在NGX_HTTP_SERVER_REWRITE_PHASE阶段开始。它经过与普通请求相同的后续阶段,并根据其自己的 URI 分配了一个位置。

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

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

  • 最初,主请求处于活动状态。

  • 创建后,活动请求的第一个子请求立即变为活动状态。

  • 发送完该请求之前的所有数据后,ngx_http_postpone_filter将激活活动请求的子请求列表中的下一个请求。

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

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

  • NGX_HTTP_SUBREQUEST_IN_MEMORY-输出不会发送到客户端,而是存储在内存中。该标志仅影响由代理模块之一处理的子请求。子请求完成后,其输出在ngx_buf_t类型的r->out中可用。

  • NGX_HTTP_SUBREQUEST_WAITED-子请求的done标志将被设置,即使子请求在完成时未处于活动状态。该子请求标志由 SSI 过滤器使用。

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

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

ngx_int_t            rc;
ngx_str_t            uri;
ngx_http_request_t  *sr;

...

ngx_str_set(&uri, "/foo");

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

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

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

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

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

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

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

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

    return rc;
}

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

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

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

    last = 0;

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

    /* Output explicit output buffers */

    rc = ngx_http_next_body_filter(r, in);

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

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

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

    ngx_http_set_ctx(r, NULL, ngx_http_foo_filter_module);

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

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

    b->last_buf = 1;

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

    return ngx_http_output_filter(r, &out);
}

子请求也可以出于数据输出以外的其他目的而创建。例如,ngx_http_auth_request_module模块在NGX_HTTP_ACCESS_PHASE阶段创建一个子请求。要在此时禁用输出,在子请求上设置header_only标志。这样可以防止将子请求正文发送到客户端。请注意,子请求的 Headers 永远不会发送到客户端。子请求的结果可以在回调处理程序中进行分析。

Request finalization

通过调用函数ngx_http_finalize_request(r, rc)最终确定 HTTP 请求。通常在所有输出缓冲区发送到过滤器链后由内容处理程序完成此操作。此时,可能没有将所有输出发送到客户端,其中一些仍保留在过滤器链中的某个位置。如果是,则ngx_http_finalize_request(r, rc)函数自动安装一个特殊的处理程序ngx_http_writer(r)来完成输出的发送。如果发生错误或需要将标准 HTTP 响应代码返回给客户端,则请求也会最终确定。

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

  • NGX_DONE-快速完成。减少请求count,如果请求达到零,则将其销毁。当前请求被销毁后,客户端连接可用于更多请求。

  • NGX_ERRORNGX_HTTP_REQUEST_TIME_OUT(408),NGX_HTTP_CLIENT_CLOSED_REQUEST(499)-错误完成。尽快终止请求并关闭客户端连接。

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

  • 其他代码被认为是成功的完成代码,并且可能会激活请求编写器以完成发送响应正文。一旦正文被完全发送,请求count就会递减。如果达到零,该请求将被销毁,但客户端连接仍可用于其他请求。如果count是肯定的,则请求中有未完成的活动,这些活动将在以后完成。

Request body

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

不允许从子请求中读取或丢弃客户端请求主体。它必须始终在主请求中完成。创建子请求后,如果主请求先前已读取请求正文,则该子请求将继承父请求的request_body对象。

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

下面的示例读取一个客户端请求正文并返回其大小。

ngx_int_t
ngx_http_foo_content_handler(ngx_http_request_t *r)
{
    ngx_int_t  rc;

    rc = ngx_http_read_client_request_body(r, ngx_http_foo_init);

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

    return NGX_DONE;
}

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

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

    len = 0;

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

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

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

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

    rc = ngx_http_send_header(r);

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

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

    rc = ngx_http_output_filter(r, &out);

    ngx_http_finalize_request(r, rc);
}

请求的以下字段确定如何读取请求正文:

  • request_body_in_single_buf-将正文读取到单个内存缓冲区。

  • request_body_in_file_only-始终将正文读取到文件中,即使它适合内存缓冲区。

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

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

  • request_body_file_group_access-通过将默认的 0600 访问掩码替换为 0660 来启用对文件的组访问。

  • request_body_file_log_level-记录文件错误的严重性级别。

  • request_body_no_buffering-读取请求正文而不进行缓冲。

request_body_no_buffering标志启用读取请求正文的非缓冲模式。在这种模式下,调用ngx_http_read_client_request_body()之后,bufs链可能只保留身体的一部分。要阅读下一部分,请调用ngx_http_read_unbuffered_request_body(r)函数。返回值NGX_AGAIN和请求标志reading_body表示有更多数据可用。如果调用此函数后bufs为 NULL,则此刻没有任何内容可读取。当请求正文的下一部分可用时,将调用请求回调read_event_handler

Response

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

Response header

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

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

    /* send header */

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

    /* X-Foo: foo */

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

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

    rc = ngx_http_send_header(r);

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

    /* send body */

    ...
}

Header filters

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

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

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

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

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

static ngx_http_module_t  ngx_http_foo_header_filter_module_ctx = {
    NULL,                                   /* preconfiguration */
    ngx_http_foo_header_filter_init,        /* postconfiguration */

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

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

    NULL,                                   /* create location configuration */
    NULL                                    /* merge location configuration */
};

ngx_module_t  ngx_http_foo_header_filter_module = {
    NGX_MODULE_V1,
    &ngx_http_foo_header_filter_module_ctx, /* module context */
    NULL,                                   /* module directives */
    NGX_HTTP_MODULE,                        /* module type */
    NULL,                                   /* init master */
    NULL,                                   /* init module */
    NULL,                                   /* init process */
    NULL,                                   /* init thread */
    NULL,                                   /* exit thread */
    NULL,                                   /* exit process */
    NULL,                                   /* exit master */
    NGX_MODULE_V1_PADDING
};

static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;

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

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

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

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

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

    return ngx_http_next_header_filter(r);
}

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

    return NGX_OK;
}

Response body

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

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

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

    /* send header */

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

    rc = ngx_http_send_header(r);

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

    /* send body */

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

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

    b->memory = 1;

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

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

    return ngx_http_output_filter(r, &out);
}

Body filters

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

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

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

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

typedef struct {
    off_t  count;
} ngx_http_counter_filter_ctx_t;

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

static ngx_http_module_t  ngx_http_counter_filter_module_ctx = {
    ngx_http_counter_add_variables,        /* preconfiguration */
    ngx_http_counter_filter_init,          /* postconfiguration */

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

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

    NULL,                                  /* create location configuration */
    NULL                                   /* merge location configuration */
};

ngx_module_t  ngx_http_counter_filter_module = {
    NGX_MODULE_V1,
    &ngx_http_counter_filter_module_ctx,   /* module context */
    NULL,                                  /* module directives */
    NGX_HTTP_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};

static ngx_http_output_body_filter_pt  ngx_http_next_body_filter;

static ngx_str_t  ngx_http_counter_name = ngx_string("counter");

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

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

        ngx_http_set_ctx(r, ctx, ngx_http_counter_filter_module);
    }

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

    return ngx_http_next_body_filter(r, in);
}

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

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

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

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

    return NGX_OK;
}

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

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

    var->get_handler = ngx_http_counter_variable;

    return NGX_OK;
}

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

    return NGX_OK;
}

构建过滤器模块

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

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

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

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

. auto/module

Buffer reuse

发布或更改缓冲区流时,通常需要重用已分配的缓冲区。 Nginx 代码中一种标准且被广泛采用的方法是为此目的保留两个缓冲区链:freebusyfree链保留所有可用缓冲区,这些缓冲区可以重用。 busy链保留当前模块发送的所有仍由其他过滤器处理程序使用的缓冲区。如果缓冲区的大小大于零,则认为该缓冲区正在使用中。通常,当缓冲区被过滤器占用时,其pos(对于文件缓冲区为file_pos)将移向last(对于文件缓冲区为file_last)。一旦缓冲区被完全消耗掉,就可以重新使用它了。要将新释放的缓冲区添加到free链,就足以在busy链上进行迭代并将其顶部的零大小缓冲区移到free。此操作非常普遍,因此有一个特殊的功能ngx_chain_update_chains(free, busy, out, tag)。该函数将输出链out附加到busy,并将空闲缓冲区从busy的顶部移到free。仅重用具有指定tag的缓冲区。这样一来,模块就只能重用它自己分配的缓冲区。

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

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

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

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

        ngx_http_set_ctx(r, ctx, ngx_http_foo_filter_module);
    }

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

    ll = &out;

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

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

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

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

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

        /* append the next incoming buffer */

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

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

    *ll = NULL;

    /* send the new chain */

    rc = ngx_http_next_body_filter(r, out);

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

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

    return rc;
}

Load balancing

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

least_connhash模块实现了替代的负载平衡方法,但实际上被实现为上游循环模块的扩展,并与之共享许多代码,例如服务器组的表示。 keepalive模块是扩展上游功能的独立模块。

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

struct ngx_http_upstream_srv_conf_s {
    ngx_http_upstream_peer_t         peer;
    void                           **srv_conf;

    ngx_array_t                     *servers;  /* ngx_http_upstream_server_t */

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

#if (NGX_HTTP_UPSTREAM_ZONE)
    ngx_shm_zone_t                  *shm_zone;
#endif
};
  • srv_conf —上游模块的配置上下文。

  • serversngx_http_upstream_server_t的数组,即upstream块中解析一组server指令的结果。

  • flags —这些标志主要标记负载平衡方法支持哪些功能。这些功能被配置为server指令的参数:

  • NGX_HTTP_UPSTREAM_CREATE —与由proxy_pass指令和“ friends”(FastCGI,SCGI 等)自动创建的上游区别开来。

    • 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 —配置文件的名称和upstream块所在的行。

  • portno_port-不用于显式定义的上游组。

  • shm_zone —此上游组使用的共享内存区域(如果有)。

  • peer —包含用于初始化上游配置的通用方法的对象:

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

实现负载平衡算法的模块必须设置这些方法并初始化私有data。如果在配置解析期间未初始化init_upstream,则ngx_http_upstream_module将其设置为默认的ngx_http_upstream_init_round_robin算法。

  • init_upstream(cf, us) —配置时方法,负责初始化一组服务器,并在成功的情况下初始化init()方法。典型的负载平衡模块使用upstream块中的服务器列表来创建有效的数据结构,并使用该结构并将其自己的配置保存到data字段中。

    • init(r, us) —初始化用于负载平衡的按请求ngx_http_upstream_peer_t.peer的结构(不要与上面描述的按上游的ngx_http_upstream_srv_conf_t.peer混淆)。它作为data参数传递给处理服务器选择的所有回调。

当 nginx 必须将请求传递给另一个主机进行处理时,它使用配置的负载平衡方法来获取要连接的地址。该方法是从ngx_peer_connection_t类型的ngx_http_upstream_t.peer对象获得的:

struct ngx_peer_connection_s {
    ...

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

    ngx_uint_t                       tries;

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

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

    ...
};

该结构具有以下字段:

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

  • data —负载均衡方法的按请求数据;保持选择算法的状态,通常包括到上游配置的链接。它作为参数传递给处理服务器选择的所有方法(请参见below)。

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

  • getfreenotifyset_sessionsave_session-负载均衡模块的方法,如下所述。

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

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

  • NGX_OK —已选择服务器。

    • NGX_ERROR —发生内部错误。

    • NGX_BUSY —当前没有服务器可用。发生这种情况的原因可能有很多,其中包括:动态服务器组为空,组中的所有服务器都处于故障状态,或者组中的所有服务器已经在处理最大数量的连接。

    • NGX_DONE —基础连接已被重用,无需创建与上游服务器的新连接。该值由keepalive模块设置。

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

  • NGX_PEER_FAILED —尝试是unsuccessful

    • NGX_PEER_NEXT —上游服务器返回不视为failure的代码403404的特殊情况。

    • NGX_PEER_KEEPALIVE —当前未使用

此方法还减少了tries计数器。

  • notify(pc, data, type) —当前未在 OSS 版本中使用。

  • set_session(pc, data)save_session(pc, data)-特定于 SSL 的方法,用于将会话缓存到上游服务器。该实现由循环平衡方法提供。

Examples

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

Code style

General rules

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

  • 缩进是 4 个空格

  • 没有制表符,没有尾随空格

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

  • 十六进制文字为小写

  • 文件名,函数和类型名以及全局变量具有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

典型的源文件可能包含以下几节,两行之间用空行分隔:

  • copyright statements

  • includes

  • preprocessor definitions

  • type definitions

  • function prototypes

  • variable definitions

  • function definitions

版权声明如下:

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

如果对该文件进行了重大修改,则应更新作者列表,然后将新作者添加到顶部。

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

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

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

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

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

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

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

Preprocessor

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

#define NGX_CONF_BUFFER  4096

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

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

#define ngx_null_string  { 0, NULL }

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

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

Types

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

typedef ngx_uint_t  ngx_rbtree_key_t;

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

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

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

typedef struct ngx_list_part_s  ngx_list_part_t;

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

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

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

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

结构内部的函数指针已定义以“ _pt”结尾的类型:

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

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

枚举的类型以“ _e”结尾:

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

Variables

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

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

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

static ngx_str_t  ngx_http_memcached_key = ngx_string("memcached_key");
static ngx_uint_t  mday[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static uint32_t  ngx_crc32_table16[] = {
    0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
    ...
    0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
};

有很多常用的类型/名称组合:

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

Functions

所有函数(甚至是静态函数)都应具有原型。原型包括参数名称。长的原型在续行上用单个压痕包裹:

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

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

定义中的函数名称以换行开头。功能主体的开合括号位于单独的行上。函数的主体是缩进的。函数之间有两个空行:

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

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

函数名称和左括号后没有空格。长函数调用被包装起来,以使连续行从第一个函数参数的位置开始。如果这不可能,请格式化第一条连续线,使其在位置 79 处结束:

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

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

应该使用ngx_inline宏而不是inline

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

Expressions

除“ .”和“ ->”以外的二进制运算符应与其操作数分隔一个空格。一元运算符和下标之间的空格不会与操作数分开:

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

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

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

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

if (status == NGX_HTTP_MOVED_PERMANENTLY
    || status == NGX_HTTP_MOVED_TEMPORARILY
    || status == NGX_HTTP_SEE_OTHER
    || status == NGX_HTTP_TEMPORARY_REDIRECT
    || status == NGX_HTTP_PERMANENT_REDIRECT)
{
    ...
}
p->temp_file->warn = "an upstream response is buffered "
                     "to a temporary file";

作为最后的选择,可以包装一个表达式,以使续行在位置 79 处结束:

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

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

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

有时,在强制转换之后包装表达式很方便。在这种情况下,续行缩进:

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

将指针与NULL(不是0)进行显式比较:

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

条件和循环

关键字“ if”与条件分隔一个空格。开括号位于同一行,或者,如果情况需要多行,则位于专用行。右括号位于专用线路上,可选地后跟“ else if/else”。通常,“ else if/else”部分之前有一个空行:

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

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

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

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

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

类似的格式设置规则适用于“ do”和“ while”循环:

while (p < last && *p == ' ') {
    p++;
}
do {
    ctx->node = rn;
    ctx = ctx->next;
} while (ctx);

关键字“ switch”与条件分隔一个空格。撑杆位于同一行。闭合支架位于专用线路上。 “ case”关键字与“ switch”对齐:

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

case '<':
    copy_end = p;
    break;

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

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

for (i = 0; i < ccf->env.nelts; i++) {
    ...
}
for (q = ngx_queue_head(locations);
     q != ngx_queue_sentinel(locations);
     q = ngx_queue_next(q))
{
    ...
}

如果省略了“ for”语句的某些部分,则由“ /* void */”注释指示:

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

带有空主体的循环也由“ /* void */”注释指示,该注释可以放在同一行上:

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

一个无尽的循环看起来像这样:

for ( ;; ) {
    ...
}

Labels

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

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;

Common Pitfalls

编写 C 模块

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

在启动模块之前,请考虑以下问题:

  • 是否可以使用available modules实现所需的功能?

  • 是否可以使用内置脚本语言(例如Perlnjs)解决问题?

C Strings

Nginx 中最常用的字符串类型ngx_str_t不是 C 样式的零终止字符串。您不能将数据传递给标准 C 库函数,例如strlen()strstr()。相反,应该使用接受ngx_str_t的 nginx counterparts或指向数据和长度的指针。但是,在某些情况下ngx_str_t持有指向零终止字符串的指针:由于配置文件解析而来的字符串为零终止。

Global Variables

避免在模块中使用全局变量。这很可能是具有全局变量的错误。任何全局数据都应绑定到configuration cycle并从相应的memory pool分配。这使 nginx 可以执行优美的配置重载。尝试使用全局变量可能会破坏此功能,因为不可能同时具有两个配置并摆脱它们。有时需要全局变量。在这种情况下,需要特别注意以正确管理重新配置。另外,检查代码使用的库是否具有隐式全局状态,该状态可能在重新加载时被破坏。

手动内存管理

了解如何使用 nginx pools而不是处理容易出错的 malloc/free 方法。创建池并将其绑定到对象configurationcycleconnectionHTTP request。当对象被销毁时,关联的池也被销毁。因此,在使用对象时,可以从相应的池中分配所需的数量,并且即使在发生错误的情况下也不必担心释放内存。

Threads

建议避免在 nginx 中使用线程,因为它肯定会破坏事情:大多数 nginx 函数都不是线程安全的。预期线程将仅执行系统调用和线程安全的库函数。如果您需要运行与客户端请求处理无关的代码,则正确的方法是在init_process模块处理程序中安排计时器并在计时器处理程序中执行所需的操作。 nginx 在内部使用threads来增强与 IO 相关的操作,但这是一种特殊情况,有很多限制。

Blocking Libraries

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

对外部服务的 HTTP 请求

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

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

  • 在处理客户端请求的上下文中(例如,在内容处理程序中)

  • 在工作进程(例如计时器处理程序)的上下文中

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

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