开发指南

Introduction
     Code layout
     Include files
     Integers
     Common return codes
     Error handling
Strings
     Overview
     Formatting
     Numeric conversion
     Regular expressions
Time
Containers
     Array
     List
     Queue
     Red-Black tree
     Hash
Memory management
     Heap
     Pool
     Shared memory
Logging
Cycle
Buffer
Networking
     Connection
Events
     Event
     I/O events
     Timer events
     Posted events
     Event loop
Processes
Threads
Modules
     Adding new modules
     Core Modules
     Configuration Directives
HTTP
     Connection
     Request
     Configuration
     Phases
     Variables
     Complex values
     Request redirection
     Subrequests
     Request finalization
     Request body
     Request body filters
     Response
     Response body
     Response body filters
     Building filter modules
     Buffer reuse
     Load balancing
Examples
Code style
     General rules
     Files
     Comments
     Preprocessor
     Types
     Variables
     Functions
     Expressions
     Conditionals and Loops
     Labels
Debugging memory issues
Common Pitfalls
     Writing a C module
     C Strings
     Global Variables
     Manual Memory Management
     Threads
     Blocking Libraries
     HTTP Requests to External Services

介绍

代码布局

包含文件

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

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

除此之外,HTTP 代码还应该包括

#include <ngx_http.h>

邮件代码应包括

#include <ngx_mail.h>

流代码应包括

#include <ngx_stream.h>

整数

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

常见返回码

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

错误处理

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

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

使用示例ngx_errno

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

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

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

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

        return 1;
    }

    return 0;
}

弦乐

概述

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

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

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

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

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

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

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

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

格式化

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

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

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

例如:

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

/* set n here */

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

数值转换

nginx 中实现了几个数值转换的函数。前四个分别将给定长度的字符串转换为指定类型的正整数。他们NGX_ERROR出错时返回。

还有两个附加的数字转换函数。与前四个一样,它们会返回NGX_ERROR错误。

常用表达

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

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

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

u_char                errstr[NGX_MAX_CONF_ERRSTR];

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

ngx_memzero(&rc, sizeof(ngx_regex_compile_t));

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

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

re = rc.regex;
#endif

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

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

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

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

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

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

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

参数ngx_regex_exec()是已编译的正则表达式re、要匹配的字符串input、用于保存captures找到的任何整数的可选整数数组以及数组的size. captures根据 PCRE API的要求,数组的大小必须是三的倍数。在示例中,大小是根据捕获总数加上匹配字符串本身的 1 来计算的。

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

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

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

/* accessing named captures */

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

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

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

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

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

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

时间

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

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

该结构是UNIX 平台和Windows 上的ngx_tm_t别名 。 struct tmSYSTEMTIME

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

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

和宏返回当前时间值(以秒为单位),是访问缓存时间值的首选方法 ngx_time()ngx_timeofday()

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

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

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

集装箱

大批

nginx数组类型ngx_array_t定义如下

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

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

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

ngx_array_t  *a, b;

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

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

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

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

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

列表

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

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

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

typedef struct ngx_list_part_s  ngx_list_part_t;

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

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

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

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

/* add items to the list */

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

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

/* iterate over the list */

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

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

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

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

    ngx_do_smth(&v[i]);
}

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

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

队列

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

typedef struct ngx_queue_s  ngx_queue_t;

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

头队列节点不与任何数据链接。使用前使用ngx_queue_init(q)调用初始化列表头。队列支持以下操作:

一个例子:

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

ngx_foo_t    *f;
ngx_queue_t   values, *q;

ngx_queue_init(&values);

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

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

/* insert more nodes here */

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

    ngx_do_smth(&f->value);
}

红黑树

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

