首页 运维 nginx Nginx服务器中location配置实例分析

Nginx服务器中location配置实例分析

May 24, 2023 pm 02:05 PM
nginx location

        首先我来大概的介绍一下location的种类和匹配规则,以nginx wiki的例子做说明:

location = / { 
 # matches the query / only. 
 [ configuration a ]  
} 
location / { 
 # matches any query, since all queries begin with /, but regular 
 # expressions and any longer conventional blocks will be 
 # matched first. 
 [ configuration b ]  
} 
location ^~ /images/ { 
 # matches any query beginning with /images/ and halts searching, 
 # so regular expressions will not be checked. 
 [ configuration c ]  
} 
location ~* \.(gif|jpg|jpeg)$ { 
 # matches any request ending in gif, jpg, or jpeg. however, all 
 # requests to the /images/ directory will be handled by 
 # configuration c.   
 [ configuration d ]  
} 
 
location @named { 
 # such locations are not used during normal processing of requests,  
 # they are intended only to process internally redirected requests (for example error_page, try_files). 
 [ configuration e ]  
}
登录后复制

可以看到上面的例子中有5种不同类型的location,其中第4个带 “~” 号前缀的为需要正则匹配的location,nginx在进行url解析时对这5种不同类型的location具有不同的优先级规则,大致的规则如下:

1,字符串精确匹配到一个带 “=” 号前缀的location,则停止,且使用这个location的配置;

2,字符串匹配剩下的非正则和非特殊location,如果匹配到某个带 "^~" 前缀的location,则停止;

3,正则匹配,匹配顺序为location在配置文件中出现的顺序。如果匹配到某个正则location,则停止,并使用这个location的配置;否则,使用步骤2中得到的具有最大字符串匹配的location配置。

例如,对下面的请求有:

1, / -> 精确匹配到第1个location,匹配停止,使用configuration a
2,/some/other/url -> 首先前缀部分字符串匹配到了第2个location,然后进行正则匹配,显然没有匹配上,则使用第2个location的配置configurationb
3,/images /1.jpg -> 首先前缀部分字符串匹配到了第2个location,但是接着对第3个location也前缀匹配上了,而且这时已经是配置文件里面对这个url的最大字符串匹配了,并且location带有 "^~" 前缀,则不再进行正则匹配,最终使用configuration c
4,/some/other/path/to/1.jpg -> 首先前缀部分同样字符串匹配到了第2个location,然后进行正则匹配,这时正则匹配成功,则使用congifuration d

nginx的url匹配规则实际上有点不妥,大部分情况下一个url必须先进行字符串匹配,然后再做正则匹配,但是实际上如果先做正则匹配,没有匹配上再 做字符串匹配,在很多情况下可以节省掉做字符串匹配的时间。不管怎样,先来看一下nginx源码里面的实现,在介绍匹配location过程之前,先来介 绍一下nginx里面对location的组织方式,实际上在配置解析阶段,nginx将字符串匹配的location和正则匹配的location分别 存储在http core模块的loc配置ngx_http_core_loc_conf_t结构的下面2个字段:

ngx_http_location_tree_node_t  *static_locations; 
(ngx_pcre) 
ngx_http_core_loc_conf_t    **regex_locations; 
if
登录后复制

从这2个字段的类型可以看出,字符串匹配的location被组织成了一个location tree,而正则匹配的location只是一个数组,

location tree和regex_locations数组建立过程在ngx_http_block中:
/* create location trees */ 
 
  for (s = 0; s < cmcf->servers.nelts; s++) { 
 
    clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index]; 
 
    if (ngx_http_init_locations(cf, cscfp[s], clcf) != ngx_ok) { 
      return ngx_conf_error; 
    } 
 
    if (ngx_http_init_static_location_trees(cf, clcf) != ngx_ok) { 
      return ngx_conf_error; 
    } 
  }
登录后复制

经过配置的读取之后,所有server都被保存在http core模块的main配置中的servers数组中,而每个server里面的location都被按配置中出现的顺序保存在http core模块的loc配置的locations队列中,上面的代码中先对每个server的location进行排序和分类处理,这一步发生在 ngx_http_init_location()函数中:

