开发指南

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
Response
Response body
Body filters
Building filter modules
Buffer reuse
Load balancing

Introduction

Code layout

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分别。

Common return codes

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

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的示例:

void
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字段保存字符串长度,数据保存字符串数据。保存在ngx_str_t中的字符串在len字节之后可能是或不是以null结尾。在大多数情况下,它不是。然而,在代码的某些部分(例如,在解析配置时),ngx_str_t对象已知为空终止,这简化了字符串比较,并使得更容易将字符串传递给系统调用。

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

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

以下功能执行案例转换和比较:

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

Formatting

以下格式化功能支持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;

Numeric conversion

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

还有两个额外的数字转换功能。像前四个一样,他们返回NGX_ERROR出错。

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结构中的捕获named_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,与s匹配的字符串,一个可选的整数数组,用于保存任何捕获,以及数组的大小根据PCRE API的要求,捕获数组的大小必须为3的倍数。在该示例中,从匹配的字符串本身的捕获总数加上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

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_current_msec变量​​保存自Epoch以来所经过的毫秒数,并截断为ngx_msec_t

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

The ngx_time() and ngx_timeofday() macros return the current time value in seconds and are the preferred way to access the cached time value.

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

以下函数将time_t转换为指定的分解时间表示。每一对中的第一个函数将time_t转换为ngx_tm_t,将第二个(使用_libc _中缀)转换为struct tm

ngx_http_time(buf,time)函数返回适用于HTTP标头的字符串表示形式(例如,“1970年9月28日星期一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));

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

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

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输入和输出标题。

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

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

一个例子:

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,详见文档它们通常可由用户配置。哈希初始化设置与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;

是一个指向从字符串创建哈希整数键的函数的指针。有两个通用的键创建功能: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;
}

键数组需要排序,并且必须将初始化结果添加到组合散列中。初始化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

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

Pool

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

池内部将连续的内存块分配对象。一旦块已满,将分配一个新的块并将其添加到池内存块列表。当请求的分配太大而不能嵌入块时,请求被转发到系统分配器,并且返回的指针被存储在池中用于进一步的解除分配。

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

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

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

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

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

链接(ngx_chain_t)在nginx中被主动使用,所以nginx池实现提供了一种重用它们的方式。ngx_pool_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);
}

Shared memory

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

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

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

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

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

要保护共享内存中的数据不受并发访问,请使用ngx_slab_pool_tmutex字段中提供的互斥体。互斥锁在平台池中最常用于分配和释放内存,但它可以用于保护在共享区域中分配的任何其他用户数据结构。要锁定或解锁互斥锁,请分别调用ngx_shmtx_lock(&amp; shpool-&gt; mutex)ngx_shmtx_unlock(&amp; shpool-&gt; 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对象。nginx记录器支持多种类型的输出:

记录器实例可以是通过下一个字段彼此链接的记录器链。在这种情况下,每条消息都将写入链中的所有记录器。

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

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

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

Nginx提供以下日志记录宏:

日志消息格式化为堆栈中大小为NGX_MAX_ERROR_STR(当前为2048字节)的缓冲区。该消息前面附有严重性级别,进程ID(PID),连接ID(存储在log-&gt;连接中)和系统错误文本。For non-debug messages log->handler is called as well to prepend more specific information to the log message. HTTP模块将ngx_http_log_error()作为日志处理程序设置为日志客户端和服务器地址,当前操作(存储在log-&gt; 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工作人员启动时继承。每次重新加载nginx配置,都会从新的nginx配置创建一个新的循环;旧循环通常在新的循环被成功创建之后被删除。

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

周期成员包括:

Buffer

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

ngx_buf_t结构具有以下字段:

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

typedef struct ngx_chain_s  ngx_chain_t;

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

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

使用缓冲区和链的一个例子:

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

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

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

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

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

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

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

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

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

    return out;
}

Networking

Connection

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

nginx连接可以透明地封装SSL层。In this case the connection's ssl field holds a pointer to an ngx_ssl_connection_t structure, keeping all SSL-related data for the connection, including SSL_CTX and SSL. recv发送recv_chainsend_chain处理程序也设置为启用SSL的功能。

nginx配置中的worker_connections指令限制每个nginx worker的连接数。当工作人员启动并存储在循环对象的连接字段中时,将对所有连接结构进行预处理。要检索连接结构,请使用ngx_get_connection(s,log)函数。它将参数作为套接字描述符,它需要被包装在一个连接结构中。

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

Events

Event

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

ngx_event_t中的字段包括:

I/O events

通过调用ngx_get_connection()函数获得的每个连接都有两个附加事件,即c-> readc-> write用于接收套接字准备好读取或写入的通知。所有这些事件都以边沿触发模式运行,这意味着只有当套接字状态发生变化时,才会触发通知。例如,在套接字上进行部分读取不会使nginx传递重复的读取通知,直到更多的数据到达套接字。即使底层I / O通知机制本质上是Level-Triggered(poll选择等),nginx将通知转换为Edge-Triggered。为使nginx事件通知在不同平台上的所有通知系统保持一致,必须在处理I之后调用函数ngx_handle_read_event(rev,flags)ngx_handle_write_event(wev,lowat) / O套接字通知或调用该套接字上的任何I / O功能。通常,函数在每个读或写事件处理程序结束时调用一次。

Timer events

事件可以设置为在超时到期时发送通知。函数ngx_add_timer(ev,timer)为事件设置超时,ngx_del_timer(ev)删除先前设置的超时。全局超时红黑树ngx_event_timer_rbtree存储当前设置的所有超时。树中的密钥类型为ngx_msec_t,是事件到期的时间,以1970年1月1日午夜以来的毫秒数表示,模数ngx_msec_t最大值。树结构可以实现快速插入和删除操作,以及访问最近的超时,nginx用于查找等待I / O事件多长时间以及到期超时事件。

Posted events

一个事件可以被发布,这意味着它的处理程序将在稍后的当前事件循环迭代中被调用。发布事件是简化代码和转义堆栈溢出的好习惯。发布的事件被保存在一个后期队列中。ngx_post_event(ev,q) mscro将事件ev发布到后期队列qngx_delete_posted_event(ev)宏从当前发布的队列中删除事件ev在所有I / O和定时器事件已经被处理之后,通常情况下,事件会被发布到事件循环迟到的ngx_posted_events队列。调用函数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 */ }
}

