开发#
Angie 是一个开源项目,
欢迎所有贡献者。 您的更改应与 Angie 其余代码保持一致;
编码约定 是一个很好的起点。 小技巧 如有疑问,请检查附近的代码以遵循其风格,
或者直接在代码库中搜索以获取灵感。 从历史上看,提交日志使用英语维护。 以一行总结所做的工作开始。
它可以有一个前缀,提交日志将其用于受影响的代码部分。
总结最多可以有 67 个字符,
后面可以跟一个空行和更多详细信息。 一个好的消息会说明是什么导致了更改、对此做了什么,
以及现在的情况如何: 可能会被忽略的细节: 总结以句号结尾,并以大写字母开头。 如果使用前缀,则后面跟小写字母。 双空格分隔单行内的句子。 尽最大努力验证更改在 所有 目标平台上都能正常工作。 对于每个平台,运行测试套件以确保没有回归: 详细信息请参阅 确保您对 法律条款 感到满意。 要提交补丁,请在我们的
GitHub 镜像 上创建拉取请求。 如有问题和建议,请通过
GitHub Issues 联系开发人员。 源代码遵循以下结构和规范。 以下两个 除此之外,HTTP 代码应包含 邮件代码应包含 流代码应包含 对于一般用途,Angie 代码使用两种整数类型,
Angie 中的大多数函数返回以下代码: 使用 对于 C 字符串,Angie 使用无符号字符类型指针
Angie 字符串类型 Angie 中的字符串操作在
其他字符串函数是 Angie 特定的: 以下函数执行大小写转换和比较: 以下宏简化了字符串初始化: 以下格式化函数支持 Angie 特定类型: 这些函数支持的格式化选项的完整列表在
您可以在大多数类型前加上 Angie 中实现了几个数值转换函数。
前四个函数各自将给定长度的字符串转换为指定类型的正整数。
它们在出错时返回 还有两个额外的数值转换函数。
与前四个一样,它们在出错时返回 Angie 中的正则表达式接口是对
PCRE 库的包装。
相应的头文件是 要使用正则表达式进行字符串匹配,首先需要
编译它,这通常在配置阶段完成。
请注意,由于 PCRE 支持是可选的,所有使用该接口的代码必须
由周围的 成功编译后, 然后可以使用编译后的正则表达式来匹配字符串: 编译成功后, 然后可以使用编译后的正则表达式来匹配字符串: 如果有匹配,可以按如下方式访问捕获: 要获取当前时间,通常只需访问一个可用的全局变量,
该变量以所需格式表示缓存的时间值。 可用的字符串表示形式有: 要显式获取时间,使用 以下函数将 Angie 数组类型 数组的元素在 使用 使用以下函数向数组添加元素: 如果当前分配的内存量不足以容纳新元素,则会分配一个新的内存块并将现有元素复制到其中。
新内存块通常是现有内存块的两倍大。 在 Angie 中,列表是一系列数组,针对插入可能大量的项目进行了优化。
实际项目存储在列表部分中,定义如下: 在使用之前,必须通过调用
列表主要用于 HTTP 输入和输出头。 列表不支持项目删除。
但是,在需要时,可以在内部将项目标记为缺失,而无需实际从列表中删除。
例如,要将 HTTP 输出头(存储为
在 Angie 中,队列是一个侵入式双向链表,每个节点定义如下: 头队列节点不与任何数据链接。
在使用之前使用 示例: 要将树作为一个整体来处理,你需要两个节点:根节点和哨兵节点。
通常,它们被添加到一个自定义结构中,允许你将数据组织成一棵树,
其中叶子节点包含指向你的数据的链接或嵌入你的数据。 要初始化一棵树: 要遍历树并插入新值,使用 " 遍历过程非常直观,可以用以下查找函数模式来演示: 要向树中添加节点,分配一个新节点,初始化它并调用
要删除节点,调用 哈希表函数在 在初始化哈希表之前,你需要知道它将容纳的元素数量,
以便 Angie 可以最优地构建它。
需要配置的两个参数是 哈希键存储在 要将键插入哈希键数组,使用
要构建哈希表,调用
如果 当哈希表构建完成后,使用
要创建支持通配符的哈希表,使用 可以使用 该函数识别通配符并将键添加到相应的数组中。
有关通配符语法和匹配算法的描述,请参阅
Map 模块文档。 根据添加的键的内容,你可能需要初始化最多三个键数组:
一个用于精确匹配(如上所述),另外两个用于从字符串的头部或尾部开始匹配: 键数组需要排序,初始化结果必须添加到组合哈希表中。
组合哈希表中的查找由
要从系统堆分配内存,使用以下函数: 大多数 Angie 分配都在内存池中完成。
在 Angie 内存池中分配的内存在内存池销毁时会自动释放。
这提供了良好的分配性能并使内存控制变得容易。 内存池在内部以连续的内存块分配对象。
一旦块满了,就会分配一个新块并添加到内存池的内存块列表中。
当请求的分配太大而无法放入块中时,
请求会转发到系统分配器,返回的指针会存储在内存池中以便后续释放。 Angie 内存池的类型是 链接节点( 可以在内存池中注册清理处理程序。
清理处理程序是一个带参数的回调函数,在内存池销毁时调用。
内存池通常与特定的 Angie 对象(如 HTTP 请求)绑定,
并在对象到达其生命周期结束时销毁。
注册内存池清理是释放资源、关闭文件描述符
或对与主对象关联的共享数据进行最终调整的便捷方式。 要注册内存池清理,调用
Angie 使用共享内存在进程之间共享公共数据。
共享内存条目结构 共享区域条目在配置解析后在 为了在共享内存中分配,Angie 提供了 slab 池
slab 内存池将所有共享区域划分为页面。
每个页面用于分配相同大小的对象。
指定的大小必须是 2 的幂,且大于最小大小 8 字节。
不符合的值会被向上舍入。
对于大于半页(通常为 2048 字节)的大小,
分配一次完成整个页面。 要保护共享内存中的数据免受并发访问,
使用 对于日志记录,Angie 使用 stderr — 记录到标准错误(stderr) file — 记录到文件 syslog — 记录到 syslog memory — 记录到内部内存存储以用于开发目的;该内存
可以稍后通过调试器访问 日志记录器实例可以是一个日志记录器链,通过
对于每个日志记录器,严重性级别控制哪些消息被写入
日志(只有分配了该级别或更高级别的事件才会被记录)。
支持以下严重性级别: 对于调试日志记录,还会检查调试掩码。
调试掩码包括: 通常,日志记录器由现有的 Angie 代码从
Angie 提供以下日志记录宏: 日志消息在栈上大小为
上面的示例会产生如下的日志条目: cycle 对象存储从特定配置创建的 Angie 运行时上下文。
其类型是 cycle 由 cycle 的成员包括: path loader — 在启动或重新加载
Angie 后 60 秒内仅执行一次。
通常,加载器读取目录并将数据存储在 Angie 共享
内存中。
该处理程序从专用的 Angie 进程"cache loader"调用。 path manager — 定期执行。
通常,管理器从目录中删除旧文件并更新 Angie
内存以反映更改。
该处理程序从专用的"cache manager"进程调用。 对于输入/输出操作,Angie 提供缓冲区类型
对于输入和输出操作,缓冲区以链的形式链接。
链是类型为 每个链式链接保持对其缓冲区的引用和对下一个
链式链接的引用。 使用缓冲区和链的示例: 连接类型 Angie 连接可以透明地封装 SSL 层。
在这种情况下,连接的 Angie 配置中的 由于每个工作进程的连接数有限,Angie 提供了一种
获取当前正在使用的连接的方法。
要启用或禁用连接的重用,请调用
Angie 中的事件对象 通过调用 可以设置事件以在超时到期时发送通知。
事件使用的定时器从某个未指定的时间点开始计算毫秒数,
截断为 函数 可以发布事件,这意味着其处理程序将在当前事件循环迭代中的某个时刻被调用。
发布事件是简化代码和避免栈溢出的良好实践。
已发布的事件保存在发布队列中。
示例: 除了 Angie 主进程外,所有 Angie 进程都执行 I/O 操作,因此都有一个事件循环。
(Angie 主进程则将大部分时间花在 事件循环包含以下阶段: 通过调用 通过调用处理程序来处理 I/O 事件,该处理程序特定于 Angie 配置选择的事件通知机制。
该处理程序至少等待一个 I/O 事件发生,但仅等待到下一个超时时间过期。
当读或写事件发生时, 通过调用 通过调用 所有 Angie 进程也处理信号。
信号处理程序仅设置全局变量,这些变量在 Angie 中有几种类型的进程。
进程的类型保存在 Angie 进程处理以下信号: 虽然所有 Angie 工作进程都能够接收并正确处理 POSIX 信号,但主进程不使用标准的 可以将原本会阻塞 Angie 工作进程的任务卸载到单独的线程中。
例如,Angie 可以配置为使用线程执行 文件 I/O。
另一个用例是没有异步接口的库,因此无法正常与 Angie 一起使用。
请记住,线程接口是现有异步处理客户端连接方法的辅助工具,绝不是替代品。 为了处理同步,提供了以下 Angie 实现了 线程池 策略,而不是为每个任务创建新线程。
可以为不同的目的配置多个线程池(例如,在不同的磁盘集上执行 I/O)。
每个线程池在启动时创建,包含有限数量的线程来处理任务队列。
当任务完成时,调用预定义的完成处理程序。 在配置阶段,希望使用线程的模块需要通过调用 要在运行时将 要在线程中执行函数,请传递参数并使用 每个独立的 Angie 模块都位于一个单独的目录中,该目录至少包含两个文件:
以下模块通常用作参考。
默认情况下,过滤器模块放置在模块列表中 要将模块静态编译到 Angie 中,请在 configure 脚本中使用 模块是 Angie 的构建块,其大部分功能都是作为模块实现的。
模块源文件必须包含一个 省略的私有部分包括模块版本和签名,并使用预定义的宏 每个模块在 配置指令处理程序在主进程的上下文中按照它们在配置文件中出现的顺序被调用。 配置成功解析后,在主进程的上下文中调用 主进程创建一个或多个工作进程,并在每个进程中调用 当工作进程从主进程接收到关闭或终止命令时,它会调用 主进程在退出之前调用 由于线程在 Angie 中仅用作具有自己 API 的补充 I/O 设施,因此当前不会调用 模块 核心模块集包括 其中 例如,一个名为 其中 例如,一个名为 使用特殊值 指令类型的标志: 指令上下文定义了它可以出现在配置中的位置: 配置解析器使用这些标志对放错位置的指令抛出错误,并使用适当的配置指针调用指令处理程序,
以便不同位置的相同指令可以将其值存储在不同的位置。 每个 HTTP 客户端连接都会经历以下阶段: 对于每个客户端 HTTP 请求,都会创建 请注意,对于 HTTP 连接, 请求通常通过 每个 HTTP 模块可以有三种类型的配置: 主配置 — 应用于整个 服务器配置 — 应用于单个 位置配置 — 应用于单个 配置结构在 Angie 配置阶段通过调用分配结构、初始化结构和合并结构的函数来创建。
以下示例展示了如何为模块创建简单的位置配置。
该配置有一个设置 如示例所示, 以下宏可用于在配置时访问 HTTP 模块的配置。
它们都将 以下示例获取标准 核心 HTTP 模块 的位置配置指针,
并替换存储在结构的 以下宏可用于在运行时访问 HTTP 模块的配置。 这些宏接收对 HTTP 请求 每个 HTTP 请求都会经过一系列阶段。
在每个阶段,对请求执行不同类型的处理。
模块特定的处理器可以在大多数阶段注册,
许多标准 Angie 模块注册其阶段处理器作为在请求处理的特定阶段被调用的方式。
阶段按顺序处理,一旦请求到达该阶段,就会调用阶段处理器。
以下是 Angie HTTP 阶段的列表。 以下是预访问阶段处理器的示例。 阶段处理器应返回特定的代码: 阶段处理器返回的任何其他值都被视为请求
终结代码,特别是 HTTP 响应代码。
请求将使用提供的代码终结。 对于某些阶段,返回代码的处理方式略有不同。
在内容阶段,除
nginx-dev-examples
仓库提供了同样适用于 Angie 的 nginx 模块示例。 最大文本宽度为 80 个字符 缩进为 4 个空格 不使用制表符,不使用尾随空格 同一行上的列表元素用空格分隔 十六进制字面量使用小写 文件名、函数和类型名称以及全局变量具有
典型的源文件可能包含以下部分,用
两个空行分隔: 版权声明 包含文件 预处理器定义 类型定义 函数原型 变量定义 函数定义 版权声明如下所示: 如果文件被显著修改,应更新作者列表,
新作者添加到顶部。 始终首先包含 头文件应包含所谓的"头文件保护": 不使用 文本使用英语,首选美式拼写 多行注释格式如下: 宏名称以 条件在括号内,否定在外: 类型名称以 结构类型使用 在文件中的不同结构之间保持相同的对齐。
指向自身的结构的名称以
每个结构成员在单独的行上声明: 结构内部的函数指针具有以
枚举类型以 变量声明按基本类型的长度排序,然后按字母顺序排序。
类型名称和变量名称对齐。
类型和名称"列"用两个空格分隔。
大型数组放在声明块的末尾: 静态和全局变量可以在声明时初始化: 有一些常用的类型/名称组合: 所有函数(即使是静态函数)都应有原型。
原型包含参数名称。
长原型在续行上使用单个缩进换行: 定义中的函数名称从新行开始。
函数体的左右花括号在单独的行上。
函数体缩进。
函数之间有两个空行: 函数名称和左括号之间没有空格。
长函数调用换行,使续行从
第一个函数参数的位置开始。
如果这不可能,则格式化第一个续行,使其
在第 79 个位置结束: 应使用 除 类型转换用一个空格与被转换的表达式分隔。
类型转换中的星号用一个空格与类型名称分隔: 如果表达式无法放入单行,则进行换行。
首选的换行点是二元运算符。
续行与表达式的开始对齐: 作为最后的手段,可以对表达式进行换行,使续行在第 79 列结束: 上述规则也适用于子表达式,
其中每个子表达式都有自己的缩进级别: 有时在类型转换后换行表达式很方便。
在这种情况下,续行进行缩进: 指针显式地与
类似的格式规则适用于 大多数 如果 空循环体也用
无限循环如下所示: 标签周围有空行,并在前一级别缩进: 要调试内存问题,如缓冲区溢出或释放后使用错误,您可以使用
AddressSanitizer
(ASan),一些现代编译器支持它。
要在 由于 Angie 中的大多数分配都是从 Angie 内部
池 进行的,启用 ASan 可能并不总是足以调试
内存问题。
内部池从系统分配一大块内存,并从中切割较小的分配。
但是,可以通过将
以下配置行总结了上面提供的信息。
建议在开发第三方模块和在不同平台上测试 Angie 时使用。 最常见的陷阱是在可以避免的情况下尝试编写一个完整的 C 模块。
在大多数情况下,您的任务可以通过创建适当的配置来完成。
如果编写模块不可避免,请尝试使其
尽可能小而简单。
例如,一个模块可以只导出一些
变量。 在开始编写模块之前,请考虑以下问题: Angie 中最常用的字符串类型
ngx_str_t 不是 C 风格的
以零结尾的字符串。
您不能将数据传递给标准 C 库函数,
例如 避免在模块中使用全局变量。
使用全局变量很可能是一个错误。
任何全局数据都应该绑定到 配置周期,
并从相应的 内存池 中分配。
这允许 Angie 执行平滑的配置重载。
尝试使用全局变量可能会破坏此功能,
因为将无法同时拥有两个配置并
摆脱它们。
有时需要全局变量。
在这种情况下,需要特别注意正确管理重新配置。
此外,检查您的代码使用的库是否具有隐式
全局状态,这些状态可能在重载时被破坏。 不要使用容易出错的 malloc/free 方法,
而是学习如何使用 Angie 的 内存池。
内存池被创建并绑定到一个对象 -
配置、
周期、
连接
或 HTTP 请求。
当对象被销毁时,关联的内存池也会被销毁。
因此,在处理对象时,可以从相应的内存池中分配所需的数量,
而不必担心释放内存,
即使在出现错误的情况下也是如此。 建议避免在 Angie 中使用线程,因为它肯定会破坏某些功能:
大多数 Angie 函数不是线程安全的。
预期线程只执行系统调用和
线程安全的库函数。
如果您需要运行一些与客户端请求处理无关的代码,
正确的方法是在 一个常见的错误是使用内部阻塞的库。
大多数现有的库本质上是同步和阻塞的。
换句话说,它们一次执行一个操作,并浪费
时间等待对方的响应。
因此,当使用这样的库处理请求时,整个
Angie worker 会被阻塞,从而破坏性能。
只使用提供异步接口且不会
阻塞整个进程的库。 模块通常需要对某些外部服务执行 HTTP 调用。
一个常见的错误是使用某些外部库(例如 libcurl)
来执行 HTTP 请求。
为了完成 Angie 本身就能完成的任务,
引入大量外部(可能是 阻塞的!)代码
是完全没有必要的。 当需要外部请求时,有两种基本使用场景: 在处理客户端请求的上下文中(例如,在内容处理程序中) 在 worker 进程的上下文中(例如,定时器处理程序) 在第一种情况下,最好使用
子请求 API。
您不是直接访问外部服务,而是在
Angie 配置中声明一个 location,
并将子请求定向到此 location。
此 location 不限于
代理
请求,还可以包含其他 Angie 指令。
这种方法的一个例子是
Auth Request 中实现的
auth_request 指令。 对于第二种情况,可以使用 Angie 中可用的基本 HTTP 客户端功能。
例如,
OCSP 模块
实现了简单的 HTTP 客户端。源代码#
编码风格#
提交消息#
API: bad things removed, good things added.
As explained elsewhere[1], the original API was bad because stuff;
this change was introduced to improve that aspect locally.
Levels of goodness have been implemented to mitigate the badness;
this is now the preferred way to work. Also, the badness is gone.
[1] https://example.com
最终检查#
$ cd tests
$ prove .
tests/README 文件。提交贡献#
编码规范#
代码布局#
auto — 构建脚本srccore — 基本类型和函数 — 字符串、数组、日志、
内存池等。event — 事件核心modules — 事件通知模块:
epoll、kqueue、select
等。http — 核心 HTTP 模块和通用代码modules — 其他 HTTP 模块v2 — HTTP/2mail — 邮件模块os — 平台特定代码unixwin32stream — 流模块包含文件#
#include 语句必须出现在每个 Angie 文件的
开头:#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_mail.h>
#include <ngx_stream.h>
整数#
ngx_int_t 和 ngx_uint_t,它们分别是
intptr_t 和 uintptr_t 的类型定义。常见返回码#
NGX_OK — 操作成功。NGX_ERROR — 操作失败。NGX_AGAIN — 操作未完成;再次调用该函数。NGX_DECLINED — 操作被拒绝,例如,因为它在
配置中被禁用。这绝不是错误。NGX_BUSY — 资源不可用。NGX_DONE — 操作完成或在其他地方继续。
也用作替代成功代码。NGX_ABORT — 函数被中止。
也用作替代错误代码。错误处理#
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 的示例: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;
}
字符串#
概述#
u_char *。ngx_str_t 定义如下:typedef struct {
size_t len;
u_char *data;
} ngx_str_t;
len 字段保存字符串长度,
data 保存字符串数据。
保存在 ngx_str_t 中的字符串在 len 字节之后可能以 null 结尾,也可能不以 null 结尾。
在大多数情况下不以 null 结尾。
但是,在代码的某些部分(例如,解析配置时),
ngx_str_t 对象已知以 null 结尾,这
简化了字符串比较,并使将字符串传递给
系统调用变得更容易。src/core/ngx_string.h 中声明。
其中一些是标准 C 函数的包装器:ngx_strcmp()ngx_strncmp()ngx_strstr()ngx_strlen()ngx_strchr()ngx_memcmp()ngx_memset()ngx_memcpy()ngx_memmove()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) — 从 C 字符串字面量
text 为 ngx_str_t 类型的静态初始化器ngx_null_string — ngx_str_t 类型的静态空字符串初始化器ngx_str_set(str, text) — 使用 C 字符串
字面量 text 初始化 ngx_str_t * 类型的字符串
strngx_str_null(str) — 使用空字符串初始化 ngx_str_t * 类型的字符串 str格式化#
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 。数值转换#
NGX_ERROR。ngx_atoi(line, n) — ngx_int_tngx_atosz(line, n) — ssize_tngx_atoof(line, n) — off_tngx_atotm(line, n) — time_tNGX_ERROR。ngx_atofp(line, n, point) — 将给定长度的定点数
转换为 ngx_int_t 类型的正整数。
结果左移 point 个十进制
位置。
数字的字符串表示预期不超过
point 个小数位。
例如,ngx_atofp("10.5", 4, 2) 返回
1050。ngx_hextoi(line, n) — 将正整数的十六进制表示
转换为 ngx_int_t。正则表达式#
src/core/ngx_regex.h。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 可以设置为 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
ngx_regex_compile_t 结构中的 captures 和 named_captures 字段分别包含在正则表达式中找到的所有捕获和命名捕获的计数。#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
ngx_regex_compile_t 结构中的 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,以及数组的 size。
captures 数组的大小必须是三的倍数,
这是 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() 函数接受一个 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;
ngx_tm_t 结构在 UNIX 平台上是 struct tm 的别名,
在 Windows 上是 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 — 用于 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 的指针)。
当 Angie 从系统调用返回到事件循环时,时间总是会更新。
要立即更新时间,调用 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 cookie 的字符串表示形式
("Thu, 31-Dec-37 23:55:55 GMT")。容器#
数组#
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);
列表#
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]);
}
ngx_table_elt_t 对象)标记为缺失,将 ngx_table_elt_t 中的
hash 字段设置为零。
在遍历头时,会显式跳过以这种方式标记的项目。队列#
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 中声明。
支持精确匹配和通配符匹配。
后者需要额外的设置,将在下面的单独章节中描述。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);
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() 的包装器。内存池#
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)在 Angie 中被积极使用,
因此 Angie 内存池实现提供了一种重用它们的方法。
ngx_pool_t 的 chain 字段保存了
先前分配的可供重用的链接节点列表。
要在内存池中高效分配链接节点,使用
ngx_alloc_chain_link(pool) 函数。
此函数在内存池列表中查找空闲链接节点,如果内存池列表为空则分配新的链接节点。
要释放链接节点,调用 ngx_free_chain(pool, cl) 函数。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);
}
共享内存#
ngx_shared_memory_add(cf, name, size, tag) 函数
向循环添加一个新的共享内存条目 ngx_shm_zone_t。
该函数接收共享区域的 name 和 size。
每个共享区域必须有唯一的名称。
如果已存在具有提供的 name 和 tag 的共享区域条目,
则重用现有的区域条目。
如果具有相同名称的现有条目具有不同的标签,该函数将失败并返回错误。
通常,模块结构的地址作为 tag 传递,
使得可以在一个 Angie 模块内按名称重用共享区域。ngx_shm_zone_t 具有以下字段:init — 初始化回调,在共享区域映射到实际内存后调用data — 数据上下文,用于将任意数据传递给
init 回调noreuse — 禁用从旧循环重用共享区域的标志tag — 共享区域标签shm — 类型为 ngx_shm_t 的平台特定对象,
至少具有以下字段:addr — 映射的共享内存地址,初始为 NULLsize — 共享内存大小name — 共享内存名称log — 共享内存日志exists — 指示共享内存是否从主进程继承的标志
(Windows 特定)ngx_init_cycle() 中映射到实际内存。
在 POSIX 系统上,使用 mmap() 系统调用创建共享匿名映射。
在 Windows 上,使用 CreateFileMapping() /
MapViewOfFileEx() 对。ngx_slab_pool_t 类型。
用于分配内存的 slab 池会在每个 Angie 共享区域中自动创建。
该池位于共享区域的开头,可以通过表达式
(ngx_slab_pool_t *) shm_zone->shm.addr 访问。
要在共享区域中分配内存,请调用
ngx_slab_alloc(pool, size) 或
ngx_slab_calloc(pool, size)。
要释放内存,请调用 ngx_slab_free(pool, p)。ngx_slab_pool_t 的 mutex 字段中可用的互斥锁。
互斥锁最常用于 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 Angie 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;
}
日志#
ngx_log_t 对象。
Angie 日志记录器支持几种输出类型:next 字段相互链接。
在这种情况下,每条消息都会写入链中的所有日志记录器。NGX_LOG_EMERGNGX_LOG_ALERTNGX_LOG_CRITNGX_LOG_ERRNGX_LOG_WARNNGX_LOG_NOTICENGX_LOG_INFONGX_LOG_DEBUGNGX_LOG_DEBUG_CORENGX_LOG_DEBUG_ALLOCNGX_LOG_DEBUG_MUTEXNGX_LOG_DEBUG_EVENTNGX_LOG_DEBUG_HTTPNGX_LOG_DEBUG_MAILNGX_LOG_DEBUG_STREAMerror_log 指令创建,并且在处理的几乎每个阶段都可用,
包括 cycle、配置、客户端连接和其他对象。ngx_log_error(level, log, err, fmt, ...) — 错误日志记录ngx_log_debug0(level, log, err, fmt)、
ngx_log_debug1(level, log, err, fmt, arg1) 等 — 调试
日志记录,最多支持八个格式化参数NGX_MAX_ERROR_STR (当前为 2048 字节)的缓冲区中格式化。
消息前面会添加严重性级别、进程 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);
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);
}
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#
ngx_cycle_t。
当前 cycle 由 ngx_cycle 全局
变量引用,并在 Angie worker 启动时被继承。
每次重新加载 Angie 配置时,都会从新的
Angie 配置创建一个新的 cycle;旧的 cycle 通常在新的 cycle 成功创建后被删除。ngx_init_cycle() 函数创建,该函数
将前一个 cycle 作为其参数。
该函数定位前一个 cycle 的配置文件,并尽可能从前一个 cycle 继承资源。
在 Angie 启动时会创建一个名为"init cycle"的占位符 cycle,然后
被从配置构建的实际 cycle 替换。pool — Cycle 内存池。
为每个新 cycle 创建。log — Cycle 日志。
最初从旧 cycle 继承,在读取配置后设置为指向
new_log。new_log — Cycle 日志,由配置创建。
它受根作用域 error_log 指令的影响。connections、connection_n —
类型为 ngx_connection_t 的连接数组,由
事件模块在初始化每个 Angie worker 时创建。
Angie 配置中的 worker_connections 指令
设置连接数 connection_n。free_connections、
free_connection_n — 当前可用连接的列表和数量。
如果没有可用连接,Angie worker 将拒绝接受新客户端
或连接到上游服务器。files、files_n — 用于将文件
描述符映射到 Angie 连接的数组。
此映射由具有
NGX_USE_FD_EVENT 标志的事件模块使用(当前为
poll 和 devpoll)。conf_ctx — 核心模块配置数组。
这些配置在读取 Angie 配置
文件时创建和填充。modules、modules_n — 类型为
ngx_module_t 的模块数组,包括静态和动态模块,由
当前配置加载。listening — 类型为
ngx_listening_t 的监听对象数组。
监听对象通常由不同模块的 listen
指令添加,这些指令调用
ngx_create_listening() 函数。
监听套接字基于监听对象创建。paths — 类型为 ngx_path_t 的路径数组。
路径由将要操作特定目录的模块通过调用函数 ngx_add_path() 添加。
如果缺少这些目录,Angie 会在读取配置后创建它们。
此外,可以为每个路径添加两个处理程序:open_files — 类型为
ngx_open_file_t 的打开文件对象列表,通过调用函数
ngx_conf_open_file() 创建。
目前,Angie 使用这种打开文件进行日志记录。
读取配置后,Angie 打开 open_files 列表中的所有文件
并将每个文件描述符存储在对象的 fd 字段中。
文件以追加模式打开,如果缺少则创建。
Angie worker 在收到重新打开信号(最常见的是 USR1)时重新打开列表中的文件。
在这种情况下,fd 字段中的描述符会更改为新值。shared_memory — 共享内存区域列表,每个区域通过调用
ngx_shared_memory_add() 函数添加。
共享区域映射到所有 Angie 进程中的相同地址范围,
用于共享公共数据,例如 HTTP 缓存内存树。缓冲区#
ngx_buf_t。
通常,它用于保存要写入目的地或从源读取的数据。
缓冲区可以引用内存或文件中的数据,并且从技术上讲,
缓冲区可以同时引用两者。
缓冲区的内存是单独分配的,与缓冲区
结构 ngx_buf_t 无关。ngx_buf_t 结构具有以下字段:start、end — 为缓冲区分配的内存
块的边界。pos、last — 内存
缓冲区的边界;通常是 start ..
end 的子范围。file_pos、file_last — 文件
缓冲区的边界,表示为从文件开头的偏移量。tag — 用于区分缓冲区的唯一值;由
不同的 Angie 模块创建,通常用于缓冲区重用。file — 文件对象。temporary — 指示缓冲区引用
可写内存的标志。memory — 指示缓冲区引用只读
内存的标志。in_file — 指示缓冲区引用
文件中数据的标志。flush — 指示缓冲区之前的所有数据
需要刷新的标志。recycled — 指示缓冲区可以重用
并且需要尽快消费的标志。sync — 指示缓冲区不携带数据或
特殊信号(如 flush 或 last_buf)的标志。
默认情况下,Angie 将此类缓冲区视为错误条件,但此标志告诉
Angie 跳过错误检查。last_buf — 指示缓冲区是
输出中最后一个的标志。last_in_chain — 指示请求或子请求中没有更多数据
缓冲区的标志。shadow — 对与
当前缓冲区相关的另一个("shadow")缓冲区的引用,通常意味着缓冲区使用来自
shadow 的数据。
当缓冲区被消费时,shadow 缓冲区通常也被标记为
已消费。last_shadow — 指示缓冲区是
引用特定 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 — 标志,指示连接正在被重用并需要关闭。ssl 字段持有指向
ngx_ssl_connection_t 结构的指针,保存连接的所有 SSL 相关数据,
包括 SSL_CTX 和
SSL。
recv、send、
recv_chain 和 send_chain 处理程序也被设置为支持 SSL 的函数。worker_connections 指令
限制每个 Angie 工作进程的连接数。
所有连接结构在工作进程启动时预先创建并存储在
循环对象的 connections 字段中。
要检索连接结构,请使用
ngx_get_connection(s, log) 函数。
它将套接字描述符作为其 s 参数,该描述符需要被包装在连接结构中。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 客户端连接是 Angie 中可重用连接的一个例子;它们
被标记为可重用,直到从客户端接收到第一个请求字节。事件#
事件#
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 — 用于将事件发布到队列的队列节点。I/O 事件#
ngx_get_connection()
函数获得的每个连接都附加了两个事件,c->read 和
c->write,用于接收套接字准备好读取或写入的通知。
所有这些事件都在边缘触发模式下运行,这意味着它们仅在套接字状态发生变化时触发通知。
例如,在套接字上进行部分读取不会使 Angie 传递重复的读取通知,直到套接字上有更多数据到达。
即使底层 I/O 通知机制本质上是电平触发(poll、select 等),Angie
也会将通知转换为边缘触发。
为了使 Angie 事件通知在不同平台上的所有通知系统中保持一致,
必须在处理 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 类型,是事件发生的时间。
树结构支持快速插入和删除操作,以及访问最近的超时,Angie 使用它来确定等待 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 */ }
}
事件循环#
sigsuspend() 调用上,等待信号到达。)
Angie 事件循环在 ngx_process_events_and_timers() 函数中实现,该函数会被重复调用,直到进程退出。ngx_event_find_timer() 查找最接近过期的超时时间。
该函数在定时器树中查找最左侧的节点,并返回该节点过期前的毫秒数。ready 标志被设置,并调用事件的处理程序。
对于 Linux,通常使用 ngx_epoll_process_events() 处理程序,它调用 epoll_wait() 来等待 I/O 事件。ngx_event_expire_timers() 使定时器过期。
从最左侧元素向右遍历定时器树,直到找到未过期的超时时间。
对于每个过期的节点,设置 timedout 事件标志,重置 timer_set 标志,并调用事件处理程序。ngx_event_process_posted() 处理已发布的事件。
该函数重复从已发布事件队列中移除第一个元素并调用该元素的处理程序,直到队列为空。ngx_process_events_and_timers() 调用后被检查。进程#
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()。NGX_SHUTDOWN_SIGNAL (在大多数系统上为 SIGQUIT)— 优雅关闭。
收到此信号后,主进程向所有子进程发送关闭信号。
当没有子进程剩余时,主进程销毁循环池并退出。
当工作进程收到此信号时,它关闭所有监听套接字并等待,直到没有不可取消的事件被调度,然后销毁循环池并退出。
当缓存管理器或缓存加载器进程收到此信号时,它立即退出。
当进程收到此信号时,ngx_quit 变量被设置为 1,并在处理后立即重置。
当工作进程处于关闭状态时,ngx_exiting 变量被设置为 1。NGX_TERMINATE_SIGNAL (在大多数系统上为 SIGTERM)— 终止。
收到此信号后,主进程向所有子进程发送终止信号。
如果子进程在 1 秒内未退出,主进程发送 SIGKILL 信号将其杀死。
当没有子进程剩余时,主进程销毁循环池并退出。
当工作进程、缓存管理器进程或缓存加载器进程收到此信号时,它销毁循环池并退出。
收到此信号时,变量 ngx_terminate 被设置为 1。NGX_NOACCEPT_SIGNAL (在大多数系统上为 SIGWINCH)— 关闭所有工作进程和辅助进程。
收到此信号后,主进程关闭其子进程。
如果先前启动的新 Angie 二进制文件退出,旧主进程的子进程将再次启动。
当工作进程收到此信号时,它在由 debug_points 指令设置的调试模式下关闭。NGX_RECONFIGURE_SIGNAL (在大多数系统上为 SIGHUP)— 重新配置。
收到此信号后,主进程重新读取配置并基于它创建新的循环。
如果新循环创建成功,旧循环被删除并启动新的子进程。
同时,旧的子进程收到 NGX_SHUTDOWN_SIGNAL 信号。
在单进程模式下,Angie 创建新循环,但保留旧循环,直到不再有客户端与其绑定的活动连接。
工作进程和辅助进程忽略此信号。NGX_REOPEN_SIGNAL (在大多数系统上为 SIGUSR1)— 重新打开文件。
主进程将此信号发送给工作进程,工作进程重新打开与循环相关的所有 open_files。NGX_CHANGEBIN_SIGNAL (在大多数系统上为 SIGUSR2)— 更改 Angie 二进制文件。
主进程启动新的 Angie 二进制文件并传入所有监听套接字的列表。
以文本格式传递的列表在 "NGINX" 环境变量中,由分号分隔的描述符编号组成。
新的 Angie 二进制文件读取 "NGINX" 变量并将套接字添加到其初始化循环中。
其他进程忽略此信号。kill() 系统调用将信号传递给工作进程和辅助进程。
相反,Angie 使用进程间套接字对,允许在所有 Angie 进程之间发送消息。
然而,目前消息仅从主进程发送到其子进程。
这些消息携带标准信号。线程#
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);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 Angie 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;
}
模块#
添加新模块#
config 和一个包含模块源代码的文件。
config 文件包含 Angie 集成该模块所需的所有信息,例如: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 — 模块名称,显示在 configure 脚本的控制台输出中。ngx_module_srcs — 用于编译模块的源文件的空格分隔列表。
可以使用 $ngx_addon_dir 变量来表示模块目录的路径。ngx_module_incs — 构建模块所需的包含路径ngx_module_deps — 模块依赖项的空格分隔列表。
通常是头文件列表。ngx_module_libs — 要与模块链接的库的空格分隔列表。
例如,使用 ngx_module_libs=-lpthread 来链接
libpthread 库。
可以使用以下宏来链接与 Angie 相同的库:
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 之前,以便过滤器处理程序在复制过滤器处理程序之后执行。
对于其他模块类型,默认值为空字符串。--add-module=/path/to/module 参数。
要编译模块以便稍后动态加载到 Angie 中,请使用 --add-dynamic-module=/path/to/module 参数。核心模块#
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 数组中指定的配置指令,并可以在 Angie 生命周期的特定阶段被调用。
模块生命周期包括以下事件:init_module 处理程序。
每次加载配置时,都会在主进程中调用 init_module 处理程序。init_process 处理程序。exit_process 处理程序。exit_master 处理程序。init_thread 和 exit_thread 处理程序。
也没有 init_master 处理程序,因为它会造成不必要的开销。type 准确定义了 ctx 字段中存储的内容。
其值为以下类型之一:NGX_CORE_MODULENGX_EVENT_MODULENGX_HTTP_MODULENGX_MAIL_MODULENGX_STREAM_MODULENGX_CORE_MODULE 是最基本的,因此也是最通用和最低级的模块类型。
其他模块类型在其之上实现,并提供了一种更方便的方式来处理相应的领域,例如处理事件或 HTTP 请求。ngx_core_module、
ngx_errlog_module、ngx_regex_module、
ngx_thread_pool_module 和
ngx_openssl_module 模块。
HTTP 模块、stream 模块、mail 模块和 event 模块也是核心模块。
核心模块的上下文定义为: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
分别是指向创建和初始化模块配置的函数的指针。
对于核心模块,Angie 在解析新配置之前调用 create_conf,在所有配置成功解析后调用 init_conf。
典型的 create_conf 函数为配置分配内存并设置默认值。ngx_foo_module 的简单模块可能如下所示: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 是分别指向创建和初始化模块配置的函数的指针。
对于核心模块,Angie 在解析新配置之前调用 create_conf,在所有配置解析成功后调用 init_conf。
典型的 create_conf 函数为配置分配内存并设置默认值。ngx_foo_module 的简单模块可能如下所示:/*
* Copyright (C) Author.
*/
#include <ngx_config.h>
#include <ngx_core.h>
typedef struct {
ngx_flag_t enable;
} ngx_foo_conf_t;
static void *ngx_foo_create_conf(ngx_cycle_t *cycle);
static char *ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf);
static char *ngx_foo_enable(ngx_conf_t *cf, void *post, void *data);
static ngx_conf_post_t ngx_foo_enable_post = { ngx_foo_enable };
static ngx_command_t ngx_foo_commands[] = {
{ ngx_string("foo_enabled"),
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
0,
offsetof(ngx_foo_conf_t, enable),
&ngx_foo_enable_post },
ngx_null_command
};
static ngx_core_module_t ngx_foo_module_ctx = {
ngx_string("foo"),
ngx_foo_create_conf,
ngx_foo_init_conf
};
ngx_module_t ngx_foo_module = {
NGX_MODULE_V1,
&ngx_foo_module_ctx, /* module context */
ngx_foo_commands, /* module directives */
NGX_CORE_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static void *
ngx_foo_create_conf(ngx_cycle_t *cycle)
{
ngx_foo_conf_t *fcf;
fcf = ngx_pcalloc(cycle->pool, sizeof(ngx_foo_conf_t));
if (fcf == NULL) {
return NULL;
}
fcf->enable = NGX_CONF_UNSET;
return fcf;
}
static char *
ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf)
{
ngx_foo_conf_t *fcf = conf;
ngx_conf_init_value(fcf->enable, 0);
return NGX_CONF_OK;
}
static char *
ngx_foo_enable(ngx_conf_t *cf, void *post, void *data)
{
ngx_flag_t *fp = data;
if (*fp == 0) {
return NGX_CONF_OK;
}
ngx_log_error(NGX_LOG_NOTICE, cf->log, 0, "Foo Module is enabled");
return NGX_CONF_OK;
}
配置指令#
ngx_command_t 类型定义了单个配置指令。
每个支持配置的模块都提供一个此类结构的数组,用于描述如何处理参数以及调用哪些处理程序:typedef struct ngx_command_s ngx_command_t;
struct ngx_command_s {
ngx_str_t name;
ngx_uint_t type;
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
ngx_uint_t conf;
ngx_uint_t offset;
void *post;
};
ngx_null_command 终止数组。
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 — 指令可以接受不同数量的参数。
选项仅限于指定的数量。
例如,:samp:NGX_CONF_TAKE12 表示它接受一个或两个参数。NGX_CONF_BLOCK — 指令是一个块,也就是说,它可以在其开闭大括号内包含其他指令,
甚至可以实现自己的解析器来处理内部内容。NGX_CONF_FLAG — 指令接受一个布尔值,
on 或 off。NGX_MAIN_CONF — 在顶层上下文中。NGX_HTTP_MAIN_CONF — 在 http 块中。NGX_HTTP_SRV_CONF — 在 http 块内的
server 块中。NGX_HTTP_LOC_CONF — 在 http 块内的
location 块中。NGX_HTTP_UPS_CONF — 在 http 块内的
upstream 块中。NGX_HTTP_SIF_CONF — 在 http 块内
server 块中的 if 块中。NGX_HTTP_LIF_CONF — 在 http 块内
location 块中的 if 块中。NGX_HTTP_LMT_CONF — 在 http 块内的
limit_except 块中。NGX_STREAM_MAIN_CONF — 在 stream 块中。NGX_STREAM_SRV_CONF — 在 stream 块内的
server 块中。NGX_STREAM_UPS_CONF — 在 stream 块内的
upstream 块中。NGX_MAIL_MAIN_CONF — 在 mail 块中。NGX_MAIL_SRV_CONF — 在 mail 块内的
server 块中。NGX_EVENT_CONF — 在 events 块中。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_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 字段中传递的以 null 结尾的 ngx_conf_enum_t 数组
定义了可接受的字符串和相应的整数值。ngx_conf_set_bitmask_slot — 将提供的参数转换为
ngx_uint_t 值。
每个参数的掩码值通过 OR 运算产生结果。
在 post 字段中传递的以 null 结尾的 ngx_conf_bitmask_t 数组
定义了可接受的字符串和相应的掩码值。ngx_conf_set_path_slot — 将提供的参数转换为
ngx_path_t 值并执行所有必要的初始化。
有关详细信息,请参阅 proxy_temp_path 指令的文档。ngx_conf_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 — http 块内
server 块的配置。NGX_HTTP_LOC_CONF_OFFSET — http 块内
location 块的配置。NGX_STREAM_MAIN_CONF_OFFSET — stream 块的配置。NGX_STREAM_SRV_CONF_OFFSET — stream 块内
server 块的配置。NGX_MAIL_MAIN_CONF_OFFSET — mail 块的配置。NGX_MAIL_SRV_CONF_OFFSET — mail 块内
server 块的配置。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#
连接#
ngx_event_accept() 接受客户端 TCP 连接。
此处理器在监听套接字上的读通知响应中被调用。
在此阶段创建一个新的 ngx_connection_t 对象来封装新接受的客户端套接字。
每个 Angie 监听器都提供一个处理器来传递新的连接对象。
对于 HTTP 连接,它是 ngx_http_init_connection(c)。ngx_http_init_connection() 执行 HTTP 连接的早期初始化。
在此阶段为连接创建一个 ngx_http_connection_t 对象,
并将其引用存储在连接的 data 字段中。
稍后它将被 HTTP 请求对象替换。
PROXY 协议解析器和 SSL 握手也在此阶段启动。ngx_http_wait_request_handler() 读事件处理器
在客户端套接字上有数据可用时被调用。
在此阶段创建一个 HTTP 请求对象 ngx_http_request_t,
并设置到连接的 data 字段。ngx_http_process_request_line() 读事件处理器
读取客户端请求行。
该处理器由 ngx_http_wait_request_handler() 设置。
数据被读入连接的 buffer 中。
缓冲区的大小最初由指令 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() 会同时销毁请求和连接。请求#
ngx_http_request_t 对象。
此对象的一些字段包括:connection — 指向 ngx_connection_t 客户端连接对象的指针。
多个请求可以同时引用同一个连接对象 - 一个主请求及其子请求。
请求被删除后,可以在同一连接上创建新请求。ngx_connection_t 的 data 字段指向请求。
这样的请求称为活动请求,与绑定到连接的其他请求相对。
活动请求用于处理客户端连接事件,并允许将其响应输出到客户端。
通常,每个请求在某个时刻都会变为活动状态,以便可以发送其输出。ctx — HTTP 模块上下文数组。
类型为 NGX_HTTP_MODULE 的每个模块都可以在请求中存储任何值(通常是指向结构的指针)。
该值存储在模块的 ctx_index 位置的 ctx 数组中。
以下宏提供了获取和设置请求上下文的便捷方式: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 的池。header_in — 读取客户端 HTTP 请求头的缓冲区。headers_in、headers_out — 输入和输出 HTTP 头对象。
两个对象都包含类型为 ngx_list_t 的 headers 字段,
用于保存原始头列表。
除此之外,特定的头可以作为单独的字段进行获取和设置,
例如 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 Host 进行服务器查找、重写、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 可能因部分字符串匹配而缓冲数据,
copy 过滤器可能因缺少空闲输出缓冲区而缓冲数据等。
只要此值非零,请求就不会最终化,等待刷新。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 — 标志,
请求在内存缓冲区而非文件中产生输出。
这是向 copy 过滤器发出的信号,即使启用了 sendfile,也要从文件缓冲区读取数据。
这两个标志的区别在于设置它们的过滤器模块的位置。
在过滤器链中 postpone 过滤器之前调用的过滤器设置
filter_need_in_memory,请求仅将当前请求的输出放入内存缓冲区。
在过滤器链中稍后调用的过滤器设置
main_filter_need_in_memory,请求主请求和所有子请求
在发送输出时将文件读入内存。filter_need_temporary — 标志,请求在临时缓冲区中产生请求输出,
而不是在只读内存缓冲区或文件缓冲区中。
这用于可能直接在发送输出的缓冲区中更改输出的过滤器。配置#
http 块。
作为模块的全局设置。server 块。
作为模块的服务器特定设置。location、
if 或 limit_except 块。
作为模块的位置特定设置。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() 将配置与更高级别的配置合并。
实际上,服务器和位置配置不仅存在于服务器和位置级别,
还会在它们之上的所有级别创建。
具体来说,服务器配置也会在主级别创建,
位置配置会在主级别、服务器级别和位置级别创建。
这些配置使得可以在 Angie 配置文件的任何级别指定服务器和位置特定的设置。
最终配置会向下合并。
提供了许多宏,如 NGX_CONF_UNSET 和 NGX_CONF_UNSET_UINT,
用于指示缺失的设置并在合并期间忽略它。
标准的 Angie 合并宏,如 ngx_conf_merge_value() 和
ngx_conf_merge_uint_value(),提供了一种便捷的方式来合并设置,
并在所有配置都未提供显式值时设置默认值。
有关不同类型的完整宏列表,请参见 src/core/ngx_conf_file.h。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)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;
}
ngx_http_get_module_main_conf(r, module)ngx_http_get_module_srv_conf(r, module)ngx_http_get_module_loc_conf(r, module)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);
...
}
阶段#
NGX_HTTP_POST_READ_PHASE — 第一个阶段。
RealIP 模块在此阶段注册其处理器,
以便在调用任何其他模块之前替换客户端地址。NGX_HTTP_SERVER_REWRITE_PHASE — 处理在 server 块中
定义的重写指令(但在 location 块之外)的阶段。
Rewrite 模块在此阶段安装其处理器。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 — 不同类型处理器的通用阶段,
与访问控制无关。
标准 Angie 模块 Limit Conn 和 Limit Req
在此阶段注册其处理器。NGX_HTTP_ACCESS_PHASE — 验证客户端是否有权发出请求的阶段。
标准 Angie 模块如 Access 和 Auth Basic
在此阶段注册其处理器。
默认情况下,客户端必须通过此阶段注册的所有处理器的授权检查,
请求才能继续到下一阶段。
可以使用 satisfy 指令,
如果任何阶段处理器授权客户端,则允许继续处理。NGX_HTTP_POST_ACCESS_PHASE — 处理 satisfy 指令的特殊阶段。
如果某些访问阶段处理器拒绝访问且没有明确允许,则请求被终止。
此阶段不能注册额外的处理器。NGX_HTTP_PRECONTENT_PHASE — 在生成内容之前调用处理器的阶段。
标准模块如 try_files 和 Mirror
在此阶段注册其处理器。NGX_HTTP_CONTENT_PHASE — 通常生成响应的阶段。
多个标准 Angie 模块在此阶段注册其处理器,
包括 Index。
它们按顺序调用,直到其中一个产生输出。
也可以按位置设置内容处理器。
如果 HTTP 模块 模块的位置配置设置了 handler,
它将作为内容处理器被调用,此阶段安装的处理器将被忽略。NGX_HTTP_LOG_PHASE — 执行请求日志记录的阶段。
目前,只有 Log 模块在此阶段注册其处理器用于访问日志记录。
日志阶段处理器在请求处理的最后调用,就在释放请求之前。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() 来恢复阶段处理。NGX_DECLINED 之外的任何返回代码都被视为终结代码。
来自位置内容处理器的任何返回代码都被视为
终结代码。
在访问阶段,在
satisfy any 模式下,
返回除 NGX_OK、
NGX_DECLINED、NGX_AGAIN、
NGX_DONE 之外的代码被视为拒绝。
如果后续的访问处理器没有使用不同的
代码允许或拒绝访问,则拒绝代码将成为终结代码。示例#
代码风格#
通用规则#
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_ 或 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)
{
...
}
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);
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";
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 (而不是 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;
调试内存问题#
gcc 和 clang 中启用 ASan,
请使用 -fsanitize=address 编译器和链接器选项。
在构建 Angie 时,可以通过将该选项添加到
configure 脚本的 --with-cc-opt 和 --with-ld-opt 参数来完成。NGX_DEBUG_PALLOC 宏设置为 1 来禁用此机制。
在这种情况下,分配直接传递给系统分配器,使其完全控制缓冲区边界。auto/configure --with-cc-opt='-fsanitize=address -DNGX_DEBUG_PALLOC=1'
--with-ld-opt=-fsanitize=address
常见陷阱#
编写 C 模块#
C 字符串#
strlen() 或 strstr()。
相反,应该使用 Angie 的 对应函数,
它们接受 ngx_str_t 或指向数据的指针和长度。
但是,有一种情况下 ngx_str_t 持有
指向以零结尾的字符串的指针:作为配置文件解析结果的字符串是以零结尾的。全局变量#
手动内存管理#
线程#
init_process
模块处理程序中调度一个定时器,并在定时器处理程序中执行所需的操作。
Angie 内部使用 线程 来
增强 IO 相关操作,但这是一个有很多
限制的特殊情况。阻塞库#
向外部服务发送 HTTP 请求#