typedef struct {
    ngx_rbtree_t       rbtree;
    ngx_rbtree_node_t  sentinel;

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

typedef struct {
    ngx_rbtree_node_t  rbnode;

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

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

初始化一棵树:

my_tree_t  root;

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

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

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

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

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

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

    while (node != sentinel) {

        n = (my_node_t *) node;

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

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

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

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

        return n;
    }

    return NULL;
}

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

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

    my_node_t          *my_node;
    ngx_rbtree_node_t  *node;

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

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

    ngx_rbtree_insert(&root->rbtree, node);

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

ngx_rbtree_delete(&root->rbtree, node);

哈希值

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

在初始化哈希之前,您需要知道它将保存的元素数量,以便 nginx 能够以最佳方式构建它。需要配置的两个参数是max_sizebucket_size,详见单独的 文档。它们通常可由用户配置。哈希初始化设置与类型一起存储 ngx_hash_init_t,哈希本身是 ngx_hash_t

ngx_hash_t       foo_hash;
ngx_hash_init_t  hash;

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

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

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

ngx_hash_keys_arrays_t  foo_keys;

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

ngx_hash_keys_array_init(&foo_keys, NGX_HASH_SMALL);

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

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

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

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

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

max_size如果或 bucket_size参数不够大, 该函数将失败。

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

my_data_t   *data;
ngx_uint_t   key;

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

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

通配符匹配

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

ngx_hash_init_t      hash
ngx_hash_combined_t  foo_hash;

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

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

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

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

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

if (foo_keys.dns_wc_head.nelts) {

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

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

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

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

键数组需要排序,初始化结果必须添加到组合哈希中。数组的初始化dns_wc_tail也是类似的。

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

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

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

内存管理

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

水池

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

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

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

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

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

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

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

链链接 ( ngx_chain_t) 在 nginx 中被积极使用,因此 nginx 池实现提供了一种重用它们的方法。字段chain保存ngx_pool_t先前分配的链接列表以供重用。为了有效分配池中的链节,请使用该 ngx_alloc_chain_link(pool)函数。该函数在池列表中查找空闲链链接,如果池列表为空,则分配新的链链接。要释放链接,请调用该ngx_free_chain(pool, cl)函数。

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

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

ngx_pool_cleanup_t  *cln;

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

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

...

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

    ngx_do_smth(msg);
}

共享内存

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

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

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

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

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

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

ngx_str_t        name;
ngx_foo_ctx_t   *ctx;
ngx_shm_zone_t  *shm_zone;

ngx_str_set(&name, "foo");

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

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

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


...


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

    size_t            len;
    ngx_foo_ctx_t    *ctx;
    ngx_slab_pool_t  *shpool;

    value = shm_zone->data;

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

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

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

    /* initialize shared zone */

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

    shpool->data = ctx->value;

    return NGX_OK;
}

记录

nginx 使用ngx_log_t对象来记录日志。nginx 记录器支持多种类型的输出:

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

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

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

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

Nginx 提供了以下日志宏:

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

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

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

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

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

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

循环

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

该函数创建一个循环ngx_init_cycle(),该函数将前一个循环作为其参数。该函数定位上一个周期的配置文件,并从上一个周期继承尽可能多的资源。一个名为“init Cycle”的占位符周期在 nginx 启动时创建,然后被根据配置构建的实际周期替换。

该周期的成员包括:

缓冲

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

ngx_buf_t结构体有以下字段:

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

typedef struct ngx_chain_s  ngx_chain_t;

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

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

使用缓冲区和链的示例:

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

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

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

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

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

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

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

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

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

    return out;
}

联网

联系

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

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

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

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

活动

事件

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

中的字段ngx_event_t包括:

输入/输出事件

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

定时器事件

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

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

发布的事件

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

一个例子:

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

    rev = c->read;

    ngx_add_timer(rev, 1000);

    rev->handler = ngx_my_read_handler;

    ngx_my_read(rev);
}


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

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

    c = rev->data;

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

        if (n == NGX_AGAIN) {
            break;
        }

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

        /* process buf */
    }

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

事件循环

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

事件循环有以下阶段:

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

流程

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

nginx 进程处理以下信号:

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

线程数

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

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

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

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

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

typedef struct ngx_thread_pool_s  ngx_thread_pool_t;

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

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

在配置时,愿意使用线程的模块必须通过调用 来获取对线程池的引用 ngx_thread_pool_add(cf, name),这要么使用给定的名称创建一个新的线程池name,要么返回对具有该名称的池的引用(如果该池已存在)。

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

typedef struct {
    int    foo;
} my_thread_ctx_t;


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

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


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

    /* executed in nginx event loop */
}


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

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

    ctx = task->ctx;

    ctx->foo = 42;

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

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

    return NGX_OK;
}

模块

添加新模块

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

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

. auto/module

ngx_addon_name=$ngx_module_name

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

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

核心模块

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

struct ngx_module_s {

    /* private part is omitted */

    void                 *ctx;
    ngx_command_t        *commands;
    ngx_uint_t            type;

