Nginx

Development guide

开发指南

  • 介绍

介绍

代码布局

  • auto - 建立脚本

- `http` — Core HTTP module and common code - `modules` — Other HTTP modules - `v2` — HTTP/2

- `mail` — Mail modules - `os` — Platform-specific code - `unix` - `win32`

- `stream` — Stream modules

包含文件

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

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

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

#include <ngx_http.h>

Mail 代码应该包括

#include <ngx_mail.h>

流代码应包括

#include <ngx_stream.h>

整型

一般用途,nginx的代码使用了两个整数类型,ngx_int_tngx_uint_t,这分别是类型定义intptr_tuintptr_t

常用的返回码

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

  • NGX_OK - 操作成功。

错误处理

ngx_errno宏返回最后一个系统错误代码。 它在POSIX平台上映射到errno,在Windows中映射到GetLastError()。 ngx_socket_errno宏返回最后一个套接字错误号。 像ngx_errno宏一样,它在POSIX平台上映射到errno。 它映射到Windows上的WSAGetLastError()调用。 连续访问ngx_errno或ngx_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; }

字符串

概观

对于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字节之后可以或不可以以null结尾。 在大多数情况下不是。 但是,在代码的某些部分(例如解析配置时),已知ngx_str_t对象以空字符结尾,这简化了字符串比较,并使字符串传递到系统调用变得更简单。

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

  • ngx_strcmp()

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

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

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

  • ngx_tolower()

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

  • ngx_string(text) - 来自C字符串文本文本的ngx_str_t类型的静态初始值设定项

格式化

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

  • ngx_sprintf(buf, fmt, ...)

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

  • %Ooff_t

你可以在大多数类型上加上前缀来使它们无符号。 要将输出转换为十六进制,请使用X或x。

例如:

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_atoi(line, n)ngx_int_t

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

  • ngx_atofp(line, n, point)- 将给定长度的定点浮点数转换为类型的正整数ngx_int_t。结果左移point小数位。数字的字符串表示预计不会超过points小数位数。例如,ngx_atofp("10.5", 4, 2)退货1050

常用表达

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结构中的capture和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的倍数。 在该示例中,大小是根据匹配的字符串本身的捕获总数加1one来计算的。

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

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结构是struct tmUNIX平台和SYSTEMTIMEWindows 上的别名。

要获取当前时间,通常只需访问一个可用的全局变量即可,它表示所需格式的缓存时间值。例如,ngx_current_msec变量保存从Epoch开始并截断到的毫秒数ngx_msec_t

可用的字符串表示是:

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

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_gmtime(), ngx_libc_gmtime() - 以UTC表示的时间

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")。

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字段为array包含元素的数量。size字段保存单个元素的大小,并在数组初始化时设置。

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

ngx_array_t *a, b; /* create an array of strings with preallocated memory for 10 elements */ a = ngx_array_create(pool, 10, sizeof(ngx_str_t) /* initialize string array for 10 elements */ ngx_array_init(&b, pool, 10, sizeof(ngx_str_t)

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

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

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

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对象)标记为缺失,请将该hash字段设置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)呼叫使用前初始化列表头。队列支持以下操作:

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

一个例子:

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;

为了处理一棵树,你需要两个节点: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

哈希

哈希表函数在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

该功能可识别通配符并将键添加到相应的阵列中。有关通配符语法和匹配算法的说明,请参阅地图模块文档。

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

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

内存管理

Heap

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

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

Pool

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

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

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

  • ngx_create_pool(size, log) - 创建一个具有指定块大小的池。返回的池对象也在池中分配。

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为循环添加一个新的共享内存条目。该功能接收区域namesize区域。每个共享区域必须具有唯一的名称。如果共享区域条目与提供的name并且tag已存在,则现有区域条目将被重新使用。如果具有相同名称的现有条目具有不同的标签,则该函数将失败并显示错误。通常,将模块结构的地址作为参数传递tag,从而可以在一个nginx模块中按名称重用共享区域。

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

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

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

为了在共享内存中分配,nginx提供了slab池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)。

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

要保护共享内存中的数据免受并发访问,请使用ngx_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; }

Logging

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

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

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

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

  • NGX_LOG_EMERG

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

  • NGX_LOG_DEBUG_CORE

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

Nginx提供了以下日志记录宏:

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