static ngx_int_t 
ngx_http_init_locations(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, 
  ngx_http_core_loc_conf_t *pclcf) 
{ 
 ... 
  locations = pclcf->locations; 
 
 ... 
  /* 按照类型排序location,排序完后的队列: (exact_match 或 inclusive) (排序好的,如果某个exact_match名字和inclusive location相同,exact_match排在前面) 
    | regex(未排序)| named(排序好的) | noname(未排序)*/ 
  ngx_queue_sort(locations, ngx_http_cmp_locations); 
 
  named = null; 
  n = 0; 
#if (ngx_pcre) 
  regex = null; 
  r = 0; 
#endif 
 
  for (q = ngx_queue_head(locations); 
     q != ngx_queue_sentinel(locations); 
     q = ngx_queue_next(q)) 
  { 
    lq = (ngx_http_location_queue_t *) q; 
 
    clcf = lq->exact ? lq->exact : lq->inclusive; 
    /* 由于可能存在nested location,也就是location里面嵌套的location,这里需要递归的处理一下当前location下面的nested location */ 
    if (ngx_http_init_locations(cf, null, clcf) != ngx_ok) { 
      return ngx_error; 
    } 
 
#if (ngx_pcre) 
 
    if (clcf->regex) { 
      r++; 
 
      if (regex == null) { 
        regex = q; 
      } 
 
      continue; 
    } 
 
#endif 
 
    if (clcf->named) { 
      n++; 
 
      if (named == null) { 
        named = q; 
      } 
 
      continue; 
    } 
 
    if (clcf->noname) { 
      break; 
    } 
  } 
 
  if (q != ngx_queue_sentinel(locations)) { 
    ngx_queue_split(locations, q, &tail); 
  } 
  /* 如果有named location,将它们保存在所属server的named_locations数组中 */ 
  if (named) { 
    clcfp = ngx_palloc(cf->pool, 
              (n + 1) * sizeof(ngx_http_core_loc_conf_t **)); 
    if (clcfp == null) { 
      return ngx_error; 
    } 
 
    cscf->named_locations = clcfp; 
 
    for (q = named; 
       q != ngx_queue_sentinel(locations); 
       q = ngx_queue_next(q)) 
    { 
      lq = (ngx_http_location_queue_t *) q; 
 
      *(clcfp++) = lq->exact; 
    } 
 
    *clcfp = null; 
 
    ngx_queue_split(locations, named, &tail); 
  } 
 
#if (ngx_pcre) 
  /* 如果有正则匹配location,将它们保存在所属server的http core模块的loc配置的regex_locations 数组中, 
    这里和named location保存位置不同的原因是由于named location只能存在server里面,而regex location可以作为nested location */ 
  if (regex) { 
 
    clcfp = ngx_palloc(cf->pool, 
              (r + 1) * sizeof(ngx_http_core_loc_conf_t **)); 
    if (clcfp == null) { 
      return ngx_error; 
    } 
 
    pclcf->regex_locations = clcfp; 
 
    for (q = regex; 
       q != ngx_queue_sentinel(locations); 
       q = ngx_queue_next(q)) 
    { 
      lq = (ngx_http_location_queue_t *) q; 
 
      *(clcfp++) = lq->exact; 
    } 
 
    *clcfp = null; 
 
    ngx_queue_split(locations, regex, &tail); 
  } 
 
#endif 
 
  return ngx_ok; 
}
登录后复制


上面的步骤将正则匹配的location保存好了,location tree的建立在ngx_http_init_static_location_trees中进行:

static ngx_int_t 
ngx_http_init_static_location_trees(ngx_conf_t *cf, 
  ngx_http_core_loc_conf_t *pclcf) 
{ 
  ngx_queue_t        *q, *locations; 
  ngx_http_core_loc_conf_t  *clcf; 
  ngx_http_location_queue_t *lq; 
 
  locations = pclcf->locations; 
 
  if (locations == null) { 
    return ngx_ok; 
  } 
 
  if (ngx_queue_empty(locations)) { 
    return ngx_ok; 
  } 
  /* 这里也是由于nested location,需要递归一下 */ 
  for (q = ngx_queue_head(locations); 
     q != ngx_queue_sentinel(locations); 
     q = ngx_queue_next(q)) 
  { 
    lq = (ngx_http_location_queue_t *) q; 
 
    clcf = lq->exact ? lq->exact : lq->inclusive; 
 
    if (ngx_http_init_static_location_trees(cf, clcf) != ngx_ok) { 
      return ngx_error; 
    } 
  } 
  /* join队列中名字相同的inclusive和exact类型location,也就是如果某个exact_match的location名字和普通字符串匹配的location名字相同的话, 
    就将它们合到一个节点中,分别保存在节点的exact和inclusive下,这一步的目的实际是去重,为后面的建立排序树做准备 */ 
  if (ngx_http_join_exact_locations(cf, locations) != ngx_ok) { 
    return ngx_error; 
  } 
  /* 递归每个location节点,得到当前节点的名字为其前缀的location的列表,保存在当前节点的list字段下 */ 
  ngx_http_create_locations_list(locations, ngx_queue_head(locations)); 
 
  /* 递归建立location三叉排序树 */ 
  pclcf->static_locations = ngx_http_create_locations_tree(cf, locations, 0); 
  if (pclcf->static_locations == null) { 
    return ngx_error; 
  } 
 
  return ngx_ok; 
}
登录后复制