    ngx_int_t           (*init_master)(ngx_log_t *log);

    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);

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

    void                (*exit_master)(ngx_cycle_t *cycle);

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

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

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

因为线程在 nginx 中仅用作具有自己的 API 的补充 I/O 设施,init_thread并且exit_thread 当前不调用处理程序。也没有init_master处理程序,因为这会产生不必要的开销。

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

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

核心模块集包括ngx_core_modulengx_errlog_modulengx_regex_modulengx_thread_pool_module模块 ngx_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 分别指向创建和初始化模块配置的函数的指针。对于核心模块,nginxcreate_conf在解析新配置之前以及init_conf所有配置解析成功之后调用。典型的create_conf函数为配置分配内存并设置默认值。

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

/*
 * Copyright (C) Author.
 */


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


typedef struct {
    ngx_flag_t  enable;
} ngx_foo_conf_t;


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

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


static ngx_command_t  ngx_foo_commands[] = {

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

      ngx_null_command
};


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


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


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

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

    fcf->enable = NGX_CONF_UNSET;

    return fcf;
}


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

    ngx_conf_init_value(fcf->enable, 0);

    return NGX_CONF_OK;
}


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

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

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

    return NGX_CONF_OK;
}

配置指令

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

typedef struct ngx_command_s  ngx_command_t;

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

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

指令类型的标志是:

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

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

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

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

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

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

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

参数postngx_conf_post_t 对象本身,data是指向值的指针,由主处理程序从参数转换为适当的类型。

HTTP协议

联系

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

要求

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

配置

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

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

typedef struct {
    ngx_uint_t  foo;
} ngx_http_foo_loc_conf_t;


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

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

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

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


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

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

    conf->foo = NGX_CONF_UNSET_UINT;

    return conf;
}


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

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

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

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

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

static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r);


static ngx_command_t  ngx_http_foo_commands[] = {

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

      ngx_null_command
};


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

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

    return NGX_CONF_OK;
}

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

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

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

    flcf = ngx_http_get_module_loc_conf(r, ngx_http_foo_module);

    ...
}

阶段

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

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

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

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

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

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


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

    ua = r->headers_in.user_agent;

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

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

    return NGX_DECLINED;
}


static ngx_int_t
ngx_http_foo_init(ngx_conf_t *cf)
{
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

    h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_http_foo_handler;

    return NGX_OK;
}

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

对于某些阶段,返回代码的处理方式略有不同。在内容阶段,除被 NGX_DECLINED视为最终代码之外的任何返回代码。来自位置内容处理程序的任何返回代码都被视为最终确定代码。在访问阶段,在 满足任何NGX_OK模式下,除, NGX_DECLINED, NGX_AGAIN, 之外的任何返回码NGX_DONE均被视为拒绝。如果后续访问处理程序没有允许或拒绝使用不同代码的访问,则拒绝代码将成为最终确定代码。

变量

访问现有变量

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

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

index = ngx_http_get_variable_index(cf, &name);

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

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

typedef ngx_variable_value_t  ngx_http_variable_value_t;

typedef struct {
    unsigned    len:28;

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

    u_char     *data;
} ngx_variable_value_t;

在哪里:

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

ngx_http_variable_value_t  *v;

v = ngx_http_get_flushed_variable(r, index);

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

/* some meaningful value is found */

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

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

创建变量

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

如果出现错误,该函数将返回 NULL,否则返回指向 ngx_http_variable_t以下内容的指针:

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

调用getset处理程序来获取或设置变量值, data将其传递给变量处理程序,并 index保存用于引用该变量的分配的变量索引。

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

static ngx_http_variable_t  ngx_http_foo_vars[] = {

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

      ngx_http_null_variable
};

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

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

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

    return NGX_OK;
}

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

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

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

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

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

    return NGX_OK;
}

NGX_ERROR如果出现内部错误(例如,内存分配失败)或其他情况, 它将返回NGX_OK。要了解变量评估的状态,请检查中的标志(请参阅上面的ngx_http_variable_value_t描述 )。

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

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

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

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

    s = ngx_parse_size(&val);

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

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

    *sp = s;

    return;
}

复数值

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

中的复杂值描述 ngx_http_compile_complex_value在配置阶段编译,在ngx_http_complex_value_t 运行时使用以获得表达式求值的结果。

ngx_str_t                         *value;
ngx_http_complex_value_t           cv;
ngx_http_compile_complex_value_t   ccv;

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

ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

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

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