日志消息格式化为NGX_MAX_ERROR_STR堆栈大小(当前为2048字节)的缓冲区。该消息预先包含严重性级别,进程标识(PID),连接标识(存储在中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 start,然后被一个由配置构建的实际循环替换。

周期的成员包括:

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

缓冲

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

ngx_buf_t结构具有以下阶段:

  • startend- 分配给缓冲区的内存块的边界。

对于输入和输出操作,缓冲区链接在一起。一个链是一个链式链的序列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

连接

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

  • fd - 套接字描述符

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

nginx配置中的worker_connections指令限制了每个nginx工作者的连接数量。 所有连接结构都是在工作人员启动时预先创建并存储在循环对象的连接字段中。 要检索连接结构,请使用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中可重用连接的一个例子;它们被标记为可重复使用,直到从客户端收到第一个请求字节。

事件

Event

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

ngx_event_t包括的字段:

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

I / O事件

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

计时器事件

可以设置事件以在超时到期时发送通知。 函数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事件和超时事件过期的时间。

发布事件

可以发布一个事件,这意味着它的处理程序将在当前事件循环迭代的稍后一段时间被调用。 发布事件是简化代码和避免堆栈溢出的良好实践。 发布的活动在帖子队列中进行。 ngx_post_event(ev,q)mscro将事件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()函数中实现,该函数在该进程退出之前被重复调用。

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

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

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

流程

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

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

nginx进程处理以下信号:

  • NGX_SHUTDOWN_SIGNAL(在大多数系统上为SIGQUIT) - 正常关机。在接收到该信号后,主进程向所有子进程发送关闭信号。当没有子过程时,主人会破坏循环池并退出。当一个工作进程接收到这个信号时,它会关闭所有监听套接字并等待,直到没有不可取消的事件被调度,然后销毁循环池并退出。当缓存管理器或缓存加载器进程收到该信号时,它立即退出。该ngx_quit变量被设置为1当一个进程接收该信号,并在处理之后立即复位。该ngx_exiting变量设置为1当工作进程处于关闭状态时。

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

线程

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

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

  • typedef pthread_mutex_t ngx_thread_mutex_t;

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

模块

添加新模块

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

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

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

  • ngx_module_type - 要建立的模块类型。可能的值是COREHTTPHTTP_FILTERHTTP_INIT_FILTERHTTP_AUX_FILTERMAILSTREAM,或MISC

要将模块静态编译到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

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

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

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

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

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

/* * Copyright (C) Author. */ #include <ngx_config.h> #include <ngx_core.h> typedef struct { ngx_flag_t enable; } ngx_foo_conf_t; static void *ngx_foo_create_conf(ngx_cycle_t *cycle static char *ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf static char *ngx_foo_enable(ngx_conf_t *cf, void *post, void *data static ngx_conf_post_t ngx_foo_enable_post = { ngx_foo_enable }; static ngx_command_t ngx_foo_commands[] = { { ngx_string("foo_enabled"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, 0, offsetof(ngx_foo_conf_t, enable), &ngx_foo_enable_post }, ngx_null_command }; static ngx_core_module_t ngx_foo_module_ctx = { ngx_string("foo"), ngx_foo_create_conf, ngx_foo_init_conf }; ngx_module_t ngx_foo_module = { NGX_MODULE_V1, &ngx_foo_module_ctx, /* module context */ ngx_foo_commands, /* module directives */ NGX_CORE_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static void * ngx_foo_create_conf(ngx_cycle_t *cycle) { ngx_foo_conf_t *fcf; fcf = ngx_pcalloc(cycle->pool, sizeof(ngx_foo_conf_t) if (fcf == NULL) { return NULL; } fcf->enable = NGX_CONF_UNSET; return fcf; } static char * ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf) { ngx_foo_conf_t *fcf = conf; ngx_conf_init_value(fcf->enable, 0 return NGX_CONF_OK; } static char * ngx_foo_enable(ngx_conf_t *cf, void *post, void *data) { ngx_flag_t *fp = data; if (*fp == 0) { return NGX_CONF_OK; } ngx_log_error(NGX_LOG_NOTICE, cf->log, 0, "Foo Module is enabled" return NGX_CONF_OK; }

配置指令

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

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

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

  • NGX_CONF_NOARGS - 指令不需要任何参数。

指令类型的标志是:

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

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

  • NGX_MAIN_CONF - 在顶级环境中。

配置解析器使用这些标记在错误指令的情况下引发错误,并调用指向处理程序的正确配置指针,以便不同位置的指令可以将它们的值存储在不同的位置。

set字段定义一个处理程序,该处理程序处理指令并将分析的值存储到相应的配置中。有一些执行常见转换的功能:

  • ngx_conf_set_flag_slot-在文字串转换onoffngx_flag_t;值分别为1或0,值。

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

  • NGX_HTTP_MAIN_CONF_OFFSET- http块的配置。

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

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

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

post参数是ngx_conf_post_t对象本身,并且data是一个指针值,由主处理机从参数转换与适当的类型。

HTTP

连接

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

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

请求

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

  • 连接 - 指向ngx_connection_t客户端连接对象的指针。 几个请求可以同时引用同一个连接对象 - 一个主要请求及其子请求。 删除请求后,可以在同一个连接上创建新的请求。请注意,对于HTTP连接,ngx_connection_t的数据字段指向请求。 这种请求被称为活动的,而不是与连接相关的其他请求。 活动请求用于处理客户端连接事件,并允许将其响应输出到客户端。 通常情况下,每个请求都会在某个时间点处于活动状态,以便可以发送其输出。

- `ngx_http_get_module_ctx(r, module)` — Returns the `module`'s context - `ngx_http_set_ctx(r, c, module)` — Sets `c` as the `module`'s context

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

组态

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

  • 主配置 - 适用于整个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作为第一个参数。

  • ngx_http_conf_get_module_main_conf(cf, module)

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

static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r static ngx_command_t ngx_http_foo_commands[] = { { ngx_string("foo"), NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, ngx_http_foo, 0, 0, NULL }, ngx_null_command }; static char * ngx_http_foo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_core_loc_conf_t *clcf; clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module clcf->handler = ngx_http_bar_handler; return NGX_CONF_OK; }

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

  • ngx_http_get_module_main_conf(r, module)

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