Event loop

除了nginx主进程,所有的nginx进程都进行I / O操作,所以有一个事件循环。(nginx主程序代替大部分时间在sigsuspend()呼叫中等待信号到达。)nginx事件循环在ngx_process_events_and_timers()函数中实现,该过程将重复调用,直到进程退出。

事件循环有以下几个阶段:

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

Processes

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

nginx进程处理以下信号:

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

Threads

有可能卸载到另外阻止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)来获取对线程池的引用,该引用可以使用给定的名称创建一个新的线程池或返回一个对该名称的池的引用,如果它已经存在。

要在运行时将任务添加到指定线程池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

Adding new 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脚本,可以设置和访问以下变量:

要将模块静态地编译为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 */
};

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

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

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

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

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

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

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

其中名称是模块名字符串,create_confinit_conf是分别创建和初始化模块配置的函数的指针。对于核心模块,在解析所有配置成功解析新配置和init_conf之后,nginx调用create_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终止数组。名称是配置文件中出现的指令的名称,例如“worker_processes”或“listen”。类型是一个标志的位字段,用于指定伪指令所参数的数量,其类型及其出现的上下文。标志是:

指令类型的标志是:

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

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

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

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

偏移量定义了一个模块配置结构中保留该特定指令值的字段的偏移量。典型的用法是使用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对象本身,数据是一个指向该值的指针,由主处理程序的参数从适当的类型。

HTTP

Connection

每个HTTP客户端连接运行在以下阶段:

Request

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

Configuration

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

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

typedef struct {
    ngx_uint_t  foo;
} ngx_http_foo_loc_conf_t;


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

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

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

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


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

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

    conf->foo = NGX_CONF_UNSET_UINT;

    return conf;
}


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

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

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

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

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

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

    ...
}

Phases

每个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_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_DECLINED之外的任何返回代码都被认为是一个终结代码。来自位置内容处理程序的任何返回代码都被认为是一个完成代码。At the access phase, in satisfy any mode, any return code other than NGX_OK, NGX_DECLINED, NGX_AGAIN, NGX_DONE is considered a denial. 如果后续的访问处理程序不允许或拒绝使用不同的代码访问,则拒绝代码将成为最终化代码。

Variables

Accessing existing variables

变量可以由索引引用(这是最常见的方法)或名称(参见)。在配置阶段创建索引时,将变量添加到配置中。要获取变量索引,请使用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配置的指针,名称指向包含变量名的字符串。该函数返回错误时的NGX_ERROR,否则返回有效的索引,通常将其存储在模块配置中的某个位置,以备将来使用。

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

typedef ngx_variable_value_t  ngx_http_variable_value_t;

typedef struct {
    unsigned    len:28;

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

    u_char     *data;
} ngx_variable_value_t;

哪里:

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

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)函数可用。它从名称中搜索具有给定名称的变量及其哈希

Creating variables

要创建变量,请使用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;
};

调用get设置处理程序来获取或设置变量值,将数据传递给可变处理程序,索引保存用于引用变量的分配变量索引。

通常,一个空值终止的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_null_string, NULL, NULL, 0, 0, 0 }
};

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模块上下文的预配置字段,并在解析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_OK的情况下,返回NGX_ERRORTo learn the status of variable evaluation, inspect the flags in ngx_http_variable_value_t (see the description above).

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

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

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

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

    s = ngx_parse_size(&val);

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

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

    *sp = s;

    return;
}