这里,ccv保存初始化复数值所需的所有参数cv

zero当将结果传递到需要零终止字符串的库时, 该标志非常有用,并且在处理文件名时前缀很方便。

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

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

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

ngx_str_t  res;

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

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

请求重定向

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

ngx_http_internal_redirect(r, uri, args)还可以通过调用或 之一将请求重定向到新位置 ngx_http_named_location(r, name)

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

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

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

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

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

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

以下示例执行到指定位置@foo 的重定向。

ngx_int_t
ngx_http_foo_named_redirect(ngx_http_request_t *r)
{
    ngx_str_t  name;

    ngx_str_set(&name, "foo");

    return ngx_http_named_location(r, &name);
}

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

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

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

子请求

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

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

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

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

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

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

ngx_int_t            rc;
ngx_str_t            uri;
ngx_http_request_t  *sr;

...

ngx_str_set(&uri, "/foo");

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

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

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

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

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

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


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

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

    return rc;
}

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

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

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

    last = 0;

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

    /* Output explicit output buffers */

    rc = ngx_http_next_body_filter(r, in);

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

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

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

    ngx_http_set_ctx(r, NULL, ngx_http_foo_filter_module);

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

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

    b->last_buf = 1;

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

    return ngx_http_output_filter(r, &out);
}

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

请求最终确定

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

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

请求正文

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

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

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

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

ngx_int_t
ngx_http_foo_content_handler(ngx_http_request_t *r)
{
    ngx_int_t  rc;

    rc = ngx_http_read_client_request_body(r, ngx_http_foo_init);

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

    return NGX_DONE;
}


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

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

    len = 0;

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

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

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

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

    rc = ngx_http_send_header(r);

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

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

    rc = ngx_http_output_filter(r, &out);

    ngx_http_finalize_request(r, rc);
}

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

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

请求正文过滤器

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

如果过滤器计划延迟数据缓冲区,则应将标志设置 r->request_body->filter_need_buffering1首次调用时。

以下是一个简单的请求正文过滤器示例,它将请求正文延迟一秒。

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


#define NGX_HTTP_DELAY_BODY  1000


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


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


static ngx_http_module_t  ngx_http_delay_body_module_ctx = {
    NULL,                          /* preconfiguration */
    ngx_http_delay_body_init,      /* postconfiguration */

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

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

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


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


static ngx_http_request_body_filter_pt   ngx_http_next_request_body_filter;


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

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

    ctx = ngx_http_get_module_ctx(r, ngx_http_delay_body_filter_module);

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

        ngx_http_set_ctx(r, ctx, ngx_http_delay_body_filter_module);

        r->request_body->filter_need_buffering = 1;
    }

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

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

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

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

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

            /* add timer */

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

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

        return ngx_http_next_request_body_filter(r, NULL);
    }

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

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

    ctx->out = NULL;

    return rc;
}


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

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


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

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

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

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


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

    return NGX_OK;
}

回复

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

响应头

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

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

    /* send header */

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

    /* X-Foo: foo */

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

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

    rc = ngx_http_send_header(r);

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

    /* send body */

    ...
}

标头过滤器

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

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

以下标头过滤器模块示例将 HTTP 标头“ X-Foo: foo”添加到每个带有 status 的响应 200

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


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


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

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

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

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


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


static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;


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

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

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

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

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

    return ngx_http_next_header_filter(r);
}


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

    return NGX_OK;
}

响应体

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

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

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

    /* send header */

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

    rc = ngx_http_send_header(r);

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

    /* send body */

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

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

    b->memory = 1;

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

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

    return ngx_http_output_filter(r, &out);
}

响应主体过滤器

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

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

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

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


typedef struct {
    off_t  count;
} ngx_http_counter_filter_ctx_t;


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


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

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

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

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


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


static ngx_http_output_body_filter_pt  ngx_http_next_body_filter;

static ngx_str_t  ngx_http_counter_name = ngx_string("counter");


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

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

        ngx_http_set_ctx(r, ctx, ngx_http_counter_filter_module);
    }

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

    return ngx_http_next_body_filter(r, in);
}


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

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

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

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

    return NGX_OK;
}


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

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

    var->get_handler = ngx_http_counter_variable;

    return NGX_OK;
}


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

    return NGX_OK;
}

构建过滤模块

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

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

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

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

. auto/module

