详解(3)
if (last_space) {
start = b->pos - 1;
start_line = cf->conf_file->line;
if (ch == ' ' || ch == '\t' || ch == CR || ch == LF) {
continue;
}
switch (ch) {
case ';':
case '{':
if (cf->args->nelts == 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"unexpected \"%c\"", ch);
return NGX_ERROR;
}
if (ch == '{') {
return NGX_CONF_BLOCK_START;
}
return NGX_OK;
case '}':
if (cf->args->nelts != 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"unexpected \"}\"");
return NGX_ERROR;
}
return NGX_CONF_BLOCK_DONE;
case '#':
sharp_comment = 1;
continue;
case '\\':
quoted = 1;
last_space = 0;
continue;
case '"':
start++;
d_quoted = 1;
last_space = 0;
continue;
case '\'':
start++;
s_quoted = 1;
last_space = 0;
continue;
case '$':
variable = 1;
last_space = 0;
continue;
default:
last_space = 0;
}
} else {
if (ch == '{' && variable) {
continue;
}
variable = 0;
if (ch == '\\') {
quoted = 1;
continue;
}
if (ch == '$') {
variable = 1;
continue;
}
if (d_quoted) {
if (ch == '"') {
d_quoted = 0;
need_space = 1;
found = 1;
}
} else if (s_quoted) {
if (ch == '\'') {
s_quoted = 0;
need_space = 1;
found = 1;
}
} else if (ch == ' ' || ch == '\t' || ch == CR || ch == LF
|| ch == ';' || ch == '{')
{
last_space = 1;
found = 1;
}
if (found) {
word = ngx_array_push(cf->args);
if (word == NULL) {
return NGX_ERROR;
}
word->data = ngx_pnalloc(cf->pool, b->pos - 1 - start + 1);
if (word->data == NULL) {
return NGX_ERROR;
}
for (dst = word->data, src = start, len = 0;
src < b->pos - 1;
len++)
{
if (*src == '\\') {
switch (src[1]) {
case '"':
case '\'':
case '\\':
src++;
break;
case 't':
*dst++ = '\t';
src += 2;
continue;
case 'r':
*dst++ = '\r';
src += 2;
continue;
case 'n':
*dst++ = '\n';
src += 2;
continue;
}
}
*dst++ = *src++;
}
*dst = '\0';
word->len = len;
if (ch == ';') {
return NGX_OK;
}
if (ch == '{') {
return NGX_CONF_BLOCK_START;
}
found = 0;
}
}
这段代码是 Nginx 配置解析函数 ngx_conf_read_token
的核心部分,负责将配置文件内容拆分为 token(指令、参数、块等),并处理语法结构(如引号、变量、注释)。
if (last_space) {
- 作用:判断是否处于 新 token 的起始状态(
last_space
为真时,表示上一个字符是空格或分隔符,当前可能开始新 token)。 - 意义:进入新 token 的解析逻辑,初始化起始位置和行号
start = b->pos - 1;
- 作用:记录当前 token 的 起始位置(
b->pos
是当前字符的下一个位置,-1
指向当前字符)。
start_line = cf->conf_file->line;
- 作用:记录当前 token 的 起始行号。
- 意义:
- 当解析出错时(如未闭合的引号),Nginx 可通过
start_line
定位错误行,生成更精确的错误日志
- 当解析出错时(如未闭合的引号),Nginx 可通过
if (ch == ' ' || ch == '\t' || ch == CR || ch == LF) {
- 作用:检查当前字符是否为 空白字符(空格、制表符、回车、换行)。
- 意义:
- 跳过连续的空白字符,避免将多个空格视为 token 的一部分。
- Nginx 配置中参数由空格分隔,但连续空格会被视为单个分隔符
case ';'
和 case '{'
- 作用:处理分号
;
或左花括号{
的语法结构。 - 意义:
;
表示 指令结束,如listen 80;
。{
表示 块开始,如http { ... }
。- 这两个符号是 Nginx 配置的核心分隔符,用于界定指令和块的范围
if (cf->args->nelts == 0) { ... }
- 作用:检查当前参数列表
args
是否为空。 - 意义:
- 若
args
为空(nelts == 0
),则;
或{
前没有有效指令,属于 语法错误(如;
或{
孤立出现)。 - 例如,配置文件中出现
;
或{
而无前置指令时,会触发此错误
- 若
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"%c\"", ch);
- 作用:记录 紧急级别错误日志,提示意外字符。
- 意义:
- 输出错误信息(如
unexpected ";"
),帮助用户定位配置错误。 NGX_LOG_EMERG
表示最高优先级日志,确保错误不会被忽略
- 输出错误信息(如
if (ch == '{') { return NGX_CONF_BLOCK_START; }
- 作用:处理块开始符号
{
- 意义:
- 返回
NGX_CONF_BLOCK_START
状态,通知上层函数进入 块解析模式。 - 例如,解析到
http {
时,后续内容会被视为http
块的配置
- 返回
return NGX_OK;
- 作用:处理分号
;
,返回成功状态。 - 意义:
- 表示当前指令解析完成(如
listen 80;
),参数已存入args
数组。 - 上层函数(如
ngx_conf_handler
)会进一步处理指令
- 表示当前指令解析完成(如
case '}'
- 作用:处理右花括号
}
,表示 块结束(如http { ... }
的结束)。 - 意义:触发块结束的逻辑验证,确保语法正确性
if (cf->args->nelts != 0) { ... }
- 作用:检查参数列表
args
是否非空。 - 意义:
}
必须出现在参数列表为空时(如server { ... }
的}
前无未处理参数)。- 若
args
不为空(如server { listen 80 }
缺少分号),则}
是意外字符,属于语法错误
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"}\"");
- 作用:记录 紧急级别错误日志,提示意外的
}
。 - 意义:
- 明确告知用户配置文件中存在未闭合的块或多余参数。
- 例如:
server { listen 80 }
缺少;
,导致}
前仍有参数,触发此错误
return NGX_CONF_BLOCK_DONE;
- 作用:返回块结束状态。
- 意义:
- 通知上层函数(如
ngx_conf_parse
)当前块解析完成,需退出当前层级,继续解析父块内容 - 例如,解析完
http { ... }
后,返回此状态码,继续处理http
块外的配置。
- 通知上层函数(如
case '#':
- 作用:处理
#
字符,表示 行注释开始。 - 意义:
- 设置
sharp_comment = 1
,标记后续字符为注释,直到行尾(换行符LF
)。 continue
跳过后续逻辑,直接处理下一个字符
- 设置
sharp_comment = 1;
- 作用:标记当前处于 注释状态。
- 意义:
- 在后续循环中,所有字符(除换行符)均被跳过,直到换行符重置
sharp_comment
- 在后续循环中,所有字符(除换行符)均被跳过,直到换行符重置
case '\\':
作用:处理反斜杠
\
,表示 转义字符。- 设置
quoted = 1
,标记下一个字符需要转义(如\"
、\\
)。 last_space = 0
表示当前字符非空格
- 设置
case '"':
作用:处理双引号
"
,表示 字符串开始。start++
跳过引号本身,指向字符串内容的起始位置。d_quoted = 1
进入双引号模式,忽略内部空格和分隔符- 示例:
"Hello World"
会被解析为一个完整 token,内部的空格不会分割参数。
case '\'':
作用:处理单引号
'
,表示 字符串开始。start++
跳过引号本身,指向字符串内容的起始位置。s_quoted = 1
进入单引号模式,功能与双引号类似,但不支持变量插值- 示例:
'Hello World'
会被解析为一个完整 token。
case '$':
作用:处理
$
符号,表示 变量开始。- 设置
variable = 1
,标记当前处于 变量模式,允许后续字符包含{
(如${var}
) - 示例:
$var
或${var}
会被识别为变量,支持复杂变量名。
- 设置
default:
作用:处理其他非特殊字符(如字母、数字、符号)。
last_space = 0
表示当前字符非空格
if (ch == '{' && variable) { continue; }
作用:处理变量中的
{
字符。- 当处于变量模式(
variable=1
,如${var}
)时,允许{
作为变量名的一部分,而非块开始符号。 continue
跳过当前循环,避免触发块开始逻辑,确保变量解析正确
- 当处于变量模式(
variable = 0;
作用:重置变量模式标志。
- 确保变量模式仅在
$
后生效,避免后续字符被错误识别为变量的一部分。 - 例如,
$var{test}
中的{
会被视为普通字符,而非变量名的一部分
- 确保变量模式仅在
if (ch == '\\') { quoted = 1; continue; }
作用:处理转义字符
\
。- 设置
quoted=1
,标记下一个字符需要转义(如\"
、\\
)。 continue
跳过后续逻辑,直接处理转义后的字符,确保特殊字符被正确解析- 示例:
\"
会被解析为普通双引号,而非字符串闭合符号。
- 设置
if (ch == '$') { variable = 1; continue; }
作用:处理变量符号
$
。- 设置
variable=1
,进入变量模式,允许后续字符包含{
(如${var}
)。 continue
跳过后续逻辑,确保变量名正确解析
- 设置
if (d_quoted) { ... }
- 作用:检查是否处于 双引号包裹的字符串模式(
d_quoted=1
)。
if (ch == '"') { ... }
- 作用:检测双引号的闭合符号
"
。
当遇到未被转义的 "
时,结束双引号模式,准备结束当前 token
d_quoted = 0;
作用:退出双引号模式。
恢复普通解析逻辑,后续字符不再被视为字符串的一部分
need_space = 1;
作用:标记需要 空格或分隔符 结束当前 token。
- 双引号闭合后,必须跟随空格、分号或花括号等分隔符,否则语法错误
- 例如:
"Hello"world
会因缺少分隔符触发错误。
found = 1;
作用:标记当前 token 解析完成。
触发后续代码将字符从 start
到当前位置复制到 args
数组中
else if (s_quoted) { ... }
作用:处理 单引号包裹的字符串模式(s_quoted=1
)。
- 单引号内的内容会被视为完整 token,但不支持变量插值(如
'${var}'
会被保留原样)
if (ch == '\'') { ... }
作用:检测单引号的闭合符号
'
。遇到未被转义的
'
时,结束单引号模式
s_quoted = 0;
作用:退出单引号模式。
恢复普通解析逻辑,后续字符不再被视为单引号字符串的一部分
else if (ch == ' ' || ... || ch == '{') { ... }
作用:处理 非引号模式下的分隔符(空格、换行符、
;
、{
)。- 这些字符标志着当前 token 的结束,触发参数提取
- 例如:
listen 80;
中的;
会结束listen 80
的解析。
last_space = 1;
作用:标记当前字符为 分隔符。
- 下一个非空白字符将被视为新 token 的起始位置
- 例如:
server {
中的空格会被标记为分隔符,{
触发块开始。
found = 1;
- 作用:标记当前 token 解析完成。
- 意义:
- 触发后续代码将字符从
start
到当前位置复制到args
数组中
- 触发后续代码将字符从
if (found)
判断是否成功解析到一个完整的 token ,并触发后续处理逻辑。found 是一个状态标志,表示当前字符是否标志着 token 的结束
word = ngx_array_push(cf->args);
作用:向动态数组
cf->args
中 追加新元素,并返回指向该元素的指针。此处将解析出的 token(如指令、参数)存入
cf->args
,供后续指令处理函数
if (word == NULL) { return NGX_ERROR; }
- 作用:检查内存分配是否失败。
word->data = ngx_pnalloc(cf->pool, b->pos - 1 - start + 1);
if (word->data == NULL) {
return NGX_ERROR;
}
从内存池 cf->pool
分配内存,存储当前解析的 token 内容。
检查内存分配是否失败。
for (dst = word->data, src = start, len = 0;
src < b->pos - 1;
len++)
{
for (dst = word->data, src = start, len = 0; ... )
初始化循环变量。
dst = word->data
:指向新分配的内存地址,用于存储处理后的 token 内容
src = start
:指向当前 token 在缓冲区中的起始位置
len = 0
:初始化字符计数器,记录 token 的实际长度
src < b->pos - 1;
定义循环的终止条件。
b->pos
:指向当前处理字符的下一个位置(因 b->pos
在解析时已递增)
src < b->pos - 1
:确保循环处理从 start
到当前字符前一个位置的所有字符(避免越界)
示例:若 start
指向 l
,b->pos
指向 ;
,则处理范围是 l
到 n
(listen
的最后一个字符)。
len++
在每次循环迭代后递增 len
。
统计已处理字符的数量,最终赋值给 word->len
,表示 token 的实际长度
if (*src == '\\') {
switch (src[1]) {
case '"':
case '\'':
case '\\':
src++;
break;
case 't':
*dst++ = '\t';
src += 2;
continue;
case 'r':
*dst++ = '\r';
src += 2;
continue;
case 'n':
*dst++ = '\n';
src += 2;
continue;
}
这段代码负责处理配置文件中的转义字符。
具体来说,它处理的是当遇到反斜杠 (\
) 时,如何解析紧随其后的字符。以下是这段代码的详细解释:
检查当前字符是否为反斜杠 (
\
):- 如果当前字符是反斜杠 (
\
),则进入switch
语句,检查下一个字符 (src[1]
)。
- 如果当前字符是反斜杠 (
处理转义字符:
case '"'
: 如果下一个字符是双引号 ("
),则跳过反斜杠,直接将双引号字符写入目标缓冲区 (dst
)。这意味着\"
被解释为一个普通的双引号字符。case '\''
: 如果下一个字符是单引号 ('
),则跳过反斜杠,直接将单引号字符写入目标缓冲区 (dst
)。这意味着\'
被解释为一个普通的单引号字符。case '\\'
: 如果下一个字符是反斜杠 (\
),则跳过第一个反斜杠,直接将第二个反斜杠字符写入目标缓冲区 (dst
)。这意味着\\
被解释为一个普通的反斜杠字符。case 't'
: 如果下一个字符是t
,则将制表符 (\t
) 写入目标缓冲区 (dst
)。这意味着\t
被解释为一个制表符。case 'r'
: 如果下一个字符是r
,则将回车符 (\r
) 写入目标缓冲区 (dst
)。这意味着\r
被解释为一个回车符。case 'n'
: 如果下一个字符是n
,则将换行符 (\n
) 写入目标缓冲区 (dst
)。这意味着\n
被解释为一个换行符。
更新指针:
- 在处理完转义字符后,更新源指针 (
src
) 和目标指针 (dst
) 的位置,以便继续处理后续字符。
- 在处理完转义字符后,更新源指针 (
if (ch == ';') {
return NGX_OK;
}
if (ch == '{') {
return NGX_CONF_BLOCK_START;
}
found = 0;
1. if (ch == ';') {
- 作用:检查当前字符
ch
是否是一个分号 (;
)。 - 逻辑:
- 在 Nginx 配置文件中,分号 (
;
) 用于表示一个配置指令的结束。 - 如果当前字符是分号,说明当前配置指令已经解析完毕。
- 在 Nginx 配置文件中,分号 (
- 意图:
- 当遇到分号时,表示当前配置项已经完整解析,可以结束当前解析过程并返回成功状态。
2. return NGX_OK;
- 作用:返回
NGX_OK
,表示当前配置指令解析成功。 - 逻辑:
NGX_OK
是 Nginx 中定义的一个常量,表示操作成功。- 返回
NGX_OK
后,解析器会继续处理下一个配置指令。
- 意图:
- 告诉调用者当前配置指令已经成功解析,可以继续处理后续内容。
3. if (ch == '{') {
- 作用:检查当前字符
ch
是否是一个左大括号 ({
)。 - 逻辑:
- 在 Nginx 配置文件中,左大括号 (
{
) 用于表示一个配置块的开始。 - 配置块通常包含一组相关的配置指令,例如
server
块或location
块。
- 在 Nginx 配置文件中,左大括号 (
- 意图:
- 当遇到左大括号时,表示当前配置指令是一个配置块的开始,需要进入块解析模式。
4. return NGX_CONF_BLOCK_START;
- 作用:返回
NGX_CONF_BLOCK_START
,表示当前配置指令是一个配置块的开始。 - 逻辑:
NGX_CONF_BLOCK_START
是 Nginx 中定义的一个常量,表示配置块的开始。- 返回
NGX_CONF_BLOCK_START
后,解析器会进入块解析模式,继续解析块内的配置指令。
- 意图:
- 告诉调用者当前配置指令是一个配置块的开始,需要进一步解析块内的内容。
5. found = 0;
- 作用:将变量
found
的值重置为0
。 - 逻辑:
found
是一个标志变量,用于表示是否找到了一个完整的配置项(例如一个单词或一个字符串)。- 在解析过程中,
found
可能被设置为1
,表示当前配置项已经解析完成。 - 重置
found
为0
是为了准备解析下一个配置项。
- 意图:
- 确保解析器在解析下一个配置项时,
found
标志处于初始状态,避免影响后续解析逻辑。
- 确保解析器在解析下一个配置项时,
这段代码的作用是处理配置文件中两个关键字符:分号 (;
) 和左大括号 ({
)。它们的含义如下:
- 分号 (
;
):表示当前配置指令的结束,解析器返回NGX_OK
,表示成功解析。 - 左大括号 (
{
):表示一个配置块的开始,解析器返回NGX_CONF_BLOCK_START
,表示进入块解析模式。 - 重置
found
标志:为解析下一个配置项做准备。