static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r) { ngx_http_foo_loc_conf_t *flcf; flcf = ngx_http_get_module_loc_conf(r, ngx_http_foo_module ... }

Phases

每个HTTP请求都通过一系列阶段。在每个阶段,对请求执行不同类型的处理。特定于模块的处理程序可以在大多数阶段注册,并且许多标准nginx模块注册它们的阶段处理程序,作为在请求处理的特定阶段调用的方式。阶段被连续地处理,并且阶段处理程序在请求到达阶段时被调用。以下是nginx HTTP阶段列表。

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

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

static ngx_http_module_t ngx_http_foo_module_ctx = { NULL, /* preconfiguration */ ngx_http_foo_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r) { ngx_str_t *ua; ua = r->headers_in->user_agent; if (ua == NULL) { return NGX_DECLINED; } /* reject requests with "User-Agent: foo" */ if (ua->value.len == 3 && ngx_strncmp(ua->value.data, "foo", 3) == 0) { return NGX_HTTP_FORBIDDEN; } return NGX_DECLINED; } static ngx_int_t ngx_http_foo_init(ngx_conf_t *cf) { ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers if (h == NULL) { return NGX_ERROR; } *h = ngx_http_foo_handler; return NGX_OK; }

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

  • NGX_OK - 继续下一个阶段。

对于某些阶段,返回代码的处理方式稍有不同。在内容阶段,任何NGX_DECLINED被认为是最终代码的返回代码。来自位置内容处理程序的任何返回代码都被视为终结代码。在接入阶段,在满足任何模式下,比其它任何返回代码NGX_OKNGX_DECLINEDNGX_AGAINNGX_DONE被认为是一个否定。如果后续访问处理程序不允许或拒绝使用其他代码访问,则拒绝代码将成为终止代码。

变量

访问现有变量

变量可以通过索引(这是最常用的方法)或名称(请参见下文)进行引用。索引在配置阶段创建,当一个变量添加到配置中时。要获得变量索引,请使用ngx_http_get_variable_index()

ngx_str_t name; /* ngx_string("foo") */ ngx_int_t index; index = ngx_http_get_variable_index(cf, &name

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

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

typedef ngx_variable_value_t ngx_http_variable_value_t; typedef struct { unsigned len:28; unsigned valid:1; unsigned no_cacheable:1; unsigned not_found:1; unsigned escape:1; u_char *data; } ngx_variable_value_t;

如下:

  • len - 值的长度

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)功能可用。它搜索一个给定的变量,namekey从该名称派生哈希。

创建变量

要创建一个变量,请使用该ngx_http_add_variable()函数。它以配置(变量被注册的地方),变量名称和控制函数行为的标志作为参数:

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

该函数在发生错误时返回NULL,否则返回ngx_http_variable_t的指针:

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

getset处理程序调用以获取或设置变量值,data传递给可变处理程序,以及index保存指派用于引用变量变量索引。

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

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

该示例中的函数用于初始化preconfigurationHTTP模块上下文的字段,并在解析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

  • cf - 配置指针

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

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

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

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

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

鉴于请求r和以前编译的值cv,函数将评估表达式并将结果写入res

请求重定向

一个HTTP请求总是通过ngx_http_request_t结构的loc_conf字段连接到一个位置。这意味着在任何时候都可以通过调用ngx_http_get_module_loc_conf(r,module)从请求中检索任何模块的位置配置。请求的位置可以在请求的生命周期中多次更改。最初,默认服务器的默认服务器位置被分配给请求。如果请求切换到不同的服务器(由HTTP“主机”头或SSL SNI扩展选择),请求也会切换到该服务器的默认位置。位置的下一次更改发生在NGX_HTTP_FIND_CONFIG_PHASE请求阶段。在此阶段,通过为服务器配置的所有非命名位置中的请求URI选择一个位置。由于rewrite指令,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 }