经过ngx_http_init_location()函数处理之后,locations队列已经是排好序的了,建立三叉树的过程的主要工作都在ngx_http_create_locations_list()和ngx_http_create_locations_tree()中完成,这2个 函数都是递归函数,第1个函数递归locations队列中的每个节点,得到以当前节点的名字为前缀的location,并保存在当前节点的list字段 下,例如,对下列location:

location /xyz { 
 
} 
 
location = /xyz { 
 
} 
location /xyza { 
 
} 
 
location /xyzab { 
 
} 
location /xyzb { 
 
} 
location /abc { 
 
} 
location /efg { 
 
} 
location /efgaa { 
 
}
登录后复制

排序的结果为/abc /efg /efgaa =/xyz /xyz /xyza /xyzab /xyzb,去重后结果为 /abc /efg /efgaa /xyz /xyza /xyzab/xyzb,ngx_http_create_locations_list()执行后的结果为:

Nginx服务器中location配置实例分析

最后,来看下ngx_http_create_locations_tree函数:

static ngx_http_location_tree_node_t * 
ngx_http_create_locations_tree(ngx_conf_t *cf, ngx_queue_t *locations, 
  size_t prefix) 
{ 
  ... 
  /* 根节点为locations队列的中间节点 */ 
  q = ngx_queue_middle(locations); 
 
  lq = (ngx_http_location_queue_t *) q; 
  len = lq->name->len - prefix; 
   
  node = ngx_palloc(cf->pool, 
           offsetof(ngx_http_location_tree_node_t, name) + len); 
  if (node == null) { 
    return null; 
  } 
 
  node->left = null; 
  node->right = null; 
  node->tree = null; 
  node->exact = lq->exact; 
  node->inclusive = lq->inclusive; 
 
  node->auto_redirect = (u_char) ((lq->exact && lq->exact->auto_redirect) 
              || (lq->inclusive && lq->inclusive->auto_redirect)); 
 
  node->len = (u_char) len; 
  ngx_memcpy(node->name, &lq->name->data[prefix], len); 
 
  /* 从中间节点开始断开 */ 
  ngx_queue_split(locations, q, &tail); 
 
  if (ngx_queue_empty(locations)) { 
    /* 
     * ngx_queue_split() insures that if left part is empty, 
     * then right one is empty too 
     */ 
    goto inclusive; 
  } 
 
  /* 从locations左半部分得到左子树 */ 
  node->left = ngx_http_create_locations_tree(cf, locations, prefix); 
  if (node->left == null) { 
    return null; 
  } 
 
  ngx_queue_remove(q); 
 
  if (ngx_queue_empty(&tail)) { 
    goto inclusive; 
  } 
  
 
  /* 从locations右半部分得到右子树 */ 
  node->right = ngx_http_create_locations_tree(cf, &tail, prefix); 
  if (node->right == null) { 
    return null; 
  } 
 
inclusive: 
 
  if (ngx_queue_empty(&lq->list)) { 
    return node; 
  } 
 
  /* 从list队列得到tree子树 */ 
  node->tree = ngx_http_create_locations_tree(cf, &lq->list, prefix + len); 
  if (node->tree == null) { 
    return null; 
  } 
 
  return node; 
} 
     location tree节点的ngx_http_location_tree_node_s结构:
struct ngx_http_location_tree_node_s { 
  ngx_http_location_tree_node_t  *left; 
  ngx_http_location_tree_node_t  *right; 
  ngx_http_location_tree_node_t  *tree; 
 
  ngx_http_core_loc_conf_t    *exact; 
  ngx_http_core_loc_conf_t    *inclusive; 
 
  u_char              auto_redirect; 
  u_char              len; 
  u_char              name[1]; 
};
登录后复制

