• Nginx之 Location 的生成


    1. Location 的生成

    location 的生成大致有三种:

    • 由 location 指令直接生成
    • 命令 location:仅用于 server 内部跳转,如 rewrite 就是命名 location,命令 location 不能包含在其他 location 里。
    location @rewrite {
        rewrite ~ /wiki/search(.*)$ /search.php?serach=$1 last;
    }
    
    • 未命名 location:由 limit_except 和 if 等指令会间接导致生成 location,即为未命名 location。

    1.1 由 location 指令直接生成

    static ngx_command_t  ngx_http_core_commands[] = {
        ...
        
        { ngx_string("location"),
          NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12,
          ngx_http_core_location,
          NGX_HTTP_SRV_CONF_OFFSET,
          0,
          NULL },
        ...
    };
    

    标准的 location 指令配置语法为:

    location [ = | ~ | ~* | ^~ ] uri { ... }
    
    • location = /uri:= 表示精确匹配,只有完全匹配上才能生效
    • location ^~ /uri:^~ 开头对 URL 路径进行前缀匹配,并且在正则之前
    • location ~ pattern:开头表示区分大小写的正则匹配
    • location ~* pattern:开头表示不区分大小写的正则匹配
    • location /uri:不带任何修饰符,也表示前缀匹配,但是在正则匹配之后
    • location /:通用匹配,任何未匹配到其它 location 的请求都会匹配到,相当于 switch 中的 default

    前缀匹配时,Nginx 不对 url 做编码,因此请求为/static/20%/aa,可以被规则^~ /static/ /aa匹配到(注意是空格)。

    多个 location 配置的情况下匹配顺序为(参考资料而来):

    • 首先精确匹配 =
    • 其次前缀匹配 ^~
    • 其次是按文件中顺序的正则匹配
    • 然后匹配不带任何修饰符的前缀匹配
    • 最后是交给 / 通用匹配
    • 当有匹配成功时,停止匹配,按当前匹配规则处理请求

    注意:前缀匹配,如果有包含关系时,按最大匹配原则进行匹配。比如在前缀匹配:location /dir01location /dir01/dir02,如有请求http://localhost/dir01/dir02/file将最终匹配到location /dir01/dir02

    有如下匹配规则:

    location = / {
        echo "规则A";
    }
    location = /login {
        echo "规则B";
    }
    location ^~ /static/ {
        echo "规则C";
    }
    location ^~ /static/files {
        echo "规则X";
    }
    location ~ .(gif|jpg|png|js|css)$ {
        echo "规则D";
    }
    location ~* .png$ {
        echo "规则E";
    }
    location /img {
        echo "规则Y";
    }
    location / {
        echo "规则F";
    }
    

    则产生的效果如下:

    • 访问根目录/,比如http://localhost/将匹配规则 A
    • 访问http://localhost/login将匹配规则 B,http://localhost/register则匹配规则 F
    • 访问http://localhost/static/a.html将匹配规则 C
    • 访问http://localhost/static/files/a.exe将匹配规则 X,虽然规则 C 也能匹配到,但因为最大匹配原则,最终选中了规则 X。
    • 访问http://localhost/a.gifhttp://localhost/b.jpg将匹配规则 D 和规则 E,但是规则 D 顺序优先,规则 E 不起作用,而http://localhost/static/c.png则优先匹配到规则 C。
    • 访问http://localhost/a.PNG则匹配规则 E,而不会匹配规则 D,因为规则 E 不区分大小写。
    • 访问http://localhost/img/a.gif会匹配上规则 D,虽然规则 Y 也可以匹配上,但是因为正则匹配优先,而忽略了规则 Y。
    • 访问http://localhost/img/a.tiff会匹配上规则 Y。
    • 访问http://localhost/category/id/111则最终会匹配到规则 F,因为以上规则都不匹配,这个时候应该是 Nginx 转发请求给后端应用服务器,比如 FastCGI(php),tomcat(jsp),Nginx 作为反向代理服务器存在。

    1.1.1 ngx_http_core_location

    static char *
    ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
    {
        char                      *rv;
        u_char                    *mod;
        size_t                     len;
        ngx_str_t                 *value, *name;
        ngx_uint_t                 i;
        ngx_conf_t                 save;
        ngx_http_module_t         *module;
        ngx_http_conf_ctx_t       *ctx, *pctx;
        ngx_http_core_loc_conf_t  *clcf, *pclcf;
    
        /* 为当前location{}分配一个存储所有配置项的配置上下文结构体 */
        ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
        if (ctx == NULL) {
            return NGX_CONF_ERROR;
        }
    
        /* pctx 指向当前location的父级 {} 的配置上下文结构体,如 server{} */
        pctx = cf->ctx;
        /* 当前location{}的main_conf、srv_conf都继承自父级 {} 的配置上下文结构体 */
        ctx->main_conf = pctx->main_conf;
        ctx->srv_conf = pctx->srv_conf;
    
        /* 当前location{}仅需为所有 HTTP 模块分配 loc 级别的配置项存储空间 */
        ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
        if (ctx->loc_conf == NULL) {
            return NGX_CONF_ERROR;
        }
    
        /* 调用所有 HTTP 模块的 create_loc_conf(若有实现的话),将各个 HTTP 模块
         * 生成的 loc 级别的配置项结构体保存到当前location的 loc_conf 数组中的
         * 各自 ctx_index 索引处 */
        for (i = 0; cf->cycle->modules[i]; i++) {
            if (cf->cycle->modules[i]->type != NGX_HTTP_MODULE) {
                continue;
            }
    
            module = cf->cycle->modules[i]->ctx;
    
            if (module->create_loc_conf) {
                ctx->loc_conf[cf->cycle->modules[i]->ctx_index] =
                                                       module->create_loc_conf(cf);
                if (ctx->loc_conf[cf->cycle->modules[i]->ctx_index] == NULL) {
                    return NGX_CONF_ERROR;
                }
            }
        }
    
        /* 获取当前location的loc_conf数组中 ngx_http_core_module 模块的loc级别配置项结构体 */
        clcf = ctx->loc_conf[ngx_http_core_module.ctx_index];
        /* ngx_http_core_module 模块的 ngx_http_core_loc_conf_t 结构体中的 loc_conf
         * 成员指向当前 location 的 loc_conf 数组 */
        clcf->loc_conf = ctx->loc_conf;
    
        /* 获取配置文件中 location 的第一个参数,即为 "location" */
        value = cf->args->elts;
    
        /* 若当前location配置指令总共有三个参数, 如: location = /test */
        if (cf->args->nelts == 3) {
    
            len = value[1].len;
            mod = value[1].data;
            name = &value[2];
    
            /* 若第二个参数为 '=',则表示该 location 为完全匹配,如: location = /test */
            if (len == 1 && mod[0] == '=') {
    
                /* 当前 location 的名称 */
                clcf->name = *name;
                /* 标志位,为 1 表示当前location为完全匹配类型 */
                clcf->exact_match = 1;
    
            /* 若第二个参数为 "^~",如: location ^~ /static/,则表示为对 URL 路径
             * 进行前缀匹配,并且优先级大于正则匹配  */
            } else if (len == 2 && mod[0] == '^' && mod[1] == '~') {
    
                clcf->name = *name;
                /* 标志位,为 1 表示不是正则匹配 */
                clcf->noregex = 1;
    
            /* 若第二个参数为 "~",如:location ~ .(gif|jpg|png|js|css)$,则表示为
             * 区分大小写的正则匹配 */
            } else if (len == 1 && mod[0] == '~') {
    
                if (ngx_http_core_regex_location(cf, clcf, name, 0) != NGX_OK) {
                    return NGX_CONF_ERROR;
                }
    
            /* 若第二个参数为 "~*",如:location ~* .png$,则表示为不区分大小写的正则匹配 */
            } else if (len == 2 && mod[0] == '~' && mod[1] == '*') {
    
                if (ngx_http_core_regex_location(cf, clcf, name, 1) != NGX_OK) {
                    return NGX_CONF_ERROR;
                }
    
            /* 除此之外的其他情况表示错误 */
            } else {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "invalid location modifier "%V"", &value[1]);
                return NGX_CONF_ERROR;
            }
    
        /* 若为两个参数的情况,如:location /test */
        } else {
    
            /* 该location 的名称 */
            name = &value[1];
    
            /* 若第一个字符为'=',如:location =/test,则表示完全匹配 */
            if (name->data[0] == '=') {
    
                clcf->name.len = name->len - 1;
                clcf->name.data = name->data + 1;
                /* 标志位,为 1 表示完全匹配 */
                clcf->exact_match = 1;
    
            /* 若 1、2 字节为 "^~",如: location ^~/static/ */
            } else if (name->data[0] == '^' && name->data[1] == '~') {
    
                clcf->name.len = name->len - 2;
                clcf->name.data = name->data + 2;
                /* 标志位,为 1 表示不是正则匹配 */
                clcf->noregex = 1;
    
            /* 正则匹配 */
            } else if (name->data[0] == '~') {
    
                name->len--;
                name->data++;
    
                /* location ~*.png$,不区分大小写的正则匹配 */
                if (name->data[0] == '*') {
    
                    name->len--;
                    name->data++;
    
                    if (ngx_http_core_regex_location(cf, clcf, name, 1) != NGX_OK) {
                        return NGX_CONF_ERROR;
                    }
    
                /* 否则为 location ~.(gif|jpg|png|js|css)$,表示区分大小写的正则匹配 */
                } else {
                    if (ngx_http_core_regex_location(cf, clcf, name, 0) != NGX_OK) {
                        return NGX_CONF_ERROR;
                    }
                }
    
            } else {
    
                clcf->name = *name;
                
                /* 类似: location @rewrite,属于命名location,仅用于内部 server 跳转,
                 * 如 rewrite 指令 */
                if (name->data[0] == '@') {
                    /* 标志位,为 1 表示为命名location */
                    clcf->named = 1;
                }
            }
        }
    
        /* 获取父级(一般为 server{}或 location{}(嵌套location的情况))下loc_conf数组中 
         * ngx_http_core_module 模块的上下文配置结构体 */
        pclcf = pctx->loc_conf[ngx_http_core_module.ctx_index];
    
        /* 若当前的 location 位于另一个 location 内 */
        if (cf->cmd_type == NGX_HTTP_LOC_CONF) {
    
            /* nested location */
    
    #if 0
            clcf->prev_location = pclcf;
    #endif
    
            /* 若父级 location 为完全匹配的情况,则表示出错,因为
             * 完全匹配的location内不允许嵌套另一个location */
            if (pclcf->exact_match) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "location "%V" cannot be inside "
                                   "the exact location "%V"",
                                   &clcf->name, &pclcf->name);
                return NGX_CONF_ERROR;
            }
    
            /* 若父级 location 为命名 location 的情况,则同样表示错误,
             * 因为命名 location 里也不能包含其他 location,并且命令 location
             * 只能在 server 上下文里,仅用于 server 内部跳转 */
            if (pclcf->named) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "location "%V" cannot be inside "
                                   "the named location "%V"",
                                   &clcf->name, &pclcf->name);
                return NGX_CONF_ERROR;
            }
    
            /* 若当前 location 为命名 location,则同样表示错误 */
            if (clcf->named) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "named location "%V" can be "
                                   "on the server level only",
                                   &clcf->name);
                return NGX_CONF_ERROR;
            }
    
            /* 父级 location 名称长度 */
            len = pclcf->name.len;
    
    #if (NGX_PCRE)
            if (clcf->regex == NULL
                && ngx_filename_cmp(clcf->name.data, pclcf->name.data, len) != 0)
    #else
            if (ngx_filename_cmp(clcf->name.data, pclcf->name.data, len) != 0)
    #endif
            {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "location "%V" is outside location "%V"",
                                   &clcf->name, &pclcf->name);
                return NGX_CONF_ERROR;
            }
        }
    
        /* 在对参数进行解析并区分出 location 类型以及做出有效性判断后,
         * 将该 location 添加到父级的 locations 队列里,这里表明是当前
         * server{} 下的所有 location 都添加到该 server{} 下的 locations 
         * 队列中进行统一管理 */
        if (ngx_http_add_location(cf, &pclcf->locations, clcf) != NGX_OK) {
            return NGX_CONF_ERROR;
        }
    
        save = *cf;
        /* 设置配置结构体的 ctx 上下文指向当前 location{} 的上下文 */
        cf->ctx = ctx;
        /* 标记接下来解析的指令位于 location{} 内 */
        cf->cmd_type = NGX_HTTP_LOC_CONF;
    
        /* 开始解析该 location{} */
        rv = ngx_conf_parse(cf, NULL);
    
        *cf = save;
    
        return rv;
    }
    

    1.1.2 ngx_http_core_regex_location

    该函数用于解析当前 location 指令为正则匹配的情况。

    /*
     * @regex: 该 location 的正则表达式
     * @caseless: 为 0 表示区分大小写; 为 1 表示不区分大小写
     */
    static ngx_int_t
    ngx_http_core_regex_location(ngx_conf_t *cf, ngx_http_core_loc_conf_t *clcf,
        ngx_str_t *regex, ngx_uint_t caseless)
    {
    /* 必须启用了 PCRE 功能 */
    #if (NGX_PCRE)
        ngx_regex_compile_t  rc;
        u_char               errstr[NGX_MAX_CONF_ERRSTR];
    
        ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
    
        /* 该正则表达式 */
        rc.pattern = *regex;
        rc.err.len = NGX_MAX_CONF_ERRSTR;
        rc.err.data = errstr;
    
    #if (NGX_HAVE_CASELESS_FILESYSTEM)
        rc.options = NGX_REGEX_CASELESS;
    #else
        /* 为 1 表示不区分大小写,为 0 表示区分大小写 */
        rc.options = caseless ? NGX_REGEX_CASELESS : 0;
    #endif
    
        /* 暂时不继续向下分析 */
        clcf->regex = ngx_http_regex_compile(cf, &rc);
        if (clcf->regex == NULL) {
            return NGX_ERROR;
        }
    
        /* 将该正则表达式置为当前 location 的名称 */
        clcf->name = *regex;
    
        return NGX_OK;
    
    #else
    
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "using regex "%V" requires PCRE library",
                           regex);
        return NGX_ERROR;
    
    #endif
    }
    

    1.1.3 ngx_http_add_location

    ngx_int_t
    ngx_http_add_location(ngx_conf_t *cf, ngx_queue_t **locations,
        ngx_http_core_loc_conf_t *clcf)
    {
        ngx_http_location_queue_t  *lq;
    
        /* 若该 server{} 下的 locations 队列还没有生成,则创建 */
        if (*locations == NULL) {
            *locations = ngx_palloc(cf->temp_pool,
                                    sizeof(ngx_http_location_queue_t));
            if (*locations == NULL) {
                return NGX_ERROR;
            }
    
            /* 该队列用于管理所有的 location */        
            ngx_queue_init(*locations);
        }
    
        /* 下面构造初始化一个 ngx_http_location_queue_t 结构体,该结构体
         * 表征一个 location,将会被挂载在 server{} 下的 loations 队列下 */
    
        lq = ngx_palloc(cf->temp_pool, sizeof(ngx_http_location_queue_t));
        if (lq == NULL) {
            return NGX_ERROR;
        }
    
        /* 绝对匹配、正则匹配以及命名或未命名地址都挂载在 exact 字段下 */
        if (clcf->exact_match
    #if (NGX_PCRE)
            || clcf->regex
    #endif
            || clcf->named || clcf->noname)
        {
            lq->exact = clcf;
            lq->inclusive = NULL;
    
        /* 其他情况,如前缀匹配则挂载在 inclusive 字段下 */
        } else {
            lq->exact = NULL;
            lq->inclusive = clcf;
        }
    
        /* 该 location 的名称 */
        lq->name = &clcf->name;
        /* 配置文件绝对路径名 */
        lq->file_name = cf->conf_file->file.name.data;
        /* 解析当前的 location 时在 nginx.conf 中的行号 */
        lq->line = cf->conf_file->line;
    
        /* 初始化该 lq->list 队列 */
        ngx_queue_init(&lq->list);
    
        /* 将该 lq 插入到 locations 队列的尾部 */
        ngx_queue_insert_tail(*locations, &lq->queue);
    
        return NGX_OK;
    }
    

    一个 server{} 下若有 4 个 location,则会形成如下图所示的框架:

    如果整个配置里存在多个 location 的层次嵌套,那么此时对应的组织结构图如下图所示。

    此外,由 Nginx 的源码可知,ngx_http_add_location 函数也会被 ngx_http_core_limit_except() 和 ngx_http_rewrite_if() 调用。因此,除了 location 指令之外,还有指令 limit_except 和 if 也会间接的导致生成 location,也即未命名 location,它们对应的 noname 字段会被设置为 1(即 clcf->noname = 1;)。这些 location 同样会加入到上图的 location tree 中,因为虽然它们最终并没有用在一般的请求地址匹配查找过程里,但是在进行配置合并的时候,这样可以让它们可以继承来之上层 location 的相关设置值。

  • 相关阅读:
    java如何将char类型的数字转换成int型的数字,而不是Ascii
    java 二分查找的注意事项
    IntelliJ IDEA 下的svn配置及使用的非常详细的图文总结
    java中Math的常用方法整理
    判断字符串是否可由重复子字符串组成
    P3558 [POI2013]BAJ-Bytecomputer
    BZOJ 3329. Xorequ
    Codeforces 1221F. Choose a Square
    Codeforces 1221E. Game With String
    Codeforces 1221D. Make The Fence Great Again
  • 原文地址:https://www.cnblogs.com/jimodetiantang/p/9246407.html
Copyright © 2020-2023  润新知