Complex values

复杂的值,尽管它的名字,提供了一个简单的方法来评估可以包含文本,变量及其组合的表达式。

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所需的所有参数:

当结果要传递给需要零终止字符串的库时,标志很有用,前缀在处理文件名时很方便。

编译成功后,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“主机”头或SSL SNI扩展名选择),请求也切换到该服务器的默认位置。位置的下一个更改发生在NGX_HTTP_FIND_CONFIG_PHASE请求阶段。在此阶段,在为服务器配置的所有未命名位置之间,请求URI选择一个位置。作为重写指令的结果,ngx_http_rewrite_module可以更改NGX_HTTP_REWRITE_PHASE请求阶段的请求URI,并将请求发送回NGX_HTTP_FIND_CONFIG_PHASE阶段,用于基于新的URI选择新位置。

也可以通过调用ngx_http_internal_redirect(r,uri,args)ngx_http_named_location(r,name)之一来将任务重定向到任何位置。

ngx_http_internal_redirect(r,uri,args)函数更改请求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模块已经在请求的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)增加了请求计数对于一致的请求引用计数,请在重定向请求后调用ngx_http_finalize_request(r,NGX_DONE)这将完成当前的请求代码路径并减少计数器。

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

Subrequests

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

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

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

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

通过调用函数ngx_http_subrequest(r,uri,args,psr,ps,flags)创建子请求,其中r是父请求,uri t2 >和args是子请求的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标志。这样可以防止将子请求体发送到客户端。请注意,子请求的头不会发送到客户端。子请求的结果可以在回调处理程序中进行分析。

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值:

Request body

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

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

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

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

ngx_int_t
ngx_http_foo_content_handler(ngx_http_request_t *r)
{
    ngx_int_t  rc;

    rc = ngx_http_read_client_request_body(r, ngx_http_foo_init);

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

    return NGX_DONE;
}


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

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

    len = 0;

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

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

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

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

    rc = ngx_http_send_header(r);

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

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

    rc = ngx_http_output_filter(r, &out);

    ngx_http_finalize_request(r, rc);
}

请求的以下字段决定了请求体的读取方式:

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

Response

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

Response header

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

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变量中的第一个标头过滤器处理程序来调用头过滤器链。假设每个头处理程序调用链中的下一个处理程序,直到调用最终处理程序ngx_http_header_filter(r)最终头处理程序根据r-> header_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)函数。该函数可以多次调用。每次它以缓冲链的形式发送响应体的一部分。在最后一个body缓冲区中设置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变量中的第一个体过滤器处理程序来调用body过滤器链。假设每个body处理程序调用链中的下一个处理程序,直到调用最终处理程序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;
}

Building filter modules

在编写身体或头部过滤器时,请特别注意过滤器的位置。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代码中,标准和广泛采用的方法是为了保持两个缓冲链:空闲免费链保留所有可用的缓冲区,可以重复使用。busy链保留当前模块发送的所有缓冲区,这些缓冲区仍被其他过滤器处理程序使用。如果缓冲区的大小大于零,则会考虑使用缓冲区。通常,当缓冲区被一个过滤器消耗时,其文件缓冲区的pos(或file_pos)移动到最后一个file_last用于文件缓冲区)。一旦缓冲区被完全消耗,它就可以重新使用了。要将新释放的缓冲区添加到空闲链中,足以迭代链,并将其头部的零大小缓冲区移动到空闲 t2 >。这个操作是很常见的,因为它有一个特殊的功能,ngx_chain_update_chains(free,busy,out,tag)该功能将输出链输出,并将空闲缓冲区从的顶部移动到空闲只有具有指定标签的缓冲区被重用。这使得模块只能重用它自己分配的缓冲区。

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

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


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

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

        ngx_http_set_ctx(r, ctx, ngx_http_foo_filter_module);
    }

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

    ll = &out;

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

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

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

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

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

        /* append the next incoming buffer */

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

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

    *ll = NULL;

    /* send the new chain */

    rc = ngx_http_next_body_filter(r, out);

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

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

    return rc;
}

Load balancing

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

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

可以通过将相应的上游块放入配置文件中,或者通过使用接受URL的指令(例如proxy_pass)来明确地配置ngx_http_upstream_module这在某些时候被评估到服务器列表中。替代的负载平衡方法仅在明确的上游配置下可用。上游模块配置有自己的指令上下文NGX_HTTP_UPS_CONF结构定义如下:

struct ngx_http_upstream_srv_conf_s {
    ngx_http_upstream_peer_t         peer;
    void                           **srv_conf;

    ngx_array_t                     *servers;  /* ngx_http_upstream_server_t */

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

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

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

struct ngx_peer_connection_s {
    ...

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

    ngx_uint_t                       tries;

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

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

    ...
};

该结构具有以下字段:

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