location tree结构用到的是left,right,tree 这3个字段, location tree实际上是一个三叉的字符串排序树,而且这里如果某个节点只考虑左,右子树,它是一颗平衡树,它的建立过程有点类似于一颗平衡排序二叉树的建立过程,先排序再用二分查找找到的节点顺序插入,ngx_http_location_tree_node_s的tree节点也是一颗平衡排序树,它是用该节点由ngx_http_create_locations_list()得到的list建立的,也就是该节点的名字是它的tree子树里面的所有节点名字的前缀,所以tree子树里面的所有节点的名字不用保存公共前缀,而且查找的时候,如果是转向tree节点的话,也是不需要再比较父节点的那段字符串了。
ngx_http_create_locations_tree()函数写的很清晰,它有一个参数是队列locations,它返回一颗三叉树,根节点为locations的中间节点,其左子树为locations队列的左半部分建立的location tree,右子树为location队列的右半部分建立的tree,tree节点为该根节点的list队列建立的tree。

最终建立的location tree如下(为了方便阅读,图中列出了tree节点的完整名字):

Nginx服务器中location配置实例分析

ps:关于 location modifier
1. =
这会完全匹配指定的 pattern ,且这里的 pattern 被限制成简单的字符串,也就是说这里不能使用正则表达式。

example:
server {
  server_name jb51.net;
  location = /abcd {
  […]
  }
}
登录后复制

匹配情况:

  http://jb51.net/abcd    # 正好完全匹配
  http://jb51.net/abcd    # 如果运行 nginx server 的系统本身对大小写不敏感,比如 windows ,那么也匹配
  http://jb51.net/abcd?param1&para;m2  # 忽略查询串参数(query string arguments),这里就是 /abcd 后面的 ?param1&para;m2
  http://jb51.net/abcd/  # 不匹配,因为末尾存在反斜杠(trailing slash),nginx 不认为这种情况是完全匹配
  http://jb51.net/abcde  # 不匹配,因为不是完全匹配
登录后复制

2. (none)
可以不写 location modifier ,nginx 仍然能去匹配 pattern 。这种情况下,匹配那些以指定的 patern 开头的 uri,注意这里的 uri 只能是普通字符串,不能使用正则表达式。

example:
server {
  server_name jb51.net;
  location /abcd {
  […]
  }
}
登录后复制

匹配情况:

  http://jb51.net/abcd    # 正好完全匹配
  http://jb51.net/abcd    # 如果运行 nginx server 的系统本身对大小写不敏感,比如 windows ,那么也匹配
  http://jb51.net/abcd?param1&para;m2  # 忽略查询串参数(query string arguments),这里就是 /abcd 后面的 ?param1&para;m2
  http://jb51.net/abcd/  # 末尾存在反斜杠(trailing slash)也属于匹配范围内
  http://jb51.net/abcde  # 仍然匹配,因为 uri 是以 pattern 开头的
登录后复制

3. ~
这个 location modifier 对大小写敏感,且 pattern 须是正则表达式

example:
server {
  server_name jb51.net;
  location ~ ^/abcd$ {
  […]
  }
}
登录后复制

匹配情况:

  http://jb51.net/abcd    # 完全匹配
  http://jb51.net/abcd    # 不匹配,~ 对大小写是敏感的
  http://jb51.net/abcd?param1&para;m2  # 忽略查询串参数(query string arguments),这里就是 /abcd 后面的 ?param1&para;m2
  http://jb51.net/abcd/  # 不匹配,因为末尾存在反斜杠(trailing slash),并不匹配正则表达式 ^/abcd$
  http://jb51.net/abcde  # 不匹配正则表达式 ^/abcd$
登录后复制

注意:对于一些对大小写不敏感的系统,比如 windows ,~ 和 ~* 都是不起作用的,这主要是操作系统的原因。

4. ~*
与 ~ 类似,但这个 location modifier 不区分大小写,pattern 须是正则表达式

example:
server {
  server_name jb51.net;
  location ~* ^/abcd$ {
  […]
  }
}
登录后复制

匹配情况:

 http://jb51.net/abcd    # 完全匹配
  http://jb51.net/abcd    # 匹配,这就是它不区分大小写的特性
  http://jb51.net/abcd?param1&para;m2  # 忽略查询串参数(query string arguments),这里就是 /abcd 后面的 ?param1&para;m2
  http://jb51.net/abcd/  # 不匹配,因为末尾存在反斜杠(trailing slash),并不匹配正则表达式 ^/abcd$
  http://jb51.net/abcde  # 不匹配正则表达式 ^/abcd$
