开发指南
介绍
代码布局
-
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>
Stream 代码应包括
#include <ngx_stream.h>
整数
对于一般用途,nginx 代码使用两种整数类型,ngx_int_t和ngx_uint_t,它们是
typedefs 的intptr_t和uintptr_t分别。
常见返回代码
nginx 中的大多数函数都返回以下代码:
-
NGX_OK—作成功。 -
NGX_ERROR—作失败。 -
NGX_AGAIN—作未完成;再次调用该函数。 -
NGX_DECLINED—作被拒绝,例如,因为它是 disabled。这绝不是错误。 -
NGX_BUSY— 资源不可用。 -
NGX_DONE— 行动完成或在其他地方继续。 也用作替代成功代码。 -
NGX_ABORT— 函数已中止。 也用作替代错误代码。
错误处理
这ngx_errnomacro 返回最后一个系统错误代码。
它被映射到errno在 POSIX 平台上,并将GetLastError()调用。
这ngx_socket_errnomacro 返回最后一个套接字错误
数。
与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 使用 unsigned 字符类型指针u_char *.
nginx 字符串类型ngx_str_t定义如下:
typedef struct {
size_t len;
u_char *data;
} ngx_str_t;
这lenfield 保存字符串长度,data保存字符串 data。
字符串,被ngx_str_t,可能是也可能不是
null-terminated 在len字节。
在大多数情况下,情况并非如此。
但是,在代码的某些部分(例如,解析配置时),ngx_str_t已知对象以 null 结尾,这
简化了字符串比较,并使其更容易将字符串传递给
sys调用。
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(),但此调用永远不会被 编译器的 Dead Store Elimination Optimization。 该功能可用于清除密码、密钥等敏感数据。 -
ngx_cpymem()— 与ngx_memcpy(),但返回最终目标地址 这个选项对于在一行中附加多个字符串很方便。 -
ngx_movemem()— 与ngx_memmove(),但返回最终目标地址。 -
ngx_strlchr()— 在字符串中搜索字符, 由 2 个指针分隔。
以下函数执行大小写转换和比较:
-
ngx_tolower() -
ngx_toupper() -
ngx_strlow() -
ngx_strcasecmp() -
ngx_strncasecmp()
以下宏简化了字符串初始化:
-
ngx_string(text)— static 初始化器ngx_str_ttype 从 C 字符串文本text -
ngx_null_string— static 空字符串初始化器ngx_str_t类型 -
ngx_str_set(str, text)— 初始化字符串str之ngx_str_t *type 替换为 C 字符串 字面text -
ngx_str_null(str)— 初始化字符串str之ngx_str_t *type 替换为空字符串
格式
以下格式化函数支持 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 *(以 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;
数字转换
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)- 转换固定点浮点数 Number of given length 到 a type of a positive integerngx_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.
要使用正则表达式进行字符串匹配,它首先需要是
compiled,这通常在配置阶段完成。
请注意,由于 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_tstructure 包含所有
captures 和 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、要匹配的字符串input,
一个可选的整数数组,用于保存任何captures他们是
found 和数组的size.
的大小capturesarray 必须是 3 的倍数,
根据 PCRE API 的要求。
在此示例中,大小是根据捕获总数加上
一个用于匹配的字符串本身。
如果存在匹配项,则可以按如下方式访问捕获:
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()function 接受ngx_regex_elt_t元素(只是编译为常规的
表达式、要匹配的字符串和日志。
该函数将数组中的表达式应用于字符串,直到
找到匹配项或没有更多表达式。
返回值为NGX_OK当存在匹配项时,并且NGX_DECLINED否则,或NGX_ERROR如果出现错误。
时间
这ngx_time_tstructure 表示时间,具有三个单独的
类型表示秒、毫秒和 GMT 偏移量:
typedef struct {
time_t sec;
ngx_uint_t msec;
ngx_int_t gmtoff;
} ngx_time_t;
这ngx_tm_tstructure 是struct tm在 UNIX 平台上,以及SYSTEMTIME在 Windows 上。
要获取当前时间,通常只需访问 available global variables,表示所需 格式。
可用的字符串表示形式包括:
-
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 标头:"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_infix 设置为struct tm:
-
ngx_gmtime(), ngx_libc_gmtime()— 以 UTC 表示的时间 -
ngx_localtime(), ngx_libc_localtime()— 时间表示 相对于本地时区
这ngx_http_time(buf, time)function 返回一个字符串
适合在 HTTP 标头中使用的表示形式(例如"Mon, 28 Sep 1970 06:00:00 GMT").
这ngx_http_cookie_time(buf, time)返回一个字符串
表示函数返回一个适合的字符串表示
对于 HTTP Cookie ("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田。
这neltsfield 包含元素的数量。
这sizefield 保存单个元素的大小,并设置
初始化数组时。
使用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)添加一个 tail 元素并返回 pointer 到 -
ngx_array_push_n(a, n)增加n尾部元素 并返回指向第一个
如果当前分配的内存量不足以容纳 新元素、分配新的内存块和现有元素 将复制到该数据库。 新内存块通常是现有内存块的两倍。
s = ngx_array_push(a); ss = ngx_array_push_n(&b, 3);
列表
在 nginx 中,列表是一系列数组,针对插入潜在的
大量项目。
这ngx_list_tlist 类型定义如下:
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).
这两个函数都将单个项目的大小和数量
items per list part 的 items (每个列表部分的项数)。
要将项目添加到列表中,请使用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对象)设置为 missing,请将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)调用 name 来初始化 list head
使用前。
队列支持以下作:
-
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 对象 iteration at -
ngx_queue_data(q, type, link)— 获取对 queue 节点数据结构的开头,考虑 它
一个例子:
typedef struct {
ngx_str_t value;
ngx_queue_t queue;
} ngx_foo_t;
ngx_foo_t *f;
ngx_queue_t values, *q;
ngx_queue_init(&values);
f = ngx_palloc(pool, sizeof(ngx_foo_t));
if (f == NULL) { /* error */ }
ngx_str_set(&f->value, "foo");
ngx_queue_insert_tail(&values, &f->queue);
/* insert more nodes here */
for (q = ngx_queue_head(&values);
q != ngx_queue_sentinel(&values);
q = ngx_queue_next(q))
{
f = ngx_queue_data(q, ngx_foo_t, queue);
ngx_do_smth(&f->value);
}
红黑树
这src/core/ngx_rbtree.h头文件提供对
红黑树的有效实施。
typedef struct {
ngx_rbtree_t rbtree;
ngx_rbtree_node_t sentinel;
/* custom per-tree data here */
} my_tree_t;
typedef struct {
ngx_rbtree_node_t rbnode;
/* custom per-node data */
foo_t val;
} my_node_t;
要将树作为一个整体进行处理,您需要两个节点:root 和 sentinel。 通常,它们会添加到自定义结构中,从而允许您 将数据组织到一个树中,其中的叶子包含指向或嵌入的链接 您的数据。
要初始化树:
my_tree_t root; ngx_rbtree_init(&root.rbtree, &root.sentinel, insert_value_function);
要遍历树并插入新值,请使用
"insert_value“ 函数。
例如,ngx_str_rbtree_insert_value功能交易
使用ngx_str_t类型。
它的参数是指向插入的根节点的指针,即新创建的
node 和 tree sentinel。
void ngx_str_rbtree_insert_value(ngx_rbtree_node_t *temp,
ngx_rbtree_node_t *node,
ngx_rbtree_node_t *sentinel)
遍历非常简单,可以用 以下 lookup 函数模式:
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()function 是一个经典的比较器函数,
返回小于、等于或大于零的值。
为了加快查找速度并避免比较可能很大的用户对象,可以使用整数
hash 字段。
要将节点添加到树中,请分配一个新节点,对其进行初始化并调用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()功能