详解(1)
cf->args->nelts = 0;
- 作用:清空参数数组,准备解析新参数。
cf->args
存储当前指令的参数列表
cf->args
是一个指向ngx_array_t
的指针,本质是一个动态数组。
数组中的每个元素是ngx_str_t
类型,表示一个参数字符串
b = cf->conf_file->buffer;
dump = cf->conf_file->dump;
- 作用:获取配置文件缓冲区和dump缓冲区指针。
start = b->pos;
start_line = cf->conf_file->line;
start
记录当前 token(即配置项参数)的起始位置
start = b->pos;
表示将当前解析位置标记为新 token 的起始位置
start_line
记录当前 token 的起始行号,用于错误提示和调试
file_size = ngx_file_size(&cf->conf_file->file.info);
获取配置文件总大小,判断是否读取完毕。
for ( ;; ) {
for (;;)
循环的主要作用是实现逐字符解析配置文件内容,直到完成整个文件的解析或遇到错误
if (b->pos >= b->last) {
检测当前缓冲区是否已处理完所有数据
if (cf->conf_file->file.offset >= file_size) {
判断是否已读取完整个配置文件
off_t
(文件偏移量)
表示当前已读取的文件字节数(即文件指针的位置)
file_size
配置文件的总大小
如果成立,表示已读取完整个配置文件(所有字节已处理)。
否则,表示文件尚未读取完毕,需要继续读取
if (cf->args->nelts > 0 || !last_space) {
检测文件结束时是否存在未完成的语法结构
如果 nelts > 0
,表示当前指令已解析了部分参数,但尚未遇到结束符
last_space
是标志位,表示最后一个处理的字符是否是空白符(空格、换行等)
如果 !last_space
,表示最后一个字符是非空白字符,可能意味着参数未正确闭合
如:
缺少分号 ;
缺少闭合的 }
if (cf->conf_file->file.fd == NGX_INVALID_FILE) {ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,"unexpected end of parameter, ""expecting \";\"");return NGX_ERROR;}
当配置文件的读取位置已经到达文件末尾时,检查是否还有未完成的参数或语法错误。如果有,则根据文件描述符是否有效,输出不同的错误信息。
区分配置来源(内存或文件)并输出精准的错误提
如果 fd == NGX_INVALID_FILE
:表示当前解析的配置内容来自内存(如命令行参数或直接传递的字符串)。
否则:表示配置内容来自磁盘文件。
return NGX_CONF_FILE_DONE;
配置文件已全部读取且无语法错误 时 返回该值
len = b->pos - start;
当 b->pos >= b->last
(缓冲区数据已处理完毕)时,需要从文件中读取新数据。此时:
start
:指向当前 token 的起始位置(可能跨缓冲区)。
b->pos
:已到达缓冲区末尾(b->last)。
通过 len = b->pos - start;:
计算剩余未处理数据的长度 :即从 start 到缓冲区末尾(b->last)的字节数
在缓冲区耗尽时,确定需要迁移的数据量
确保 Token 完整性 :跨缓冲区的 token 能被正确拼接和解析
if (len == NGX_CONF_BUFFER) {cf->conf_file->line = start_line;if (d_quoted) {ch = '"';} else if (s_quoted) {ch = '\'';} else {ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,"too long parameter \"%*s...\" started",10, start);return NGX_ERROR;}ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,"too long parameter, probably ""missing terminating \"%c\" character", ch);return NGX_ERROR;
}
if (len == NGX_CONF_BUFFER) {
检测参数长度是否超过缓冲区容量
-
NGX_CONF_BUFFER
:
为缓冲区的固定大小
表示单次读取配置文件的块大小。 -
当
len == NGX_CONF_BUFFER
时,表示:- 当前 token 或未处理数据的长度已达到缓冲区最大容量(如 4KB)。
- 但尚未遇到参数结束符(如引号闭合、分号
;
或块开始符{
)。
cf->conf_file->line = start_line;
重置行号
将错误日志的行号重置为当前 token 的起始行号(start_line
)。
确保错误信息指向参数的开始位置,而非当前解析位置。
if (d_quoted) {ch = '"';
} else if (s_quoted) {ch = '\'';
} else {// 非引号内的超长参数ngx_conf_log_error(...);return NGX_ERROR;
}
判断未闭合的引号类型
d_quoted
(双引号标志)
- 含义:
d_quoted
为1
时表示解析器当前处于双引号包裹的上下文中。 - 触发条件:当解析器遇到字符
"
时,设置d_quoted = 1
。 - 退出条件:当再次遇到
"
时,设置d_quoted = 0
。 - 行为影响:
- 在双引号内,空格和特殊字符(如
;
、{
)会被视为普通字符,而非分隔符或语法符号。 - 支持转义字符(如
\"
表示双引号本身,\t
表示制表符)。 - 示例:
proxy_set_header X-Name "Hello \"World\""; # 双引号内的内容被完整解析
- 在双引号内,空格和特殊字符(如
s_quoted
(单引号标志)
- 含义:
s_quoted
为1
时表示解析器当前处于单引号包裹的上下文中。 - 触发条件:当解析器遇到字符
'
时,设置s_quoted = 1
。 - 退出条件:当再次遇到
'
时,设置s_quoted = 0
。 - 行为影响:
- 在单引号内,所有字符(包括空格和特殊字符)均被视为普通字符。
- 不支持转义字符(单引号内的
\
会被视为普通字符)。
- 当参数长度超过缓冲区(
NGX_CONF_BUFFER
)时,根据d_quoted
或s_quoted
的状态,输出不同的错误提示:
输出错误信息
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,"too long parameter, probably missing terminating \"%c\" character",ch);
- 内容:
- 提示参数过长,并指出可能缺少闭合的引号字符(如
"
或'
)。 - 示例输出:
too long parameter, probably missing terminating '"' character
- 提示参数过长,并指出可能缺少闭合的引号字符(如
else {ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,"too long parameter \"%*s...\" started",10, start);return NGX_ERROR;}
非引号内的超长参数
错误日志记录
- 参数解释:
NGX_LOG_EMERG
:日志级别为紧急(最高级别,表示严重错误)。"%*s..."
:格式化字符串,%*s
表示动态宽度的字符串。10
:动态宽度值,表示最多显示前 10 个字符。start
:指向参数起始位置的指针。
- 输出示例:
too long parameter "aaaaaaaaa..." started
- 显示参数的前 10 字符,后接
...
表示内容被截断。
- 显示参数的前 10 字符,后接
if (len) {ngx_memmove(b->start, start, len);}
缓冲区耗尽时:当 b->pos >= b->last
时,需要从文件中读取新数据。
未处理数据的存在:如果 len > 0
,表示缓冲区中仍有未处理的数据(如跨缓冲区的 token)。
ngx_memmove(b->start, start, len);
作用:将未处理数据(从 start
开始,长度为 len
)移动到缓冲区的起始位置(b->start
)。
目的:腾出缓冲区剩余空间,以便读取新数据并拼接到未处理数据后。
size = (ssize_t) (file_size - cf->conf_file->file.offset);
计算文件中剩余未读取的字节数
限制读取大小
if (size > b->end - (b->start + len)) {size = b->end - (b->start + len);
}
- 作用:确保读取的字节数不超过缓冲区的剩余空间。
- 计算:
b->end
:缓冲区的末尾地址。b->start + len
:缓冲区中已迁移数据后的末尾地址。b->end - (b->start + len)
:缓冲区剩余可用空间的大小。
读取文件数据
n = ngx_read_file(&cf->conf_file->file, b->start + len, size,cf->conf_file->file.offset);
- 作用:从文件中读取数据到缓冲区。
- 参数:
&cf->conf_file->file
:文件结构体指针。b->start + len
:缓冲区中写入数据的起始位置(紧接已迁移数据后)。size
:实际读取的字节数(受缓冲区剩余空间限制)。cf->conf_file->file.offset
:文件的当前读取位置。
检查读取失败
if (n == NGX_ERROR) {return NGX_ERROR;
}
- 作用:检测
ngx_read_file
是否返回错误(如文件不可读、权限问题)。 - 处理:直接返回
NGX_ERROR
,终止解析流程。
检查不完整读取
if (n != size) {ngx_conf_log_error(...);return NGX_ERROR;
}
- 作用:验证实际读取的字节数(
n
)是否等于预期字节数(size
)。 - 触发条件:
- 文件被截断(如磁盘空间不足导致写入不完整)。
- 磁盘 I/O 错误(如硬件故障导致部分读取)。
- 文件被其他进程修改(读取过程中文件被缩短)。
b->pos = b->start + len;
b->last = b->pos + n;
start = b->start;if (dump) {dump->last = ngx_cpymem(dump->last, b->pos, size);
}
缓冲区指针更新
b->pos = b->start + len;
- 作用:将
pos
指针指向缓冲区中未处理数据的起始位置。 - 背景:
len
是迁移后的未处理数据长度(如跨缓冲区的 token 部分)。b->start
是缓冲区的起始地址。
b->last = b->pos + n;
- 作用:更新
last
指针,标记缓冲区中有效数据的末尾。 - 变量:
n
是实际读取的字节数(通过ngx_read_file
返回)。
start = b->start;
作用:重置 start
指针,指向缓冲区的起始位置。
为下一个 token 的解析做准备。
数据转储(Dump)
if (dump)
- 条件:
dump
是一个可选的缓冲区,用于保存原始配置内容(如调试或生成配置快照)。 - 典型用途:
- 生成配置文件的完整副本(如
nginx -t
测试配置时输出)。 - 调试时记录解析过程中的原始数据。
- 生成配置文件的完整副本(如
ngx_cpymem
操作
dump->last = ngx_cpymem(dump->last, b->pos, size);
- 作用:将新读取的数据(
b->pos
到b->pos + size
)追加到dump
缓冲区。 - 参数:
dump->last
:dump
缓冲区当前的写入位置。b->pos
:新数据的起始地址。size
:实际读取的字节数(与n
相同)。
- 返回值:
ngx_cpymem
返回新的dump->last
位置(原位置 +size
)。
- 转储同步:
dump
缓冲区实时保存所有读取的数据,生成配置文件的完整镜像。
字符读取与指针移动
ch = *b->pos++;
从缓冲区 b
的当前指针位置(b->pos
)读取一个字符,并将指针后移一位。
实现逐字符解析,确保每个字符都被处理。
换行符处理
if (ch == LF) {cf->conf_file->line++; // 行号递增if (sharp_comment) { // 结束行注释sharp_comment = 0;}
}
- 作用:
- 遇到换行符(
\n
)时,行号(cf->conf_file->line
)递增,用于错误定位。 - 如果当前处于行注释状态(
sharp_comment
为真),则结束注释。
- 遇到换行符(
- 意义:
- 行号跟踪:确保错误信息能精确到具体行(如
line 10
)。 - 注释结束:行注释(
#
开始的注释)在换行符处自动终止。
- 行号跟踪:确保错误信息能精确到具体行(如
sharp_comment
是一个状态标志,用于标识解析器当前是否处于行注释(以 # 开头的注释)的上下文中
注释跳过
if (sharp_comment) {continue;
}
作用:如果处于行注释状态(sharp_comment = 1
),跳过后续处理,直接进入下一次循环。
避免处理注释中的无效字符。
if (quoted) {quoted = 0;continue;
}
- 作用:如果处于转义状态(
quoted = 1
,即前一个字符是\
),则取消转义状态,并跳过当前字符的常规处理。