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) {
last_space
为真时,表示上一个字符是空格或分隔符,当前可能开始新 token)。start = b->pos - 1;
b->pos
是当前字符的下一个位置,-1
指向当前字符)。start_line = cf->conf_file->line;
start_line
定位错误行,生成更精确的错误日志if (ch == ' ' || ch == '\t' || ch == CR || ch == LF) {
case ';'
和 case '{'
;
或左花括号 {
的语法结构。;
表示 指令结束,如 listen 80;
。{
表示 块开始,如 http { ... }
。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
)。
'${var}'
会被保留原样)if (ch == '\'') { ... }
作用:检测单引号的闭合符号 '
。
遇到未被转义的 '
时,结束单引号模式
s_quoted = 0;
作用:退出单引号模式。
恢复普通解析逻辑,后续字符不再被视为单引号字符串的一部分
else if (ch == ' ' || ... || ch == '{') { ... }
作用:处理 非引号模式下的分隔符(空格、换行符、;
、{
)。
listen 80;
中的 ;
会结束 listen 80
的解析。last_space = 1;
作用:标记当前字符为 分隔符。
server {
中的空格会被标记为分隔符,{
触发块开始。found = 1;
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;
if (ch == ';') {
ch
是否是一个分号 (;
)。;
) 用于表示一个配置指令的结束。return NGX_OK;
NGX_OK
,表示当前配置指令解析成功。NGX_OK
是 Nginx 中定义的一个常量,表示操作成功。NGX_OK
后,解析器会继续处理下一个配置指令。if (ch == '{') {
ch
是否是一个左大括号 ({
)。{
) 用于表示一个配置块的开始。server
块或 location
块。return NGX_CONF_BLOCK_START;
NGX_CONF_BLOCK_START
,表示当前配置指令是一个配置块的开始。NGX_CONF_BLOCK_START
是 Nginx 中定义的一个常量,表示配置块的开始。NGX_CONF_BLOCK_START
后,解析器会进入块解析模式,继续解析块内的配置指令。found = 0;
found
的值重置为 0
。found
是一个标志变量,用于表示是否找到了一个完整的配置项(例如一个单词或一个字符串)。found
可能被设置为 1
,表示当前配置项已经解析完成。found
为 0
是为了准备解析下一个配置项。found
标志处于初始状态,避免影响后续解析逻辑。这段代码的作用是处理配置文件中两个关键字符:分号 (;
) 和左大括号 ({
)。它们的含义如下:
;
):表示当前配置指令的结束,解析器返回 NGX_OK
,表示成功解析。{
):表示一个配置块的开始,解析器返回 NGX_CONF_BLOCK_START
,表示进入块解析模式。found
标志:为解析下一个配置项做准备。