15.Nginx解析配置文件之ngx_conf_read_token

来源:互联网 发布:外网映射端口 编辑:程序博客网 时间:2024/05/22 08:12

Nginx的可配置选项非常的多, 意味着配置文件可以相当复杂,那么解析起来也是困难重重,今天学习一下与配置解析有关的函数ngx_conf_read_token;

顾名思义,可以看出这是一个用于从配置文件中提取token的函数, 什么是token呢?

举栗子,下面这个配置片段中,user是一个token,www-data是一个token,worker_processes、auto、pid、/run/nginx.pid、events、worker_connections、768都是token;

multi_accept、on就不是token了,因为它们在#后面,是行注释的一部分

user www-data;worker_processes auto;pid /run/nginx.pid;events {    worker_connections 768;    # multi_accept on;}

从下面的代码分析中,我们也可以看出ngx_conf_read_token并不能一次提取所有token,在解析完一个配置行(以分号结束)或者遇到配置块的开始(以左花括号开始)时就会返回;因此我们想要解析完整个配置文件, 可能需要进行多次调用

/* core/ngx_conf_file.c *//* 从配置文件解析token*/static int ngx_conf_read_token(ngx_conf_t *cf){    u_char      *start, ch, *src, *dst;    int          len;    int          found, need_space, last_space, sharp_comment;    int          quoted, s_quoted, d_quoted;    ssize_t      n;    ngx_str_t   *word;    ngx_buf_t   *b;    found = 0;                          // 用来表示是否找到一个完整的token    need_space = 0;                     // 当接下来扫描到的字符需要是空格/水平制表符/回车符/换行符时置为1    last_space = 1;                     // 当扫描到空格/水平制表符/回车符/换行符时置为1    sharp_comment = 0;                  // 用来表示当前扫描的字符是否在一个#开头的行注释中    quoted = s_quoted = d_quoted = 0;   // quoted: 当扫描到一个转义序列的\时置为1                                        // s_quoted: 当扫描到一个单引号时置为1                                        // d_quoted: 当扫描到一个双引号时置为1    cf->args->nelts = 0;                // cf->args数组用来存放解析到的token    b = cf->conf_file->buffer;          // 缓冲区b, 里面存放配置内容    start = b->pos;                     // b->pos用来标记当前将要扫描的字符位置                                        // start用来标记一个token的起始字符#if 0ngx_log_debug(cf->log, "TOKEN START");#endif    for ( ;; ) {        if (b->pos >= b->last) {            // b->last用来标记缓冲区内有效内容的结束位置,            // 这里意味着已经读取的配置内容解析完毕                        if (cf->conf_file->file.offset                                 >= ngx_file_size(&cf->conf_file->file.info)) {                // 当配置文件的当前偏移量等于配置文件大小时, 说明配置文件已经读取完毕,                // 也就意味着配置文件全部解析完毕                return NGX_CONF_FILE_DONE;            }            if (b->pos - start) {                // 意味着当前token还没解析完毕, 只扫描了它的部分字符,                // 此时将这部分字符拷贝到缓冲区头                ngx_memcpy(b->start, start, b->pos - start);            }            // 继续读取配置文件到缓冲区            n = ngx_read_file(&cf->conf_file->file,                              b->start + (b->pos - start),                              b->end - (b->start + (b->pos - start)),                              cf->conf_file->file.offset);            if (n == NGX_ERROR) {                return NGX_ERROR;            }            // 更新b->pos, 使之指向刚读取内容的起始位置            b->pos = b->start + (b->pos - start);            // start指向缓冲区起始地址, 也就是指向当前要解析的token的起始字符            start = b->start;            // 更新b->last, 使之指向刚读取内容的结束位置            b->last = b->pos + n;        }        // 读取当前扫描的字符, 并让b->pos后移        ch = *b->pos++;#if 0ngx_log_debug(cf->log, "%d:%d:%d:%d:%d '%c'" _              last_space _ need_space _              quoted _ s_quoted _ d_quoted _ ch);#endif        if (ch == LF) {            // 如果当前扫描的字符为换行符                        // cf->conf_file的line成员加1,            // line成员用来记录已扫描的配置文件行数            cf->conf_file->line++;            if (sharp_comment) {                // 如果sharp_comment为1, 那么这里的换行符意味着#行注释的结束,                // 置sharp_comment为0                sharp_comment = 0;            }        }        if (sharp_comment) {            // 如果sharp_comment为1, 那么这里的字符就是行注释的一部分, 直接跳过            continue;        }        if (quoted) {            // 如果quoted为1, 意味着上一个扫描的字符为\, 当前字符就是转义序列的一部分,             // 置quoted为0, 表示当前扫描到的转义序列结束, 然后直接跳过            quoted = 0;            continue;        }        if (need_space) {            // 当need_space为1时, 意味着当前扫描到的字符需要是空格                        if (ch == ' ' || ch == '\t' || ch == CR || ch == LF) {                // 如果当前扫描到的字符为空格/水平制表符/回车符/换行符时,                // 置need_space为0, 因为已经满足了要求;                // 置last_space为1, 标记当前扫描到了这些字符;                // 然后直接跳过                last_space = 1;                need_space = 0;                continue;            }            if (ch == ';' || ch == '{') {                return NGX_OK;            }            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,                          "unexpected '%c' in %s:%d",                          ch, cf->conf_file->file.name.data,                          cf->conf_file->line);            return NGX_ERROR;        }        if (last_space) {            // 如果上一个扫描的字符为空格/水平制表符/回车符/换行符                        if (ch == ' ' || ch == '\t' || ch == CR || ch == LF) {                // 当前扫描的字符依旧为空格/水平制表符/回车符/换行符, 那么直接跳过                continue;            }                        // 如果当前扫描的字符不为空格/水平制表符/回车符/换行符, 意味着当前扫描的字符为下一个token            // 的起始字符, start指向当前扫描的字符            start = b->pos - 1;            switch (ch) {            case ';':            case '{':                // 如果当前扫描到的字符为分号或者左花括号                                if (cf->args->nelts == 0) {                    // 如果此次解析到的token数量为0, 返回NGX_ERROR;                    // 因为分号或左花括号之前必须出现至少一个token, 否则说明配置有误                    ngx_log_error(NGX_LOG_EMERG, cf->log, 0,                                  "unexpected '%c' in %s:%d",                                  ch, cf->conf_file->file.name.data,                                  cf->conf_file->line);                    return NGX_ERROR;                }                // 意味着配置行的结束或者配置块的开始, 返回NGX_OK                return NGX_OK;            case '}':                // 如果当前扫描到的字符为右花括号                            if (cf->args->nelts > 0) {                    // 如果此次解析到的token数量大于0, 返回NGX_ERROR;                    // 因为右花括号必须独占一行, 否则说明配置有误                                        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,                                  "unexpected '}' in %s:%d",                                  cf->conf_file->file.name.data,                                  cf->conf_file->line);                    return NGX_ERROR;                }                // 意味着配置块的结束, 返回NGX_CONF_BLOCK_DONE                return NGX_CONF_BLOCK_DONE;            case '#':                // 如果当前扫描到的字符为井号,                // 意味着遇到行注释的开始, 置sharp_comment为1;                // 然后直接跳过                sharp_comment = 1;                continue;            case '\\':                // 如果当前扫描到的字符为反斜杠,                // 意味着遇到转义序列的开始, 置quoted为1;                // 置last_space为0; 然后直接跳过                quoted = 1;                last_space = 0;                continue;            case '"':                // 如果当前扫描到的字符为双引号, 加上前一个扫描的字符为空格,                // 那么我们可以推断该双引号为左双引号;                // 那么下一个token的开始字符必定为当前扫描字符的下一个字符, start向后移;                // 置d_quoted为1; 置last_space为0; 然后直接跳过                start++;                d_quoted = 1;                last_space = 0;                continue;            case '\'':                // 如果当前扫描到的字符为单引号, 与上面的双引号同理, 只是需要置s_quoted为1                start++;                s_quoted = 1;                last_space = 0;                continue;            default:                // 如果当前扫描到的字符为其他字符, 说明是token中的字符, 置last_space为0即可                last_space = 0;            }        } else {            // 如果上一个扫描的字符不为空格/水平制表符/回车符/换行符                        if (ch == '\\') {                // 如果当前扫描到的字符为反斜杠, 意味着遇到转义序列的开始, 置quoted为1;                // 然后直接跳过                quoted = 1;                continue;            }            // 如果上一个扫描的字符不为空格/水平制表符/回车符/换行符, 那么当前扫描字符为单引号或双引号时,            // 我们需要结合s_quoted或d_quoted来判断是左还是右            if (d_quoted) {                // 如果d_quoted为1, 说明之前遇到一个双引号                                if (ch == '"') {                    // 如果当前扫描的字符为双引号, 那么必为右双引号;                    // 置d_quoted为0;                     // 右双引号之后需要出现空格, 置need_space为1;                    // 右双引号意味着一个token的结束, 置found为1                    d_quoted = 0;                    need_space = 1;                    found = 1;                }            } else if (s_quoted) {                // 如果s_quoted为1, 说明之前遇到一个单引号                                if (ch == '\'') {                    // 如果当前扫描的字符为单引号, 那么必为右单引号;                    // 置s_quoted为0;                    // 右单引号之后需要出现空格, 置need_space为1;                    // 右单引号意味着一个token的结束, 置found为1                    s_quoted = 0;                    need_space = 1;                    found = 1;                }            } else if (ch == ' ' || ch == '\t' || ch == CR || ch == LF                       || ch == ';' || ch == '{') {                // 如果当前扫描的字符为空格/水平制表符/回车符/换行符/分号/左花括号,                // 意味着一个token的结束, 置found为1; 置last_space为1                last_space = 1;                found = 1;            }            if (found) {                // 如果found为1, 说明解析到一个token                                // 从cf->args数组中申请存放一个元素的内存空间                if (!(word = ngx_push_array(cf->args))) {                    return NGX_ERROR;                }                // 从cf->pool内存池中申请一块大小为b->pos - start + 1的内存空间用来存放token字符串(包含结束符);                // 其实我觉得这里应该b->pos - start就足矣                // 举栗子, "ab"_ 此时,start指向a, b->pos指向下划线模拟的空格, 只需要b->pos - start = 3个字节即可存放token                if (!(word->data = ngx_palloc(cf->pool, b->pos - start + 1))) {                    return NGX_ERROR;                }                // 遍历缓冲区中扫描到的token字符串, 将其拷贝到刚才申请到的内存中;                // 当然这里也需要验证该token的合法性和过滤其中的无用字符                for (dst = word->data, src = start, len = 0;                     src < b->pos - 1;                     len++)                {                    if (*src == '\\') {                        // 如果token的当前字符为反斜杠                                                switch (src[1]) {                        case '"':                        case '\'':                        case '\\':                            // 如果token的下一个字符为双引号/单引号/反斜杠,                            // 那么直接退出循环                            src++;                            break;                        case 't':                            // 如果token的下一个字符为t, 意味着与当前字符组成一个水平制表符,                            // 那么直接拷贝一个水平制表符                            *dst++ = '\t';                            src += 2;                            continue;                        case 'r':                            // 如果token的下一个字符为r, 意味着与当前字符组成一个回车符,                            // 那么直接拷贝一个回车符                            *dst++ = '\r';                            src += 2;                            continue;                        case 'n':                            // 如果token的下一个字符为n, 意味着与当前字符组成一个换行符,                            // 那么直接拷贝一个换行符                            *dst++ = '\n';                            src += 2;                            continue;                        }                    }                    // 将缓冲区中token的当前字符拷贝至申请的内存中                    *dst++ = *src++;                }                // 在申请的内存中加上结束符;                // 记录token长度为len                *dst = '\0';                word->len = len;#if 0ngx_log_debug(cf->log, "FOUND %d:'%s'" _ word->len _ word->data);#endif                if (ch == ';' || ch == '{') {                    // 如果当前扫描到的字符为分号/左花括号, 那么意味着配置行的结束或配置块的开始,                    // 返回NGX_OK                    return NGX_OK;                }                // 置found为0, 以查找下一个token                found = 0;            }        }    }}