Ubuntu 下 nginx-1.24.0 源码分析 - ngx_conf_read_token - 详解(3)

发布于:2025-03-04 ⋅ 阅读:(11) ⋅ 点赞:(0)

详解(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 定位错误行,生成更精确的错误日志

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 指向 lb->pos 指向 ;,则处理范围是 lnlisten 的最后一个字符)。


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;
                        }

这段代码负责处理配置文件中的转义字符。
具体来说,它处理的是当遇到反斜杠 (\) 时,如何解析紧随其后的字符。以下是这段代码的详细解释:

  1. 检查当前字符是否为反斜杠 (\):

    • 如果当前字符是反斜杠 (\),则进入 switch 语句,检查下一个字符 (src[1])。

  2. 处理转义字符:

    • case '"': 如果下一个字符是双引号 ("),则跳过反斜杠,直接将双引号字符写入目标缓冲区 (dst)。这意味着 \" 被解释为一个普通的双引号字符。
    • case '\'': 如果下一个字符是单引号 ('),则跳过反斜杠,直接将单引号字符写入目标缓冲区 (dst)。这意味着 \' 被解释为一个普通的单引号字符。
    • case '\\': 如果下一个字符是反斜杠 (\),则跳过第一个反斜杠,直接将第二个反斜杠字符写入目标缓冲区 (dst)。这意味着 \\ 被解释为一个普通的反斜杠字符。
    • case 't': 如果下一个字符是 t,则将制表符 (\t) 写入目标缓冲区 (dst)。这意味着 \t 被解释为一个制表符。
    • case 'r': 如果下一个字符是 r,则将回车符 (\r) 写入目标缓冲区 (dst)。这意味着 \r 被解释为一个回车符。
    • case 'n': 如果下一个字符是 n,则将换行符 (\n) 写入目标缓冲区 (dst)。这意味着 \n 被解释为一个换行符。
  3. 更新指针:

    • 在处理完转义字符后,更新源指针 (src) 和目标指针 (dst) 的位置,以便继续处理后续字符。

if (ch == ';') {
                    return NGX_OK;
                }

                if (ch == '{') {
                    return NGX_CONF_BLOCK_START;
                }

                found = 0;

1. if (ch == ';') {
  • 作用:检查当前字符 ch 是否是一个分号 (;)。
  • 逻辑
    • 在 Nginx 配置文件中,分号 (;) 用于表示一个配置指令的结束。
    • 如果当前字符是分号,说明当前配置指令已经解析完毕。
  • 意图
    • 当遇到分号时,表示当前配置项已经完整解析,可以结束当前解析过程并返回成功状态。

2. return NGX_OK;
  • 作用:返回 NGX_OK,表示当前配置指令解析成功。
  • 逻辑
    • NGX_OK 是 Nginx 中定义的一个常量,表示操作成功。
    • 返回 NGX_OK 后,解析器会继续处理下一个配置指令。
  • 意图
    • 告诉调用者当前配置指令已经成功解析,可以继续处理后续内容。

3. if (ch == '{') {
  • 作用:检查当前字符 ch 是否是一个左大括号 ({)。
  • 逻辑
    • 在 Nginx 配置文件中,左大括号 ({) 用于表示一个配置块的开始。
    • 配置块通常包含一组相关的配置指令,例如 server 块或 location 块。
  • 意图
    • 当遇到左大括号时,表示当前配置指令是一个配置块的开始,需要进入块解析模式。

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,表示当前配置项已经解析完成。
    • 重置 found0 是为了准备解析下一个配置项。
  • 意图
    • 确保解析器在解析下一个配置项时,found 标志处于初始状态,避免影响后续解析逻辑。

这段代码的作用是处理配置文件中两个关键字符:分号 (;) 和左大括号 ({)。它们的含义如下:

  1. 分号 (;):表示当前配置指令的结束,解析器返回 NGX_OK,表示成功解析。
  2. 左大括号 ({):表示一个配置块的开始,解析器返回 NGX_CONF_BLOCK_START,表示进入块解析模式。
  3. 重置 found 标志:为解析下一个配置项做准备。