开发指南
Introduction
Code layout
auto
- 构建脚本SRC T0>
core
- 基本类型和函数 - 字符串,数组,日志,池等事件
- 事件核心modules
- 事件通知模块:epoll
,kqueue
,选择
等。
http
- 核心HTTP模块和通用代码modules
- 其他HTTP模块v2
- HTTP / 2
邮件
- 邮件模块os
- 平台特定的代码UNIX T0>
的win32 T0>
stream
- 流模块
Include files
以下两个#include
语句必须出现在每个nginx文件的开头:
#include <ngx_config.h> #include <ngx_core.h>
除此之外,HTTP代码应该包括
#include <ngx_http.h>
邮件代码应包括
#include <ngx_mail.h>
流代码应该包括
#include <ngx_stream.h>
Integers
对于一般目的,nginx代码使用两个整数类型,ngx_int_t
和ngx_uint_t
,它们是intptr_t
和uintptr_t
的typedef分别。
Common return codes
nginx中的大多数函数返回以下代码:
NGX_OK
- 操作成功。NGX_ERROR
- 操作失败。NGX_AGAIN
- 操作不完整;再次调用该功能。NGX_DECLINED
- 操作被拒绝,例如,因为在配置中被禁用。这从来不是错误。NGX_BUSY
- 资源不可用。NGX_DONE
- 完成操作或在其他地方继续操作。也用作替代成功代码。NGX_ABORT
- 功能已中止。也用作替代错误代码。
Error handling
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_errno
和ngx_socket_errno
的值传递到记录功能ngx_log_error()
和ngx_log_debugX()
哪些情况下系统错误文本被添加到日志消息中。
使用ngx_errno
的示例:
void ngx_my_kill(ngx_pid_t pid, ngx_log_t *log, int signo) { ngx_err_t err; if (kill(pid, signo) == -1) { err = ngx_errno; ngx_log_error(NGX_LOG_ALERT, log, err, "kill(%P, %d) failed", pid, signo); if (err == NGX_ESRCH) { return 2; } return 1; } return 0; }
Strings
Overview
对于C字符串,nginx使用无符号字符类型指针u_char *
。
nginx字符串类型ngx_str_t
定义如下:
typedef struct { size_t len; u_char *data; } ngx_str_t;
len
字段保存字符串长度,数据
保存字符串数据。保存在ngx_str_t
中的字符串在len
字节之后可能是或不是以null结尾。在大多数情况下,它不是。然而,在代码的某些部分(例如,在解析配置时),ngx_str_t
对象已知为空终止,这简化了字符串比较,并使得更容易将字符串传递给系统调用。
nginx中的字符串操作在src / core / ngx_string.h
中声明。其中一些是围绕标准C函数的包装器:
ngx_strcmp() T0>
ngx_strncmp() T0>
ngx_strstr() T0>
ngx_strlen() T0>
ngx_strchr() T0>
ngx_memcmp() T0>
ngx_memset() T0>
ngx_memcpy() T0>
ngx_memmove() T0>
其他字符串函数是nginx特定的
ngx_memzero()
- 使用零填充内存。ngx_cpymem()
- 与ngx_memcpy()
相同,但返回最终的目标地址这可以方便地在行中附加多个字符串。ngx_movemem()
- 与ngx_memmove()
相同,但返回最终的目标地址。ngx_strlchr()
- 搜索字符串中的一个字符,由两个指针分隔。
以下功能执行案例转换和比较:
ngx_tolower() T0>
ngx_toupper() T0>
ngx_strlow() T0>
ngx_strcasecmp() T0>
ngx_strncasecmp() T0>
以下宏可简化字符串初始化:
ngx_string(text)
- C字符串文字文本
中ngx_str_t
类型的静态初始化程序ngx_null_string
-ngx_str_t
类型的静态空字符串初始化程序ngx_str_set(str,text)
- 使用C字符串文字文本
初始化ngx_str_t *
类型的字符串str
ngx_str_null(str)
- 使用空字符串初始化ngx_str_t *
类型的字符串str
Formatting
以下格式化功能支持nginx特定类型:
ngx_sprintf(buf,fmt,...)
ngx_snprintf(buf,max,fmt,...)
ngx_slprintf(buf,last,fmt,...)
ngx_vslprint(buf,last,fmt,args)
ngx_vsnprint(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 *
(以null结尾)%* 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;
Numeric conversion
用于数字转换的几个函数在nginx中实现。前四个将给定长度的字符串转换为指定类型的正整数。他们返回NGX_ERROR
出错。
ngx_atoi(line,n)
-ngx_int_t
ngx_atosz(line,n)
-ssize_t
ngx_atoof(line,n)
-off_t
ngx_atotm(line,n)
-time_t
还有两个额外的数字转换功能。像前四个一样,他们返回NGX_ERROR
出错。
ngx_atofp(line,n,point)
- 将给定长度的固定点浮点数转换为类型为ngx_int_t
的正整数。结果向左移动点
小数位。数字的字符串表示预计不超过个小数位数。
例如,ngx_atofp(“10.5”,4,2)
返回1050
。ngx_hextoi(line,n)
- 将正整数的十六进制表示转换为ngx_int_t
。
Regular expressions
nginx中的正则表达式接口是PCRE库之间的包装器。相应的头文件是src / core / ngx_regex.h
。
要使用正则表达式进行字符串匹配,首先需要进行编译,这通常在配置阶段完成。请注意,由于PCRE支持是可选的,因此使用该接口的所有代码必须由周围的NGX_PCRE
宏保护:
#if (NGX_PCRE) ngx_regex_t *re; ngx_regex_compile_t rc; u_char errstr[NGX_MAX_CONF_ERRSTR]; ngx_str_t value = ngx_string("message (\\d\\d\\d).*Codeword is '(?<cw>\\w+)'"); ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); rc.pattern = value; rc.pool = cf->pool; rc.err.len = NGX_MAX_CONF_ERRSTR; rc.err.data = errstr; /* rc.options are passed as is to pcre_compile() */ if (ngx_regex_compile(&rc) != NGX_OK) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err); return NGX_CONF_ERROR; } re = rc.regex; #endif
编译成功后,ngx_regex_compile_t
结构中的捕获
和named_captures
字段包含分别在常规中发现的所有捕获和命名捕获的计数表达。
然后可以使用编译的正则表达式来匹配字符串:
ngx_int_t n; int captures[(1 + rc.captures) * 3]; ngx_str_t input = ngx_string("This is message 123. Codeword is 'foobar'."); n = ngx_regex_exec(re, &input, captures, (1 + rc.captures) * 3); if (n >= 0) { /* string matches expression */ } else if (n == NGX_REGEX_NO_MATCHED) { /* no match was found */ } else { /* some error */ ngx_log_error(NGX_LOG_ALERT, log, 0, ngx_regex_exec_n " failed: %i", n); }
ngx_regex_exec()
的参数是编译的正则表达式re
,与s
匹配的字符串,一个可选的整数数组,用于保存任何捕获
,以及数组的大小
。根据PCRE API的要求,捕获
数组的大小必须为3的倍数。在该示例中,从匹配的字符串本身的捕获总数加上1
计算出大小。
如果有匹配,则可以按如下方式访问捕获:
u_char *p; size_t size; ngx_str_t name, value; /* all captures */ for (i = 0; i < n * 2; i += 2) { value.data = input.data + captures[i]; value.len = captures[i + 1] — captures[i]; } /* accessing named captures */ size = rc.name_size; p = rc.names; for (i = 0; i < rc.named_captures; i++, p += size) { /* capture name */ name.data = &p[2]; name.len = ngx_strlen(name.data); n = 2 * ((p[0] << 8) + p[1]); /* captured value */ value.data = &input.data[captures[n]]; value.len = captures[n + 1] — captures[n]; }
ngx_regex_exec_array()
函数接受ngx_regex_elt_t
元素的数组(它们只是使用关联名称编译的正则表达式),要匹配的字符串和日志。该函数将表达式从数组应用到字符串,直到找到一个匹配或者不再有其他表达式。否则返回值为NGX_OK
,否则为NGX_DECLINED
,否则为NGX_ERROR
。
Time
ngx_time_t
结构表示三个不同类型的秒,毫秒和GMT偏移量的时间:
typedef struct { time_t sec; ngx_uint_t msec; ngx_int_t gmtoff; } ngx_time_t;
ngx_tm_t
结构是UNIX平台上的struct tm
和Windows上的SYSTEMTIME
的别名。
为了获得当前时间,通常可以访问一个可用的全局变量,表示所需格式的缓存时间值。例如,ngx_current_msec
变量保存自Epoch以来所经过的毫秒数,并截断为ngx_msec_t
。
可用的字符串表示形式是:
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
- 用于syslog条目:“Sep 28 12:00:00”
ngx_cached_http_time
- 用于HTTP标头:“1970年9月28日星期一06:00:00 GMT”
ngx_cached_http_log_iso8601
- ISO 8601标准格式:“1970-09-28T12:00:00 + 06:00”
The ngx_time()
and ngx_timeofday()
macros return the current time value in seconds and are the preferred way to access the cached time value.
要明确获取时间,请使用ngx_gettimeofday()
,该更新其参数(指向struct timeval
的指针)。当nginx从系统调用返回到事件循环时,时间总是被更新。要立即更新时间,如果更新信号处理程序上下文中的时间,请调用ngx_time_update()
或ngx_time_sigsafe_update()
。
以下函数将time_t
转换为指定的分解时间表示。每一对中的第一个函数将time_t
转换为ngx_tm_t
,将第二个(使用_libc _
中缀)转换为struct tm
:
ngx_gmtime(),ngx_libc_gmtime()
- 以UTC表示的时间ngx_localtime(),ngx_libc_localtime()
- 相对于本地时区表示的时间
ngx_http_time(buf,time)
函数返回适用于HTTP标头的字符串表示形式(例如,“1970年9月28日星期一06:00:00 GMT”
) 。ngx_http_cookie_time(buf,time)
返回字符串表示函数返回适用于HTTP Cookie的字符串表示(“Thu,31-Dec-37 23:55:55 GMT”
)。
Containers
Array
nginx数组类型ngx_array_t
定义如下
typedef struct { void *elts; ngx_uint_t nelts; size_t size; ngx_uint_t nalloc; ngx_pool_t *pool; } ngx_array_t;
阵列的元素在elts
字段中可用。nelts
字段包含元素的数量。size
字段保存单个元素的大小,并在数组初始化时设置。
使用ngx_array_create(pool,n,size)
调用在池中创建一个数组,并调用ngx_array_init(array,pool,n,size)
调用初始化数组已经分配的对象。
ngx_array_t *a, b; /* create an array of strings with preallocated memory for 10 elements */ a = ngx_array_create(pool, 10, sizeof(ngx_str_t)); /* initialize string array for 10 elements */ ngx_array_init(&b, pool, 10, sizeof(ngx_str_t));
使用以下函数将元素添加到数组:
ngx_array_push(a)
添加一个尾元素并返回指针ngx_array_push_n(a,n)
添加n
尾元素,并返回指向第一个元素的指针
如果当前分配的内存量不足以容纳新元素,则分配新的内存块并且将现有元素复制到其中。新的内存块通常是现有存储块的两倍。
s = ngx_array_push(a); ss = ngx_array_push_n(&b, 3);
List
在nginx中,列表是一系列数组,针对插入潜在的大量项目进行了优化。ngx_list_t
列表类型定义如下:
typedef struct { ngx_list_part_t *last; ngx_list_part_t part; size_t size; ngx_uint_t nalloc; ngx_pool_t *pool; } ngx_list_t;
实际项目存储在列表部分中,定义如下:
typedef struct ngx_list_part_s ngx_list_part_t; struct ngx_list_part_s { void *elts; ngx_uint_t nelts; ngx_list_part_t *next; };
在使用之前,必须通过调用ngx_list_init(list,pool,n,size)
或通过调用ngx_list_create(pool,n,size)
创建列表。两个函数都将参数作为单个项目的大小和每个列表部分的项目数量。要将项目添加到列表中,请使用ngx_list_push(list)
函数。要迭代项目,直接访问列表字段,如示例所示:
ngx_str_t *v; ngx_uint_t i; ngx_list_t *list; ngx_list_part_t *part; list = ngx_list_create(pool, 100, sizeof(ngx_str_t)); if (list == NULL) { /* error */ } /* add items to the list */ v = ngx_list_push(list); if (v == NULL) { /* error */ } ngx_str_set(v, "foo"); v = ngx_list_push(list); if (v == NULL) { /* error */ } ngx_str_set(v, "bar"); /* iterate over the list */ part = &list->part; v = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; v = part->elts; i = 0; } ngx_do_smth(&v[i]); }
列表主要用于HTTP输入和输出标题。
列表不支持删除项目。但是,当需要时,项目可以在内部被标记为丢失,而实际上不会从列表中删除。例如,要将HTTP输出标题(存储为ngx_table_elt_t
对象)标记为缺失,请将ngx_table_elt_t
中的哈希
字段设置为零。当标题被迭代时,以这种方式标记的项目被显式地跳过。
Queue
在nginx中,一个队列是一个入侵的双向链表,每个节点定义如下:
typedef struct ngx_queue_s ngx_queue_t; struct ngx_queue_s { ngx_queue_t *prev; ngx_queue_t *next; };
头部队列节点不与任何数据链接。使用ngx_queue_init(q)
调用初始化列表头。队列支持以下操作:
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)
- 获取队列sentinel对象以结束迭代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); }
Red-Black tree
src / core / ngx_rbtree.h
头文件提供对红黑树的有效实现的访问。
typedef struct { ngx_rbtree_t rbtree; ngx_rbtree_node_t sentinel; /* custom per-tree data here */ } my_tree_t; typedef struct { ngx_rbtree_node_t rbnode; /* custom per-node data */ foo_t val; } my_node_t;
要处理整个树,您需要两个节点:root和sentinel。通常,它们被添加到自定义结构中,允许您将数据组织到树中,其中叶包含指向或嵌入数据的链接。
初始化树:
my_tree_t root; ngx_rbtree_init(&root.rbtree, &root.sentinel, insert_value_function);
要遍历树并插入新值,请使用“insert_value
”函数。例如,ngx_str_rbtree_insert_value
函数处理ngx_str_t
类型。它的参数是指向插入的根节点的指针,要添加的新创建的节点和树哨。
void ngx_str_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
遍历非常简单,可以使用以下查找功能模式进行演示:
my_node_t * my_rbtree_lookup(ngx_rbtree_t *rbtree, foo_t *val, uint32_t hash) { ngx_int_t rc; my_node_t *n; ngx_rbtree_node_t *node, *sentinel; node = rbtree->root; sentinel = rbtree->sentinel; while (node != sentinel) { n = (my_node_t *) node; if (hash != node->key) { node = (hash < node->key) ? node->left : node->right; continue; } rc = compare(val, node->val); if (rc < 0) { node = node->left; continue; } if (rc > 0) { node = node->right; continue; } return n; } return NULL; }
compare()
函数是一个经典的比较器函数,返回小于等于或大于零的值。为了加快查找速度,并避免比较可能较大的用户对象,使用整数散列字段。
要将一个节点添加到树中,请分配一个新节点,将其初始化并调用ngx_rbtree_insert()
:
my_node_t *my_node; ngx_rbtree_node_t *node; my_node = ngx_palloc(...); init_custom_data(&my_node->val); node = &my_node->rbnode; node->key = create_key(my_node->val); ngx_rbtree_insert(&root->rbtree, node);
要删除节点,请调用ngx_rbtree_delete()
函数:
ngx_rbtree_delete(&root->rbtree, node);
Hash
哈希表函数在src / core / ngx_hash.h
中声明。支持精确和通配符匹配。后者需要额外的设置,并在下面的单独部分中进行描述。
在初始化哈希之前,您需要知道它将保存的元素数量,以便nginx可以最佳地构建它。需要配置的两个参数是max_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;
键
是一个指向从字符串创建哈希整数键的函数的指针。有两个通用的键创建功能: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 */ }
Wildcard matching
要创建使用通配符的哈希,请使用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);
Memory management
Heap
要从系统堆分配内存,请使用以下功能:
ngx_alloc(size,log)
- 从系统堆分配内存。这是一个包含malloc()
的记录支持。分配错误和调试信息记录到log
。ngx_calloc(size,log)
- 从系统堆分配内存,如ngx_alloc()
,但在分配后用零填充内存。ngx_memalign(alignment,size,log)
- 从系统堆分配对齐的内存。这是在提供该功能的那些平台上的posix_memalign()
之间的包装器。否则,实现将返回到提供最大对齐的ngx_alloc()
。ngx_free(p)
- 释放内存。这是一个free()
之间的包装器
Pool
大多数nginx分配是在池中完成的。当池被破坏时,在nginx池中分配的内存会被自动释放。这提供了良好的分配性能,使内存控制变得容易。
池内部将连续的内存块分配对象。一旦块已满,将分配一个新的块并将其添加到池内存块列表。当请求的分配太大而不能嵌入块时,请求被转发到系统分配器,并且返回的指针被存储在池中用于进一步的解除分配。
nginx池的类型是ngx_pool_t
。支持以下操作:
ngx_create_pool(size,log)
- 创建一个指定块大小的池。返回的池对象也在池中分配。ngx_destroy_pool(pool)
- 释放所有的池内存,包括池对象本身。ngx_palloc(pool,size)
- 从指定的池中分配对齐的内存。ngx_pcalloc(pool,size)
- 从指定的池中分配对齐的内存并将其填充为零。ngx_pnalloc(pool,size)
- 从指定的池中分配未对齐的内存。主要用于分配字符串。ngx_pfree(pool,p)
- 先前在指定池中分配的空闲内存。只有转发到系统分配器的请求产生的分配才能被释放。
u_char *p; ngx_str_t *s; ngx_pool_t *pool; pool = ngx_create_pool(1024, log); if (pool == NULL) { /* error */ } s = ngx_palloc(pool, sizeof(ngx_str_t)); if (s == NULL) { /* error */ } ngx_str_set(s, "foo"); p = ngx_pnalloc(pool, 3); if (p == NULL) { /* error */ } ngx_memcpy(p, "foo", 3);
链接(ngx_chain_t
)在nginx中被主动使用,所以nginx池实现提供了一种重用它们的方式。ngx_pool_t
的链
字段保留了先前分配的链接的列表,以便重新使用。为了在池中有效分配链式链接,请使用ngx_alloc_chain_link(pool)
函数。此函数在池列表中查找一个空闲链接,如果池列表为空,则分配一个新的链接。要释放链接,请调用ngx_free_chain(pool,cl)
函数。
清理处理程序可以在池中注册。一个清理处理程序是一个带有参数的回调函数,当池被销毁时被调用。池通常绑定到特定的nginx对象(如HTTP请求),并在对象达到其生命周期结束时被销毁。注册池清理是释放资源,关闭文件描述符或对与主对象相关联的共享数据进行最终调整的便捷方式。
要注册池清理,请调用ngx_pool_cleanup_add(pool,size)
,返回一个要由调用者填写的ngx_pool_cleanup_t
指针。使用size
参数为清理处理程序分配上下文。
ngx_pool_cleanup_t *cln; cln = ngx_pool_cleanup_add(pool, 0); if (cln == NULL) { /* error */ } cln->handler = ngx_my_cleanup; cln->data = "foo"; ... static void ngx_my_cleanup(void *data) { u_char *msg = data; ngx_do_smth(msg); }
Shared memory
共享内存由nginx用于在进程之间共享公共数据。ngx_shared_memory_add(cf,name,size,tag)
函数将一个新的共享内存条目ngx_shm_zone_t
添加到一个循环中。该函数接收区域的名称
和大小
。每个共享区域必须具有唯一的名称。如果提供的名称
和标签
的共享区域条目已经存在,则现有的区域条目将被重用。如果具有相同名称的现有条目具有不同的标签,则该函数将失败并显示错误。通常,模块结构的地址作为标签
传递,使得可以在一个nginx模块内按名称重用共享区域。
共享内存条目结构ngx_shm_zone_t
具有以下字段:
init
- 初始化回调,在共享区域映射到实际内存后调用数据
- 用于将任意数据传递到init
回调的数据上下文noreuse
- 禁止从旧循环重用共享区域的标志标签
- 共享区域标签shm
- 类型为ngx_shm_t
的平台特定对象至少包含以下字段:addr
- 映射的共享内存地址,最初为NULLsize
- 共享内存大小名称
- 共享内存名称log
- 共享内存日志存在
- 表示共享内存的标志是从主进程继承的(Windows特定的)
解析配置后,共享区域条目将映射到ngx_init_cycle()
中的实际内存。在POSIX系统上,使用mmap()
系统调用来创建共享匿名映射。在Windows上,使用CreateFileMapping()
/ MapViewOfFileEx()
对。
为了在共享内存中分配,nginx提供了slab池ngx_slab_pool_t
类型。在每个nginx共享区域中自动创建用于分配内存的slab池。池位于共享区域的开头,可以通过(ngx_slab_pool_t *)shm_zone-&gt; shm.addr
访问。要在共享区域中分配内存,请调用ngx_slab_alloc(pool,size)
或ngx_slab_calloc(pool,size)
。要释放内存,请调用ngx_slab_free(pool,p)
。
平板池将所有共享区域划分成页面。每个页面用于分配相同大小的对象。指定的大小必须是2的幂,大于8字节的最小大小。不符合值被四舍五入。每个页面的位掩码会跟踪哪些块正在使用,哪些块可用于分配。对于大于一半页(通常为2048字节)的大小,一次完成整个页面的分配
要保护共享内存中的数据不受并发访问,请使用ngx_slab_pool_t
的mutex
字段中提供的互斥体。互斥锁在平台池中最常用于分配和释放内存,但它可以用于保护在共享区域中分配的任何其他用户数据结构。要锁定或解锁互斥锁,请分别调用ngx_shmtx_lock(&amp; shpool-&gt; mutex)
或ngx_shmtx_unlock(&amp; shpool-&gt; mutex)
。
ngx_str_t name; ngx_foo_ctx_t *ctx; ngx_shm_zone_t *shm_zone; ngx_str_set(&name, "foo"); /* allocate shared zone context */ ctx = ngx_pcalloc(cf->pool, sizeof(ngx_foo_ctx_t)); if (ctx == NULL) { /* error */ } /* add an entry for 64k shared zone */ shm_zone = ngx_shared_memory_add(cf, &name, 65536, &ngx_foo_module); if (shm_zone == NULL) { /* error */ } /* register init callback and context */ shm_zone->init = ngx_foo_init_zone; shm_zone->data = ctx; ... static ngx_int_t ngx_foo_init_zone(ngx_shm_zone_t *shm_zone, void *data) { ngx_foo_ctx_t *octx = data; size_t len; ngx_foo_ctx_t *ctx; ngx_slab_pool_t *shpool; value = shm_zone->data; if (octx) { /* reusing a shared zone from old cycle */ ctx->value = octx->value; return NGX_OK; } shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; if (shm_zone->shm.exists) { /* initialize shared zone context in Windows nginx worker */ ctx->value = shpool->data; return NGX_OK; } /* initialize shared zone */ ctx->value = ngx_slab_alloc(shpool, sizeof(ngx_uint_t)); if (ctx->value == NULL) { return NGX_ERROR; } shpool->data = ctx->value; return NGX_OK; }
Logging
对于日志记录,nginx使用ngx_log_t
对象。nginx记录器支持多种类型的输出:
- stderr - 记录到标准错误(stderr)
- 文件 - 记录到文件
- syslog - 记录到syslog
- 记忆 - 记录到内部存储器用于开发目的;内存可以稍后通过调试器进行访问
记录器实例可以是通过下一个
字段彼此链接的记录器链。在这种情况下,每条消息都将写入链中的所有记录器。
对于每个记录器,严重性级别控制将哪些消息写入日志(仅记录分配了该级别或更高级别的事件)。支持以下严重性级别:
NGX_LOG_EMERG T0>
NGX_LOG_ALERT T0>
NGX_LOG_CRIT T0>
NGX_LOG_ERR T0>
NGX_LOG_WARN T0>
NGX_LOG_NOTICE T0>
NGX_LOG_INFO T0>
NGX_LOG_DEBUG T0>
对于调试日志记录,也会检查调试掩码。调试掩码是:
NGX_LOG_DEBUG_CORE T0>
NGX_LOG_DEBUG_ALLOC T0>
NGX_LOG_DEBUG_MUTEX T0>
NGX_LOG_DEBUG_EVENT T0>
NGX_LOG_DEBUG_HTTP T0>
NGX_LOG_DEBUG_MAIL T0>
NGX_LOG_DEBUG_STREAM T0>
通常,记录器是由error_log
指令中的现有nginx代码创建的,并且可以在循环,配置,客户端连接和其他对象的几乎每个处理阶段都可用。
Nginx提供以下日志记录宏:
ngx_log_error(level,log,err,fmt,...)
- 记录错误ngx_log_debug0(level,log,err,fmt)
,ngx_log_debug1(level,log,err,fmt,arg1)
etc - 使用最多八个支持的格式参数来调试日志记录
日志消息格式化为堆栈中大小为NGX_MAX_ERROR_STR
(当前为2048字节)的缓冲区。该消息前面附有严重性级别,进程ID(PID),连接ID(存储在log-&gt;连接
中)和系统错误文本。For non-debug messages log->handler
is called as well to prepend more specific information to the log message. HTTP模块将ngx_http_log_error()
作为日志处理程序设置为日志客户端和服务器地址,当前操作(存储在log-&gt; action
),客户端请求行,服务器名称等。
/* specify what is currently done */ log->action = "sending mp4 to client”; /* error and debug log */ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client prematurely closed connection”); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 start:%ui, length:%ui”, mp4->start, mp4->length);
上面的示例导致如下所示的日志条目:
2016/09/16 22:08:52 [info] 17445#0: *1 client prematurely closed connection while sending mp4 to client, client: 127.0.0.1, server: , request: "GET /file.mp4 HTTP/1.1” 2016/09/16 23:28:33 [debug] 22140#0: *1 mp4 start:0, length:10000
Cycle
循环对象存储从特定配置创建的nginx运行时上下文。它的类型是ngx_cycle_t
。当前循环由ngx_cycle
全局变量引用,并由nginx工作人员启动时继承。每次重新加载nginx配置,都会从新的nginx配置创建一个新的循环;旧循环通常在新的循环被成功创建之后被删除。
一个周期由ngx_init_cycle()
函数创建,该函数以前一个循环为参数。该函数定位上一个循环的配置文件,并从上一个循环继承尽可能多的资源。一个称为“init cycle”的占位符循环被创建为nginx启动,然后被由配置构建的实际循环替换。
周期成员包括:
池
- 循环池。为每个新循环创建。log
- 循环日志。最初从旧循环继承,读取配置后,设置为指向new_log
。new_log
- 由配置创建的循环日志。它受到根范围error_log
指令的影响。连接
,connection_n
- 初始化每个nginx工作者时由事件模块创建的类型为ngx_connection_t
的连接数组。nginx配置中的worker_connections
指令设置连接数connection_n
。free_connections
,free_connection_n
- 当前可用连接的列表和编号。如果没有连接可用,则nginx工作程序拒绝接受新客户端或连接到上游服务器。文件
,files_n
- 用于将文件描述符映射到nginx连接的数组。事件模块使用该映射,具有NGX_USE_FD_EVENT
标志(当前为poll
和devpoll
)。conf_ctx
- 核心模块配置阵列。在读取nginx配置文件期间创建和填充配置。modules
,modules_n
- 类型为ngx_module_t
的模块,由静态和动态组成,由当前配置加载。listen
- 类型为ngx_listening_t
的侦听对象的数组。通常由调用ngx_create_listening()
函数的不同模块的listen
指令添加侦听对象。监听套接字是基于侦听对象创建的。路径
- 类型为ngx_path_t
的路径数组。通过从某些目录上要运行的模块调用函数ngx_add_path()
来添加路径。这些目录是由nginx在读取配置后创建的,如果缺少。此外,可以为每个路径添加两个处理程序:- 路径加载程序 - 在启动或重新加载nginx后的60秒内只执行一次。通常,加载器读取目录并将数据存储在nginx共享内存中。处理程序从专用的nginx进程“nginx缓存加载程序”中调用。
- 路径管理器 - 定期执行。通常,管理员从目录中删除旧文件,并更新nginx内存以反映更改。处理程序从专用的“nginx缓存管理器”进程调用。
open_files
- 通过调用函数ngx_conf_open_file()
创建的类型为ngx_open_file_t
的打开的文件对象的列表。目前,nginx使用这种开放文件进行日志记录。读取配置后,nginx打开open_files
列表中的所有文件,并将每个文件描述符存储在对象的fd
字段中。这些文件以附加模式打开,如果丢失则创建。列表中的文件在接收到重新打开信号(最常见USR1
)时由nginx工作人员重新打开。在这种情况下,fd
字段中的描述符将更改为新值。shared_memory
- 共享内存区域列表,每个区域都通过调用ngx_shared_memory_add()
函数来添加。共享区域映射到所有nginx进程中的相同地址范围,并用于共享公共数据,例如HTTP缓存内存中的树。
Buffer
对于输入/输出操作,nginx提供缓冲区类型ngx_buf_t
。通常,它用于保存要写入目的地的数据或从源读取。缓冲区可以引用内存或文件中的数据,而且在技术上可以让缓冲区同时引用两者。缓冲区的内存分开分配,与缓冲区结构ngx_buf_t
无关。
ngx_buf_t
结构具有以下字段:
start
,end
- 分配给缓冲区的内存块的边界。pos
,last
- 内存缓冲区的边界;通常是起始
..结束
的子范围。file_pos
,file_last
- 文件缓冲区的边界,以文件开头的偏移量表示。标签
- 用于区分缓冲区的唯一值;由不同的nginx模块创建,通常用于缓冲区重用。file
- 文件对象。temporary
- 表示缓冲区引用可写内存的标志。内存
- 表示缓冲区引用只读存储器的标志。in_file
- 表示缓冲区引用文件中的数据的标志。flush
- 表示缓冲区之前的所有数据需要刷新的标志。回收
- 表示缓冲区可以重新使用并需要尽快消费的标志。sync
- 指示缓冲区不携带数据或特殊信号(如flush
或last_buf
的标志。默认情况下,nginx认为这样的缓冲区是一个错误条件,但这个标志告诉nginx跳过错误检查。last_buf
- 表示缓冲区是输出中的最后一个的标志。last_in_chain
- 表示请求或子请求中没有更多数据缓冲区的标志。shadow
- 引用与当前缓冲区相关的另一个(“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; }
Networking
Connection
连接类型ngx_connection_t
是围绕套接字描述符的包装器。它包括以下字段:
fd
- 套接字描述符数据
- 任意连接上下文。通常,它是指向连接上建立的较高级别对象的指针,例如HTTP请求或Stream会话。读取
,写入
- 读取连接的事件。recv
,发送
,recv_chain
,send_chain
- 连接的I / O操作。池
- 连接池。log
- 连接日志。sockaddr
,socklen
,addr_text
- 二进制和文本形式的远程套接字地址。local_sockaddr
,local_socklen
- 二进制形式的本地套接字地址。最初,这些字段是空的。使用ngx_connection_local_sockaddr()
函数获取本地套接字地址。proxy_protocol_addr
,proxy_protocol_port
- PROXY协议客户端地址和端口,如果PROXY协议已启用连接。ssl
- 连接的SSL上下文。可重用
- 表示连接处于使其有资格重新使用的状态的标志。关闭
- 表示连接正在重复使用并需要关闭的标志。
nginx连接可以透明地封装SSL层。In this case the connection's ssl
field holds a pointer to an ngx_ssl_connection_t
structure, keeping all SSL-related data for the connection, including SSL_CTX
and SSL
. recv
,发送
,recv_chain
和send_chain
处理程序也设置为启用SSL的功能。
nginx配置中的worker_connections
指令限制每个nginx worker的连接数。当工作人员启动并存储在循环对象的连接
字段中时,将对所有连接结构进行预处理。要检索连接结构,请使用ngx_get_connection(s,log)
函数。它将参数作为套接字描述符,它需要被包装在一个连接结构中。
因为每个工作人员的连接数量有限,所以nginx提供了一种抓取当前正在使用的连接的方法。要启用或禁用连接的重用,请调用ngx_reusable_connection(c,可重用)
功能。调用ngx_reusable_connection(c,1)
在连接结构中设置重用
标志,并将连接插入到循环的reusable_connections_queue
中。每当ngx_get_connection()
发现循环的free_connections
列表中没有可用的连接时,它会调用ngx_drain_connections()
以释放特定数量的可重用连接。对于每个这样的连接,设置关闭
标志,并且调用其读取处理程序,通过调用ngx_close_connection(c)
使其可以重新使用,释放连接。当连接可被重用时退出状态ngx_reusable_connection(c,0)
被调用。HTTP客户端连接是nginx中可重用连接的示例;它们被标记为可重用,直到从客户端接收到第一个请求字节为止。
Events
Event
nginx中的事件对象ngx_event_t
提供了一种特定事件发生的通知机制。
ngx_event_t
中的字段包括:
data
- 事件处理程序中使用的任意事件上下文,通常指向与事件相关的连接的指针。handler
- 事件发生时调用的回调函数。写
- 表示写入事件的标志。没有标志表示读取事件。活动
- 表示事件已注册用于接收I / O通知的标志,通常来自通知机制,如epoll
,kqueue
,T3>。
ready
- 表示事件已收到I / O通知的标志。延迟
- 表示I / O由于速率限制而延迟的标志。定时器
- 用于将事件插入定时器树中的红黑树节点。timer_set
- 表示事件定时器已设置但尚未过期的标志。timedout
- 表示事件计时器已过期的标志。eof
- 表示读取数据时发生EOF的标志。pending_eof
- 表示EOF在套接字上挂起的标志,即使可能有一些数据可用。标志通过EPOLLRDHUP
epoll
事件或EV_EOF
kqueue
标志传送。错误
- 表示在读取(读取事件)或写入(写入事件)期间发生错误的标志。可取消
- 定时器事件标志,表示在关闭工作时应忽略该事件。正常的工作人员关机延迟,直到没有不可取消的计时器事件被安排。posted
- 表示事件发布到队列的标志。queue
- 用于将事件发布到队列的队列节点。
I/O events
通过调用ngx_get_connection()
函数获得的每个连接都有两个附加事件,即c-> read
和c-> write
用于接收套接字准备好读取或写入的通知。所有这些事件都以边沿触发模式运行,这意味着只有当套接字状态发生变化时,才会触发通知。例如,在套接字上进行部分读取不会使nginx传递重复的读取通知,直到更多的数据到达套接字。即使底层I / O通知机制本质上是Level-Triggered(poll
,选择
等),nginx将通知转换为Edge-Triggered。为使nginx事件通知在不同平台上的所有通知系统保持一致,必须在处理I之后调用函数ngx_handle_read_event(rev,flags)
和ngx_handle_write_event(wev,lowat)
/ O套接字通知或调用该套接字上的任何I / O功能。通常,函数在每个读或写事件处理程序结束时调用一次。
Timer events
事件可以设置为在超时到期时发送通知。函数ngx_add_timer(ev,timer)
为事件设置超时,ngx_del_timer(ev)
删除先前设置的超时。全局超时红黑树ngx_event_timer_rbtree
存储当前设置的所有超时。树中的密钥类型为ngx_msec_t
,是事件到期的时间,以1970年1月1日午夜以来的毫秒数表示,模数ngx_msec_t
最大值。树结构可以实现快速插入和删除操作,以及访问最近的超时,nginx用于查找等待I / O事件多长时间以及到期超时事件。
Posted events
一个事件可以被发布,这意味着它的处理程序将在稍后的当前事件循环迭代中被调用。发布事件是简化代码和转义堆栈溢出的好习惯。发布的事件被保存在一个后期队列中。ngx_post_event(ev,q)
mscro将事件ev
发布到后期队列q
。ngx_delete_posted_event(ev)
宏从当前发布的队列中删除事件ev
。在所有I / O和定时器事件已经被处理之后,通常情况下,事件会被发布到事件循环迟到的ngx_posted_events
队列。调用函数ngx_event_process_posted()
来处理事件队列。它调用事件处理程序,直到队列不为空。这意味着一个已发布的事件处理程序可以在当前事件循环迭代中发布要处理的更多事件。
一个例子:
void ngx_my_connection_read(ngx_connection_t *c) { ngx_event_t *rev; rev = c->read; ngx_add_timer(rev, 1000); rev->handler = ngx_my_read_handler; ngx_my_read(rev); } void ngx_my_read_handler(ngx_event_t *rev) { ssize_t n; ngx_connection_t *c; u_char buf[256]; if (rev->timedout) { /* timeout expired */ } c = rev->data; while (rev->ready) { n = c->recv(c, buf, sizeof(buf)); if (n == NGX_AGAIN) { break; } if (n == NGX_ERROR) { /* error */ } /* process buf */ } if (ngx_handle_read_event(rev, 0) != NGX_OK) { /* error */ } }
Event loop
除了nginx主进程,所有的nginx进程都进行I / O操作,所以有一个事件循环。(nginx主程序代替大部分时间在sigsuspend()
呼叫中等待信号到达。)nginx事件循环在ngx_process_events_and_timers()
函数中实现,该过程将重复调用,直到进程退出。
事件循环有以下几个阶段:
- 通过调用
ngx_event_find_timer()
找到最接近到期的超时。此函数查找定时器树中最左侧的节点,并返回节点到期之前的毫秒数。 - 通过调用由nginx配置选择的特定于事件通知机制的处理程序来处理I / O事件。该处理程序等待至少一个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()
调用之后检查的全局变量。
Processes
在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
) - 正常关机。在接收到该信号时,主进程向所有子进程发送关闭信号。当不存在子进程时,主节点将破坏循环池并退出。当工作进程接收到该信号时,它将关闭所有监听套接字并等待,直到没有安排不可取消事件,然后销毁循环池并退出。当缓存管理器或缓存加载程序进程接收到该信号时,它立即退出。当过程接收到该信号时,ngx_quit
变量设置为1
,并在处理完毕后立即重置。当工作进程处于关闭状态时,ngx_exiting
变量设置为1
。 -
大多数系统上的
NGX_TERMINATE_SIGNAL
(SIGTERM
) - 终止。接收到该信号后,主进程向所有子进程发送终止信号。如果子进程在1秒内没有退出,则主进程发送SIGKILL
信号以将其杀死。当不存在子进程时,主进程会破坏循环池并退出。当工作进程,缓存管理器进程或缓存加载程序进程接收到该信号时,它会破坏循环池并退出。当接收到该信号时,变量ngx_terminate
设置为1
。 -
大多数系统上的
NGX_NOACCEPT_SIGNAL
(SIGWINCH
) - 关闭所有工作人员和帮助程序。在收到此信号后,主进程关闭其子进程。如果先前启动的新的nginx二进制文件退出,则会再次启动旧主机的子进程。当工作进程接收到该信号时,将以调试模式关闭,该调试模式由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进程之间发送消息。然而,目前,消息仅从主人发送给其子女。消息携带标准信号。
Threads
有可能卸载到另外阻止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)
来获取对线程池的引用,该引用可以使用给定的名称创建一个新的线程池或返回一个对该名称的池的引用,如果它已经存在。
要在运行时将任务
添加到指定线程池tp
的队列中,请使用ngx_thread_task_post(tp,task)
函数。要执行线程中的函数,请使用ngx_thread_task_t
结构传递参数并设置完成处理程序:
typedef struct { int foo; } my_thread_ctx_t; static void my_thread_func(void *data, ngx_log_t *log) { my_thread_ctx_t *ctx = data; /* this function is executed in a separate thread */ } static void my_thread_completion(ngx_event_t *ev) { my_thread_ctx_t *ctx = ev->data; /* executed in nginx event loop */ } ngx_int_t my_task_offload(my_conf_t *conf) { my_thread_ctx_t *ctx; ngx_thread_task_t *task; task = ngx_thread_task_alloc(conf->pool, sizeof(my_thread_ctx_t)); if (task == NULL) { return NGX_ERROR; } ctx = task->ctx; ctx->foo = 42; task->handler = my_thread_func; task->event.handler = my_thread_completion; task->event.data = ctx; if (ngx_thread_task_post(conf->thread_pool, task) != NGX_OK) { return NGX_ERROR; } return NGX_OK; }
Modules
Adding new modules
每个独立的nginx模块驻留在一个单独的目录中,其中包含至少两个文件:config
以及包含模块源代码的文件。config
文件包含nginx集成模块所需的所有信息,例如:
ngx_module_type=CORE ngx_module_name=ngx_foo_module ngx_module_srcs="$ngx_addon_dir/ngx_foo_module.c" . auto/module ngx_addon_name=$ngx_module_name
config
文件是一个POSIX shell脚本,可以设置和访问以下变量:
ngx_module_type
- 要生成的模块类型。Possible values areCORE
,HTTP
,HTTP_FILTER
,HTTP_INIT_FILTER
,HTTP_AUX_FILTER
,MAIL
,STREAM
, orMISC
.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
,OPENSL
,MD5
,SHA1
,ZLIB
和PERL
。ngx_module_link
- 静态模块的动态模块或ADDON
的构建系统设置为DYNAMIC
的变量,用于确定执行依赖的不同操作链接类型。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
参数。
Core Modules
模块是nginx的构建模块,其大部分功能实现为模块。模块源文件必须包含一个类型为ngx_module_t
的全局变量,其定义如下:
struct ngx_module_s { /* private part is omitted */ void *ctx; ngx_command_t *commands; ngx_uint_t type; ngx_int_t (*init_master)(ngx_log_t *log); ngx_int_t (*init_module)(ngx_cycle_t *cycle); ngx_int_t (*init_process)(ngx_cycle_t *cycle); ngx_int_t (*init_thread)(ngx_cycle_t *cycle); void (*exit_thread)(ngx_cycle_t *cycle); void (*exit_process)(ngx_cycle_t *cycle); void (*exit_master)(ngx_cycle_t *cycle); /* stubs for future extensions are omitted */ };
省略的私人部分包括模块版本和签名,并使用预定义宏NGX_MODULE_V1
填充。
每个模块将其私有数据保留在ctx
字段中,识别命令
数组中指定的配置指令,并可在nginx生命周期的某些阶段调用。模块生命周期包括以下事件:
- 配置指令处理程序在主进程的上下文中显示在配置文件中时被调用。
- 在配置成功解析后,在主进程的上下文中调用
init_module
处理程序。每次加载配置时,主进程都会调用init_module
处理程序。 - 主进程创建一个或多个工作进程,并在每个进程中调用
init_process
处理程序。 - 当工作进程从主机接收到shutdown或terminate命令时,它调用
exit_process
处理程序。 - 主进程在退出之前调用
exit_master
处理程序。
因为线程仅在nginx中作为具有自己的API的补充I / O设施使用,因此当前未调用init_thread
和exit_thread
处理程序。还没有init_master
处理程序,因为这将是不必要的开销。
模块类型
正好定义了ctx
字段中存储的内容。它的值是以下类型之一:
NGX_CORE_MODULE T0>
NGX_EVENT_MODULE T0>
NGX_HTTP_MODULE T0>
NGX_MAIL_MODULE T0>
NGX_STREAM_MODULE T0>
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;
其中名称
是模块名字符串,create_conf
和init_conf
是分别创建和初始化模块配置的函数的指针。对于核心模块,在解析所有配置成功解析新配置和init_conf
之后,nginx调用create_conf
。典型的create_conf
功能为配置分配内存并设置默认值。
例如,一个名为ngx_foo_module
的简单模块可能如下所示:
/* * Copyright (C) Author. */ #include <ngx_config.h> #include <ngx_core.h> typedef struct { ngx_flag_t enable; } ngx_foo_conf_t; static void *ngx_foo_create_conf(ngx_cycle_t *cycle); static char *ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf); static char *ngx_foo_enable(ngx_conf_t *cf, void *post, void *data); static ngx_conf_post_t ngx_foo_enable_post = { ngx_foo_enable }; static ngx_command_t ngx_foo_commands[] = { { ngx_string("foo_enabled"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, 0, offsetof(ngx_foo_conf_t, enable), &ngx_foo_enable_post }, ngx_null_command }; static ngx_core_module_t ngx_foo_module_ctx = { ngx_string("foo"), ngx_foo_create_conf, ngx_foo_init_conf }; ngx_module_t ngx_foo_module = { NGX_MODULE_V1, &ngx_foo_module_ctx, /* module context */ ngx_foo_commands, /* module directives */ NGX_CORE_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static void * ngx_foo_create_conf(ngx_cycle_t *cycle) { ngx_foo_conf_t *fcf; fcf = ngx_pcalloc(cycle->pool, sizeof(ngx_foo_conf_t)); if (fcf == NULL) { return NULL; } fcf->enable = NGX_CONF_UNSET; return fcf; } static char * ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf) { ngx_foo_conf_t *fcf = conf; ngx_conf_init_value(fcf->enable, 0); return NGX_CONF_OK; } static char * ngx_foo_enable(ngx_conf_t *cf, void *post, void *data) { ngx_flag_t *fp = data; if (*fp == 0) { return NGX_CONF_OK; } ngx_log_error(NGX_LOG_NOTICE, cf->log, 0, "Foo Module is enabled"); return NGX_CONF_OK; }
Configuration Directives
ngx_command_t
类型定义了单个配置指令。支持配置的每个模块都提供了一个这样的结构的数组,它们描述如何处理参数和调用什么处理程序:
typedef struct ngx_command_s ngx_command_t; struct ngx_command_s { ngx_str_t name; ngx_uint_t type; char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); ngx_uint_t conf; ngx_uint_t offset; void *post; };
使用特殊值ngx_null_command
终止数组。名称
是配置文件中出现的指令的名称,例如“worker_processes”或“listen”。类型
是一个标志的位字段,用于指定伪指令所参数的数量,其类型及其出现的上下文。标志是:
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
- 指令采用布尔值,或
关闭
。
指令的上下文定义了配置中可能出现的位置:
NGX_MAIN_CONF
- 在顶层上下文中。NGX_HTTP_MAIN_CONF
- 在http
块中。NGX_HTTP_SRV_CONF
- 在http
块内的服务器
块中。NGX_HTTP_LOC_CONF
- 在http
块内的位置
块中。NGX_HTTP_UPS_CONF
- 在http
块内的上游
块中。NGX_HTTP_SIF_CONF
- 在http
块中服务器
块内的if
块。NGX_HTTP_LIF_CONF
- 在http
块中的位置
块内的if
块。NGX_HTTP_LMT_CONF
- 在http
块内的limit_except
块中。NGX_STREAM_MAIN_CONF
- 在流
块中。NGX_STREAM_SRV_CONF
- 在流
块内的服务器
块中。NGX_STREAM_UPS_CONF
- 在流
块内的上游
块中。NGX_MAIL_MAIN_CONF
- 在邮件
块中。NGX_MAIL_SRV_CONF
- 在邮件
块内的服务器
块中。NGX_EVENT_CONF
- 在事件
块中。NGX_DIRECT_CONF
- 由不创建上下文层次结构的模块使用,只有一个全局配置。该配置作为conf
参数传递给处理程序。
配置解析器使用这些标志在错位指令的情况下抛出错误,并调用指定的处理程序,并提供正确的配置指针,以便不同位置的相同伪指令可以将它们的值存储在不同的位置。
集
字段定义处理指令的处理程序,并将解析的值存储到相应的配置中。有许多功能可以执行常见的转换:
ngx_conf_set_flag_slot
- 将和关闭的文字字符串
分别转换为值为1或0的
ngx_flag_t
值。ngx_conf_set_str_slot
- 将字符串存储为ngx_str_t
类型的值。ngx_conf_set_str_array_slot
- 将值附加到字符串ngx_str_t
的数组ngx_array_t
。如果不存在,则创建数组。ngx_conf_set_keyval_slot
- 将键值对附加到键值对ngx_keyval_t
的数组ngx_array_t
中。第一个字符串成为关键字,第二个字符串成为该值。如果数组不存在,则创建该数组。ngx_conf_set_num_slot
- 将指令的参数转换为ngx_int_t
值。ngx_conf_set_size_slot
- 将大小转换为以字节表示的size_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
值。在post
字段中传递的空值终止的ngx_conf_enum_t
数组定义了可接受的字符串和对应的整数值。ngx_conf_set_bitmask_slot
- 将提供的参数转换为ngx_uint_t
值。每个参数的掩码值为OR,产生结果。post
字段中传递的以null结尾的ngx_conf_bitmask_t
数组定义了可接受的字符串和对应的掩码值。set_path_slot
- 将提供的参数转换为ngx_path_t
值,并执行所有必需的初始化。有关详细信息,请参阅proxy_temp_path指令的文档。set_access_slot
- 将提供的参数转换为文件权限掩码。有关详细信息,请参阅proxy_store_access指令的文档。
conf
字段定义将哪个配置结构传递给目录处理程序。核心模块仅具有全局配置,并设置NGX_DIRECT_CONF
标志来访问它。HTTP,Stream或Mail等模块创建了层次结构。例如,如果范围,则为服务器
,位置
和创建模块的配置。
NGX_HTTP_MAIN_CONF_OFFSET
-http
块的配置。NGX_HTTP_SRV_CONF_OFFSET
-http
块内的服务器
块的配置。NGX_HTTP_LOC_CONF_OFFSET
-http
内的位置
块的配置。NGX_STREAM_MAIN_CONF_OFFSET
-流
块的配置。NGX_STREAM_SRV_CONF_OFFSET
-流
块内的服务器
块的配置。NGX_MAIL_MAIN_CONF_OFFSET
-邮件
块的配置。NGX_MAIL_SRV_CONF_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
对象本身,数据
是一个指向该值的指针,由主处理程序的参数从适当的类型。
HTTP
Connection
每个HTTP客户端连接运行在以下阶段:
ngx_event_accept()
接受客户端TCP连接。响应于监听套接字上的读取通知,调用此处理程序。在这个阶段创建一个新的ngx_connecton_t
对象来包装新接受的客户端套接字。每个nginx侦听器提供一个处理程序来传递新的连接对象。对于HTTP连接,它是ngx_http_init_connection(c)
。ngx_http_init_connection()
执行HTTP连接的早期初始化。在此阶段,为连接创建一个ngx_http_connection_t
对象,其引用存储在连接的数据
字段中。稍后将被HTTP请求对象替换。PROXY协议解析器和SSL握手也在此阶段启动。ngx_http_wait_request_handler()
当客户端套接字上的数据可用时,会调用读取事件处理程序。在这个阶段,HTTP请求对象ngx_http_request_t
被创建并设置为连接的数据
字段。ngx_http_process_request_line()
读事件处理程序读取客户端请求行。处理程序由ngx_http_wait_request_handler()
设置。数据被读入连接的缓冲区
。缓冲区的大小最初由指令client_header_buffer_size设置。整个客户端头应该适合缓冲区。如果初始大小不足,则分配更大的缓冲区,容量由large_client_header_buffers
指令设置。ngx_http_process_request_headers()
读取事件处理程序,在ngx_http_process_request_line()
之后设置,以读取客户端请求标头。- 当请求头被完全读取和解析时,调用
ngx_http_core_run_phases()
。该功能将请求阶段从NGX_HTTP_POST_READ_PHASE
运行到NGX_HTTP_CONTENT_PHASE
。最后一个阶段是为了产生一个响应,并沿着过滤器链传递它。在此阶段,响应不一定发送给客户端。它可能会保持缓冲状态,并在完成阶段发送。 - 通常在请求生成所有输出或产生错误时调用
ngx_http_finalize_request()
。在后一种情况下,查找适当的错误页面并将其用作响应。如果此时响应没有完全发送到客户端,则HTTP写入器ngx_http_writer()
被激活以完成发送未完成的数据。 - 当完整的响应发送到客户端并且请求可以被销毁时,调用
ngx_http_finalize_connection()
。如果启用客户端连接保持功能,将调用ngx_http_set_keepalive()
,这会销毁当前请求,并等待连接上的下一个请求。否则,ngx_http_close_request()
破坏请求和连接。
Request
对于每个客户端HTTP请求,都创建了ngx_http_request_t
对象。这个对象的一些字段是:
-
连接
- 指向ngx_connection_t
客户端连接对象的指针。多个请求可以同时引用同一个连接对象 - 一个主要请求及其子请求。删除请求后,可以在同一连接上创建一个新请求。请注意,对于HTTP连接,
ngx_connection_t
的数据
字段指向请求。这种请求被称为活动的,而不是绑定到该连接的其他请求。活动请求用于处理客户端连接事件,并允许将其响应输出到客户端。通常,每个请求在某一点变得活跃,以便它可以发送其输出。 -
ctx
- HTTP模块上下文的数组。类型NGX_HTTP_MODULE
的每个模块都可以在请求中存储任何值(通常是指向结构的指针)。该值存储在模块ctx_index
位置的ctx
数组中。以下宏提供了一种方便的方法来获取和设置请求上下文:ngx_http_get_module_ctx(r,module)
- 返回模块
的上下文ngx_http_set_ctx(r,c,module)
- 将c
设置为模块
的上下文
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
处理程序。缓存
- 请求高速缓存对象缓存上游响应。upstream
- 请求上游对象进行代理。池
- 请求池。请求对象本身在此池中分配,在删除请求时被破坏。对于需要在整个客户端连接生命周期中可用的分配,请使用ngx_connection_t
的池。header_in
- 读取客户端HTTP请求头的缓冲区。headers_in
,headers_out
- 输入和输出HTTP标头对象。两个对象都包含头
类型为ngx_list_t
的字段,用于保留标题的原始列表。除此之外,特定的头可用于获取和设置为单独的字段,例如content_length_n
,状态
等。request_body
- 客户端请求身体对象。start_sec
,start_msec
- 创建请求的时间点,用于跟踪请求持续时间。方法
,method_name
- 客户端HTTP请求方法的数字和文本表示。Numeric values for methods are defined insrc/http/ngx_http_request.h
with the macrosNGX_HTTP_GET
,NGX_HTTP_HEAD
,NGX_HTTP_POST
, etc.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_data
- 由请求的最后一个正则表达式匹配产生的正则表达式捕获。在请求处理期间,可以在多个地方发生正则表达式匹配:地图查找,SNI或HTTP主机的服务器查找,重写,proxy_redirect等。通过查找生成的捕获存储在上述字段中。ncaptures
保存捕获数,捕获
保存捕获边界,captures_data
保存正则表达式匹配的字符串,用于提取捕获。每次新的正则表达式匹配后,请求捕获将重置为保存新值。count
- 请求参考计数器。该字段只对主要请求有意义。增加计数器是通过简单的r-&gt; main-&gt; count ++
完成的。要减少计数器,请调用ngx_http_finalize_request(r,rc)
。创建子请求并运行请求体读取过程都会增加计数器。子请求
- 当前子请求嵌套级别。每个子请求继承其父级的嵌套级别,减少一个。如果值达到零,则会产生错误。主请求的值由NGX_HTTP_MAX_SUBREQUESTS
常量定义。uri_changes
- 请求的剩余URI更改数。请求可以更改其URI的总次数受限于NGX_HTTP_MAX_URI_CHANGES
常量。每次更改时,值递减直到达到零,此时生成错误。重写和内部重定向到正常或命名的位置被认为是URI更改。阻止
- 请求中保存的块的计数器。虽然此值不为零,但请求无法终止。目前,通过挂起的AIO操作(POSIX AIO和线程操作)以及主动缓存锁来增加此值。缓存
- 显示哪些模块缓冲了请求产生的输出的位掩码。一些滤波器可以缓冲输出;例如,sub_filter可以缓冲数据,因为部分字符串匹配,复制过滤器可以缓冲数据,因为缺少空闲的输出缓冲区等。只要这个值不为零,请求没有被完成等待刷新。header_only
- 表示输出不需要主体的标志。例如,该标志由HTTP HEAD请求使用。-
keepalive
- 表示是否支持客户端连接keepalive的标志。该值是从HTTP版本和“连接”头的值推断的。 header_sent
- 表示输出头已经被请求发送的标志。internal
- 表示当前请求是内部的标志。要进入内部状态,请求必须通过内部重定向或作为子请求。允许内部请求进入内部位置。allow_ranges
- 表示可以按照HTTP Range头部的要求将部分响应发送到客户端的标志。-
subrequest_ranges
— Flag indicating that a partial response can be sent while a subrequest is being processed. single_range
- 表示只能将一个连续的输出数据范围发送到客户端的标志。通常在发送数据流时设置此标志,例如来自代理的服务器,并且整个响应在一个缓冲区中不可用。main_filter_need_in_memory
,filter_need_in_memory
- 请求在内存缓冲区而不是文件中生成的输出的标志。即使启用了sendfile,这也是复制过滤器从文件缓冲区读取数据的信号。两个标志之间的区别是设置它们的过滤器模块的位置。在过滤器链中推迟过滤器之前调用的过滤器设置filter_need_in_memory
,请求仅当前请求输出进入内存缓冲区。滤波器链中稍后调用的过滤器设置main_filter_need_in_memory
,请求主请求和所有子请求在发送输出时读取存储器中的文件。filter_need_temporary
- 请求在临时缓冲区中生成请求输出的标志,而不是只读内存缓冲区或文件缓冲区中的标志。这可以由可以直接在发送缓冲区的输出中改变输出的过滤器使用。
Configuration
每个HTTP模块可以有三种类型的配置:
- 主要配置 - 适用于整个
http
块。作为模块的全局设置。 - 服务器配置 - 适用于单个
服务器
块。用作模块的服务器特定设置。 - 位置配置 - 如果或
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的位置配置的指针,并替换保存在结构的处理程序
字段中的位置内容处理程序。
static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r); static ngx_command_t ngx_http_foo_commands[] = { { ngx_string("foo"), NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, ngx_http_foo, 0, 0, NULL }, ngx_null_command }; static char * ngx_http_foo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_core_loc_conf_t *clcf; clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); clcf->handler = ngx_http_bar_handler; return NGX_CONF_OK; }
以下宏可用于在运行时访问HTTP模块的配置。
ngx_http_get_module_main_conf(r,module)
ngx_http_get_module_srv_conf(r,module)
ngx_http_get_module_loc_conf(r,module)
这些宏接收到HTTP请求ngx_http_request_t
的引用。请求的主要配置永远不会更改。选择虚拟服务器后,服务器配置可能会从默认值进行更改。选择处理请求的位置配置可能由于重写操作或内部重定向而多次更改。以下示例显示了如何在运行时访问模块的HTTP配置。
static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r) { ngx_http_foo_loc_conf_t *flcf; flcf = ngx_http_get_module_loc_conf(r, ngx_http_foo_module); ... }
Phases
每个HTTP请求通过一系列阶段。在每个阶段,根据请求执行不同类型的处理。模块特定的处理程序可以在大多数阶段进行注册,并且许多标准的nginx模块将其相位处理程序注册为在特定阶段的请求处理中调用的一种方法。阶段相继处理,一旦请求达到相位,调用相位处理程序。以下是nginx HTTP阶段的列表。
NGX_HTTP_POST_READ_PHASE
- 第一阶段。ngx_http_realip_module在此阶段注册其处理程序,以便在调用任何其他模块之前对客户端地址进行替换。NGX_HTTP_SERVER_REWRITE_PHASE
- 处理在服务器
块(但在位置
块之外)定义的重写指令的阶段。ngx_http_rewrite_module在此阶段安装其处理程序。NGX_HTTP_FIND_CONFIG_PHASE
- 根据请求URI选择位置的特殊阶段。在此阶段之前,相关虚拟服务器的默认位置被分配给该请求,并且任何请求位置配置的模块都将接收默认服务器位置的配置。此阶段a为请求分配新位置。在这个阶段不能注册额外的处理程序。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
- 验证客户端有权提出请求的阶段。在这个阶段,诸如ngx_http_access_module和ngx_http_auth_basic_module的标准nginx模块注册他们的处理程序。默认情况下,客户端必须通过在此阶段注册的所有处理程序的授权检查,以请求继续进行下一阶段。满足指令,可以用于允许处理继续,如果任何相位处理程序授权客户端。NGX_HTTP_POST_ACCESS_PHASE
- 处理满足任何指令的特殊阶段。如果某些访问阶段处理程序拒绝访问,并且无法明确地允许访问,则请求将被最终确定。在这个阶段不能注册额外的处理程序。NGX_HTTP_TRY_FILES_PHASE
- 处理try_files指令的特殊阶段。在这个阶段不能注册额外的处理程序。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_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_AGAIN
,NGX_DONE
- 暂停相位处理,直到某个将来可能是异步I / O操作的事件或者仅仅是延迟。假设通过调用ngx_http_core_run_phases()
稍后恢复相位处理。- 相位处理程序返回的任何其他值被视为请求最终化代码,特别是HTTP响应代码。请求的确定与提供的代码。
对于一些阶段,返回代码以稍微不同的方式处理。在内容阶段,除了NGX_DECLINED
之外的任何返回代码都被认为是一个终结代码。来自位置内容处理程序的任何返回代码都被认为是一个完成代码。At the access phase, in satisfy any mode, any return code other than NGX_OK
, NGX_DECLINED
, NGX_AGAIN
, NGX_DONE
is considered a denial. 如果后续的访问处理程序不允许或拒绝使用不同的代码访问,则拒绝代码将成为最终化代码。
Variables
Accessing existing variables
变量可以由索引引用(这是最常见的方法)或名称(参见)。在配置阶段创建索引时,将变量添加到配置中。要获取变量索引,请使用ngx_http_get_variable_index()
:
ngx_str_t name; /* ngx_string("foo") */ ngx_int_t index; index = ngx_http_get_variable_index(cf, &name);
这里,cf
是指向nginx配置的指针,名称
指向包含变量名的字符串。该函数返回错误时的NGX_ERROR
,否则返回有效的索引,通常将其存储在模块配置中的某个位置,以备将来使用。
所有HTTP变量都在给定HTTP请求的上下文中进行评估,并且结果在该HTTP请求中是特定于并缓存的。评估变量的所有函数返回ngx_http_variable_value_t
类型,表示变量值:
typedef ngx_variable_value_t ngx_http_variable_value_t; typedef struct { unsigned len:28; unsigned valid:1; unsigned no_cacheable:1; unsigned not_found:1; unsigned escape:1; u_char *data; } ngx_variable_value_t;
哪里:
len
- 值的长度data
- 值本身valid
- 该值有效not_found
- 未找到该变量,因此数据
和len
字段无关;例如,当在请求中没有传递对应的参数时,可能会发生这种变量,例如$ arg_foo
等变量no_cacheable
- 不缓存结果escape
- 由日志记录模块内部使用来标记需要在输出中转义的值。
使用ngx_http_get_flushed_variable()
和ngx_http_get_indexed_variable()
函数来获取变量的值。它们具有相同的接口 - 接受HTTP请求r
作为用于评估变量的上下文和标识它的索引
。典型用法的例子:
ngx_http_variable_value_t *v; v = ngx_http_get_flushed_variable(r, index); if (v == NULL || v->not_found) { /* we failed to get value or there is no such variable, handle it */ return NGX_ERROR; } /* some meaningful value is found */
函数之间的区别是,ngx_http_get_indexed_variable()
返回一个缓存的值,而ngx_http_get_flushed_variable()
将缓存刷新为不可缓存的变量。
某些模块(如SSI和Perl)需要处理在配置时不知名的变量。因此,索引不能用于访问它们,但ngx_http_get_variable(r,name,key)
函数可用。它从名称中搜索具有给定名称
的变量及其哈希键
。
Creating variables
要创建变量,请使用ngx_http_add_variable()
函数。它将参数作为参数(变量注册的位置),变量名称和控制函数行为的标志:
NGX_HTTP_VAR_CHANGEABLE
- 启用对变量的重新定义:如果另一个模块定义了具有相同名称的变量,则不会有冲突。这允许设置指令来覆盖变量。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
和设置
处理程序来获取或设置变量值,将数据
传递给可变处理程序,索引保存用于引用变量的分配变量索引。
通常,一个空值终止的ngx_http_variable_t
结构的静态数组由模块创建,并在预配置阶段处理,以将变量添加到配置中,例如:
static ngx_http_variable_t ngx_http_foo_vars[] = { { ngx_string("foo_v1"), NULL, ngx_http_foo_v1_variable, 0, 0, 0 }, { ngx_null_string, NULL, NULL, 0, 0, 0 } }; static ngx_int_t ngx_http_foo_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var, *v; for (v = ngx_http_foo_vars; v->name.len; v++) { var = ngx_http_add_variable(cf, &v->name, v->flags); if (var == NULL) { return NGX_ERROR; } var->get_handler = v->get_handler; var->data = v->data; } return NGX_OK; }
该示例中的此功能用于初始化HTTP模块上下文的预配置
字段,并在解析HTTP配置之前调用,以便解析器可以引用这些变量。
get
处理程序负责评估特定请求的上下文中的变量,例如:
static ngx_int_t ngx_http_variable_connection(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; p = ngx_pnalloc(r->pool, NGX_ATOMIC_T_LEN); if (p == NULL) { return NGX_ERROR; } v->len = ngx_sprintf(p, "%uA", r->connection->number) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; }
在内部错误(例如,内存分配失败)或NGX_OK
的情况下,返回NGX_ERROR
。To learn the status of variable evaluation, inspect the flags in ngx_http_variable_value_t
(see the description above).
set
处理程序允许设置变量引用的属性。例如,$ limit_rate
变量的设置处理程序修改请求的limit_rate
字段:
... { ngx_string("limit_rate"), ngx_http_variable_request_set_size, ngx_http_variable_request_get_size, offsetof(ngx_http_request_t, limit_rate), NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE, 0 }, ... static void ngx_http_variable_request_set_size(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ssize_t s, *sp; ngx_str_t val; val.len = v->len; val.data = v->data; s = ngx_parse_size(&val); if (s == NGX_ERROR) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid size \"%V\"", &val); return; } sp = (ssize_t *) ((char *) r + data); *sp = s; return; }
Complex values
复杂的值,尽管它的名字,提供了一个简单的方法来评估可以包含文本,变量及其组合的表达式。
ngx_http_compile_complex_value
中的复值描述在配置阶段被编译为在运行时使用的ngx_http_complex_value_t
,以获得表达式求值的结果。
ngx_str_t *value; ngx_http_complex_value_t cv; ngx_http_compile_complex_value_t ccv; value = cf->args->elts; /* directive arguments */ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = &cv; ccv.zero = 1; ccv.conf_prefix = 1; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; }
这里,ccv
保存初始化复合值cv
所需的所有参数:
cf
- 配置指针value
- 要解析的字符串(输入)complex_value
- 编译的值(输出)零
- 启用零终止值的标志conf_prefix
- 使用配置前缀(nginx当前正在寻找配置的目录)前缀结果root_prefix
- 使用根前缀(正常的nginx安装前缀)前缀,
当结果要传递给需要零终止字符串的库时,零
标志很有用,前缀在处理文件名时很方便。
编译成功后,cv.lengths
包含有关表达式中存在变量的信息。NULL值意味着该表达式仅包含静态文本,因此可以存储在简单的字符串而不是复杂的值中。
ngx_http_set_complex_value_slot()
是一个方便的函数,用于在指令声明本身中完全初始化复杂值。
在运行时,可以使用ngx_http_complex_value()
函数来计算复值。
ngx_str_t res; if (ngx_http_complex_value(r, &cv, &res) != NGX_OK) { return NGX_ERROR; }
给定请求r
和以前编译的值cv
,函数将计算表达式并将结果写入res
。
Request redirection
HTTP请求始终通过ngx_http_request_t
结构的loc_conf
字段连接到某个位置。这意味着任何一个模块的位置配置都可以通过调用ngx_http_get_module_loc_conf(r,module)
从请求中检索出来。请求位置可以在请求的生命周期内更改多次。最初,将默认服务器的默认服务器位置分配给请求。如果请求切换到不同的服务器(由HTTP“主机”头或SSL SNI扩展名选择),请求也切换到该服务器的默认位置。位置的下一个更改发生在NGX_HTTP_FIND_CONFIG_PHASE
请求阶段。在此阶段,在为服务器配置的所有未命名位置之间,请求URI选择一个位置。作为重写指令的结果,ngx_http_rewrite_module可以更改NGX_HTTP_REWRITE_PHASE
请求阶段的请求URI,并将请求发送回NGX_HTTP_FIND_CONFIG_PHASE
阶段,用于基于新的URI选择新位置。
也可以通过调用ngx_http_internal_redirect(r,uri,args)
或ngx_http_named_location(r,name)
之一来将任务重定向到任何位置。
ngx_http_internal_redirect(r,uri,args)
函数更改请求URI并将请求返回到NGX_HTTP_SERVER_REWRITE_PHASE
阶段。请求进行服务器默认位置。稍后在NGX_HTTP_FIND_CONFIG_PHASE
,基于新的请求URI选择一个新位置。
以下示例使用新的请求参数执行内部重定向。
ngx_int_t ngx_http_foo_redirect(ngx_http_request_t *r) { ngx_str_t uri, args; ngx_str_set(&uri, "/foo"); ngx_str_set(&args, "bar=1"); return ngx_http_internal_redirect(r, &uri, &args); }
功能ngx_http_named_location(r,name)
将请求重定向到命名位置。该位置的名称作为参数传递。在当前服务器的所有命名位置之间查找位置,之后请求切换到NGX_HTTP_REWRITE_PHASE
阶段。
以下示例执行到命名位置@foo的重定向。
ngx_int_t ngx_http_foo_named_redirect(ngx_http_request_t *r) { ngx_str_t name; ngx_str_set(&name, "foo"); return ngx_http_named_location(r, &name); }
当nginx模块已经在请求的ctx中存储了一些上下文时,可以调用这两个函数 -
字段。这些上下文可能与新的位置配置不一致。为了防止不一致,所有请求上下文都被两个重定向功能擦除。ngx_http_internal_redirect(r,uri,args)
和ngx_http_named_location(r,name)
调用ngx_http_internal_redirect(r,uri,args)
或ngx_http_named_location(r,name)
增加了请求计数
。对于一致的请求引用计数,请在重定向请求后调用ngx_http_finalize_request(r,NGX_DONE)
。这将完成当前的请求代码路径并减少计数器。
重定向和重写请求变为内部,可以访问内部位置。内部请求设置了内部
标志。
Subrequests
子请求主要用于将一个请求的输出插入另一个请求,可能与其他数据混合。子请求看起来像一个普通请求,但与其父项共享一些数据。特别地,与客户端输入相关的所有字段都是共享的,因为子请求没有从客户端接收任何其他输入。子请求的请求字段parent
包含指向其父请求的链接,对于主请求为NULL。字段main
包含一组请求中的主要请求的链接。
子请求从NGX_HTTP_SERVER_REWRITE_PHASE
阶段开始。它通过与正常请求相同的后续阶段,并根据其自己的URI分配一个位置。
子请求中的输出头总是被忽略。ngx_http_postpone_filter
将子请求的输出正文放置在相对于父请求产生的其他数据的正确位置。
子请求与活动请求的概念有关。如果c-> data == r
,则请求r
被认为是活动的,其中c
是客户端连接对象。在任何给定的点,只有请求组中的活动请求被允许将其缓冲区输出到客户端。非活动请求仍然可以将其输出发送到过滤器链,但是它不会超出ngx_http_postpone_filter
,并保持该过滤器的缓冲,直到请求变为活动状态为止。以下是请求激活的一些规则:
- 最初,主要请求是活动的。
- 主动请求的第一个子请求在创建后立即生效。
- 一旦发送该请求之前的所有数据,
ngx_http_postpone_filter
激活活动请求的子请求列表中的下一个请求。 - 当一个请求被定稿时,它的父项被激活。
通过调用函数ngx_http_subrequest(r,uri,args,psr,ps,flags)
创建子请求,其中r
是父请求,uri t2 >和
以下标志可用:args
是子请求的URI和参数,psr
是输出参数,它接收新创建的子请求引用,ps
是一个回调对象用于通知父请求正在确定子请求,并且flags
是位掩码的标志。
NGX_HTTP_SUBREQUEST_IN_MEMORY
- 输出不发送到客户端,而是存储在内存中。该标志仅影响由其中一个代理模块处理的子请求。在子请求完成之后,其输出可在ngx_buf_t
类型的r->上游缓冲区
中获得。NGX_HTTP_SUBREQUEST_WAITED
- 子请求的完成
标志设置为即使子请求在最终确定时未激活。该子请求标志由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
标志。这样可以防止将子请求体发送到客户端。请注意,子请求的头不会发送到客户端。子请求的结果可以在回调处理程序中进行分析。
Request finalization
通过调用函数ngx_http_finalize_request(r,rc)
来完成HTTP请求。在将所有输出缓冲区发送到过滤器链之后,它通常由内容处理程序完成。在这一点上,所有的输出可能都不会发送到客户端,其中一些输出仍然沿着过滤器链缓存。如果是,则ngx_http_finalize_request(r,rc)
函数自动安装特殊处理程序ngx_http_writer(r)
以完成发送输出。如果发生错误,或者如果需要将客户端返回标准的HTTP响应代码,则还会确定请求。
函数ngx_http_finalize_request(r,rc)
需要以下rc
值:
NGX_DONE
- 快速完成。递减请求count
,并在请求达到零时销毁该请求。客户端连接可以在当前请求被销毁后用于更多的请求。NGX_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 (
对于这些值,nginx或者向客户端发送代码的默认响应页面,或者执行内部重定向到error_page位置(如果为代码配置)。300
) - 特别响应确定。- 其他代码被认为是成功的终结代码,并且可以激活请求写入器来完成发送响应体。身体完全发送后,请求
count
递减。如果达到零,则请求被销毁,但客户端连接仍然可以用于其他请求。如果count
为正,则请求中有未完成的活动,稍后将被确定。
Request body
为了处理客户端请求的正文,nginx提供了ngx_http_read_client_request_body(r,post_handler)
和ngx_http_discard_request_body(r)
函数。第一个函数读取请求正文,并通过request_body
请求字段使其可用。第二个功能指示nginx放弃(读取和忽略)请求主体。必须为每个请求调用其中一个函数。通常,内容处理程序将进行调用。
不允许从子请求中读取或丢弃客户端请求正文。它必须始终在主要请求中完成。当创建子请求时,如果主请求先前已经读请求主体,它将继承父请求的request_body
对象,该对象可以由子请求使用。
函数ngx_http_read_client_request_body(r,post_handler)
开始读取请求体的过程。当主体完全读取时,调用post_handler
回调来继续处理请求。如果请求正文丢失或已被读取,则立即调用回调。函数ngx_http_read_client_request_body(r,post_handler)
分配类型为ngx_http_request_body_t
的request_body
请求字段。该对象的字段bufs
将结果保持为缓冲区链。如果由client_body_buffer_size指令指定的容量不足以将整个身体装入内存,则身体可以保存在内存缓冲区或文件缓冲区中。
以下示例读取客户端请求正文并返回其大小。
ngx_int_t ngx_http_foo_content_handler(ngx_http_request_t *r) { ngx_int_t rc; rc = ngx_http_read_client_request_body(r, ngx_http_foo_init); if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { /* error */ return rc; } return NGX_DONE; } void ngx_http_foo_init(ngx_http_request_t *r) { off_t len; ngx_buf_t *b; ngx_int_t rc; ngx_chain_t *in, out; if (r->request_body == NULL) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } len = 0; for (in = r->request_body->bufs; in; in = in->next) { len += ngx_buf_size(in->buf); } b = ngx_create_temp_buf(r->pool, NGX_OFF_T_LEN); if (b == NULL) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } b->last = ngx_sprintf(b->pos, "%O", len); b->last_buf = (r == r->main) ? 1: 0; b->last_in_chain = 1; r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = b->last - b->pos; rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { ngx_http_finalize_request(r, rc); return; } out.buf = b; out.next = NULL; rc = ngx_http_output_filter(r, &out); ngx_http_finalize_request(r, rc); }
请求的以下字段决定了请求体的读取方式:
request_body_in_single_buf
- 将主体读取到单个内存缓冲区。request_body_in_file_only
- 始终将身体读取到文件中,即使适合内存缓冲区。request_body_in_persistent_file
- 创建后不要立即取消链接文件。具有此标志的文件可以移动到另一个目录。request_body_in_clean_file
- 请求完成后取消文件的链接。当文件应该被移动到另一个目录但由于某种原因没有移动时,这可能是有用的。request_body_file_group_access
- 通过用0660替换默认的0600访问掩码,启用对该文件的组访问。request_body_file_log_level
- 记录文件错误的严重性级别。request_body_no_buffering
- 读取请求体而不缓冲。
request_body_no_buffering
标志启用读缓冲请求体的无缓冲模式。在此模式下,调用ngx_http_read_client_request_body()
之后,bufs
链可能只会保留正文的一部分。要阅读下一部分,请调用ngx_http_read_unbuffered_request_body(r)
函数。返回值NGX_AGAIN
和请求标志reading_body
表示有更多数据可用。调用此函数后,如果bufs
为NULL,则此时无法读取。当请求主体的下一部分可用时,请求回调read_event_handler
将被调用。
Response
在nginx中,HTTP响应是通过发送响应头跟随可选的响应主体产生的。头文件和正文都通过一系列过滤器,最终写入客户端套接字。一个nginx模块可以将其处理程序安装到头或体过滤器链中,并处理来自前一个处理程序的输出。
Response header
ngx_http_send_header(r)
函数发送输出头。直到r-&gt; headers_out
包含生成HTTP响应头所需的所有数据时,不要调用此函数。必须始终设置r-&gt; headers_out
中的状态
字段。如果响应状态指示响应主体跟随标题,则也可以设置content_length_n
。该字段的默认值为-1
,这意味着正文大小未知。在这种情况下,使用分块传输编码。要输出任意头,请附加头
列表。
static ngx_int_t ngx_http_foo_content_handler(ngx_http_request_t *r) { ngx_int_t rc; ngx_table_elt_t *h; /* send header */ r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = 3; /* X-Foo: foo */ h = ngx_list_push(&r->headers_out.headers); if (h == NULL) { return NGX_ERROR; } h->hash = 1; ngx_str_set(&h->key, "X-Foo"); ngx_str_set(&h->value, "foo"); rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { return rc; } /* send body */ ... }
Header filters
ngx_http_send_header(r)
函数通过调用存储在ngx_http_top_header_filter
变量中的第一个标头过滤器处理程序来调用头过滤器链。假设每个头处理程序调用链中的下一个处理程序,直到调用最终处理程序ngx_http_header_filter(r)
。最终头处理程序根据r-> header_out
构建HTTP响应,并将其传递给ngx_http_writer_filter
以输出。
要向头过滤器链添加处理程序,请在配置时将其地址存储在全局变量ngx_http_top_header_filter
中。前一个处理程序地址通常存储在模块中的静态变量中,并在退出之前由新添加的处理程序调用。
标题过滤器模块的以下示例将HTTP头“X-Foo:foo
”添加到状态为200
的每个响应中。
#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> static ngx_int_t ngx_http_foo_header_filter(ngx_http_request_t *r); static ngx_int_t ngx_http_foo_header_filter_init(ngx_conf_t *cf); static ngx_http_module_t ngx_http_foo_header_filter_module_ctx = { NULL, /* preconfiguration */ ngx_http_foo_header_filter_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; ngx_module_t ngx_http_foo_header_filter_module = { NGX_MODULE_V1, &ngx_http_foo_header_filter_module_ctx, /* module context */ NULL, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_int_t ngx_http_foo_header_filter(ngx_http_request_t *r) { ngx_table_elt_t *h; /* * The filter handler adds "X-Foo: foo" header * to every HTTP 200 response */ if (r->headers_out.status != NGX_HTTP_OK) { return ngx_http_next_header_filter(r); } h = ngx_list_push(&r->headers_out.headers); if (h == NULL) { return NGX_ERROR; } h->hash = 1; ngx_str_set(&h->key, "X-Foo"); ngx_str_set(&h->value, "foo"); return ngx_http_next_header_filter(r); } static ngx_int_t ngx_http_foo_header_filter_init(ngx_conf_t *cf) { ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_foo_header_filter; return NGX_OK; }
Response body
要发送响应主体,请调用ngx_http_output_filter(r,cl)
函数。该函数可以多次调用。每次它以缓冲链的形式发送响应体的一部分。在最后一个body缓冲区中设置last_buf
标志。
以下示例将以“foo”作为其主体生成完整的HTTP响应。为了作为子请求以及主请求的示例,在输出的最后一个缓冲区中设置了last_in_chain
标志。last_buf
标志仅针对主请求设置,因为子请求的最后一个缓冲区不会结束整个输出。
static ngx_int_t ngx_http_bar_content_handler(ngx_http_request_t *r) { ngx_int_t rc; ngx_buf_t *b; ngx_chain_t out; /* send header */ r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = 3; rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { return rc; } /* send body */ b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } b->last_buf = (r == r->main) ? 1: 0; b->last_in_chain = 1; b->memory = 1; b->pos = (u_char *) "foo"; b->last = b->pos + 3; out.buf = b; out.next = NULL; return ngx_http_output_filter(r, &out); }
Body filters
函数ngx_http_output_filter(r,cl)
通过调用存储在ngx_http_top_body_filter
变量中的第一个体过滤器处理程序来调用body过滤器链。假设每个body处理程序调用链中的下一个处理程序,直到调用最终处理程序ngx_http_write_filter(r,cl)
。
身体过滤器处理程序接收缓冲区链。处理程序应该处理缓冲区,并将可能的新链接传递给下一个处理程序。值得注意的是,链接链接ngx_chain_t
属于呼叫者,不能重复使用或更改。在处理程序完成之后,调用者可以使用其输出链链接来跟踪其发送的缓冲区。为了保存缓冲区链或在传递给下一个过滤器之前替换一些缓冲区,处理程序需要分配自己的链接。
以下是一个简单的身体过滤器的例子,它计算身体中的字节数。结果可用作访问日志中可以使用的$ counter
变量。
#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> typedef struct { off_t count; } ngx_http_counter_filter_ctx_t; static ngx_int_t ngx_http_counter_body_filter(ngx_http_request_t *r, ngx_chain_t *in); static ngx_int_t ngx_http_counter_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_counter_add_variables(ngx_conf_t *cf); static ngx_int_t ngx_http_counter_filter_init(ngx_conf_t *cf); static ngx_http_module_t ngx_http_counter_filter_module_ctx = { ngx_http_counter_add_variables, /* preconfiguration */ ngx_http_counter_filter_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; ngx_module_t ngx_http_counter_filter_module = { NGX_MODULE_V1, &ngx_http_counter_filter_module_ctx, /* module context */ NULL, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_output_body_filter_pt ngx_http_next_body_filter; static ngx_str_t ngx_http_counter_name = ngx_string("counter"); static ngx_int_t ngx_http_counter_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_chain_t *cl; ngx_http_counter_filter_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_counter_filter_module); if (ctx == NULL) { ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_counter_filter_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_counter_filter_module); } for (cl = in; cl; cl = cl->next) { ctx->count += ngx_buf_size(cl->buf); } return ngx_http_next_body_filter(r, in); } static ngx_int_t ngx_http_counter_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; ngx_http_counter_filter_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_counter_filter_module); if (ctx == NULL) { v->not_found = 1; return NGX_OK; } p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN); if (p == NULL) { return NGX_ERROR; } v->data = p; v->len = ngx_sprintf(p, "%O", ctx->count) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; return NGX_OK; } static ngx_int_t ngx_http_counter_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var; var = ngx_http_add_variable(cf, &ngx_http_counter_name, 0); if (var == NULL) { return NGX_ERROR; } var->get_handler = ngx_http_counter_variable; return NGX_OK; } static ngx_int_t ngx_http_counter_filter_init(ngx_conf_t *cf) { ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_counter_body_filter; return NGX_OK; }
Building filter modules
在编写身体或头部过滤器时,请特别注意过滤器的位置。nginx标准模块注册了一些标题和主体过滤器。nginx标准模块注册了许多头和身体过滤器,并且在相对于它们的正确位置注册一个新的过滤器模块很重要。通常,模块在其后配置处理程序中注册过滤器。在处理过程中调用过滤器的顺序显然与注册顺序相反。
对于第三方过滤器模块,nginx提供了一个特殊的插槽HTTP_AUX_FILTER_MODULES
。要在此插槽中注册过滤器模块,请将模块配置中的ngx_module_type
变量设置为HTTP_AUX_FILTER
。
以下示例显示了一个过滤器模块配置文件,假设只有一个源文件ngx_http_foo_filter_module.c
的模块。
ngx_module_type=HTTP_AUX_FILTER ngx_module_name=ngx_http_foo_filter_module ngx_module_srcs="$ngx_addon_dir/ngx_http_foo_filter_module.c" . auto/module
Buffer reuse
当发布或更改缓冲区流时,通常需要重新使用已分配的缓冲区。在nginx代码中,标准和广泛采用的方法是为了保持两个缓冲链:空闲
和忙
。免费
链保留所有可用的缓冲区,可以重复使用。busy
链保留当前模块发送的所有缓冲区,这些缓冲区仍被其他过滤器处理程序使用。如果缓冲区的大小大于零,则会考虑使用缓冲区。通常,当缓冲区被一个过滤器消耗时,其文件缓冲区的pos
(或file_pos
)移动到最后一个
(file_last
用于文件缓冲区)。一旦缓冲区被完全消耗,它就可以重新使用了。要将新释放的缓冲区添加到空闲
链中,足以迭代忙
链,并将其头部的零大小缓冲区移动到空闲 t2 >。
这个操作是很常见的,因为它有一个特殊的功能,ngx_chain_update_chains(free,busy,out,tag)
。该功能将输出链输出
到忙
,并将空闲缓冲区从忙
的顶部移动到空闲
。只有具有指定标签
的缓冲区被重用。这使得模块只能重用它自己分配的缓冲区。
以下示例是在每个传入缓冲区之前插入字符串“foo”的正文过滤器。如果可能,由模块分配的新缓冲区将重新使用。请注意,为了使此示例正常工作,还需要设置头过滤器并将content_length_n
重置为-1
,但相关代码为不在这里提供
typedef struct { ngx_chain_t *free; ngx_chain_t *busy; } ngx_http_foo_filter_ctx_t; ngx_int_t ngx_http_foo_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_int_t rc; ngx_buf_t *b; ngx_chain_t *cl, *tl, *out, **ll; ngx_http_foo_filter_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_foo_filter_module); if (ctx == NULL) { ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_foo_filter_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_foo_filter_module); } /* create a new chain "out" from "in" with all the changes */ ll = &out; for (cl = in; cl; cl = cl->next) { /* append "foo" in a reused buffer if possible */ tl = ngx_chain_get_free_buf(r->pool, &ctx->free); if (tl == NULL) { return NGX_ERROR; } b = tl->buf; b->tag = (ngx_buf_tag_t) &ngx_http_foo_filter_module; b->memory = 1; b->pos = (u_char *) "foo"; b->last = b->pos + 3; *ll = tl; ll = &tl->next; /* append the next incoming buffer */ tl = ngx_alloc_chain_link(r->pool); if (tl == NULL) { return NGX_ERROR; } tl->buf = cl->buf; *ll = tl; ll = &tl->next; } *ll = NULL; /* send the new chain */ rc = ngx_http_next_body_filter(r, out); /* update "busy" and "free" chains for reuse */ ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, (ngx_buf_tag_t) &ngx_http_foo_filter_module); return rc; }
Load balancing
ngx_http_upstream_module提供将请求传递给远程服务器所需的基本功能。实现特定协议的模块(如HTTP或FastCGI)使用此功能。该模块还提供了创建自定义负载平衡模块的接口,并实现了默认的循环方法。
minimum_conn和hash模块实现了替代的负载平衡方法,但实际上被实现为上游循环模块的扩展,并与其共享大量代码,例如服务器组的表示。keepalive模块是一个扩展上游功能的独立模块。
可以通过将相应的上游块放入配置文件中,或者通过使用接受URL的指令(例如proxy_pass)来明确地配置ngx_http_upstream_module这在某些时候被评估到服务器列表中。替代的负载平衡方法仅在明确的上游配置下可用。上游模块配置有自己的指令上下文NGX_HTTP_UPS_CONF
。结构定义如下:
struct ngx_http_upstream_srv_conf_s { ngx_http_upstream_peer_t peer; void **srv_conf; ngx_array_t *servers; /* ngx_http_upstream_server_t */ ngx_uint_t flags; ngx_str_t host; u_char *file_name; ngx_uint_t line; in_port_t port; ngx_uint_t no_port; /* unsigned no_port:1 */ #if (NGX_HTTP_UPSTREAM_ZONE) ngx_shm_zone_t *shm_zone; #endif };
srv_conf
- 上游模块的配置上下文。服务器
-ngx_http_upstream_server_t
的数组,解析上游
块中的一组服务器指令。标志
- 主要标记负载平衡方法支持哪些功能的标志。这些功能被配置为服务器指令的参数: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
- 支持“备份
”参数NGX_HTTP_UPSTREAM_MAX_CONNS
- 支持“max_conns
”参数
host
- 上游的名称。file_name,line
- 配置文件的名称和上游
块所在的行。port
和no_port
- 不用于显式定义的上游组。shm_zone
- 此上游组使用的共享内存区域(如果有)。对等体
- 保存用于初始化上游配置的通用方法的对象:
实现负载平衡算法的模块必须设置这些方法并初始化privatetypedef struct { ngx_http_upstream_init_pt init_upstream; ngx_http_upstream_init_peer_pt init; void *data; } ngx_http_upstream_peer_t;
数据
。如果在配置解析期间未初始化init_upstream
,ngx_http_upstream_module
将其设置为默认的ngx_http_upstream_init_round_robin
算法。init_upstream(cf,us)
- 配置时方法负责初始化一组服务器,并在成功时初始化init()
方法。典型的负载平衡模块使用上游
块中的服务器列表来创建其使用的有效数据结构,并将其自己的配置保存到数据
字段。init(r,us)
- 初始化用于负载平衡的每个请求ngx_http_upstream_peer_t.peer
结构(不要与ngx_http_upstream_srv_conf_t.peer 上述)。
它作为数据
参数传递给处理服务器选择的所有回调。
当nginx必须将请求传递给另一个主机进行处理时,它将使用配置的负载平衡方法来获取要连接的地址。该方法从ngx_peer_connection_t
类型的ngx_http_upstream_t.peer
对象获取:
struct ngx_peer_connection_s { ... struct sockaddr *sockaddr; socklen_t socklen; ngx_str_t *name; ngx_uint_t tries; ngx_event_get_peer_pt get; ngx_event_free_peer_pt free; ngx_event_notify_peer_pt notify; void *data; #if (NGX_SSL || NGX_COMPAT) ngx_event_set_peer_session_pt set_session; ngx_event_save_peer_session_pt save_session; #endif ... };
该结构具有以下字段:
sockaddr
,socklen
,name
- 要连接的上游服务器的地址;这是负载平衡方法的输出参数。数据
- 负载均衡方法的每请求数据;保持选择算法的状态,通常包括到上游配置的链路。它作为参数传递给处理服务器选择的所有方法(参见)。尝试
- 允许尝试连接到上游服务器的号码。get
,free
,通知
,set_session
和save_session
平衡模块,如下所述。
所有方法至少接受两个参数:由ngx_http_upstream_srv_conf_t.peer.init()
创建的对等连接对象pc
和数据
。请注意,由于负载平衡模块的“链接”,它可能与pc.data
不同。
get(pc,data)
- 当上游模块准备好将请求传递给上游服务器并需要知道其地址时调用的方法。该方法必须填充ngx_peer_connection_t
结构的sockaddr
,socklen
和名称
字段。回报是以下之一:NGX_OK
- 已选择服务器。NGX_ERROR
- 发生内部错误。NGX_BUSY
- 目前没有服务器可用。这可能是由于许多原因,包括:动态服务器组为空,组中的所有服务器都处于故障状态,或组中的所有服务器都已处理最大连接数。NGX_DONE
- 重用基础连接,无需创建与上游服务器的新连接。该值由keepalive
模块设置。
free(pc,data,state)
- 当上游模块已完成与特定服务器的工作时调用的方法。状态
参数是上游连接的完成状态,具有以下可能值的位掩码:该方法还会减小尝试
计数器。通知(pc,数据,类型)
- 目前在OSS版本中未使用。set_session(pc,data)
和save_session(pc,data)
- 启用高速缓存会话到上游服务器的SSL特定方法。实施由循环平衡方法提供。