登录后复制

5. ^~
匹配情况类似 2. (none) 的情况,以指定匹配模式开头的 uri 被匹配,不同的是,一旦匹配成功,那么 nginx 就停止去寻找其他的 location 块进行匹配了(与 location 匹配顺序有关)

6. @
用于定义一个 location 块,且该块不能被外部 client 所访问,只能被 nginx 内部配置指令所访问,比如 try_files or error_page

以上是Nginx服务器中location配置实例分析的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前 By 尊渡假赌尊渡假赌尊渡假赌
仓库:如何复兴队友
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒险:如何获得巨型种子
4 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

tomcat服务器怎么让外网访问 tomcat服务器怎么让外网访问 Apr 21, 2024 am 07:22 AM

要让 Tomcat 服务器对外网访问,需要:修改 Tomcat 配置文件,允许外部连接。添加防火墙规则,允许访问 Tomcat 服务器端口。创建 DNS 记录,将域名指向 Tomcat 服务器公有 IP。可选:使用反向代理提升安全性和性能。可选:设置 HTTPS 以提高安全性。

thinkphp怎么运行 thinkphp怎么运行 Apr 09, 2024 pm 05:39 PM

ThinkPHP Framework 的本地运行步骤:下载并解压 ThinkPHP Framework 到本地目录。创建虚拟主机(可选),指向 ThinkPHP 根目录。配置数据库连接参数。启动 Web 服务器。初始化 ThinkPHP 应用程序。访问 ThinkPHP 应用程序 URL 运行。

nginx启动命令和停止命令是什么 nginx启动命令和停止命令是什么 Apr 02, 2024 pm 08:45 PM

Nginx 的启动和停止命令分别为 nginx 和 nginx -s quit。启动命令直接启动服务器,而停止命令优雅地关闭服务器,允许所有当前请求处理完毕。其他可用停止信号包括 stop 和 reload。

Welcome to nginx!怎么解决? Welcome to nginx!怎么解决? Apr 17, 2024 am 05:12 AM

要解决 "Welcome to nginx!" 错误,需要检查虚拟主机配置,启用虚拟主机,重新加载 Nginx,如果无法找到虚拟主机配置文件,则创建默认页面并重新加载 Nginx,这样错误消息将消失,网站将正常显示。

phpmyadmin怎么注册 phpmyadmin怎么注册 Apr 07, 2024 pm 02:45 PM

要注册 phpMyAdmin,需要先创建 MySQL 用户并授予其权限,然后下载、安装和配置 phpMyAdmin,最后登录到 phpMyAdmin 以管理数据库。

nodejs项目怎么部署到服务器 nodejs项目怎么部署到服务器 Apr 21, 2024 am 04:40 AM

Node.js 项目的服务器部署步骤:准备部署环境:获取服务器访问权限、安装 Node.js、设置 Git 存储库。构建应用程序:使用 npm run build 生成可部署代码和依赖项。上传代码到服务器:通过 Git 或文件传输协议。安装依赖项:SSH 登录服务器并使用 npm install 安装应用程序依赖项。启动应用程序:使用 node index.js 等命令启动应用程序,或使用 pm2 等进程管理器。配置反向代理(可选):使用 Nginx 或 Apache 等反向代理路由流量到应用程

访问网站出现nginx怎么解决 访问网站出现nginx怎么解决 Apr 02, 2024 pm 08:39 PM

访问网站出现 nginx,原因可能是:服务器维护、服务器繁忙、浏览器缓存、DNS 问题、防火墙阻止、网站错误配置、网络连接问题或网站已关闭。尝试以下解决方案:等待维护结束、非高峰时段访问、清除浏览器缓存、刷新 DNS 缓存、禁用防火墙或防病毒软件、联系网站管理员、检查网络连接或使用搜索引擎或 Web 存档查找其他网站副本。如果问题仍然存在,请与网站管理员联系。

docker容器之间如何通信 docker容器之间如何通信 Apr 07, 2024 pm 06:24 PM

Docker 环境中容器通信有五种方法:共享网络、Docker Compose、网络代理、共享卷、消息队列。根据隔离性和安全性需求,选择最合适的通信方法,例如利用 Docker Compose 简化连接或使用网络代理提高隔离性。

See all articles