缓冲区重用

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

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

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


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

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

        ngx_http_set_ctx(r, ctx, ngx_http_foo_filter_module);
    }

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

    ll = &out;

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

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

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

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

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

        /* append the next incoming buffer */

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

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

    *ll = NULL;

    /* send the new chain */

    rc = ngx_http_next_body_filter(r, out);

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

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

    return rc;
}

负载均衡

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

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

ngx_http_upstream_module 可以通过将相应的 上游块放入配置文件中来显式配置,也可以使用proxy_pass等指令隐式配置 ,该指令接受在某个时刻评估到服务器列表中的 URL。替代负载平衡方法仅适用于显式上游配置。上游模块配置有自己的指令上下文 。结构体定义如下: NGX_HTTP_UPS_CONF

struct ngx_http_upstream_srv_conf_s {
    ngx_http_upstream_peer_t         peer;
    void                           **srv_conf;

    ngx_array_t                     *servers;  /* ngx_http_upstream_server_t */

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

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

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

struct ngx_peer_connection_s {
    ...

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

    ngx_uint_t                       tries;

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

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

    ...
};

该结构体有以下字段:

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

例子

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

代码风格

一般规则

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

    last = p + n;

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

        c = *p;

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

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

    return len;
}

文件

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

版权声明如下所示:

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

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

和文件始终首先包含,然后是 、或之一ngx_config.h。然后遵循可选的外部头文件: ngx_core.hngx_http.hngx_stream.hngx_mail.h

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

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

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

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

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

评论

预处理器

宏名称以ngx_or NGX_ (或更具体)前缀开头。常量的宏名称是大写的。参数化宏和初始化宏都是小写的。宏名称和值至少用两个空格分隔:

#define NGX_CONF_BUFFER  4096

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

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

#define ngx_null_string  { 0, NULL }

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

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

类型

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

typedef ngx_uint_t  ngx_rbtree_key_t;

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

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

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

typedef struct ngx_list_part_s  ngx_list_part_t;

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


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

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

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

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

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

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

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

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

变量

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

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

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

static ngx_str_t  ngx_http_memcached_key = ngx_string("memcached_key");

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

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

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

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

功能

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

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

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

定义中的函数名称以新行开头。函数体左大括号和右大括号位于不同的行上。函数体是缩进的。函数之间有两个空行:

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


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

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

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

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

ngx_inline应使用宏而 不是inline

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

表达式

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

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

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

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

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

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

如果表达式无法容纳在单行中,则会将其换行。断线的首选点是二元运算符。延续行与表达式的开头对齐:

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

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

作为最后的手段,可以将表达式换行,以便续行在位置 79 处结束:

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

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

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

有时,在强制转换后包装表达式会很方便。在本例中,续行是缩进的:

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

NULL指针显式地与(not 0) 进行比较 :

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

条件和循环

if”关键字与条件之间用一个空格分隔。左大括号位于同一行,如果条件需要多行,则位于专用行。右大括号位于专用行上,可以选择后跟“ else if/ else”。else if通常,“ / ”部分之前有一个空行else

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

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

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

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

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

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

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

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

switch”关键字与条件之间用一个空格分隔。左大括号位于同一行。闭合大括号位于专用线上。“ case”关键字与“ ”对齐switch

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

case '<':
    copy_end = p;
    break;

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

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

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

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

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

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

空循环体也由“ /* void */”注释表示,可以放在同一行:

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

无限循环看起来像这样:

for ( ;; ) {
    ...
}

标签

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

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

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

    u->naddrs = i;

    ...

    return NGX_OK;

failed:

    freeaddrinfo(res);
    return NGX_ERROR;

调试内存问题

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

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

以下配置行总结了上面提供的信息。建议在开发第三方模块以及在不同平台上测试 nginx 时使用。

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

常见陷阱

编写 C 模块

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

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

C 弦乐

ngx_str_t 是 nginx 中最常用的字符串类型, 它不是 C 风格的以零结尾的字符串。您无法将数据传递给标准 C 库函数,例如strlen()strstr()。 相反,应该使用接受的nginx对应项或指向数据和长度的指针。ngx_str_t但是,存在一种情况,其中ngx_str_t包含指向以零结尾的字符串的指针:作为配置文件解析结果的字符串是以零结尾的。

全局变量

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

手动内存管理

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

线程数

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

阻止库

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

对外部服务的 HTTP 请求

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

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

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

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