这两个函数- ngx_http_internal_redirect(r, uri, args)ngx_http_named_location(r, name)可以在nginx的模块已经存储某些情况下在请求的被称为ctx场。这些上下文可能会与新的位置配置不一致。为了防止不一致,所有请求上下文都被重定向功能擦除。

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

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

子请求

子请求主要用于将一个请求的输出插入另一个请求,可能与其他数据混合。一个子请求看起来像一个普通的请求,但与其父节点共享一些数据。特别是,与客户端输入相关的所有字段都是共享的,因为子请求不会从客户端接收任何其他输入。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和args是子请求的URI和参数,psr是输出参数,它接收 新创建的子请求引用,ps是一个回调对象,用于通知父请求子请求正在终结,标志是标志的位掩码。 以下标志可用:

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

以下示例使用/ foo的URI创建子请求。

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

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

请求正文

用于处理客户机请求的主体中,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_in_single_buf - 将主体读取到单个内存缓冲区。

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将在请求主体的下一部分可用时被调用。

响应

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

响应头

ngx_http_send_header(r)函数发送输出标题。 在r-> headers_out包含生成HTTP响应头所需的全部数据之前,请不要调用此函数。 必须始终设置r-> 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 */ ... }

标头过滤器

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

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

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

#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> static ngx_int_t ngx_http_foo_header_filter(ngx_http_request_t *r static ngx_int_t ngx_http_foo_header_filter_init(ngx_conf_t *cf static ngx_http_module_t ngx_http_foo_header_filter_module_ctx = { NULL, /* preconfiguration */ ngx_http_foo_header_filter_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; ngx_module_t ngx_http_foo_header_filter_module = { NGX_MODULE_V1, &ngx_http_foo_header_filter_module_ctx, /* module context */ NULL, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_int_t ngx_http_foo_header_filter(ngx_http_request_t *r) { ngx_table_elt_t *h; /* * The filter handler adds "X-Foo: foo" header * to every HTTP 200 response */ if (r->headers_out.status != NGX_HTTP_OK) { return ngx_http_next_header_filter(r } h = ngx_list_push(&r->headers_out.headers if (h == NULL) { return NGX_ERROR; } h->hash = 1; ngx_str_set(&h->key, "X-Foo" ngx_str_set(&h->value, "foo" return ngx_http_next_header_filter(r } static ngx_int_t ngx_http_foo_header_filter_init(ngx_conf_t *cf) { ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_foo_header_filter; return NGX_OK; }

响应体

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

以下示例生成一个完整的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变量中的第一个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; }

建立过滤器模块

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

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

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

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

缓冲区重用

发布或更改缓冲区流时,通常需要重新使用分配的缓冲区。在nginx代码中一个标准和广泛采用的方法是为此保留两个缓冲链:空闲和忙碌。自由链保留所有空闲缓冲区,可以重复使用。 busy链保留当前模块发送的所有缓冲区,这些缓冲区仍被其他一些过滤器处理程序使用。如果缓冲区的大小大于零,则认为正在使用缓冲区。通常,当过滤器消耗一个缓冲区时,其pos(或文件缓冲区的file_pos)将移至最后(文件缓冲区的file_last)。一旦缓冲区完全消耗完毕,就可以重新使用了。要将新释放的缓冲区添加到自由链中,它足以遍历繁忙的链,并将零大小的缓冲区移动到空闲位置。这个操作非常常见,以至于它有一个特殊的功能,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; }

负载均衡

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

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

  • srv_conf - 上游模块的配置上下文。

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

该结构具有以下字段:

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

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

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

这种方法也减少了tries计数器。

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