开发指南
介绍
代码布局
-
auto
— 构建脚本 -
src
-
core
— 基本类型和函数 — 字符串、数组、日志、池等。 -
event
— 事件核心-
modules
— 事件通知模块:epoll
、kqueue
等select
。
-
-
http
— 核心HTTP模块和通用代码-
modules
— 其他 HTTP 模块 -
v2
— HTTP/2
-
-
mail
— 邮件模块 -
os
— 特定于平台的代码-
unix
-
win32
-
-
stream
— 流模块
-
包含文件
以下两条#include
语句必须出现在每个 nginx 文件的开头:
#include <ngx_config.h> #include <ngx_core.h>
除此之外,HTTP 代码还应该包括
#include <ngx_http.h>
邮件代码应包括
#include <ngx_mail.h>
流代码应包括
#include <ngx_stream.h>
整数
出于一般目的,nginx 代码使用两种整数类型
ngx_int_t
和ngx_uint_t
,它们分别是intptr_t
和 的typedef uintptr_t
。
常见返回码
nginx 中的大多数函数都会返回以下代码:
-
NGX_OK
— 操作成功。 -
NGX_ERROR
- 手术失败。 -
NGX_AGAIN
——操作不完整;再次调用该函数。 -
NGX_DECLINED
— 操作被拒绝,例如,因为它在配置中被禁用。这绝不是一个错误。 -
NGX_BUSY
— 资源不可用。 -
NGX_DONE
— 操作已完成或在其他地方继续进行。也用作替代成功代码。 -
NGX_ABORT
— 功能被中止。也用作替代错误代码。
错误处理
该ngx_errno
宏返回最后一个系统错误代码。它映射到errno
POSIX 平台并
GetLastError()
在 Windows 中调用。该ngx_socket_errno
宏返回最后一个套接字错误号。与宏一样ngx_errno
,它映射到
errno
POSIX 平台。它映射到WSAGetLastError()
Windows 上的调用。连续访问ngx_errno
或
多次的值可能会导致性能问题。ngx_socket_errno
如果错误值可能会多次使用,请将其存储在 类型的局部变量中ngx_err_t
。要设置错误,请使用ngx_set_errno(errno)
和
ngx_set_socket_errno(errno)
宏。
ngx_errno
和
的值ngx_socket_errno
可以传递给日志记录函数
ngx_log_error()
和ngx_log_debugX()
,在这种情况下,系统错误文本将添加到日志消息中。
使用示例ngx_errno
:
ngx_int_t ngx_my_kill(ngx_pid_t pid, ngx_log_t *log, int signo) { ngx_err_t err; if (kill(pid, signo) == -1) { err = ngx_errno; ngx_log_error(NGX_LOG_ALERT, log, err, "kill(%P, %d) failed", pid, signo); if (err == NGX_ESRCH) { return 2; } return 1; } return 0; }
弦乐
概述
对于C字符串,nginx使用无符号字符类型指针
u_char *
。
nginx字符串类型ngx_str_t
定义如下:
typedef struct { size_t len; u_char *data; } ngx_str_t;
该len
字段保存字符串长度并
data
保存字符串数据。中保存的字符串在字节ngx_str_t
之后可能以空值终止,也可能不以空值终止len
。大多数情况下并非如此。但是,在代码的某些部分(例如,解析配置时),
ngx_str_t
对象以 null 结尾,这简化了字符串比较,并使将字符串传递给系统调用变得更容易。
nginx 中的字符串操作在其中声明,
src/core/ngx_string.h
其中一些是标准 C 函数的包装器:
-
ngx_strcmp()
-
ngx_strncmp()
-
ngx_strstr()
-
ngx_strlen()
-
ngx_strchr()
-
ngx_memcmp()
-
ngx_memset()
-
ngx_memcpy()
-
ngx_memmove()
其他字符串函数是 nginx 特定的
-
ngx_memzero()
— 用零填充内存。 -
ngx_explicit_memzero()
— 与 相同ngx_memzero()
,但编译器的死存储消除优化永远不会删除此调用。此功能可用于清除密码和密钥等敏感数据。 -
ngx_cpymem()
— 与 相同ngx_memcpy()
,但返回最终目标地址 这对于在一行中附加多个字符串非常方便。 -
ngx_movemem()
— 与 相同ngx_memmove()
,但返回最终目标地址。 -
ngx_strlchr()
— 搜索字符串中的字符,由两个指针分隔。
以下函数执行大小写转换和比较:
-
ngx_tolower()
-
ngx_toupper()
-
ngx_strlow()
-
ngx_strcasecmp()
-
ngx_strncasecmp()
以下宏简化了字符串初始化:
-
ngx_string(text)
ngx_str_t
— C 字符串文字类型的 静态初始值设定项text
-
ngx_null_string
ngx_str_t
—类型 的静态空字符串初始值设定项 -
ngx_str_set(str, text)
— 使用 C 字符串文字初始化 stringstr
类型ngx_str_t *
text
-
ngx_str_null(str)
—用空字符串 初始化 stringstr
类型ngx_str_t *
格式化
以下格式化函数支持 nginx 特定类型:
-
ngx_sprintf(buf, fmt, ...)
-
ngx_snprintf(buf, max, fmt, ...)
-
ngx_slprintf(buf, last, fmt, ...)
-
ngx_vslprintf(buf, last, fmt, args)
-
ngx_vsnprintf(buf, max, fmt, args)
这些函数支持的格式化选项的完整列表位于 中src/core/ngx_string.c
。他们之中有一些是:
-
%O
—off_t
-
%T
—time_t
-
%z
—ssize_t
-
%i
—ngx_int_t
-
%p
—void *
-
%V
—ngx_str_t *
-
%s
—u_char *
(空终止) -
%*s
—size_t + u_char *
您可以u
在大多数类型前面加上前缀以使其无符号。要将输出转换为十六进制,请使用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_atosz(line, n)
—ssize_t
-
ngx_atoof(line, n)
—off_t
-
ngx_atotm(line, n)
—time_t
还有两个附加的数字转换函数。与前四个一样,它们会返回NGX_ERROR
错误。
-
ngx_atofp(line, n, point)
— 将给定长度的定点浮点数转换为 类型的正整数ngx_int_t
。结果左移point
小数点位置。数字的字符串表示形式预计不超过points
小数位。例如,ngx_atofp("10.5", 4, 2)
返回1050
. -
ngx_hextoi(line, n)
— 将正整数的十六进制表示形式转换为ngx_int_t
.
常用表达
nginx 中的正则表达式接口是PCRE
库的包装器。对应的头文件是src/core/ngx_regex.h
.
要使用正则表达式进行字符串匹配,首先需要对其进行编译,这通常在配置阶段完成。请注意,由于 PCRE 支持是可选的,因此使用该接口的所有代码都必须受到周围NGX_PCRE
宏的保护:
#if (NGX_PCRE) ngx_regex_t *re; ngx_regex_compile_t rc; u_char errstr[NGX_MAX_CONF_ERRSTR]; ngx_str_t value = ngx_string("message (\d\d\d).*Codeword is '(?<cw>\w+)'"); ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); rc.pattern = value; rc.pool = cf->pool; rc.err.len = NGX_MAX_CONF_ERRSTR; rc.err.data = errstr; /* rc.options can be set to NGX_REGEX_CASELESS */ if (ngx_regex_compile(&rc) != NGX_OK) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err); return NGX_CONF_ERROR; } re = rc.regex; #endif
成功编译后,结构中的
captures
和
字段分别包含在正则表达式中找到的所有捕获和命名捕获的计数。
named_captures
ngx_regex_compile_t
然后,编译后的正则表达式可用于匹配字符串:
ngx_int_t n; int captures[(1 + rc.captures) * 3]; ngx_str_t input = ngx_string("This is message 123. Codeword is 'foobar'."); n = ngx_regex_exec(re, &input, captures, (1 + rc.captures) * 3); if (n >= 0) { /* string matches expression */ } else if (n == NGX_REGEX_NO_MATCHED) { /* no match was found */ } else { /* some error */ ngx_log_error(NGX_LOG_ALERT, log, 0, ngx_regex_exec_n " failed: %i", n); }
参数ngx_regex_exec()
是已编译的正则表达式re
、要匹配的字符串input
、用于保存captures
找到的任何整数的可选整数数组以及数组的size
. captures
根据
PCRE API的要求,数组的大小必须是三的倍数。在示例中,大小是根据捕获总数加上匹配字符串本身的 1 来计算的。
如果存在匹配,则可以按如下方式访问捕获:
u_char *p; size_t size; ngx_str_t name, value; /* all captures */ for (i = 0; i < n * 2; i += 2) { value.data = input.data + captures[i]; value.len = captures[i + 1] — captures[i]; } /* accessing named captures */ size = rc.name_size; p = rc.names; for (i = 0; i < rc.named_captures; i++, p += size) { /* capture name */ name.data = &p[2]; name.len = ngx_strlen(name.data); n = 2 * ((p[0] << 8) + p[1]); /* captured value */ value.data = &input.data[captures[n]]; value.len = captures[n + 1] — captures[n]; }
该ngx_regex_exec_array()
函数接受元素数组
ngx_regex_elt_t
(它们只是带有关联名称的已编译正则表达式)、要匹配的字符串和日志。该函数将数组中的表达式应用于字符串,直到找到匹配项或不再有表达式为止。返回值是NGX_OK
在匹配时返回,
NGX_DECLINED
否则返回,或者NGX_ERROR
在出现错误时返回。
时间
该ngx_time_t
结构用三种不同类型表示时间:秒、毫秒和 GMT 偏移量:
typedef struct { time_t sec; ngx_uint_t msec; ngx_int_t gmtoff; } ngx_time_t;
该结构是UNIX 平台和Windows 上的ngx_tm_t
别名
。
struct tm
SYSTEMTIME
要获取当前时间,通常访问可用的全局变量之一就足够了,该变量以所需的格式表示缓存的时间值。
可用的字符串表示形式有:
-
ngx_cached_err_log_time
— 用于错误日志条目:"1970/09/28 12:00:00"
-
ngx_cached_http_log_time
— 用于 HTTP 访问日志条目:"28/Sep/1970:12:00:00 +0600"
-
ngx_cached_syslog_time
— 用于系统日志条目:"Sep 28 12:00:00"
-
ngx_cached_http_time
— 用于 HTTP 标头:"Mon, 28 Sep 1970 06:00:00 GMT"
-
ngx_cached_http_log_iso8601
— ISO 8601 标准格式:"1970-09-28T12:00:00+06:00"
和宏返回当前时间值(以秒为单位),是访问缓存时间值的首选方法
ngx_time()
。ngx_timeofday()
要显式获取时间,请使用ngx_gettimeofday()
,它会更新其参数(指向 的指针
struct timeval
)。当 nginx 从系统调用返回到事件循环时,时间总是会更新。要立即更新时间,请调用ngx_time_update()
, 或ngx_time_sigsafe_update()
如果更新信号处理程序上下文中的时间。
以下函数转换time_t
为指示的分解时间表示。每对中的第一个函数转换time_t
为
ngx_tm_t
,第二个函数(带_libc_
中缀)转换为struct tm
:
-
ngx_gmtime(), ngx_libc_gmtime()
— 以 UTC 表示的时间 -
ngx_localtime(), ngx_libc_localtime()
— 相对于当地时区表示的时间
该ngx_http_time(buf, time)
函数返回适合在 HTTP 标头中使用的字符串表示形式(例如
"Mon, 28 Sep 1970 06:00:00 GMT"
)。返回ngx_http_cookie_time(buf, time)
字符串表示形式 函数返回适合 HTTP cookies ( "Thu, 31-Dec-37 23:55:55 GMT"
) 的字符串表示形式。
集装箱
大批
nginx数组类型ngx_array_t
定义如下
typedef struct { void *elts; ngx_uint_t nelts; size_t size; ngx_uint_t nalloc; ngx_pool_t *pool; } ngx_array_t;
数组的元素在elts
字段中可用。该nelts
字段保存元素的数量。该size
字段保存单个元素的大小,并在数组初始化时设置。
使用该ngx_array_create(pool, n, size)
调用在池中创建数组,并使用该ngx_array_init(array, pool, n, size)
调用初始化已分配的数组对象。
ngx_array_t *a, b; /* create an array of strings with preallocated memory for 10 elements */ a = ngx_array_create(pool, 10, sizeof(ngx_str_t)); /* initialize string array for 10 elements */ ngx_array_init(&b, pool, 10, sizeof(ngx_str_t));
使用以下函数将元素添加到数组中:
-
ngx_array_push(a)
添加一个尾部元素并返回指向它的指针 -
ngx_array_push_n(a, n)
添加n
尾部元素并返回指向第一个元素的指针
如果当前分配的内存量不足以容纳新元素,则会分配新的内存块,并将现有元素复制到其中。新的内存块通常是现有内存块的两倍。
s = ngx_array_push(a); ss = ngx_array_push_n(&b, 3);
列表
在 nginx 中,列表是一个数组序列,针对插入可能大量的项目进行了优化。列表ngx_list_t
类型定义如下:
typedef struct { ngx_list_part_t *last; ngx_list_part_t part; size_t size; ngx_uint_t nalloc; ngx_pool_t *pool; } ngx_list_t;
实际项目存储在列表部分中,其定义如下:
typedef struct ngx_list_part_s ngx_list_part_t; struct ngx_list_part_s { void *elts; ngx_uint_t nelts; ngx_list_part_t *next; };
在使用之前,必须通过调用初始化列表
ngx_list_init(list, pool, n, size)
或通过调用创建
列表ngx_list_create(pool, n, size)
。这两个函数都将单个项目的大小和每个列表部分的项目数量作为参数。要将项目添加到列表中,请使用该ngx_list_push(list)
函数。要迭代项目,请直接访问列表字段,如示例所示:
ngx_str_t *v; ngx_uint_t i; ngx_list_t *list; ngx_list_part_t *part; list = ngx_list_create(pool, 100, sizeof(ngx_str_t)); if (list == NULL) { /* error */ } /* add items to the list */ v = ngx_list_push(list); if (v == NULL) { /* error */ } ngx_str_set(v, "foo"); v = ngx_list_push(list); if (v == NULL) { /* error */ } ngx_str_set(v, "bar"); /* iterate over the list */ part = &list->part; v = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; v = part->elts; i = 0; } ngx_do_smth(&v[i]); }
列表主要用于 HTTP 输入和输出标头。
列表不支持项目删除。但是,当需要时,可以在内部将项目标记为缺失,而无需实际从列表中删除。例如,要将 HTTP 输出标头(存储为
ngx_table_elt_t
对象)标记为缺失,请将
hash
字段设置ngx_table_elt_t
为零。当标题被迭代时,以这种方式标记的项目将被显式跳过。
队列
在nginx中,队列是一个侵入式双向链表,每个节点定义如下:
typedef struct ngx_queue_s ngx_queue_t; struct ngx_queue_s { ngx_queue_t *prev; ngx_queue_t *next; };
头队列节点不与任何数据链接。使用前使用ngx_queue_init(q)
调用初始化列表头。队列支持以下操作:
-
ngx_queue_insert_head(h, x)
,ngx_queue_insert_tail(h, x)
— 插入一个新节点 -
ngx_queue_remove(x)
— 删除队列节点 -
ngx_queue_split(h, q, n)
— 在节点处拆分队列,将队列尾部返回到单独的队列中 -
ngx_queue_add(h, n)
— 将第二个队列添加到第一个队列 -
ngx_queue_head(h)
,ngx_queue_last(h)
— 获取第一个或最后一个队列节点 -
ngx_queue_sentinel(h)
- 获取一个队列哨兵对象以结束迭代 -
ngx_queue_data(q, type, link)
— 获取对队列节点数据结构开头的引用,考虑其中的队列字段偏移量
一个例子:
typedef struct { ngx_str_t value; ngx_queue_t queue; } ngx_foo_t; ngx_foo_t *f; ngx_queue_t values, *q; ngx_queue_init(&values); f = ngx_palloc(pool, sizeof(ngx_foo_t)); if (f == NULL) { /* error */ } ngx_str_set(&f->value, "foo"); ngx_queue_insert_tail(&values, &f->queue); /* insert more nodes here */ for (q = ngx_queue_head(&values); q != ngx_queue_sentinel(&values); q = ngx_queue_next(q)) { f = ngx_queue_data(q, ngx_foo_t, queue); ngx_do_smth(&f->value); }
红黑树
头src/core/ngx_rbtree.h
文件提供了对红黑树有效实现的访问。
typedef struct { ngx_rbtree_t rbtree; ngx_rbtree_node_t sentinel; /* custom per-tree data here */ } my_tree_t; typedef struct { ngx_rbtree_node_t rbnode; /* custom per-node data */ foo_t val; } my_node_t;
要处理整个树,您需要两个节点:根节点和哨兵节点。通常,它们被添加到自定义结构中,允许您将数据组织到树中,其中叶包含指向数据的链接或嵌入数据。
初始化一棵树:
my_tree_t root; ngx_rbtree_init(&root.rbtree, &root.sentinel, insert_value_function);
要遍历树并插入新值,请使用“ insert_value
”函数。例如,ngx_str_rbtree_insert_value
函数处理类型ngx_str_t
。它的参数是指向插入的根节点、要添加的新创建的节点和树哨兵的指针。
void ngx_str_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
遍历非常简单,可以使用以下查找函数模式进行演示:
my_node_t * my_rbtree_lookup(ngx_rbtree_t *rbtree, foo_t *val, uint32_t hash) { ngx_int_t rc; my_node_t *n; ngx_rbtree_node_t *node, *sentinel; node = rbtree->root; sentinel = rbtree->sentinel; while (node != sentinel) { n = (my_node_t *) node; if (hash != node->key) { node = (hash < node->key) ? node->left : node->right; continue; } rc = compare(val, node->val); if (rc < 0) { node = node->left; continue; } if (rc > 0) { node = node->right; continue; } return n; } return NULL; }
该compare()
函数是一个经典的比较器函数,返回小于、等于或大于零的值。为了加快查找速度并避免比较可能很大的用户对象,使用了整数哈希字段。
要将节点添加到树中,请分配一个新节点,对其进行初始化并调用
ngx_rbtree_insert()
:
my_node_t *my_node; ngx_rbtree_node_t *node; my_node = ngx_palloc(...); init_custom_data(&my_node->val); node = &my_node->rbnode; node->key = create_key(my_node->val); ngx_rbtree_insert(&root->rbtree, node);
要删除节点,请调用该ngx_rbtree_delete()
函数:
ngx_rbtree_delete(&root->rbtree, node);
哈希值
哈希表函数在src/core/ngx_hash.h
. 支持精确匹配和通配符匹配。后者需要额外的设置,并在下面的单独部分中进行描述。
在初始化哈希之前,您需要知道它将保存的元素数量,以便 nginx 能够以最佳方式构建它。需要配置的两个参数是max_size
和bucket_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_SMALL
或
NGX_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_head
和dns_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);
内存管理
堆
要从系统堆分配内存,请使用以下函数:
-
ngx_alloc(size, log)
— 从系统堆中分配内存。这是一个带有malloc()
日志记录支持的包装器。分配错误和调试信息记录到log
. -
ngx_calloc(size, log)
— 类似从系统堆分配内存ngx_alloc()
,但分配后用零填充内存。 -
ngx_memalign(alignment, size, log)
— 从系统堆中分配对齐的内存。posix_memalign()
这是提供该功能的平台的包装。否则,实现会回退到ngx_alloc()
提供最大对齐的位置。 -
ngx_free(p)
— 释放分配的内存。这是一个包装器free()
水池
大多数 nginx 分配都是在池中完成的。当池被销毁时,nginx 池中分配的内存会自动释放。这提供了良好的分配性能并使内存控制变得容易。
池在内部以连续的内存块分配对象。一旦一个块已满,就会分配一个新块并将其添加到池内存块列表中。当请求的分配太大而无法放入块时,请求将转发到系统分配器,并将返回的指针存储在池中以供进一步释放。
nginx 池的类型是ngx_pool_t
. 支持以下操作:
-
ngx_create_pool(size, log)
— 创建具有指定块大小的池。返回的池对象也在池中分配。应该size
至少是NGX_MIN_POOL_SIZE
的倍数NGX_POOL_ALIGNMENT
。 -
ngx_destroy_pool(pool)
— 释放所有池内存,包括池对象本身。 -
ngx_palloc(pool, size)
— 从指定池中分配对齐的内存。 -
ngx_pcalloc(pool, size)
— 从指定的池中分配对齐的内存并用零填充。 -
ngx_pnalloc(pool, size)
— 从指定池中分配未对齐的内存。主要用于分配字符串。 -
ngx_pfree(pool, p)
— 释放先前在指定池中分配的内存。只有转发到系统分配器的请求所产生的分配才能被释放。
u_char *p; ngx_str_t *s; ngx_pool_t *pool; pool = ngx_create_pool(1024, log); if (pool == NULL) { /* error */ } s = ngx_palloc(pool, sizeof(ngx_str_t)); if (s == NULL) { /* error */ } ngx_str_set(s, "foo"); p = ngx_pnalloc(pool, 3); if (p == NULL) { /* error */ } ngx_memcpy(p, "foo", 3);
链链接 ( ngx_chain_t
) 在 nginx 中被积极使用,因此 nginx 池实现提供了一种重用它们的方法。字段chain
保存ngx_pool_t
先前分配的链接列表以供重用。为了有效分配池中的链节,请使用该
ngx_alloc_chain_link(pool)
函数。该函数在池列表中查找空闲链链接,如果池列表为空,则分配新的链链接。要释放链接,请调用该ngx_free_chain(pool, cl)
函数。
清理处理程序可以注册在池中。清理处理程序是一个带有参数的回调,当池被销毁时调用。池通常与特定的 nginx 对象(如 HTTP 请求)绑定,并在该对象到达其生命周期结束时被销毁。注册池清理是释放资源、关闭文件描述符或对与主对象关联的共享数据进行最终调整的便捷方法。
要注册池清理,请调用
ngx_pool_cleanup_add(pool, size)
,它返回一个
ngx_pool_cleanup_t
由调用者填充的指针。使用size
参数为清理处理程序分配上下文。
ngx_pool_cleanup_t *cln; cln = ngx_pool_cleanup_add(pool, 0); if (cln == NULL) { /* error */ } cln->handler = ngx_my_cleanup; cln->data = "foo"; ... static void ngx_my_cleanup(void *data) { u_char *msg = data; ngx_do_smth(msg); }
共享内存
nginx 使用共享内存在进程之间共享公共数据。该ngx_shared_memory_add(cf, name, size, tag)
函数将新的共享内存条目添加ngx_shm_zone_t
到循环中。该函数接收
区域的name
和。size
每个共享区域必须有一个唯一的名称。如果提供的共享区域条目name
已经
tag
存在,则将重用现有区域条目。如果具有相同名称的现有条目具有不同的标签,则该函数将失败并出现错误。通常,模块结构的地址作为 传递
tag
,使得可以在一个 nginx 模块内按名称重用共享区域。
共享内存条目结构ngx_shm_zone_t
具有以下字段:
-
init
— 初始化回调,在共享区映射到实际内存后调用 -
data
— 数据上下文,用于将任意数据传递给init
回调 -
noreuse
— 禁用旧循环中共享区域重用的标志 -
tag
— 共享区域标签 -
shm
— 类型的特定于平台的对象ngx_shm_t
,至少具有以下字段:-
addr
— 映射的共享内存地址,最初为 NULL -
size
— 共享内存大小 -
name
— 共享内存名称 -
log
— 共享内存日志 -
exists
— 指示共享内存是从主进程继承的标志(Windows 特定)
-
ngx_init_cycle()
解析配置后,
共享区域条目将映射到实际内存
。在 POSIX 系统上,mmap()
系统调用用于创建共享匿名映射。在 Windows 上,使用
CreateFileMapping()
/
对。MapViewOfFileEx()
为了在共享内存中分配,nginx 提供了slab pool
ngx_slab_pool_t
类型。每个 nginx 共享区都会自动创建一个用于分配内存的slab池。该池位于共享区域的开头,可以通过表达式访问(ngx_slab_pool_t *) shm_zone->shm.addr
。要在共享区域中分配内存,请调用
ngx_slab_alloc(pool, size)
或
ngx_slab_calloc(pool, size)
。要释放内存,请调用ngx_slab_free(pool, p)
.
Slab池将所有共享区域划分为页面。每个页面用于分配相同大小的对象。指定的大小必须是 2 的幂,并且大于最小大小 8 字节。不合格值会向上舍入。每个页面的位掩码跟踪哪些块正在使用以及哪些块可以自由分配。对于大于半页(通常为 2048 字节)的大小,一次分配整个页
要保护共享内存中的数据免遭并发访问,请使用 字段中可用的互斥mutex
锁
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; }
记录
nginx 使用ngx_log_t
对象来记录日志。nginx 记录器支持多种类型的输出:
- stderr — 记录到标准错误 (stderr)
- file — 记录到文件
- syslog — 记录到系统日志
- 内存 - 出于开发目的记录到内部内存存储;稍后可以使用调试器访问内存
记录器实例可以是记录器链,通过next
字段相互链接。在这种情况下,每条消息都会写入链中的所有记录器。
对于每个记录器,严重性级别控制将哪些消息写入日志(仅记录分配该级别或更高级别的事件)。支持以下严重级别:
-
NGX_LOG_EMERG
-
NGX_LOG_ALERT
-
NGX_LOG_CRIT
-
NGX_LOG_ERR
-
NGX_LOG_WARN
-
NGX_LOG_NOTICE
-
NGX_LOG_INFO
-
NGX_LOG_DEBUG
对于调试日志记录,还会检查调试掩码。调试掩码是:
-
NGX_LOG_DEBUG_CORE
-
NGX_LOG_DEBUG_ALLOC
-
NGX_LOG_DEBUG_MUTEX
-
NGX_LOG_DEBUG_EVENT
-
NGX_LOG_DEBUG_HTTP
-
NGX_LOG_DEBUG_MAIL
-
NGX_LOG_DEBUG_STREAM
通常,记录器是由现有的 nginx 代码根据
error_log
指令创建的,并且在循环、配置、客户端连接和其他对象的几乎每个处理阶段都可用。
Nginx 提供了以下日志宏:
-
ngx_log_error(level, log, err, fmt, ...)
— 错误记录 -
ngx_log_debug0(level, log, err, fmt)
等ngx_log_debug1(level, log, err, fmt, arg1)
- 使用最多八个受支持的格式化参数进行调试日志记录
日志消息在堆栈上的大小(当前为 2048 字节)的缓冲区中进行格式化
NGX_MAX_ERROR_STR
。该消息前面带有严重性级别、进程 ID (PID)、连接 ID(存储在 中log->connection
)和系统错误文本。对于非调试消息log->handler
,也会调用以将更具体的信息添加到日志消息中。HTTP 模块设置ngx_http_log_error()
函数作为日志处理程序来记录客户端和服务器地址、当前操作(存储在
log->action
)、客户端请求行、服务器名称等。
/* specify what is currently done */ log->action = "sending mp4 to client"; /* error and debug log */ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client prematurely closed connection"); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 start:%ui, length:%ui", mp4->start, mp4->length);
上面的示例会生成如下日志条目:
2016/09/16 22:08:52 [info] 17445#0: *1 client prematurely closed connection while sending mp4 to client, client: 127.0.0.1, server: , request: "GET /file.mp4 HTTP/1.1" 2016/09/16 23:28:33 [debug] 22140#0: *1 mp4 start:0, length:10000
循环
循环对象存储从特定配置创建的 nginx 运行时上下文。它的类型是ngx_cycle_t
. 当前周期由全局变量引用ngx_cycle
,并由 nginx 工作线程在启动时继承。每次重新加载 nginx 配置时,都会从新的 nginx 配置创建一个新的循环;新循环创建成功后,旧循环通常会被删除。
该函数创建一个循环ngx_init_cycle()
,该函数将前一个循环作为其参数。该函数定位上一个周期的配置文件,并从上一个周期继承尽可能多的资源。一个名为“init Cycle”的占位符周期在 nginx 启动时创建,然后被根据配置构建的实际周期替换。
该周期的成员包括:
-
pool
— 自行车池。为每个新周期创建。 -
log
— 循环日志。new_log
最初继承自旧循环,被设置为读取配置后 指向 。 -
new_log
— 循环日志,由配置创建。它受 root-scopeerror_log
指令的影响。 -
connections
,connection_n
— 类型的连接数组ngx_connection_t
,由事件模块在初始化每个 nginx 工作线程时创建。nginx 配置中的指令worker_connections
设置连接数connection_n
。 -
free_connections
,free_connection_n
— 当前可用连接的列表和数量。如果没有可用的连接,nginx 工作线程将拒绝接受新客户端或连接到上游服务器。 -
files
,files_n
— 用于将文件描述符映射到 nginx 连接的数组。该映射由事件模块使用,具有标志NGX_USE_FD_EVENT
(当前为poll
和devpoll
)。 -
conf_ctx
— 核心模块配置数组。配置是在读取 nginx 配置文件期间创建和填充的。 -
modules
,modules_n
— 类型的模块数组ngx_module_t
,包括静态和动态,由当前配置加载。 -
listening
— 类型为 的监听对象数组ngx_listening_t
。listen
监听对象通常由调用该函数的不同模块的指令 添加ngx_create_listening()
。监听套接字是根据监听对象创建的。 -
paths
— 类型路径数组ngx_path_t
。ngx_add_path()
通过从将在某些目录上操作的模块中调用函数来添加路径。这些目录是 nginx 在读取配置后创建的(如果丢失)。此外,可以为每个路径添加两个处理程序:- path loader — 在启动或重新加载 nginx 后,每 60 秒仅执行一次。通常,加载器会读取目录并将数据存储在 nginx 共享内存中。该处理程序是从专用 nginx 进程“nginx 缓存加载器”调用的。
- 路径管理器 - 定期执行。通常,管理器会从目录中删除旧文件并更新 nginx 内存以反映更改。该处理程序是从专用的“nginx 缓存管理器”进程中调用的。
-
open_files
— 类型的打开文件对象列表ngx_open_file_t
,通过调用该函数创建ngx_conf_open_file()
。目前,nginx 使用这种打开文件进行日志记录。读取配置后,nginx 打开列表中的所有文件open_files
,并将每个文件描述符存储在对象的fd
字段中。文件以追加模式打开,如果丢失则创建。列表中的文件由 nginx 工作人员在收到重新打开信号后重新打开(最常见USR1
)。在这种情况下,字段中的描述符fd
将更改为新值。 -
shared_memory
— 共享内存区域列表,每个区域都是通过调用ngx_shared_memory_add()
函数添加的。共享区域映射到所有 nginx 进程中的相同地址范围,用于共享公共数据,例如内存中的 HTTP 缓存树。
缓冲
对于输入/输出操作,nginx提供了 buffer 类型
ngx_buf_t
。通常,它用于保存要写入目标或从源读取的数据。缓冲区可以引用内存或文件中的数据,并且缓冲区同时引用两者在技术上是可能的。缓冲区的内存是单独分配的,与缓冲区结构无关ngx_buf_t
。
该ngx_buf_t
结构体有以下字段:
-
start
,end
— 为缓冲区分配的内存块的边界。 -
pos
,last
——内存缓冲区的边界;通常是...的一个子start
范围end
-
file_pos
,file_last
— 文件缓冲区的边界,表示为距文件开头的偏移量。 -
tag
— 用于区分缓冲区的唯一值;由不同的 nginx 模块创建,通常是为了缓冲区重用的目的。 -
file
— 文件对象。 -
temporary
— 指示缓冲区引用可写内存的标志。 -
memory
— 指示缓冲区引用只读存储器的标志。 -
in_file
— 指示缓冲区引用文件中数据的标志。 -
flush
— 指示缓冲区之前的所有数据都需要刷新的标志。 -
recycled
— 指示缓冲区可以重用并且需要尽快消耗的标志。 -
sync
— 指示缓冲区不携带数据或特殊信号(如flush
或 )的标志last_buf
。默认情况下,nginx 认为此类缓冲区是错误条件,但此标志告诉 nginx 跳过错误检查。 -
last_buf
— 指示缓冲区是最后一个输出的标志。 -
last_in_chain
— 指示请求或子请求中不再有数据缓冲区的标志。 -
shadow
— 引用与当前缓冲区相关的另一个(“影子”)缓冲区,通常是指该缓冲区使用来自影子的数据。当缓冲区被消耗时,影子缓冲区通常也被标记为已消耗。 -
last_shadow
— 指示该缓冲区是最后一个引用特定影子缓冲区的标志。 -
temp_file
— 指示缓冲区位于临时文件中的标志。
对于输入和输出操作,缓冲区以链的形式链接。链是 类型的链节序列ngx_chain_t
,定义如下:
typedef struct ngx_chain_s ngx_chain_t; struct ngx_chain_s { ngx_buf_t *buf; ngx_chain_t *next; };
每个链节都保留对其缓冲区的引用和对下一个链节的引用。
使用缓冲区和链的示例:
ngx_chain_t * ngx_get_my_chain(ngx_pool_t *pool) { ngx_buf_t *b; ngx_chain_t *out, *cl, **ll; /* first buf */ cl = ngx_alloc_chain_link(pool); if (cl == NULL) { /* error */ } b = ngx_calloc_buf(pool); if (b == NULL) { /* error */ } b->start = (u_char *) "foo"; b->pos = b->start; b->end = b->start + 3; b->last = b->end; b->memory = 1; /* read-only memory */ cl->buf = b; out = cl; ll = &cl->next; /* second buf */ cl = ngx_alloc_chain_link(pool); if (cl == NULL) { /* error */ } b = ngx_create_temp_buf(pool, 3); if (b == NULL) { /* error */ } b->last = ngx_cpymem(b->last, "foo", 3); cl->buf = b; cl->next = NULL; *ll = cl; return out; }
联网
联系
连接类型ngx_connection_t
是套接字描述符的包装器。它包括以下字段:
-
fd
— 套接字描述符 -
data
— 任意连接上下文。通常,它是指向构建在连接之上的更高级别对象的指针,例如 HTTP 请求或 Stream 会话。 -
read
,write
— 连接的读写事件。 -
recv
,send
,recv_chain
,send_chain
— 连接的 I/O 操作。 -
pool
— 连接池。 -
log
— 连接日志。 -
sockaddr
,socklen
,addr_text
— 二进制和文本形式的远程套接字地址。 -
local_sockaddr
,local_socklen
— 二进制形式的本地套接字地址。最初,这些字段是空的。使用该ngx_connection_local_sockaddr()
函数获取本地套接字地址。 -
proxy_protocol_addr
,proxy_protocol_port
- PROXY 协议客户端地址和端口(如果为连接启用了 PROXY 协议)。 -
ssl
— 连接的 SSL 上下文。 -
reusable
— 指示连接处于可重用状态的标志。 -
close
— 指示连接正在被重用并且需要关闭的标志。
nginx 连接可以透明封装 SSL 层。在这种情况下,连接的ssl
字段保存一个指向
ngx_ssl_connection_t
结构的指针,保存连接的所有 SSL 相关数据,包括SSL_CTX
和
SSL
。recv
、send
、
recv_chain
和处理程序send_chain
也设置为启用 SSL 的函数。
nginx 配置中的指令worker_connections
限制每个 nginx 工作线程的连接数。所有连接结构都是在工作进程启动时预先创建的,并存储在connections
循环对象的字段中。要检索连接结构,请使用该
ngx_get_connection(s, log)
函数。它以s
套接字描述符作为参数,该描述符需要包装在连接结构中。
由于每个worker的连接数是有限的,nginx提供了一种获取当前正在使用的连接的方法。要启用或禁用连接的重用,请调用该
ngx_reusable_connection(c, reusable)
函数。调用在连接结构中ngx_reusable_connection(c, 1)
设置
reuse
标志并将连接插入到reusable_connections_queue
循环中。每当ngx_get_connection()
发现循环列表中没有可用连接时free_connections
,它就会ngx_drain_connections()
调用释放特定数量的可重用连接。对于每个这样的连接,close
都会设置标志并调用其读取处理程序,该处理程序应该通过调用来释放连接
ngx_close_connection(c)
并使其可供重用。当连接可以重用时退出状态
ngx_reusable_connection(c, 0)
被调用。HTTP 客户端连接是 nginx 中可重用连接的一个示例;它们被标记为可重用,直到从客户端收到第一个请求字节。
活动
事件
nginx 中的事件对象ngx_event_t
提供了一种通知特定事件已发生的机制。
中的字段ngx_event_t
包括:
-
data
— 事件处理程序中使用的任意事件上下文,通常作为指向与事件相关的连接的指针。 -
handler
— 事件发生时调用的回调函数。 -
write
— 指示写入事件的标志。缺少该标志表示读取事件。 -
active
— 指示事件已注册以接收 I/O 通知的标志,通常来自epoll
、kqueue
、等通知机制poll
。 -
ready
— 指示事件已收到 I/O 通知的标志。 -
delayed
— 指示 I/O 由于速率限制而延迟的标志。 -
timer
— 用于将事件插入定时器树的红黑树节点。 -
timer_set
— 指示事件计时器已设置且尚未到期的标志。 -
timedout
— 指示事件计时器已过期的标志。 -
eof
— 指示读取数据时发生 EOF 的标志。 -
pending_eof
— 指示 EOF 正在套接字上挂起的标志,即使在它之前可能有一些可用的数据。标志是通过EPOLLRDHUP
epoll
事件或EV_EOF
kqueue
标志传递的。 -
error
— 指示读取(对于读取事件)或写入(对于写入事件)期间发生错误的标志。 -
cancelable
— 计时器事件标志,指示关闭工作程序时应忽略该事件。正常工作进程关闭会延迟,直到没有安排不可取消的计时器事件为止。 -
posted
— 指示事件已发布到队列的标志。 -
queue
— 用于将事件发布到队列的队列节点。
输入/输出事件
通过调用该ngx_get_connection()
函数获得的每个连接都有两个附加事件c->read
和
c->write
,用于接收套接字已准备好读取或写入的通知。所有此类事件都在边缘触发模式下运行,这意味着它们仅在套接字状态更改时触发通知。例如,在套接字上执行部分读取不会使 nginx 发送重复读取通知,直到更多数据到达套接字为止。即使底层 I/O 通知机制本质上是级别触发的(poll
等select
),nginx 也会将通知转换为边缘触发。为了使 nginx 事件通知在不同平台上的所有通知系统中保持一致,必须在处理 I/O 套接字通知或调用该套接字上的任何 I/O 函数之后调用ngx_handle_read_event(rev, flags)
和
函数
。ngx_handle_write_event(wev, lowat)
通常,这些函数在每个读或写事件处理程序结束时调用一次。
定时器事件
可以设置事件以在超时到期时发送通知。事件使用的计时器从过去某个未指定的点被截断到ngx_msec_t
键入开始计算毫秒数。可以从变量中获取其当前值ngx_current_msec
。
该函数ngx_add_timer(ev, timer)
为事件设置超时,ngx_del_timer(ev)
删除先前设置的超时。全局超时红黑树ngx_event_timer_rbtree
存储当前设置的所有超时。树中的键是类型ngx_msec_t
,并且是事件发生的时间。树结构可以实现快速插入和删除操作,以及访问最近的超时,nginx 使用它来找出等待 I/O 事件和过期超时事件的时间。
发布的事件
可以发布事件,这意味着将在当前事件循环迭代中稍后的某个时刻调用其处理程序。发布事件是简化代码和避免堆栈溢出的好习惯。发布的事件保存在发布队列中。该ngx_post_event(ev, q)
宏将事件发布
ev
到发布队列q
。该ngx_delete_posted_event(ev)
宏将事件
ev
从当前发布的队列中删除。通常,事件会发布到队列ngx_posted_events
,该队列在事件循环的后期进行处理 — 在所有 I/O 和计时器事件都已处理之后。ngx_event_process_posted()
调用该函数来处理事件队列。它调用事件处理程序,直到队列不为空。这意味着发布的事件处理程序可以发布更多要在当前事件循环迭代中处理的事件。
一个例子:
void ngx_my_connection_read(ngx_connection_t *c) { ngx_event_t *rev; rev = c->read; ngx_add_timer(rev, 1000); rev->handler = ngx_my_read_handler; ngx_my_read(rev); } void ngx_my_read_handler(ngx_event_t *rev) { ssize_t n; ngx_connection_t *c; u_char buf[256]; if (rev->timedout) { /* timeout expired */ } c = rev->data; while (rev->ready) { n = c->recv(c, buf, sizeof(buf)); if (n == NGX_AGAIN) { break; } if (n == NGX_ERROR) { /* error */ } /* process buf */ } if (ngx_handle_read_event(rev, 0) != NGX_OK) { /* error */ } }
事件循环
除了 nginx 主进程之外,所有 nginx 进程都进行 I/O,因此都有一个事件循环。(nginx主进程反而将大部分时间花在调用
sigsuspend()
等待信号到达上。)nginx事件循环是在函数中实现的
ngx_process_events_and_timers()
,该函数会被重复调用,直到进程退出。
事件循环有以下阶段:
-
通过调用 查找最接近到期的超时
ngx_event_find_timer()
。该函数查找计时器树中最左边的节点,并返回该节点到期之前的毫秒数。 -
通过调用特定于事件通知机制的处理程序来处理 I/O 事件,由 nginx 配置选择。该处理程序至少等待一个 I/O 事件发生,但只等待下一次超时到期。当发生读或写事件时,
ready
将设置该标志并调用该事件的处理程序。对于 Linux,ngx_epoll_process_events()
通常使用处理程序,它调用epoll_wait()
等待 I/O 事件。 -
通过调用使计时器过期
ngx_event_expire_timers()
。计时器树从最左边的元素向右迭代,直到找到未到期的超时。对于每个过期节点timedout
,都会设置事件标志,timer_set
重置该标志,并调用事件处理程序 -
通过调用 处理发布的事件
ngx_event_process_posted()
。该函数重复从发布的事件队列中删除第一个元素并调用该元素的处理程序,直到队列为空。
所有 nginx 进程也处理信号。信号处理程序仅设置调用后检查的全局变量
ngx_process_events_and_timers()
。
流程
nginx中有多种类型的进程。进程的类型保存在ngx_process
全局变量中,并且是以下类型之一:
-
NGX_PROCESS_MASTER
— 主进程,读取 NGINX 配置、创建循环、启动和控制子进程。它不执行任何 I/O,仅响应信号。其循环函数为ngx_master_process_cycle()
。 -
NGX_PROCESS_WORKER
— 工作进程,处理客户端连接。它由主进程启动,并响应其信号和通道命令。其循环函数为ngx_worker_process_cycle()
。根据指令的配置,可以有多个工作进程worker_processes
。 -
NGX_PROCESS_SINGLE
— 单个进程,仅存在于master_process off
模式中,并且是在该模式下运行的唯一进程。它创建循环(像主进程一样)并处理客户端连接(像工作进程一样)。其循环函数为ngx_single_process_cycle()
。 -
NGX_PROCESS_HELPER
— 辅助进程,目前有两种类型:缓存管理器和缓存加载器。两者的循环函数都是ngx_cache_manager_process_cycle()
。
nginx 进程处理以下信号:
-
NGX_SHUTDOWN_SIGNAL
(SIGQUIT
在大多数系统上)— 正常关闭。主进程收到此信号后,向所有子进程发送关闭信号。当没有剩余子进程时,master 销毁循环池并退出。当工作进程收到此信号时,它会关闭所有侦听套接字并等待,直到没有调度的不可取消事件,然后销毁循环池并退出。当缓存管理器或缓存加载器进程收到此信号时,立即退出。当进程收到此信号时该ngx_quit
变量被设置,并在处理后立即重置。当工作进程处于关闭状态时,1
该ngx_exiting
变量被设置为。1
-
NGX_TERMINATE_SIGNAL
(SIGTERM
在大多数系统上)— 终止。主进程收到此信号后,向所有子进程发送终止信号。如果子进程在1秒内没有退出,主进程会发送信号SIGKILL
杀死它。当没有子进程剩余时,主进程销毁循环池并退出。当工作进程、缓存管理器进程或缓存加载器进程收到此信号时,它会销毁循环池并退出。该变量ngx_terminate
被设置为1
接收到该信号的时间。 -
NGX_NOACCEPT_SIGNAL
(SIGWINCH
在大多数系统上) - 关闭所有工作进程和帮助进程。收到此信号后,主进程将关闭其子进程。如果先前启动的新 nginx 二进制文件退出,则旧 master 的子进程将再次启动。当工作进程收到此信号时,它会以指令设置的调试模式关闭debug_points
。 -
NGX_RECONFIGURE_SIGNAL
(SIGHUP
在大多数系统上) - 重新配置。收到此信号后,主进程重新读取配置并基于它创建一个新周期。如果新的循环创建成功,则删除旧的循环并启动新的子进程。同时,老的子进程接收到NGX_SHUTDOWN_SIGNAL
信号。在单进程模式下,nginx 创建一个新循环,但保留旧循环,直到不再有与其绑定的活动连接的客户端为止。工作进程和辅助进程忽略此信号。 -
NGX_REOPEN_SIGNAL
(SIGUSR1
在大多数系统上)- 重新打开文件。主进程将此信号发送给工作进程,工作进程重新打开所有open_files
与循环相关的内容。 -
NGX_CHANGEBIN_SIGNAL
(SIGUSR2
在大多数系统上)- 更改 nginx 二进制文件。主进程启动一个新的 nginx 二进制文件并传入所有侦听套接字的列表。在环境变量中传递的文本格式列表“NGINX”
由用分号分隔的描述符编号组成。新的 nginx 二进制文件读取该“NGINX”
变量并将套接字添加到其初始化周期中。其他进程忽略该信号。
虽然所有 nginx 工作进程都能够接收并正确处理 POSIX 信号,但主进程不使用标准系统kill()
调用将信号传递给工作进程和帮助进程。相反,nginx 使用进程间套接字对,允许在所有 nginx 进程之间发送消息。然而,目前消息仅从主节点发送到其子节点。这些消息携带标准信号。
线程数
可以将任务卸载到单独的线程中,否则这些任务会阻塞 nginx 工作进程。例如,nginx 可以配置为使用线程来执行 文件 I/O。另一个用例是一个没有异步接口的库,因此不能正常与 nginx 一起使用。请记住,线程接口是处理客户端连接的现有异步方法的帮助者,绝不是为了替代。
pthreads
为了处理同步,可以使用
以下原语包装器
:
-
typedef pthread_mutex_t ngx_thread_mutex_t;
-
ngx_int_t ngx_thread_mutex_create(ngx_thread_mutex_t *mtx, ngx_log_t *log);
-
ngx_int_t ngx_thread_mutex_destroy(ngx_thread_mutex_t *mtx, ngx_log_t *log);
-
ngx_int_t ngx_thread_mutex_lock(ngx_thread_mutex_t *mtx, ngx_log_t *log);
-
ngx_int_t ngx_thread_mutex_unlock(ngx_thread_mutex_t *mtx, ngx_log_t *log);
-
-
typedef pthread_cond_t ngx_thread_cond_t;
-
ngx_int_t ngx_thread_cond_create(ngx_thread_cond_t *cond, ngx_log_t *log);
-
ngx_int_t ngx_thread_cond_destroy(ngx_thread_cond_t *cond, ngx_log_t *log);
-
ngx_int_t ngx_thread_cond_signal(ngx_thread_cond_t *cond, ngx_log_t *log);
-
ngx_int_t ngx_thread_cond_wait(ngx_thread_cond_t *cond, ngx_thread_mutex_t *mtx, ngx_log_t *log);
-
nginx 实现了thread_pool策略 ,而不是为每个任务创建一个新线程。可以为不同的目的配置多个线程池(例如,在不同的磁盘组上执行 I/O)。每个线程池在启动时创建,并包含处理任务队列的有限数量的线程。当任务完成时,将调用预定义的完成处理程序。
头文件src/core/ngx_thread_pool.h
包含相关定义:
struct ngx_thread_task_s { ngx_thread_task_t *next; ngx_uint_t id; void *ctx; void (*handler)(void *data, ngx_log_t *log); ngx_event_t event; }; typedef struct ngx_thread_pool_s ngx_thread_pool_t; ngx_thread_pool_t *ngx_thread_pool_add(ngx_conf_t *cf, ngx_str_t *name); ngx_thread_pool_t *ngx_thread_pool_get(ngx_cycle_t *cycle, ngx_str_t *name); ngx_thread_task_t *ngx_thread_task_alloc(ngx_pool_t *pool, size_t size); ngx_int_t ngx_thread_task_post(ngx_thread_pool_t *tp, ngx_thread_task_t *task);
在配置时,愿意使用线程的模块必须通过调用 来获取对线程池的引用
ngx_thread_pool_add(cf, name)
,这要么使用给定的名称创建一个新的线程池name
,要么返回对具有该名称的池的引用(如果该池已存在)。
要在运行时
将线程添加task
到指定线程池的队列中
,请使用该函数。要在线程中执行函数,请传递参数并使用以下结构设置完成处理程序:
tp
ngx_thread_task_post(tp, task)
ngx_thread_task_t
typedef struct { int foo; } my_thread_ctx_t; static void my_thread_func(void *data, ngx_log_t *log) { my_thread_ctx_t *ctx = data; /* this function is executed in a separate thread */ } static void my_thread_completion(ngx_event_t *ev) { my_thread_ctx_t *ctx = ev->data; /* executed in nginx event loop */ } ngx_int_t my_task_offload(my_conf_t *conf) { my_thread_ctx_t *ctx; ngx_thread_task_t *task; task = ngx_thread_task_alloc(conf->pool, sizeof(my_thread_ctx_t)); if (task == NULL) { return NGX_ERROR; } ctx = task->ctx; ctx->foo = 42; task->handler = my_thread_func; task->event.handler = my_thread_completion; task->event.data = ctx; if (ngx_thread_task_post(conf->thread_pool, task) != NGX_OK) { return NGX_ERROR; } return NGX_OK; }
模块
添加新模块
每个独立的 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
— 要构建的模块类型。可能的值为CORE
、HTTP
、HTTP_FILTER
、HTTP_INIT_FILTER
、HTTP_AUX_FILTER
、MAIL
、STREAM
、 或MISC
。 -
ngx_module_name
— 模块名称。要从一组源文件构建多个模块,请指定以空格分隔的名称列表。第一个名称表示动态模块的输出二进制文件的名称。列表中的名称必须与源代码中使用的名称匹配。 -
ngx_addon_name
— 配置脚本控制台输出中显示的模块名称。 -
ngx_module_srcs
— 用于编译模块的以空格分隔的源文件列表。该$ngx_addon_dir
变量可用于表示模块目录的路径。 -
ngx_module_incs
— 包括构建模块所需的路径 -
ngx_module_deps
— 以空格分隔的模块依赖项列表。通常,它是头文件列表。 -
ngx_module_libs
— 与模块链接的以空格分隔的库列表。例如,用于ngx_module_libs=-lpthread
链接libpthread
库。以下宏可用于链接与 nginx 相同的库:LIBXSLT
、LIBGD
、GEOIP
、PCRE
、OPENSSL
、MD5
、SHA1
、ZLIB
和PERL
。 -
ngx_module_link
— 由构建系统为DYNAMIC
动态模块或ADDON
静态模块设置的变量,用于根据链接类型确定要执行的不同操作。 -
ngx_module_order
— 模块的加载顺序;对于HTTP_FILTER
和HTTP_AUX_FILTER
模块类型很有用。此选项的格式是空格分隔的模块列表。列表中当前模块名称后面的所有模块都在全局模块列表中结束,该列表设置模块初始化的顺序。对于过滤器模块来说,稍后初始化意味着更早执行。以下模块通常用作参考。它
ngx_http_copy_filter_module
读取其他过滤器模块的数据,并将其放置在列表底部附近,以便它是最先执行的模块之一。将ngx_http_write_filter_module
数据写入客户端套接字并放置在列表顶部附近,并且是最后执行的。默认情况下,过滤器模块放置
ngx_http_copy_filter
在模块列表之前,以便过滤器处理程序在复制过滤器处理程序之后执行。对于其他模块类型,默认值为空字符串。
要将模块静态编译到 nginx,请使用
--add-module=/path/to/module
配置脚本的参数。要编译模块以便稍后动态加载到 nginx 中,请使用该
--add-dynamic-module=/path/to/module
参数。
核心模块
模块是 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 生命周期的某些阶段调用。模块生命周期由以下事件组成:
- 配置指令处理程序在主进程上下文中的配置文件中出现时被调用。
-
配置解析成功后,
init_module
handler会在master进程的上下文中被调用。init_module
每次加载配置时,都会在主进程中调用该处理程序 。 -
主进程创建一个或多个工作进程,并
init_process
在每个进程中调用处理程序。 -
当工作进程收到来自主进程的关闭或终止命令时,它会调用
exit_process
处理程序。 -
主进程
exit_master
在退出之前调用处理程序。
因为线程在 nginx 中仅用作具有自己的 API 的补充 I/O 设施,init_thread
并且exit_thread
当前不调用处理程序。也没有init_master
处理程序,因为这会产生不必要的开销。
该模块type
准确定义了字段中存储的内容
ctx
。它的值是以下类型之一:
NGX_CORE_MODULE
NGX_EVENT_MODULE
NGX_HTTP_MODULE
NGX_MAIL_MODULE
NGX_STREAM_MODULE
这NGX_CORE_MODULE
是最基本的,因此也是最通用和最低级别的模块类型。其他模块类型在其之上实现,并提供更方便的方式来处理相应的域,例如处理事件或 HTTP 请求。
核心模块集包括ngx_core_module
、
ngx_errlog_module
、ngx_regex_module
和
ngx_thread_pool_module
模块
ngx_openssl_module
。HTTP模块、流模块、邮件模块和事件模块也是核心模块。核心模块的上下文定义为:
typedef struct { ngx_str_t name; void *(*create_conf)(ngx_cycle_t *cycle); char *(*init_conf)(ngx_cycle_t *cycle, void *conf); } ngx_core_module_t;
其中name
是模块名称字符串,
create_conf
是init_conf
分别指向创建和初始化模块配置的函数的指针。对于核心模块,nginxcreate_conf
在解析新配置之前以及init_conf
所有配置解析成功之后调用。典型的create_conf
函数为配置分配内存并设置默认值。
例如,一个名为的简单模块ngx_foo_module
可能如下所示:
/* * Copyright (C) Author. */ #include <ngx_config.h> #include <ngx_core.h> typedef struct { ngx_flag_t enable; } ngx_foo_conf_t; static void *ngx_foo_create_conf(ngx_cycle_t *cycle); static char *ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf); static char *ngx_foo_enable(ngx_conf_t *cf, void *post, void *data); static ngx_conf_post_t ngx_foo_enable_post = { ngx_foo_enable }; static ngx_command_t ngx_foo_commands[] = { { ngx_string("foo_enabled"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, 0, offsetof(ngx_foo_conf_t, enable), &ngx_foo_enable_post }, ngx_null_command }; static ngx_core_module_t ngx_foo_module_ctx = { ngx_string("foo"), ngx_foo_create_conf, ngx_foo_init_conf }; ngx_module_t ngx_foo_module = { NGX_MODULE_V1, &ngx_foo_module_ctx, /* module context */ ngx_foo_commands, /* module directives */ NGX_CORE_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static void * ngx_foo_create_conf(ngx_cycle_t *cycle) { ngx_foo_conf_t *fcf; fcf = ngx_pcalloc(cycle->pool, sizeof(ngx_foo_conf_t)); if (fcf == NULL) { return NULL; } fcf->enable = NGX_CONF_UNSET; return fcf; } static char * ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf) { ngx_foo_conf_t *fcf = conf; ngx_conf_init_value(fcf->enable, 0); return NGX_CONF_OK; } static char * ngx_foo_enable(ngx_conf_t *cf, void *post, void *data) { ngx_flag_t *fp = data; if (*fp == 0) { return NGX_CONF_OK; } ngx_log_error(NGX_LOG_NOTICE, cf->log, 0, "Foo Module is enabled"); return NGX_CONF_OK; }
配置指令
该ngx_command_t
类型定义单个配置指令。每个支持配置的模块都提供了一个此类结构的数组,这些结构描述了如何处理参数以及要调用哪些处理程序:
typedef struct ngx_command_s ngx_command_t; struct ngx_command_s { ngx_str_t name; ngx_uint_t type; char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); ngx_uint_t conf; ngx_uint_t offset; void *post; };
使用特殊值终止数组ngx_null_command
。这name
是配置文件中出现的指令名称,例如“worker_processes”或“listen”。是type
一个标志位字段,指定指令采用的参数数量、其类型及其出现的上下文。标志是:
-
NGX_CONF_NOARGS
— 指令不带任何参数。 -
NGX_CONF_1MORE
— 指令采用一个或多个参数。 -
NGX_CONF_2MORE
— 指令需要两个或多个参数。 -
NGX_CONF_TAKE1
..NGX_CONF_TAKE7
— 指令恰好采用指定数量的参数。 -
NGX_CONF_TAKE12
,NGX_CONF_TAKE13
,NGX_CONF_TAKE23
,NGX_CONF_TAKE123
,NGX_CONF_TAKE1234
— 指令可以采用不同数量的参数。选项仅限于给定的数字。例如,NGX_CONF_TAKE12
意味着它需要一个或两个参数。
指令类型的标志是:
-
NGX_CONF_BLOCK
— 指令是一个块,也就是说,它可以在其左大括号和右大括号内包含其他指令,甚至可以实现自己的解析器来处理内部内容。 -
NGX_CONF_FLAG
— 指令采用布尔值,或者on
或off
。
指令的上下文定义了它可能出现在配置中的位置:
-
NGX_MAIN_CONF
— 在顶层环境中。 -
NGX_HTTP_MAIN_CONF
——在http
街区里。 -
NGX_HTTP_SRV_CONF
— 在区块server
内的区块中http
。 -
NGX_HTTP_LOC_CONF
— 在区块location
内的区块中http
。 -
NGX_HTTP_UPS_CONF
— 在区块upstream
内的区块中http
。 -
NGX_HTTP_SIF_CONF
— 在块if
中的块server
中的块中http
。 -
NGX_HTTP_LIF_CONF
— 在块if
中的块location
中的块中http
。 -
NGX_HTTP_LMT_CONF
— 在区块limit_except
内的区块中http
。 -
NGX_STREAM_MAIN_CONF
——在stream
街区里。 -
NGX_STREAM_SRV_CONF
— 在区块server
内的区块中stream
。 -
NGX_STREAM_UPS_CONF
— 在区块upstream
内的区块中stream
。 -
NGX_MAIL_MAIN_CONF
——在mail
街区里。 -
NGX_MAIL_SRV_CONF
— 在区块server
内的区块中mail
。 -
NGX_EVENT_CONF
——在event
街区里。 -
NGX_DIRECT_CONF
— 由不创建上下文层次结构且仅具有一个全局配置的模块使用。此配置作为参数传递给处理程序conf
。
配置解析器使用这些标志在指令放错位置时抛出错误,并调用提供正确配置指针的指令处理程序,以便不同位置的相同指令可以将其值存储在不同的位置。
该set
字段定义一个处理程序,用于处理指令并将解析后的值存储到相应的配置中。有许多执行常见转换的函数:
-
ngx_conf_set_flag_slot
— 将文字字符串on
和分别转换off
为ngx_flag_t
值为 1 或 0 的值。 -
ngx_conf_set_str_slot
— 将字符串存储为该ngx_str_t
类型的值。 -
ngx_conf_set_str_array_slot
— 将一个值附加到ngx_array_t
字符串数组中ngx_str_t
。如果该数组尚不存在,则创建该数组。 -
ngx_conf_set_keyval_slot
— 将键值对追加到ngx_array_t
键值对数组中ngx_keyval_t
。第一个字符串成为键,第二个字符串成为值。如果数组尚不存在,则创建该数组。 -
ngx_conf_set_num_slot
— 将指令的参数转换为值ngx_int_t
。 -
ngx_conf_set_size_slot
— 将 大小转换为size_t
以字节表示的值。 -
ngx_conf_set_off_slot
— 将 偏移量转换为off_t
以字节表示的值。 -
ngx_conf_set_msec_slot
— 将 时间转换为ngx_msec_t
以毫秒表示的值。 -
ngx_conf_set_sec_slot
— 将 时间转换为time_t
以秒为单位的值。 -
ngx_conf_set_bufs_slot
— 将提供的两个参数转换为ngx_bufs_t
保存缓冲区数量和 大小的对象。 -
ngx_conf_set_enum_slot
— 将提供的参数转换为ngx_uint_t
值。ngx_conf_enum_t
字段中传递的 以 null 结尾的数组post
定义了可接受的字符串和相应的整数值。 -
ngx_conf_set_bitmask_slot
— 将提供的参数转换为ngx_uint_t
值。对每个参数的掩码值进行“或”运算以产生结果。ngx_conf_bitmask_t
字段中传递的 以 null 结尾的数组post
定义可接受的字符串和相应的掩码值。 -
set_path_slot
— 将提供的参数转换为ngx_path_t
值并执行所有必需的初始化。 有关详细信息,请参阅proxy_temp_path指令的文档 。 -
set_access_slot
— 将提供的参数转换为文件权限掩码。 有关详细信息,请参阅proxy_store_access指令的文档 。
该conf
字段定义将哪个配置结构传递给目录处理程序。核心模块只有全局配置并设置
NGX_DIRECT_CONF
标志来访问它。HTTP、Stream 或 Mail 等模块创建配置层次结构。例如,模块的配置是为server
、
location
和if
范围创建的。
-
NGX_HTTP_MAIN_CONF_OFFSET
— 块的配置http
。 -
NGX_HTTP_SRV_CONF_OFFSET
— 块server
内块的配置http
。 -
NGX_HTTP_LOC_CONF_OFFSET
—location
中块的配置http
。 -
NGX_STREAM_MAIN_CONF_OFFSET
— 块的配置stream
。 -
NGX_STREAM_SRV_CONF_OFFSET
— 块server
内块的配置stream
。 -
NGX_MAIL_MAIN_CONF_OFFSET
— 块的配置mail
。 -
NGX_MAIL_SRV_CONF_OFFSET
— 块server
内块的配置mail
。
定义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_connection_t
在此阶段创建一个新对象来包装新接受的客户端套接字。每个 nginx 监听器都提供一个处理程序来传递新的连接对象。对于 HTTP 连接,它是ngx_http_init_connection(c)
. -
ngx_http_init_connection()
执行 HTTP 连接的早期初始化。在此阶段,ngx_http_connection_t
将为连接创建一个对象,并将其引用存储在连接的data
字段中。稍后它将被 HTTP 请求对象取代。PROXY 协议解析器和 SSL 握手也在此阶段启动。 -
ngx_http_wait_request_handler()
当客户端套接字上有数据可用时,将调用 read 事件处理程序。在此阶段,将创建 HTTP 请求对象ngx_http_request_t
并将其设置到连接的data
字段中。 -
ngx_http_process_request_line()
read 事件处理程序读取客户端请求行。处理程序由设置ngx_http_wait_request_handler()
。数据被读入连接的buffer
. 缓冲区的大小最初由指令 client_header_buffer_size设置。整个客户端标头应该适合缓冲区。如果初始大小不够,则会分配更大的缓冲区,容量由指令设置large_client_header_buffers
。 -
ngx_http_process_request_headers()
ngx_http_process_request_line()
read 事件处理程序,在读取客户端请求标头 后设置。 -
ngx_http_core_run_phases()
当请求头被完全读取和解析时调用。此函数运行从NGX_HTTP_POST_READ_PHASE
到 的 请求阶段NGX_HTTP_CONTENT_PHASE
。最后一个阶段旨在生成响应并将其沿着过滤器链传递。在此阶段,响应不一定发送到客户端。它可能会保持缓冲状态并在最终确定阶段发送。 -
ngx_http_finalize_request()
通常在请求生成所有输出或产生错误时调用。在后一种情况下,会查找适当的错误页面并将其用作响应。如果此时响应尚未完全发送到客户端,ngx_http_writer()
则会激活 HTTP 编写器以完成发送未完成的数据。 -
ngx_http_finalize_connection()
当完整的响应已发送到客户端并且请求可以被销毁时调用。如果启用了客户端连接保活功能,ngx_http_set_keepalive()
则会调用 ,这会销毁当前请求并等待连接上的下一个请求。否则,ngx_http_close_request()
将破坏请求和连接。
要求
对于每个客户端 HTTP 请求,ngx_http_request_t
都会创建该对象。该对象的一些字段是:
-
connection
— 指向ngx_connection_t
客户端连接对象的指针。多个请求可以同时引用同一个连接对象 - 一个主请求及其子请求。删除请求后,可以在同一连接上创建新请求。请注意,对于 HTTP 连接
ngx_connection_t
,字段data
指向请求。与与连接相关的其他请求相反,此类请求称为活动请求。主动请求用于处理客户端连接事件,并允许将其响应输出到客户端。通常,每个请求都会在某个时刻变为活动状态,以便可以发送其输出。 -
ctx
— HTTP 模块上下文数组。每个类型的模块都NGX_HTTP_MODULE
可以在请求中存储任何值(通常是指向结构的指针)。该值存储在ctx
模块ctx_index
位置的数组中。以下宏提供了一种获取和设置请求上下文的便捷方法:-
ngx_http_get_module_ctx(r, module)
— 返回 的module
上下文 -
ngx_http_set_ctx(r, c, module)
— 设置c
为 的module
上下文
-
-
main_conf
,srv_conf
,loc_conf
— 当前请求配置的数组。配置存储在模块的ctx_index
位置。 -
read_event_handler
,write_event_handler
- 读取和写入请求的事件处理程序。通常,HTTP 连接的读取和写入事件处理程序都设置为ngx_http_request_handler()
。该函数调用当前活动请求的read_event_handler
和 处理程序。write_event_handler
-
cache
— 用于缓存上游响应的请求缓存对象。 -
upstream
— 请求上游对象进行代理。 -
pool
— 请求池。请求对象本身分配在这个池中,当请求被删除时,该池也被销毁。对于需要在整个客户端连接生命周期内可用的分配,请改用ngx_connection_t
's pool。 -
header_in
— 读取客户端 HTTP 请求标头的缓冲区。 -
headers_in
,headers_out
— 输入和输出 HTTP 标头对象。这两个对象都包含用于保存原始标头列表的headers
类型字段 。ngx_list_t
除此之外,特定标头可用于获取和设置为单独的字段,例如等content_length_n
。status
-
request_body
— 客户端请求主体对象。 -
start_sec
,start_msec
— 请求创建的时间点,用于跟踪请求持续时间。 -
method
,method_name
— 客户端 HTTP 请求方法的数字和文本表示。方法的数值是用src/http/ngx_http_request.h
宏NGX_HTTP_GET
、NGX_HTTP_HEAD
、NGX_HTTP_POST
等 定义的。 -
http_protocol
— 原始文本形式的客户端 HTTP 协议版本(“HTTP/1.0”、“HTTP/1.1”等)。 -
http_version
— 数字形式的客户端 HTTP 协议版本(NGX_HTTP_VERSION_10
、NGX_HTTP_VERSION_11
等)。 -
http_major
,http_minor
— 数字形式的客户端 HTTP 协议版本,分为主要部分和次要部分。 -
request_line
,unparsed_uri
— 原始客户端请求中的请求行和 URI。 -
uri
,args
,exten
— 当前请求的 URI、参数和文件扩展名。由于标准化,此处的 URI 值可能与客户端发送的原始 URI 不同。在整个请求处理过程中,这些值可能会随着内部重定向的执行而发生变化。 -
main
— 指向主请求对象的指针。创建此对象是为了处理客户端 HTTP 请求,而不是创建子请求来执行主请求中的特定子任务。 -
parent
— 指向子请求的父请求的指针。 -
postponed
— 输出缓冲区和子请求的列表,按照发送和创建的顺序排列。当子请求创建部分列表时,推迟过滤器使用该列表来提供一致的请求输出。 -
post_subrequest
— 指向处理程序的指针,其中包含子请求完成时要调用的上下文。未用于主要请求。 -
posted_requests
— 要启动或恢复的请求列表,这是通过调用请求的write_event_handler
. 通常,该处理程序包含请求主函数,该函数首先运行请求阶段,然后生成输出。请求通常是通过
ngx_http_post_request(r, NULL)
调用发出的。它始终发布到主请求posted_requests
列表中。该函数ngx_http_run_posted_requests(c)
运行在传递的连接的活动请求的主请求中发布的所有请求。所有事件处理程序都会调用ngx_http_run_posted_requests
,这可能会导致新的发布请求。通常,它在调用请求的读或写处理程序后被调用。 -
phase_handler
— 当前请求阶段的索引。 -
ncaptures
,captures
,captures_data
— 正则表达式捕获由请求的最后一个正则表达式匹配生成的内容。在请求处理期间,正则表达式匹配可能发生在多个位置:地图查找、SNI 或 HTTP 主机的服务器查找、重写、proxy_redirect 等。查找生成的捕获存储在上述字段中。该字段ncaptures
保存捕获的数量,captures
保存捕获边界并captures_data
保存正则表达式匹配的字符串以及用于提取捕获的字符串。每次新的正则表达式匹配后,请求捕获都会重置以保存新值。 -
count
— 请求参考计数器。该字段仅对主要请求有意义。增加计数器是通过简单的r->main->count++
. 要减少计数器,请致电ngx_http_finalize_request(r, rc)
。创建子请求和运行请求正文读取过程都会增加计数器。 -
subrequests
— 当前子请求嵌套级别。每个子请求都会继承其父级的嵌套级别,并减少一级。如果该值达到零,则会生成错误。主请求的值由常量定义NGX_HTTP_MAX_SUBREQUESTS
。 -
uri_changes
— 请求剩余的 URI 更改数量。请求可以更改其 URI 的总次数受该常数限制NGX_HTTP_MAX_URI_CHANGES
。每次更改时,该值都会递减,直到达到零,此时会生成错误。重写和内部重定向到正常或命名位置被视为 URI 更改。 -
blocked
— 根据请求持有的块的计数器。当该值非零时,请求无法终止。目前,该值因挂起的 AIO 操作(POSIX AIO 和线程操作)和活动缓存锁而增加。 -
buffered
— 显示哪些模块已缓冲请求生成的输出的位掩码。多个过滤器可以缓冲输出;例如,sub_filter 可以因为部分字符串匹配而缓冲数据,复制过滤器可以因为缺少空闲输出缓冲区而缓冲数据等。只要该值非零,请求就不会最终确定,等待刷新。 -
header_only
— 指示输出不需要正文的标志。例如,此标志由 HTTP HEAD 请求使用。 -
keepalive
— 指示是否支持客户端连接保活的标志。该值是根据 HTTP 版本和“Connection”标头的值推断出来的。 -
header_sent
— 指示输出标头已由请求发送的标志。 -
internal
— 指示当前请求是内部请求的标志。要进入内部状态,请求必须通过内部重定向或者是子请求。允许内部请求进入内部位置。 -
allow_ranges
— 指示可以按照 HTTP Range 标头的请求将部分响应发送到客户端的标志。 -
subrequest_ranges
— 指示在处理子请求时可以发送部分响应的标志。 -
single_range
— 标志表明只能将单个连续范围的输出数据发送到客户端。该标志通常在发送数据流时设置,例如从代理服务器发送数据流,并且整个响应在一个缓冲区中不可用。 -
main_filter_need_in_memory
,filter_need_in_memory
— 请求在内存缓冲区而不是文件中生成输出的标志。这是向复制过滤器发出的信号,即使启用了 sendfile,也要从文件缓冲区读取数据。这两个标志之间的区别在于设置它们的过滤器模块的位置。在过滤器链 set 中的推迟过滤器之前调用的过滤器filter_need_in_memory
,请求仅当前请求输出进入内存缓冲区。稍后在过滤器链 set 中调用的过滤器main_filter_need_in_memory
,请求主请求和所有子请求在发送输出时读取内存中的文件。 -
filter_need_temporary
— 请求在临时缓冲区中生成请求输出但不在只读存储器缓冲区或文件缓冲区中生成的标志。这由过滤器使用,过滤器可以直接更改发送的缓冲区中的输出。
配置
每个 HTTP 模块可以具有三种类型的配置:
-
主要配置 - 适用于整个
http
块。用作模块的全局设置。 -
服务器配置 — 适用于单个
server
块。用作模块的服务器特定设置。 -
位置配置 — 适用于单个
location
,if
或limit_except
块。用作模块的位置特定设置。
配置结构是在 nginx 配置阶段通过调用函数创建的,这些函数分配结构、初始化它们并合并它们。以下示例显示如何为模块创建简单的位置配置。foo
该配置有一项无符号整数类型的
设置。
typedef struct { ngx_uint_t foo; } ngx_http_foo_loc_conf_t; static ngx_http_module_t ngx_http_foo_module_ctx = { NULL, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_foo_create_loc_conf, /* create location configuration */ ngx_http_foo_merge_loc_conf /* merge location configuration */ }; static void * ngx_http_foo_create_loc_conf(ngx_conf_t *cf) { ngx_http_foo_loc_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_foo_loc_conf_t)); if (conf == NULL) { return NULL; } conf->foo = NGX_CONF_UNSET_UINT; return conf; } static char * ngx_http_foo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_foo_loc_conf_t *prev = parent; ngx_http_foo_loc_conf_t *conf = child; ngx_conf_merge_uint_value(conf->foo, prev->foo, 1); }
如示例所示,该ngx_http_foo_create_loc_conf()
函数创建一个新的配置结构,并将
ngx_http_foo_merge_loc_conf()
配置与更高级别的配置合并。事实上,服务器和位置配置不仅仅存在于服务器和位置级别,还为它们之上的所有级别创建。具体来说,还在主级别创建服务器配置,并在主级别、服务器级别和位置级别创建位置配置。这些配置使得可以在 nginx 配置文件的任何级别指定特定于服务器和位置的设置。最终配置被合并。提供了许多宏,例如NGX_CONF_UNSET
和 ,
NGX_CONF_UNSET_UINT
用于指示丢失的设置并在合并时忽略它。标准 nginx 合并宏如ngx_conf_merge_value()
和
ngx_conf_merge_uint_value()
提供了一种方便的方法来合并设置并在没有配置提供显式值的情况下设置默认值。有关不同类型的宏的完整列表,请参阅
src/core/ngx_conf_file.h
。
以下宏可用。用于在配置时访问 HTTP 模块的配置。它们都以ngx_conf_t
引用作为第一个参数。
-
ngx_http_conf_get_module_main_conf(cf, module)
-
ngx_http_conf_get_module_srv_conf(cf, module)
-
ngx_http_conf_get_module_loc_conf(cf, module)
以下示例获取指向标准 nginx 核心模块ngx_http_core_module
的位置配置的指针
,并替换handler
结构体字段
中保存的位置内容处理程序。
static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r); static ngx_command_t ngx_http_foo_commands[] = { { ngx_string("foo"), NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, ngx_http_foo, 0, 0, NULL }, ngx_null_command }; static char * ngx_http_foo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_core_loc_conf_t *clcf; clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); clcf->handler = ngx_http_bar_handler; return NGX_CONF_OK; }
以下宏可用于在运行时访问 HTTP 模块的配置。
-
ngx_http_get_module_main_conf(r, module)
-
ngx_http_get_module_srv_conf(r, module)
-
ngx_http_get_module_loc_conf(r, module)
这些宏接收对 HTTP 请求的引用
ngx_http_request_t
。请求的主要配置永远不会改变。选择请求的虚拟服务器后,服务器配置可以更改为默认值。由于重写操作或内部重定向,选择用于处理请求的位置配置可能会多次更改。以下示例显示如何在运行时访问模块的 HTTP 配置。
static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r) { ngx_http_foo_loc_conf_t *flcf; flcf = ngx_http_get_module_loc_conf(r, ngx_http_foo_module); ... }
阶段
每个 HTTP 请求都会经历一系列阶段。在每个阶段,都会对请求执行不同类型的处理。特定于模块的处理程序可以在大多数阶段注册,并且许多标准 nginx 模块将其阶段处理程序注册为在请求处理的特定阶段调用的一种方式。阶段被连续处理,一旦请求到达阶段,阶段处理程序就会被调用。以下是 nginx HTTP 阶段的列表。
-
NGX_HTTP_POST_READ_PHASE
- 第一阶段。ngx_http_realip_module 在此阶段注册其处理程序,以便在调用任何其他模块之前启用客户端地址的替换 。 -
NGX_HTTP_SERVER_REWRITE_PHASE
—处理server
块中(但块外) 定义的重写指令的阶段。location
ngx_http_rewrite_module 在此阶段安装其处理程序 。 -
NGX_HTTP_FIND_CONFIG_PHASE
— 根据请求 URI 选择位置的特殊阶段。在此阶段之前,相关虚拟服务器的默认位置已分配给请求,并且任何请求位置配置的模块都会接收默认服务器位置的配置。此阶段为请求分配一个新位置。在此阶段不能注册其他处理程序。 -
NGX_HTTP_REWRITE_PHASE
— 与 相同NGX_HTTP_SERVER_REWRITE_PHASE
,但适用于在上一阶段选择的位置中定义的重写规则。 -
NGX_HTTP_POST_REWRITE_PHASE
— 特殊阶段,如果在重写期间其 URI 发生更改,请求将被重定向到新位置。这是通过再次请求来实现的NGX_HTTP_FIND_CONFIG_PHASE
。在此阶段不能注册其他处理程序。 -
NGX_HTTP_PREACCESS_PHASE
— 不同类型处理程序的公共阶段,与访问控制无关。标准 nginx 模块 ngx_http_limit_conn_module 和 ngx_http_limit_req_module在此阶段注册其处理程序。 -
NGX_HTTP_ACCESS_PHASE
— 验证客户端是否有权发出请求的阶段。标准 nginx 模块(例如 ngx_http_access_module和 ngx_http_auth_basic_module) 在此阶段注册其处理程序。默认情况下,客户端必须通过此阶段注册的所有处理程序的授权检查,请求才能继续下一阶段。满足指令可用于允许在任何阶段处理程序授权客户端时继续处理。 -
NGX_HTTP_POST_ACCESS_PHASE
—处理满足任意指令的 特殊阶段 。如果某些访问阶段处理程序拒绝访问并且没有明确允许访问,则请求将最终确定。在此阶段不能注册其他处理程序。 -
NGX_HTTP_PRECONTENT_PHASE
— 在生成内容之前调用处理程序的阶段。标准模块(例如 ngx_http_try_files_module和 ngx_http_mirror_module) 在此阶段注册其处理程序。 -
NGX_HTTP_CONTENT_PHASE
— 正常生成响应的阶段。多个 nginx 标准模块在此阶段注册其处理程序,包括 ngx_http_index_module或ngx_http_static_module
. 它们被顺序调用,直到其中之一产生输出。还可以根据每个位置设置内容处理程序。如果 ngx_http_core_module的位置配置已handler
设置,它将被称为内容处理程序,并且在此阶段安装的处理程序将被忽略。 -
NGX_HTTP_LOG_PHASE
— 执行请求记录的阶段。目前,只有 ngx_http_log_module 在此阶段注册其处理程序以进行访问日志记录。日志阶段处理程序在请求处理的最后、释放请求之前被调用。
以下是预访问阶段处理程序的示例。
static ngx_http_module_t ngx_http_foo_module_ctx = { NULL, /* preconfiguration */ ngx_http_foo_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r) { ngx_table_elt_t *ua; ua = r->headers_in.user_agent; if (ua == NULL) { return NGX_DECLINED; } /* reject requests with "User-Agent: foo" */ if (ua->value.len == 3 && ngx_strncmp(ua->value.data, "foo", 3) == 0) { return NGX_HTTP_FORBIDDEN; } return NGX_DECLINED; } static ngx_int_t ngx_http_foo_init(ngx_conf_t *cf) { ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } *h = ngx_http_foo_handler; return NGX_OK; }
阶段处理程序预计返回特定代码:
-
NGX_OK
— 进入下一阶段。 -
NGX_DECLINED
— 继续当前阶段的下一个处理程序。如果当前处理程序是当前阶段的最后一个处理程序,则移至下一阶段。 -
NGX_AGAIN
,NGX_DONE
— 暂停阶段处理,直到发生某个未来事件,例如异步 I/O 操作或只是延迟。假设稍后将通过调用 来恢复阶段处理ngx_http_core_run_phases()
。 - 阶段处理程序返回的任何其他值都被视为请求完成代码,特别是 HTTP 响应代码。使用提供的代码完成请求。
对于某些阶段,返回代码的处理方式略有不同。在内容阶段,除被
NGX_DECLINED
视为最终代码之外的任何返回代码。来自位置内容处理程序的任何返回代码都被视为最终确定代码。在访问阶段,在
满足任何NGX_OK
模式下,除,
NGX_DECLINED
, NGX_AGAIN
,
之外的任何返回码NGX_DONE
均被视为拒绝。如果后续访问处理程序没有允许或拒绝使用不同代码的访问,则拒绝代码将成为最终确定代码。
变量
访问现有变量
变量可以通过索引(这是最常见的方法)或名称(见下文)来引用。索引是在配置阶段将变量添加到配置时创建的。要获取变量索引,请使用
ngx_http_get_variable_index()
:
ngx_str_t name; /* ngx_string("foo") */ ngx_int_t index; index = ngx_http_get_variable_index(cf, &name);
这里,cf
是一个指向 nginx 配置的指针,并
name
指向一个包含变量名称的字符串。该函数NGX_ERROR
在出错时返回,否则返回有效索引,该索引通常存储在模块配置中的某个位置以供将来使用。
所有 HTTP 变量都在给定 HTTP 请求的上下文中进行评估,结果特定于该 HTTP 请求并缓存在该 HTTP 请求中。所有计算变量的函数都会返回
ngx_http_variable_value_t
代表变量值的类型:
typedef ngx_variable_value_t ngx_http_variable_value_t; typedef struct { unsigned len:28; unsigned valid:1; unsigned no_cacheable:1; unsigned not_found:1; unsigned escape:1; u_char *data; } ngx_variable_value_t;
在哪里:
-
len
— 值的长度 -
data
——价值本身 -
valid
— 该值有效 -
not_found
— 未找到变量,因此data
和len
字段不相关;例如,对于变量,例如$arg_foo
未在请求中传递相应参数时, 可能会发生这种情况 -
no_cacheable
— 不缓存结果 -
escape
— 由日志记录模块在内部使用来标记需要在输出时转义的值。
ngx_http_get_flushed_variable()
和
函数ngx_http_get_indexed_variable()
用于获取变量的值。它们具有相同的接口 - 接受 HTTP 请求r
作为评估变量和index
标识变量的上下文。典型用法示例:
ngx_http_variable_value_t *v; v = ngx_http_get_flushed_variable(r, index); if (v == NULL || v->not_found) { /* we failed to get value or there is no such variable, handle it */ return NGX_ERROR; } /* some meaningful value is found */
函数之间的区别在于,
ngx_http_get_indexed_variable()
返回一个缓存值并ngx_http_get_flushed_variable()
刷新不可缓存变量的缓存。
某些模块(例如 SSI 和 Perl)需要处理在配置时未知名称的变量。因此不能使用索引来访问它们,但该
ngx_http_get_variable(r, name, key)
功能可用。它搜索具有给定名称的变量
name
及其key
从名称派生的哈希值。
创建变量
要创建变量,请使用该ngx_http_add_variable()
函数。它将配置(变量注册的位置)、变量名称和控制函数行为的标志作为参数:
NGX_HTTP_VAR_CHANGEABLE
— 启用变量的重新定义:如果另一个模块定义同名变量,则不会发生冲突。这允许 set指令覆盖变量。NGX_HTTP_VAR_NOCACHEABLE
— 禁用缓存,这对于诸如$time_local
.NGX_HTTP_VAR_NOHASH
— 表示该变量只能通过索引访问,不能通过名称访问。当已知 SSI 或 Perl 等模块中不需要该变量时,这是一个小的优化。NGX_HTTP_VAR_PREFIX
— 变量的名称是前缀。在这种情况下,处理程序必须实现额外的逻辑来获取特定变量的值。例如,所有“arg_
”变量都由同一处理程序处理,该处理程序在请求参数中执行查找并返回特定参数的值。
如果出现错误,该函数将返回 NULL,否则返回指向
ngx_http_variable_t
以下内容的指针:
struct ngx_http_variable_s { ngx_str_t name; ngx_http_set_variable_pt set_handler; ngx_http_get_variable_pt get_handler; uintptr_t data; ngx_uint_t flags; ngx_uint_t index; };
调用get
和set
处理程序来获取或设置变量值,
data
将其传递给变量处理程序,并
index
保存用于引用该变量的分配的变量索引。
通常,一个以 null 结尾的静态
ngx_http_variable_t
结构数组由模块创建,并在预配置阶段进行处理,以将变量添加到配置中,例如:
static ngx_http_variable_t ngx_http_foo_vars[] = { { ngx_string("foo_v1"), NULL, ngx_http_foo_v1_variable, 0, 0, 0 }, ngx_http_null_variable }; static ngx_int_t ngx_http_foo_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var, *v; for (v = ngx_http_foo_vars; v->name.len; v++) { var = ngx_http_add_variable(cf, &v->name, v->flags); if (var == NULL) { return NGX_ERROR; } var->get_handler = v->get_handler; var->data = v->data; } return NGX_OK; }
示例中的该函数用于初始化preconfiguration
HTTP模块上下文的字段,并在解析HTTP配置之前调用,以便解析器可以引用这些变量。
处理程序get
负责在特定请求的上下文中评估变量,例如:
static ngx_int_t ngx_http_variable_connection(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; p = ngx_pnalloc(r->pool, NGX_ATOMIC_T_LEN); if (p == NULL) { return NGX_ERROR; } v->len = ngx_sprintf(p, "%uA", r->connection->number) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; }
NGX_ERROR
如果出现内部错误(例如,内存分配失败)或其他情况,
它将返回NGX_OK
。要了解变量评估的状态,请检查中的标志(请参阅上面的ngx_http_variable_value_t
描述
)。
处理程序set
允许设置变量引用的属性。例如,$limit_rate
变量的设置处理程序修改请求的limit_rate
字段:
... { ngx_string("limit_rate"), ngx_http_variable_request_set_size, ngx_http_variable_request_get_size, offsetof(ngx_http_request_t, limit_rate), NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE, 0 }, ... static void ngx_http_variable_request_set_size(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ssize_t s, *sp; ngx_str_t val; val.len = v->len; val.data = v->data; s = ngx_parse_size(&val); if (s == NGX_ERROR) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid size \"%V\"", &val); return; } sp = (ssize_t *) ((char *) r + data); *sp = s; return; }
复数值
尽管有其名称,复杂值提供了一种简单的方法来计算可以包含文本、变量及其组合的表达式。
中的复杂值描述
ngx_http_compile_complex_value
在配置阶段编译,在ngx_http_complex_value_t
运行时使用以获得表达式求值的结果。
ngx_str_t *value; ngx_http_complex_value_t cv; ngx_http_compile_complex_value_t ccv; value = cf->args->elts; /* directive arguments */ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = &cv; ccv.zero = 1; ccv.conf_prefix = 1; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; }
这里,ccv
保存初始化复数值所需的所有参数cv
:
-
cf
— 配置指针 -
value
— 要解析的字符串(输入) -
complex_value
— 编译值(输出) -
zero
— 启用零终止值的标志 -
conf_prefix
— 在结果前添加配置前缀(nginx当前查找配置的目录) -
root_prefix
— 在结果中添加 root 前缀(正常的 nginx 安装前缀)
zero
当将结果传递到需要零终止字符串的库时,
该标志非常有用,并且在处理文件名时前缀很方便。
成功编译后,cv.lengths
包含有关表达式中变量存在的信息。NULL 值意味着表达式仅包含静态文本,因此可以存储在简单字符串中,而不是复杂值。
这ngx_http_set_complex_value_slot()
是一个方便的函数,用于在指令声明本身中完全初始化复杂值。
在运行时,可以使用以下函数计算复数值
ngx_http_complex_value()
:
ngx_str_t res; if (ngx_http_complex_value(r, &cv, &res) != NGX_OK) { return NGX_ERROR; }
给定请求r
和先前编译的值cv
,该函数将计算表达式并将结果写入res
.
请求重定向
loc_conf
HTTP 请求始终通过结构的字段
连接到某个位置
ngx_http_request_t
。这意味着在任何时候都可以通过调用从请求中检索任何模块的位置配置
ngx_http_get_module_loc_conf(r, module)
。请求位置在请求的生命周期内可能会多次更改。最初,将默认服务器的默认服务器位置分配给请求。如果请求切换到不同的服务器(通过 HTTP“主机”标头或 SSL SNI 扩展选择),请求也会切换到该服务器的默认位置。位置的下一次更改发生在
NGX_HTTP_FIND_CONFIG_PHASE
请求阶段。在此阶段,通过请求 URI 在为服务器配置的所有非命名位置中选择一个位置。ngx_http_rewrite_module
可以在请求阶段根据重写指令
更改请求 URI
,
并将请求发送回阶段以根据新 URI 选择新位置。
NGX_HTTP_REWRITE_PHASE
NGX_HTTP_FIND_CONFIG_PHASE
ngx_http_internal_redirect(r, uri, args)
还可以通过调用或
之一将请求重定向到新位置
ngx_http_named_location(r, name)
。
该ngx_http_internal_redirect(r, uri, args)
函数更改请求 URI 并将请求返回到阶段
NGX_HTTP_SERVER_REWRITE_PHASE
。请求将在服务器默认位置继续进行。稍后NGX_HTTP_FIND_CONFIG_PHASE
根据新的请求 URI 选择新位置。
以下示例使用新请求参数执行内部重定向。
ngx_int_t ngx_http_foo_redirect(ngx_http_request_t *r) { ngx_str_t uri, args; ngx_str_set(&uri, "/foo"); ngx_str_set(&args, "bar=1"); return ngx_http_internal_redirect(r, &uri, &args); }
该函数ngx_http_named_location(r, name)
将请求重定向到指定位置。位置的名称作为参数传递。在当前服务器的所有命名位置中查找该位置,然后请求切换到该
NGX_HTTP_REWRITE_PHASE
阶段。
以下示例执行到指定位置@foo 的重定向。
ngx_int_t ngx_http_foo_named_redirect(ngx_http_request_t *r) { ngx_str_t name; ngx_str_set(&name, "foo"); return ngx_http_named_location(r, &name); }
当 nginx 模块已经在请求字段中存储了一些上下文时,这
两个函数ngx_http_internal_redirect(r, uri, args)
都可以被调用。这些上下文可能与新位置配置不一致。为了防止不一致,两个重定向函数都会删除所有请求上下文。
ngx_http_named_location(r, name)
ctx
调用ngx_http_internal_redirect(r, uri, args)
或ngx_http_named_location(r, name)
增加请求
count
。为了保持一致的请求引用计数,请
ngx_http_finalize_request(r, NGX_DONE)
在重定向请求后调用。这将最终确定当前请求代码路径并减少计数器。
重定向和重写的请求成为内部请求,并且可以访问
内部
位置。内部请求已internal
设置标志。
子请求
子请求主要用于将一个请求的输出插入到另一个请求中,可能与其他数据混合。子请求看起来像普通请求,但与其父请求共享一些数据。特别是,与客户端输入相关的所有字段都是共享的,因为子请求不接收来自客户端的任何其他输入。子请求的请求字段parent
包含指向其父请求的链接,并且对于主请求为 NULL。该字段main
包含指向一组请求中的主要请求的链接。
子请求在该NGX_HTTP_SERVER_REWRITE_PHASE
阶段开始。它会经历与普通请求相同的后续阶段,并根据其自己的 URI 分配一个位置。
子请求中的输出标头始终被忽略。将ngx_http_postpone_filter
子请求的输出正文放置在相对于父请求生成的其他数据的正确位置。
子请求与活动请求的概念相关。r
如果 ,则认为
请求处于活动状态c->data == r
,其中c
是客户端连接对象。在任何给定点,仅允许请求组中的活动请求将其缓冲区输出到客户端。非活动请求仍然可以将其输出发送到过滤器链,但它不会超出过滤器ngx_http_postpone_filter
并保持由该过滤器缓冲,直到请求变为活动状态。以下是请求激活的一些规则:
- 最初,主要请求处于活动状态。
- 活动请求的第一个子请求在创建后立即变为活动状态。
ngx_http_postpone_filter
一旦发送了该请求之前的所有数据,就会激活活动请求的子请求列表中的下一个请求 。- 当请求完成时,其父级被激活。
通过调用函数创建子请求
ngx_http_subrequest(r, uri, args, psr, ps, flags)
,其中
r
是父请求,uri
是
args
子请求的 URI 和参数,psr
是输出参数,接收新创建的子请求引用,ps
是回调对象,用于通知父请求该子请求是正在最终确定,并且
flags
是标志的位掩码。以下标志可用:
-
NGX_HTTP_SUBREQUEST_IN_MEMORY
- 输出不发送到客户端,而是存储在内存中。该标志仅影响由代理模块之一处理的子请求。子请求完成后,其输出的r->out
类型为ngx_buf_t
。 -
NGX_HTTP_SUBREQUEST_WAITED
-done
即使子请求在完成时未处于活动状态,也会设置子请求的标志。该子请求标志由 SSI 过滤器使用。 -
NGX_HTTP_SUBREQUEST_CLONE
- 子请求被创建为其父请求的克隆。它在与父请求相同的位置启动并从同一阶段继续。
以下示例创建一个 URI 为 的子请求/foo
。
ngx_int_t rc; ngx_str_t uri; ngx_http_request_t *sr; ... ngx_str_set(&uri, "/foo"); rc = ngx_http_subrequest(r, &uri, NULL, &sr, NULL, 0); if (rc == NGX_ERROR) { /* error */ }
此示例克隆当前请求并为子请求设置最终回调。
ngx_int_t ngx_http_foo_clone(ngx_http_request_t *r) { ngx_http_request_t *sr; ngx_http_post_subrequest_t *ps; ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t)); if (ps == NULL) { return NGX_ERROR; } ps->handler = ngx_http_foo_subrequest_done; ps->data = "foo"; return ngx_http_subrequest(r, &r->uri, &r->args, &sr, ps, NGX_HTTP_SUBREQUEST_CLONE); } ngx_int_t ngx_http_foo_subrequest_done(ngx_http_request_t *r, void *data, ngx_int_t rc) { char *msg = (char *) data; ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "done subrequest r:%p msg:%s rc:%i", r, msg, rc); return rc; }
子请求通常在主体过滤器中创建,在这种情况下,可以将其输出视为任何显式请求的输出。这意味着子请求的输出最终会在子请求创建之前传递的所有显式缓冲区之后以及创建后传递的任何缓冲区之前发送到客户端。即使对于大型子请求层次结构,也会保留这种顺序。以下示例将子请求的输出插入到所有请求数据缓冲区之后、带有标志的最终缓冲区之前last_buf
。
ngx_int_t ngx_http_foo_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_int_t rc; ngx_buf_t *b; ngx_uint_t last; ngx_chain_t *cl, out; ngx_http_request_t *sr; ngx_http_foo_filter_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_foo_filter_module); if (ctx == NULL) { return ngx_http_next_body_filter(r, in); } last = 0; for (cl = in; cl; cl = cl->next) { if (cl->buf->last_buf) { cl->buf->last_buf = 0; cl->buf->last_in_chain = 1; cl->buf->sync = 1; last = 1; } } /* Output explicit output buffers */ rc = ngx_http_next_body_filter(r, in); if (rc == NGX_ERROR || !last) { return rc; } /* * Create the subrequest. The output of the subrequest * will automatically be sent after all preceding buffers, * but before the last_buf buffer passed later in this function. */ if (ngx_http_subrequest(r, ctx->uri, NULL, &sr, NULL, 0) != NGX_OK) { return NGX_ERROR; } ngx_http_set_ctx(r, NULL, ngx_http_foo_filter_module); /* Output the final buffer with the last_buf flag */ b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } b->last_buf = 1; out.buf = b; out.next = NULL; return ngx_http_output_filter(r, &out); }
除了数据输出之外,还可以出于其他目的创建子请求。例如,
ngx_http_auth_request_module模块在该阶段创建一个子请求NGX_HTTP_ACCESS_PHASE
。要在此时禁用输出,
header_only
请在子请求上设置该标志。这可以防止子请求正文发送到客户端。请注意,子请求的标头永远不会发送到客户端。可以在回调处理程序中分析子请求的结果。
请求最终确定
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
并在达到零时销毁该请求。当前请求销毁后,客户端连接可以用于更多请求。 -
NGX_ERROR
,NGX_HTTP_REQUEST_TIME_OUT
(408
),NGX_HTTP_CLIENT_CLOSED_REQUEST
(499
) - 错误完成。尽快终止请求并关闭客户端连接。 -
NGX_HTTP_CREATED
(201
)、NGX_HTTP_NO_CONTENT
(204
)、大于或等于NGX_HTTP_SPECIAL_RESPONSE
(300
) 的代码 - 特殊响应最终确定。对于这些值,nginx 要么向客户端发送代码的默认响应页面,要么执行内部重定向到 error_page位置(如果为代码配置了该位置)。 -
其他代码被视为成功的最终代码,并且可能会激活请求编写器以完成发送响应正文。一旦正文完全发送,请求
count
就会减少。如果达到零,则请求被销毁,但客户端连接仍然可以用于其他请求。如果count
为正,则请求中存在未完成的活动,这些活动将在稍后完成。
请求正文
为了处理客户端请求的正文,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_in_file_only
- 始终将正文读取到文件中,即使适合内存缓冲区。 -
request_body_in_persistent_file
- 创建文件后不要立即取消链接。具有此标志的文件可以移动到另一个目录。 -
request_body_in_clean_file
- 请求完成后取消文件链接。当文件应该移动到另一个目录但由于某种原因没有移动时,这可能很有用。 -
request_body_file_group_access
- 通过将默认的 0600 访问掩码替换为 0660,启用对文件的组访问。 -
request_body_file_log_level
- 记录文件错误的严重级别。 -
request_body_no_buffering
- 无需缓冲即可读取请求正文。
该request_body_no_buffering
标志启用读取请求正文的无缓冲模式。在这种模式下,调用 后
ngx_http_read_client_request_body()
,
bufs
链可能只保留主体的一部分。要阅读下一部分,请调用该
ngx_http_read_unbuffered_request_body(r)
函数。返回值NGX_AGAIN
和请求标志
reading_body
表明有更多数据可用。如果bufs
调用此函数后为NULL,则此时没有任何内容可读取。read_event_handler
当请求正文的下一部分可用时,将调用请求
回调。
请求正文过滤器
读取请求主体部分后,通过调用存储在变量中的第一个主体过滤器处理程序,将其传递到请求主体过滤器链ngx_http_top_request_body_filter
。假设每个主体处理程序都调用链中的下一个处理程序,直到ngx_http_request_body_save_filter(r, cl)
调用最后一个处理程序。该处理程序收集缓冲区
r->request_body->bufs
并在必要时将它们写入文件。最后一个请求正文缓冲区具有非零last_buf
标志。
如果过滤器计划延迟数据缓冲区,则应将标志设置
r->request_body->filter_need_buffering
为
1
首次调用时。
以下是一个简单的请求正文过滤器示例,它将请求正文延迟一秒。
#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> #define NGX_HTTP_DELAY_BODY 1000 typedef struct { ngx_event_t event; ngx_chain_t *out; } ngx_http_delay_body_ctx_t; static ngx_int_t ngx_http_delay_body_filter(ngx_http_request_t *r, ngx_chain_t *in); static void ngx_http_delay_body_cleanup(void *data); static void ngx_http_delay_body_event_handler(ngx_event_t *ev); static ngx_int_t ngx_http_delay_body_init(ngx_conf_t *cf); static ngx_http_module_t ngx_http_delay_body_module_ctx = { NULL, /* preconfiguration */ ngx_http_delay_body_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; ngx_module_t ngx_http_delay_body_filter_module = { NGX_MODULE_V1, &ngx_http_delay_body_module_ctx, /* module context */ NULL, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_request_body_filter_pt ngx_http_next_request_body_filter; static ngx_int_t ngx_http_delay_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_int_t rc; ngx_chain_t *cl, *ln; ngx_http_cleanup_t *cln; ngx_http_delay_body_ctx_t *ctx; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "delay request body filter"); ctx = ngx_http_get_module_ctx(r, ngx_http_delay_body_filter_module); if (ctx == NULL) { ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_delay_body_ctx_t)); if (ctx == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_delay_body_filter_module); r->request_body->filter_need_buffering = 1; } if (ngx_chain_add_copy(r->pool, &ctx->out, in) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (!ctx->event.timedout) { if (!ctx->event.timer_set) { /* cleanup to remove the timer in case of abnormal termination */ cln = ngx_http_cleanup_add(r, 0); if (cln == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } cln->handler = ngx_http_delay_body_cleanup; cln->data = ctx; /* add timer */ ctx->event.handler = ngx_http_delay_body_event_handler; ctx->event.data = r; ctx->event.log = r->connection->log; ngx_add_timer(&ctx->event, NGX_HTTP_DELAY_BODY); } return ngx_http_next_request_body_filter(r, NULL); } rc = ngx_http_next_request_body_filter(r, ctx->out); for (cl = ctx->out; cl; /* void */) { ln = cl; cl = cl->next; ngx_free_chain(r->pool, ln); } ctx->out = NULL; return rc; } static void ngx_http_delay_body_cleanup(void *data) { ngx_http_delay_body_ctx_t *ctx = data; if (ctx->event.timer_set) { ngx_del_timer(&ctx->event); } } static void ngx_http_delay_body_event_handler(ngx_event_t *ev) { ngx_connection_t *c; ngx_http_request_t *r; r = ev->data; c = r->connection; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "delay request body event"); ngx_post_event(c->read, &ngx_posted_events); } static ngx_int_t ngx_http_delay_body_init(ngx_conf_t *cf) { ngx_http_next_request_body_filter = ngx_http_top_request_body_filter; ngx_http_top_request_body_filter = ngx_http_delay_body_filter; return NGX_OK; }
回复
在 nginx 中,HTTP 响应是通过发送响应标头和可选的响应正文来生成的。header 和 body 都通过一系列过滤器传递,并最终写入客户端套接字。nginx 模块可以将其处理程序安装到 header 或 body 过滤器链中,并处理来自前一个处理程序的输出。
响应头
该ngx_http_send_header(r)
函数发送输出标头。r->headers_out
在包含生成 HTTP 响应标头所需的所有数据之前,请勿调用此函数。
必须始终设置status
中的字段。r->headers_out
如果响应状态表明响应正文位于标头后面,
content_length_n
也可以设置。该字段的默认值为-1
,这意味着主体大小未知。在这种情况下,使用分块传输编码。要输出任意标头,请附加headers
列表。
static ngx_int_t ngx_http_foo_content_handler(ngx_http_request_t *r) { ngx_int_t rc; ngx_table_elt_t *h; /* send header */ r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = 3; /* X-Foo: foo */ h = ngx_list_push(&r->headers_out.headers); if (h == NULL) { return NGX_ERROR; } h->hash = 1; ngx_str_set(&h->key, "X-Foo"); ngx_str_set(&h->value, "foo"); rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { return rc; } /* send body */ ... }
标头过滤器
该ngx_http_send_header(r)
函数通过调用变量中存储的第一个标头过滤器处理程序来调用标头过滤器链ngx_http_top_header_filter
。假设每个标头处理程序都调用链中的下一个处理程序,直到ngx_http_header_filter(r)
调用最后一个处理程序。最终的标头处理程序基于构造 HTTP 响应
r->headers_out
并将其传递给
ngx_http_writer_filter
for 输出。
ngx_http_top_header_filter
要将处理程序添加到标头过滤器链,请在配置时
将其地址存储在全局变量中。先前的处理程序地址通常存储在模块中的静态变量中,并在退出之前由新添加的处理程序调用。
以下标头过滤器模块示例将 HTTP 标头“ X-Foo: foo
”添加到每个带有 status 的响应
200
。
#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> static ngx_int_t ngx_http_foo_header_filter(ngx_http_request_t *r); static ngx_int_t ngx_http_foo_header_filter_init(ngx_conf_t *cf); static ngx_http_module_t ngx_http_foo_header_filter_module_ctx = { NULL, /* preconfiguration */ ngx_http_foo_header_filter_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; ngx_module_t ngx_http_foo_header_filter_module = { NGX_MODULE_V1, &ngx_http_foo_header_filter_module_ctx, /* module context */ NULL, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_int_t ngx_http_foo_header_filter(ngx_http_request_t *r) { ngx_table_elt_t *h; /* * The filter handler adds "X-Foo: foo" header * to every HTTP 200 response */ if (r->headers_out.status != NGX_HTTP_OK) { return ngx_http_next_header_filter(r); } h = ngx_list_push(&r->headers_out.headers); if (h == NULL) { return NGX_ERROR; } h->hash = 1; ngx_str_set(&h->key, "X-Foo"); ngx_str_set(&h->value, "foo"); return ngx_http_next_header_filter(r); } static ngx_int_t ngx_http_foo_header_filter_init(ngx_conf_t *cf) { ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_foo_header_filter; return NGX_OK; }
响应体
要发送响应正文,请调用该
ngx_http_output_filter(r, cl)
函数。该函数可以被多次调用。每次,它都会以缓冲链的形式发送响应正文的一部分。last_buf
在最后一个主体缓冲区中
设置标志。
以下示例生成一个完整的 HTTP 响应,其正文为“foo”。为了使示例既充当子请求又充当主请求,该last_in_chain
标志设置在输出的最后一个缓冲区中。该last_buf
标志仅针对主请求设置,因为子请求的最后一个缓冲区不会结束整个输出。
static ngx_int_t ngx_http_bar_content_handler(ngx_http_request_t *r) { ngx_int_t rc; ngx_buf_t *b; ngx_chain_t out; /* send header */ r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = 3; rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { return rc; } /* send body */ b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } b->last_buf = (r == r->main) ? 1 : 0; b->last_in_chain = 1; b->memory = 1; b->pos = (u_char *) "foo"; b->last = b->pos + 3; out.buf = b; out.next = NULL; return ngx_http_output_filter(r, &out); }
响应主体过滤器
该函数ngx_http_output_filter(r, cl)
通过调用变量中存储的第一个主体过滤器处理程序来调用主体过滤器链ngx_http_top_body_filter
。假设每个主体处理程序都调用链中的下一个处理程序,直到ngx_http_write_filter(r, cl)
调用最后一个处理程序。
主体过滤器处理程序接收缓冲区链。处理程序应该处理缓冲区并将可能的新链传递给下一个处理程序。值得注意的是,ngx_chain_t
传入链的链链接属于调用者,不得重复使用或更改。处理程序完成后,调用者可以使用其输出链链接来跟踪其已发送的缓冲区。为了保存缓冲区链或在传递到下一个过滤器之前替换一些缓冲区,处理程序需要分配自己的链链接。
以下是一个简单的主体过滤器示例,用于计算主体中的字节数。结果可作为$counter
可在访问日志中使用的变量。
#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> typedef struct { off_t count; } ngx_http_counter_filter_ctx_t; static ngx_int_t ngx_http_counter_body_filter(ngx_http_request_t *r, ngx_chain_t *in); static ngx_int_t ngx_http_counter_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_counter_add_variables(ngx_conf_t *cf); static ngx_int_t ngx_http_counter_filter_init(ngx_conf_t *cf); static ngx_http_module_t ngx_http_counter_filter_module_ctx = { ngx_http_counter_add_variables, /* preconfiguration */ ngx_http_counter_filter_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; ngx_module_t ngx_http_counter_filter_module = { NGX_MODULE_V1, &ngx_http_counter_filter_module_ctx, /* module context */ NULL, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_output_body_filter_pt ngx_http_next_body_filter; static ngx_str_t ngx_http_counter_name = ngx_string("counter"); static ngx_int_t ngx_http_counter_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_chain_t *cl; ngx_http_counter_filter_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_counter_filter_module); if (ctx == NULL) { ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_counter_filter_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_counter_filter_module); } for (cl = in; cl; cl = cl->next) { ctx->count += ngx_buf_size(cl->buf); } return ngx_http_next_body_filter(r, in); } static ngx_int_t ngx_http_counter_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; ngx_http_counter_filter_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_counter_filter_module); if (ctx == NULL) { v->not_found = 1; return NGX_OK; } p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN); if (p == NULL) { return NGX_ERROR; } v->data = p; v->len = ngx_sprintf(p, "%O", ctx->count) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; return NGX_OK; } static ngx_int_t ngx_http_counter_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var; var = ngx_http_add_variable(cf, &ngx_http_counter_name, 0); if (var == NULL) { return NGX_ERROR; } var->get_handler = ngx_http_counter_variable; return NGX_OK; } static ngx_int_t ngx_http_counter_filter_init(ngx_conf_t *cf) { ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_counter_body_filter; return NGX_OK; }
构建过滤模块
编写主体或标头过滤器时,请特别注意过滤器在过滤器顺序中的位置。nginx 标准模块注册了许多 header 和 body 过滤器。nginx 标准模块注册了许多头过滤器和主体过滤器,在正确的位置注册新的过滤器模块非常重要。通常,模块在其配置后处理程序中注册过滤器。处理过程中调用过滤器的顺序显然与它们注册的顺序相反。
nginx为第三方过滤模块提供了一个特殊的槽
HTTP_AUX_FILTER_MODULES
。要在此插槽中注册过滤器模块,请在模块的配置中将
ngx_module_type
变量
设置为 。HTTP_AUX_FILTER
以下示例显示了一个过滤器模块配置文件,假设该模块只有一个源文件ngx_http_foo_filter_module.c
.
ngx_module_type=HTTP_AUX_FILTER ngx_module_name=ngx_http_foo_filter_module ngx_module_srcs="$ngx_addon_dir/ngx_http_foo_filter_module.c" . auto/module
缓冲区重用
当发出或更改缓冲区流时,通常需要重用分配的缓冲区。nginx 代码中广泛采用的标准方法是为此目的保留两个缓冲区链:
free
和busy
。该free
链保留所有可以重用的空闲缓冲区。该busy
链保留当前模块发送的所有缓冲区,这些缓冲区仍在由某些其他过滤器处理程序使用。如果缓冲区的大小大于零,则认为该缓冲区正在使用。通常,当过滤器消耗缓冲区时,其pos
(或file_pos
对于文件缓冲区)将移向
last
(file_last
对于文件缓冲区)。一旦缓冲区被完全消耗,就可以重新使用了。要将新释放的缓冲区添加到free
链中,只需迭代busy
链并将其头部的零大小缓冲区移动到free
. 此操作非常常见,因此有一个特殊的函数
ngx_chain_update_chains(free, busy, out, tag)
。该函数将输出链附加out
到
busy
,并将空闲缓冲区从顶部移动
busy
到free
。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)的模块使用此功能。该模块还提供了一个用于创建自定义负载平衡模块的接口,并实现了默认的循环方法。
less_conn 和hash 模块实现了替代的负载平衡方法,但实际上是作为上游循环模块的扩展实现的,并与其共享许多代码,例如服务器组的表示。keepalive模块是一个独立的模块,扩展了上游功能。
ngx_http_upstream_module
可以通过将相应的
上游块放入配置文件中来显式配置,也可以使用proxy_pass等指令隐式配置
,该指令接受在某个时刻评估到服务器列表中的 URL。替代负载平衡方法仅适用于显式上游配置。上游模块配置有自己的指令上下文
。结构体定义如下:
NGX_HTTP_UPS_CONF
struct ngx_http_upstream_srv_conf_s { ngx_http_upstream_peer_t peer; void **srv_conf; ngx_array_t *servers; /* ngx_http_upstream_server_t */ ngx_uint_t flags; ngx_str_t host; u_char *file_name; ngx_uint_t line; in_port_t port; ngx_uint_t no_port; /* unsigned no_port:1 */ #if (NGX_HTTP_UPSTREAM_ZONE) ngx_shm_zone_t *shm_zone; #endif };
-
srv_conf
— 上游模块的配置上下文。 -
servers
— 数组 ,解析块中一组服务器指令ngx_http_upstream_server_t
的结果 。upstream
-
flags
— 主要标记负载平衡方法支持哪些功能的标志。这些功能被配置为服务器指令的参数:-
NGX_HTTP_UPSTREAM_CREATE
— 区分显式定义的上游与由proxy_pass指令和“朋友”(FastCGI、SCGI 等) 自动创建的上游 -
NGX_HTTP_UPSTREAM_WEIGHT
weight
—支持 “ ”参数 -
NGX_HTTP_UPSTREAM_MAX_FAILS
max_fails
—支持 “ ”参数 -
NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
fail_timeout
—支持 “ ”参数 -
NGX_HTTP_UPSTREAM_DOWN
down
—支持 “ ”参数 -
NGX_HTTP_UPSTREAM_BACKUP
backup
—支持 “ ”参数 -
NGX_HTTP_UPSTREAM_MAX_CONNS
max_conns
—支持 “ ”参数
-
-
host
— 上游名称。 -
file_name, line
— 配置文件的名称和块所在的行upstream
。 -
port
和no_port
— 不用于明确定义的上游组。 -
shm_zone
— 该上游组使用的共享内存区域(如果有)。 -
peer
— 包含用于初始化上游配置的通用方法的对象:
实现负载平衡算法的模块必须设置这些方法并初始化 privatetypedef struct { ngx_http_upstream_init_pt init_upstream; ngx_http_upstream_init_peer_pt init; void *data; } ngx_http_upstream_peer_t;
data
。如果init_upstream
在配置解析期间未初始化,ngx_http_upstream_module
则将其设置为默认ngx_http_upstream_init_round_robin
算法。-
init_upstream(cf, us)
— 配置时方法负责初始化一组服务器并init()
在成功时初始化该方法。典型的负载平衡模块使用块中的服务器列表upstream
来创建它使用的高效数据结构,并将其自己的配置保存到字段中data
。 -
init(r, us)
ngx_http_upstream_peer_t.peer
— 初始化用于负载平衡的 每个请求 结构(不要与ngx_http_upstream_srv_conf_t.peer
上面描述的每个上游的结构混淆)。它作为data
参数传递给所有处理服务器选择的回调。
-
当nginx必须将请求传递给另一台主机进行处理时,它使用配置的负载平衡方法来获取要连接的地址。该方法是从
ngx_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 ... };
该结构体有以下字段:
-
sockaddr
,socklen
,name
— 要连接的上游服务器的地址;这是负载平衡方法的输出参数。 -
data
— 负载均衡方法的每个请求数据;保留选择算法的状态,通常包括到上游配置的链接。它作为参数传递给所有处理服务器选择的方法(见下文)。 -
tries
— 允许 尝试连接上游服务器的 次数。 -
get
、free
、notify
、set_session
和save_session
- 负载平衡模块的方法,如下所述。
所有方法至少接受两个参数:对等连接对象
pc
和data
创建的
ngx_http_upstream_srv_conf_t.peer.init()
。pc.data
请注意,由于负载平衡模块的“链接”,
它可能有所不同。
-
get(pc, data)
— 当上游模块准备好将请求传递到上游服务器并且需要知道其地址时调用的方法。该方法必须填充结构体的sockaddr
、socklen
、 和name
字段ngx_peer_connection_t
。返回值为以下之一:-
NGX_OK
— 服务器已选择。 -
NGX_ERROR
— 发生内部错误。 -
NGX_BUSY
— 当前没有可用的服务器。发生这种情况的原因有很多,包括:动态服务器组为空、组中的所有服务器均处于故障状态,或者组中的所有服务器已处理最大连接数。 -
NGX_DONE
— 底层连接已被重用,无需创建与上游服务器的新连接。该值由模块设置keepalive
。
-
-
free(pc, data, state)
— 当上游模块完成与特定服务器的工作时调用的方法。参数state
是上游连接的完成状态,一个具有以下可能值的位掩码:-
NGX_PEER_FAILED
——尝试 没有成功 -
NGX_PEER_NEXT
403
— 上游服务器返回代码或 的 特殊情况404
,这不被视为 失败。 -
NGX_PEER_KEEPALIVE
— 目前未使用
tries
计数器。 -
-
notify(pc, data, type)
— 目前在 OSS 版本中未使用。 -
set_session(pc, data)
—save_session(pc, data)
SSL 特定方法,可缓存与上游服务器的会话。该实现是通过循环平衡方法提供的。
例子
nginx-dev-examples存储 库 提供了 nginx 模块示例。
代码风格
一般规则
- 最大文本宽度为 80 个字符
- 缩进为4个空格
- 没有制表符,没有尾随空格
- 同一行的列表元素之间用空格分隔
- 十六进制文字是小写的
-
文件名、函数和类型名称以及全局变量具有
ngx_
或更具体的前缀,例如ngx_http_
和ngx_mail_
size_t ngx_utf8_length(u_char *p, size_t n) { u_char c, *last; size_t len; last = p + n; for (len = 0; p < last; len++) { c = *p; if (c < 0x80) { p++; continue; } if (ngx_utf8_decode(&p, last - p) > 0x10ffff) { /* invalid UTF-8 */ return n; } } return len; }
文件
典型的源文件可能包含以下由两个空行分隔的部分:
- 版权声明
- 包括
- 预处理器定义
- 类型定义
- 函数原型
- 变量定义
- 函数定义
版权声明如下所示:
/* * Copyright (C) Author Name * Copyright (C) Organization, Inc. */
如果文件被重大修改,则应更新作者列表,新作者将添加到顶部。
和文件始终首先包含,然后是
、或之一ngx_config.h
。然后遵循可选的外部头文件:
ngx_core.h
ngx_http.h
ngx_stream.h
ngx_mail.h
#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> #include <libxml/parser.h> #include <libxml/tree.h> #include <libxslt/xslt.h> #if (NGX_HAVE_EXSLT) #include <libexslt/exslt.h> #endif
头文件应该包含所谓的“头保护”:
#ifndef _NGX_PROCESS_CYCLE_H_INCLUDED_ #define _NGX_PROCESS_CYCLE_H_INCLUDED_ ... #endif /* _NGX_PROCESS_CYCLE_H_INCLUDED_ */
评论
-
“
//
”注释不使用 - 文本以英文书写,首选美式拼写
-
多行注释的格式如下:
/* * The red-black tree code is based on the algorithm described in * the "Introduction to Algorithms" by Cormen, Leiserson and Rivest. */
/* find the server configuration for the address:port */
预处理器
宏名称以ngx_
or NGX_
(或更具体)前缀开头。常量的宏名称是大写的。参数化宏和初始化宏都是小写的。宏名称和值至少用两个空格分隔:
#define NGX_CONF_BUFFER 4096 #define ngx_buf_in_memory(b) (b->temporary || b->memory || b->mmap) #define ngx_buf_size(b) \ (ngx_buf_in_memory(b) ? (off_t) (b->last - b->pos): \ (b->file_last - b->file_pos)) #define ngx_null_string { 0, NULL }
条件在括号内,否定在括号外:
#if (NGX_HAVE_KQUEUE) ... #elif ((NGX_HAVE_DEVPOLL && !(NGX_TEST_BUILD_DEVPOLL)) \ || (NGX_HAVE_EVENTPORT && !(NGX_TEST_BUILD_EVENTPORT))) ... #elif (NGX_HAVE_EPOLL && !(NGX_TEST_BUILD_EPOLL)) ... #elif (NGX_HAVE_POLL) ... #else /* select */ ... #endif /* NGX_HAVE_KQUEUE */
类型
类型名称以“ _t
”后缀结尾。定义的类型名称至少由两个空格分隔:
typedef ngx_uint_t ngx_rbtree_key_t;
结构类型是使用 定义的typedef
。内部结构、成员类型和名称是对齐的:
typedef struct { size_t len; u_char *data; } ngx_str_t;
保持文件中不同结构之间的对齐方式相同。指向自身的结构有名称,以“ _s
”结尾。相邻的结构定义用两个空行分隔:
typedef struct ngx_list_part_s ngx_list_part_t; struct ngx_list_part_s { void *elts; ngx_uint_t nelts; ngx_list_part_t *next; }; typedef struct { ngx_list_part_t *last; ngx_list_part_t part; size_t size; ngx_uint_t nalloc; ngx_pool_t *pool; } ngx_list_t;
每个结构成员都在自己的行上声明:
typedef struct { ngx_uint_t hash; ngx_str_t key; ngx_str_t value; u_char *lowcase_key; } ngx_table_elt_t;
结构内部的函数指针定义了以“ _pt
”结尾的类型:
typedef ssize_t (*ngx_recv_pt)(ngx_connection_t *c, u_char *buf, size_t size); typedef ssize_t (*ngx_recv_chain_pt)(ngx_connection_t *c, ngx_chain_t *in, off_t limit); typedef ssize_t (*ngx_send_pt)(ngx_connection_t *c, u_char *buf, size_t size); typedef ngx_chain_t *(*ngx_send_chain_pt)(ngx_connection_t *c, ngx_chain_t *in, off_t limit); typedef struct { ngx_recv_pt recv; ngx_recv_chain_pt recv_chain; ngx_recv_pt udp_recv; ngx_send_pt send; ngx_send_pt udp_send; ngx_send_chain_pt udp_send_chain; ngx_send_chain_pt send_chain; ngx_uint_t flags; } ngx_os_io_t;
枚举具有以“ _e
”结尾的类型:
typedef enum { ngx_http_fastcgi_st_version = 0, ngx_http_fastcgi_st_type, ... ngx_http_fastcgi_st_padding } ngx_http_fastcgi_state_e;
变量
变量声明时按基本类型的长度排序,然后按字母顺序排序。类型名称和变量名称是对齐的。类型和名称“列”用两个空格分隔。大数组放在声明块的末尾:
u_char | | *rv, *p; ngx_conf_t | | *cf; ngx_uint_t | | i, j, k; unsigned int | | len; struct sockaddr | | *sa; const unsigned char | | *data; ngx_peer_connection_t | | *pc; ngx_http_core_srv_conf_t | |**cscfp; ngx_http_upstream_srv_conf_t| | *us, *uscf; u_char | | text[NGX_SOCKADDR_STRLEN];
静态和全局变量可以在声明时初始化:
static ngx_str_t ngx_http_memcached_key = ngx_string("memcached_key");
static ngx_uint_t mday[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static uint32_t ngx_crc32_table16[] = { 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, ... 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c };
有很多常用的类型/名称组合:
u_char *rv; ngx_int_t rc; ngx_conf_t *cf; ngx_connection_t *c; ngx_http_request_t *r; ngx_peer_connection_t *pc; ngx_http_upstream_srv_conf_t *us, *uscf;
功能
所有函数(即使是静态函数)都应该有原型。原型包括参数名称。长原型在连续线上用单个缩进包裹:
static char *ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_int_t ngx_http_init_phases(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf); static char *ngx_http_merge_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf, ngx_http_module_t *module, ngx_uint_t ctx_index);
定义中的函数名称以新行开头。函数体左大括号和右大括号位于不同的行上。函数体是缩进的。函数之间有两个空行:
static ngx_int_t ngx_http_find_virtual_server(ngx_http_request_t *r, u_char *host, size_t len) { ... } static ngx_int_t ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt) { ... }
函数名称和左括号后面没有空格。长函数调用被包装,使得连续行从第一个函数参数的位置开始。如果这是不可能的,请格式化第一个连续行,使其在位置 79 处结束:
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http header: \"%V: %V\"", &h->key, &h->value); hc->busy = ngx_palloc(r->connection->pool, cscf->large_client_header_buffers.num * sizeof(ngx_buf_t *));
ngx_inline
应使用宏而
不是inline
:
static ngx_inline void ngx_cpuid(uint32_t i, uint32_t *buf);
表达式
除“ .
”和“ −>
”之外的二元运算符与其操作数之间应以一个空格分隔。一元运算符和下标与其操作数之间不以空格分隔:
width = width * 10 + (*fmt++ - '0');
ch = (u_char) ((decoded << 4) + (ch - '0'));
r->exten.data = &r->uri.data[i + 1];
类型转换与转换表达式之间用一个空格分隔。类型转换内的星号与类型名称之间用空格分隔:
len = ngx_sock_ntop((struct sockaddr *) sin6, p, len, 1);
如果表达式无法容纳在单行中,则会将其换行。断线的首选点是二元运算符。延续行与表达式的开头对齐:
if (status == NGX_HTTP_MOVED_PERMANENTLY || status == NGX_HTTP_MOVED_TEMPORARILY || status == NGX_HTTP_SEE_OTHER || status == NGX_HTTP_TEMPORARY_REDIRECT || status == NGX_HTTP_PERMANENT_REDIRECT) { ... }
p->temp_file->warn = "an upstream response is buffered " "to a temporary file";
作为最后的手段,可以将表达式换行,以便续行在位置 79 处结束:
hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t) + size * sizeof(ngx_hash_elt_t *));
上述规则也适用于子表达式,其中每个子表达式都有自己的缩进级别:
if (((u->conf->cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING) || c->stale_updating) && !r->background && u->conf->cache_background_update) { ... }
有时,在强制转换后包装表达式会很方便。在本例中,续行是缩进的:
node = (ngx_rbtree_node_t *) ((u_char *) lr - offsetof(ngx_rbtree_node_t, color));
NULL
指针显式地与(not 0
)
进行比较
:
if (ptr != NULL) { ... }
条件和循环
“ if
”关键字与条件之间用一个空格分隔。左大括号位于同一行,如果条件需要多行,则位于专用行。右大括号位于专用行上,可以选择后跟“ else if
/ else
”。else if
通常,“ / ”部分之前有一个空行else
:
if (node->left == sentinel) { temp = node->right; subst = node; } else if (node->right == sentinel) { temp = node->left; subst = node; } else { subst = ngx_rbtree_min(node->right, sentinel); if (subst->left != sentinel) { temp = subst->left; } else { temp = subst->right; } }
类似的格式化规则适用于“ do
”和“ while
”循环:
while (p < last && *p == ' ') { p++; }
do { ctx->node = rn; ctx = ctx->next; } while (ctx);
“ switch
”关键字与条件之间用一个空格分隔。左大括号位于同一行。闭合大括号位于专用线上。“ case
”关键字与“ ”对齐switch
:
switch (ch) { case '!': looked = 2; state = ssi_comment0_state; break; case '<': copy_end = p; break; default: copy_end = p; looked = 0; state = ssi_start_state; break; }
大多数“ for
”循环的格式如下:
for (i = 0; i < ccf->env.nelts; i++) { ... }
for (q = ngx_queue_head(locations); q != ngx_queue_sentinel(locations); q = ngx_queue_next(q)) { ... }
如果“ for
”语句的某些部分被省略,则由“ /* void */
”注释指示:
for (i = 0; /* void */ ; i++) { ... }
空循环体也由“ /* void */
”注释表示,可以放在同一行:
for (cl = *busy; cl->next; cl = cl->next) { /* void */ }
无限循环看起来像这样:
for ( ;; ) { ... }
标签
标签由空行包围,并在上一级缩进:
if (i == 0) { u->err = "host not found"; goto failed; } u->addrs = ngx_pcalloc(pool, i * sizeof(ngx_addr_t)); if (u->addrs == NULL) { goto failed; } u->naddrs = i; ... return NGX_OK; failed: freeaddrinfo(res); return NGX_ERROR;
调试内存问题
要调试内存问题(例如缓冲区溢出或释放后使用错误),您可以使用某些现代编译器支持的
AddressSanitizer (ASan)。gcc
要使用和启用 ASan clang
,请使用-fsanitize=address
编译器和链接器选项。在构建 nginx 时,可以通过向脚本添加选项
--with-cc-opt
和--with-ld-opt
参数来完成此操作configure
。
由于 nginx 中的大部分分配都是由 nginx 内部
池进行的,因此启用 ASan 可能并不总是足以调试内存问题。内部池从系统中分配大块内存,并从中削减较小的分配。NGX_DEBUG_PALLOC
但是,可以通过将宏设置为 来禁用此机制
1
。在这种情况下,分配直接传递给系统分配器,使其完全控制缓冲区边界。
以下配置行总结了上面提供的信息。建议在开发第三方模块以及在不同平台上测试 nginx 时使用。
auto/configure --with-cc-opt='-fsanitize=address -DNGX_DEBUG_PALLOC=1' --with-ld-opt=-fsanitize=address
常见陷阱
编写 C 模块
最常见的陷阱是在可以避免的情况下尝试编写成熟的 C 模块。在大多数情况下,您可以通过创建正确的配置来完成您的任务。如果编写模块是不可避免的,请尝试使其尽可能小且简单。例如,一个模块只能导出一些 变量。
在开始模块之前,请考虑以下问题:
C 弦乐
ngx_str_t
是 nginx 中最常用的字符串类型,
它不是 C 风格的以零结尾的字符串。您无法将数据传递给标准 C 库函数,例如strlen()
或strstr()
。
相反,应该使用接受的nginx对应项或指向数据和长度的指针。ngx_str_t
但是,存在一种情况,其中ngx_str_t
包含指向以零结尾的字符串的指针:作为配置文件解析结果的字符串是以零结尾的。
全局变量
避免在模块中使用全局变量。这很可能是使用全局变量的错误。任何全局数据都应该与配置周期相关联并从相应的内存池中 分配。这允许 nginx 执行优雅的配置重新加载。尝试使用全局变量可能会破坏此功能,因为不可能同时拥有两种配置并摆脱它们。有时需要全局变量。在这种情况下,需要特别注意正确管理重新配置。另外,检查代码使用的库是否具有隐式全局状态,该状态可能在重新加载时被破坏。
手动内存管理
学习如何使用 nginx池,而不是处理容易出错的 malloc/free 方法。创建池并将其绑定到对象 配置、 周期、 连接或HTTP 请求。当对象被销毁时,关联的池也会被销毁。因此,在使用对象时,可以从相应的池中分配所需的数量,并且即使在出现错误时也不关心释放内存。
线程数
建议避免在 nginx 中使用线程,因为它肯定会破坏事情:大多数 nginx 函数都不是线程安全的。预计线程将仅执行系统调用和线程安全库函数。如果您需要运行一些与客户端请求处理无关的代码,正确的方法是在init_process
模块处理程序中调度计时器并在计时器处理程序中执行所需的操作。nginx 内部使用线程来增强 IO 相关操作,但这是一个特殊情况,有很多限制。
阻止库
一个常见的错误是使用内部阻塞的库。大多数库本质上都是同步和阻塞的。换句话说,它们一次执行一项操作,并浪费时间等待其他对等点的响应。结果,当使用这样的库处理请求时,整个 nginx 工作线程被阻塞,从而破坏了性能。仅使用提供异步接口并且不会阻止整个过程的库。
对外部服务的 HTTP 请求
通常,模块需要对某些外部服务执行 HTTP 调用。一个常见的错误是使用一些外部库(例如 libcurl)来执行 HTTP 请求。完全没有必要为 nginx 本身可以完成的任务 带来大量的外部(可能是阻塞!)代码。
需要外部请求时有两种基本的使用场景:
- 在处理客户端请求的上下文中(例如,在内容处理程序中)
- 在工作进程的上下文中(例如,计时器处理程序)
对于第一种情况,最好是使用 子请求 API。您不是直接访问外部服务,而是在 nginx 配置中声明一个位置并将子请求定向到该位置。该位置不仅限于 代理 请求,还可能包含其他 nginx 指令。这种方法的一个例子是 ngx_http_auth_request 模块 中实现的 auth_request指令。
对于第二种情况,可以使用 nginx 中提供的基本 HTTP 客户端功能。例如, OCSP模块 实现了简单的HTTP客户端。