Nginx location 匹配规则详解
本文逐段对照 Nginx 官方文档,系统讲解
location的匹配顺序:普通 location 采用"最大前缀匹配",正则 location 按配置文件中的物理顺序匹配且命中即止;普通 location 先于正则 location 匹配,正则 location 会"覆盖"普通 location 的最大前缀匹配结果,但会"让步"于普通 location 的严格精确匹配。文中还逐一说明了前缀=、^~、~、~*、@的含义与区别,并通过多个可复现的配置例题推演完整的匹配决策过程。
一、关于 location 认识的常见误区
在正式讲解之前,先纠正两个流传很广的错误认识。
误区一:location 的匹配顺序是"先匹配正则,再匹配普通"
矫正:location 的匹配顺序其实是"先匹配普通,再匹配正则"。
很多人会反驳这一点,因为按"先匹配普通,再匹配正则"似乎解释不了平时"先匹配正则,再匹配普通"的实践经验。造成这种误解的原因是:正则匹配会覆盖普通匹配(实际规则比这更复杂,后文会详细解释)。正是这种"覆盖"现象,让人误以为正则先执行。
误区二:location 的执行逻辑与 location 的编辑顺序无关
矫正:这句话不全对。
- "普通 location"的匹配规则是"最大前缀匹配",因此普通 location 的确与编辑顺序无关;
- "正则 location"的匹配规则是"顺序匹配,且只要匹配到第一个就停止后面的匹配",因此正则 location 与编辑顺序密切相关。
普通 location 与正则 location 之间的顺序是:先匹配普通 location,再"考虑"匹配正则 location。注意这里的"考虑"是"可能"的意思——匹配完普通 location 后,有时需要继续匹配正则 location,有时则不需要。
以下两种情况不需要继续匹配正则 location:
- 当普通 location 前面指定了
^~,明确告诉 Nginx:本条普通 location 一旦匹配上,就不需要继续正则匹配; - 当普通 location 恰好严格匹配上(是精确匹配,而非最大前缀匹配),则不再继续匹配正则。
一句话总结:正则 location 匹配让步普通 location 的严格精确匹配结果;但覆盖普通 location 的最大前缀匹配结果。
二、官方 location 语法
官方文档(http://wiki.nginx.org/NginxHttpCoreModule#location)对 location 的定义如下:
syntax: location [=|~|~*|^~|@] /uri/ { ... }
default: no
context: server
原文:
This directive allows different configurations depending on the URI.
译文:该指令允许根据 URI 的不同使用不同的配置。这里有两点需要注意:
- 所谓"根据 URI 使用不同配置",指的就是语法
location [=|~|~*|^~|@] /uri/ { ... }。依据不同的前缀=、^~、~、~*以及不带任何前缀([A]表示可选),表达不同的含义。简单说,尽管/uri/配置一样,但前缀不同,指令含义就不同。 - 查询字符串不在 URI 范围内。例如
/films.htm?fid=123的 URI 是/films.htm。
三、普通 location 与正则 location
原文:
It can be configured using both literal strings and regular expressions. To use regular expressions, you must use a prefix:
- "~" for case sensitive matching
- "~*" for case insensitive matching
译文:location 可以使用字面量字符串(literal strings)和正则表达式(regular expressions)两种方式配置。使用正则表达式必须带前缀:
~:区分大小写的正则匹配;~*:不区分大小写的正则匹配。
按照前缀分类,location 只有两大类:
| 类别 | 英文说法 | 包含的前缀 |
|---|---|---|
| 普通 location | location using literal strings | =、^~、@,以及不带任何前缀的 /uri/ |
| 正则 location | location using regular expressions | ~、~* |
其中 ~ 区分大小写,~* 不区分大小写。
四、匹配顺序与决策
4.1 先匹配普通 location
原文:
To determine which location directive matches a particular query, the literal strings are checked first.
译文:对于一个特定的 HTTP 请求,Nginx 应该匹配哪个 location 块?规则是:先匹配普通 location,再匹配正则表达式。官方文档这句话明确说了"先普通 location",而不是有些同学误以为的"先匹配正则 location"。
4.2 普通 location 内部:最大前缀匹配
原文:
Literal strings match the beginning portion of the query – the most specific match will be used.
译文:普通 location 匹配的是 URI 的前缀部分(beginning portion),并采用"最具体"的匹配(the most specific match),即最大前缀匹配。
因为普通 location 是"前缀匹配"而非"严格匹配",一个 HTTP 请求可能同时前缀匹配到多个普通 location。例如:
location /prefix/mid/ { ... }
location /prefix/ { ... }
对于请求 /prefix/mid/t.html,两个 location 都满足前缀匹配,此时按 the most specific match 原则,选择更长、更具体的 location /prefix/mid/ { ... }。
4.3 正则 location 内部:按编辑顺序匹配,命中即止
原文:
Afterwards, regular expressions are checked in the order defined in the configuration file. The first regular expression to match the query will stop the search.
这段话包含两层意思:
- 普通 location 先匹配,并且选出最大前缀匹配后,并不能就此停止——最大前缀匹配只是一个临时结果,Nginx 还需要继续检查正则 location。
- 正则 location 之间的匹配规则是:按照其在配置文件中的物理顺序(编辑顺序)匹配,只要匹配到第一条,就停止后续搜索。
这说明 location 并非一定与顺序无关:普通 location 与顺序无关,但正则 location 与顺序有关。
4.4 普通结果与正则结果的决策关系
原文:
If no regular expression matches are found, the result from the literal string search is used.
这句话回答了"普通 location 的最大前缀匹配结果"与"正则 location 匹配结果"之间的决策关系:
- 如果继续搜索的正则 location 有匹配上的,则正则 location 覆盖普通 location 的最大前缀匹配结果(正是这种覆盖关系,造成了"正则 location 先于普通 location 执行"的误解);
- 如果正则 location 没有匹配上,则使用普通 location 的最大前缀匹配结果。
4.5 其他补充说明
原文:
For case insensitive operating systems, like Mac OS X or Windows with Cygwin, literal string matching is done in a case insensitive way (0.7.7). However, comparison is limited to single-byte locale's only.
Regular expression may contain captures (0.7.40), which can then be used in other directives.
译文:
- 在不区分大小写的操作系统上(如 Mac OS X 或带 Cygwin 的 Windows),字面量字符串匹配也是不区分大小写的(0.7.7 起),但比较仅限于单字节 locale。
- 正则表达式可以包含捕获组(captures,0.7.40 起),捕获的结果可在其他指令中使用。
五、匹配前缀分类详解
5.1 前缀总览表
| 前缀 | 类型 | 含义 | 是否继续匹配正则 |
|---|---|---|---|
| (无前缀) | 普通 location | 最大前缀匹配 | 是(除非恰好为精确匹配) |
= |
普通 location | 严格精确匹配(exact match) | 否,命中即停止 |
^~ |
普通 location | 最大前缀匹配,命中后不再检查正则 | 否 |
~ |
正则 location | 区分大小写的正则匹配 | 命中即停止,不再检查后续正则 |
~* |
正则 location | 不区分大小写的正则匹配 | 命中即停止,不再检查后续正则 |
@ |
命名 location | 仅用于内部重定向,不处理普通请求 | 不参与常规匹配 |
5.2 ^~ 前缀
原文:
It is possible to disable regular expression checks after literal string matching by using "^~" prefix. If the most specific match literal location has this prefix: regular expressions aren't checked.
通常的规则是:匹配完普通 location 后,还需要继续匹配正则 location。但可以在普通 location 前加上 ^~ 告诉 Nginx:匹配到本条普通 location 后,不再继续匹配正则 location。
符号含义可直观理解为:^ 表示"非",~ 表示"正则",合起来即"不要继续匹配正则"。需要注意的是,^~ 依然遵守"最大前缀"匹配规则。
5.3 = 前缀
原文:
By using the "=" prefix we define the exact match between request URI and location. When matched search stops immediately. E.g., if the request "/" occurs frequently, using "location = /" will speed up processing of this request a bit as search will stop after first comparison.
= 前缀定义请求 URI 与 location 之间的严格精确匹配(exact match),命中后搜索立即停止。
^~ 和 = 都能阻止继续搜索正则 location,它们的区别在于:
- 共同点:都能阻止继续搜索正则 location;
- 不同点:
^~依然遵守"最大前缀"匹配规则;而=不是最大前缀匹配,必须是严格精确匹配。
location / {} 与 location = / {} 的区别
location / {}遵守普通 location 的最大前缀匹配。由于任何 URI 都必然以/根开头,所以对于一个 URI,如果有更具体(specific)的匹配,自然选更具体的;如果没有,/一定能"垫背"(至少能匹配到/)。因此location / {}有点"默认配置"的味道,其他更具体的配置能覆盖(overwrite)这个默认配置——这也是为什么配置中总能看到location / {}。location = / {}遵守严格精确匹配,只能匹配http://host:port/这一个请求,同时会禁止继续搜索正则 location。如果只想对GET /请求配置指令,使用location = / {}能减少正则 location 的搜索,效率比location / {}更高(前提是目的仅仅是对GET /起作用)。
5.4 隐式的精确匹配
原文:
On exact match with literal location without "=" or "^~" prefixes search is also immediately terminated.
除了在普通 location 前显式加 ^~ 或 = 来阻止正则搜索外,还有一种"隐含"方式:当"最大前缀"匹配恰好就是一个"严格精确匹配(exact match)"时,照样会停止后面的搜索。也就是说,只要遇到精确匹配,即使普通 location 没有带 = 或 ^~ 前缀,也会终止后续匹配。
先举例说明(后面的例题会用实践验证):
location /exact/match/test.html { 配置指令块1 }
location /prefix/ { 配置指令块2 }
location ~ \.html$ { 配置指令块3 }
- 请求
GET /prefix/index.html:会匹配到指令块 3。因为普通 location/prefix/依据最大前缀原则能匹配当前请求,但会被后面的正则 location 覆盖; - 请求
GET /exact/match/test.html:会匹配到指令块 1。因为这是普通 location 的 exact match,会禁止继续搜索正则 location。
5.5 官方匹配顺序总结
原文:
To summarize, the order in which directives are checked is as follows:
- Directives with the "=" prefix that match the query exactly. If found, searching stops.
- All remaining directives with conventional strings. If this match used the "^~" prefix, searching stops.
- Regular expressions, in the order they are defined in the configuration file.
- If #3 yielded a match, that result is used. Otherwise, the match from #2 is used.
官方给出的匹配检查顺序如下:
=前缀的严格精确匹配,命中则停止搜索;- 所有剩余的普通字符串(conventional strings)匹配。如果该匹配带
^~前缀,则停止搜索; - 正则表达式,按其在配置文件中定义的顺序匹配;
- 如果第 3 步有匹配,使用第 3 步结果;否则使用第 2 步的结果。
用自己的话概括仍然是那句:正则 location 匹配让步普通 location 的严格精确匹配结果;但覆盖普通 location 的最大前缀匹配结果。
5.6 URI 是解码后比较的
原文:
It is important to know that nginx does the comparison against decoded URIs. For example, if you wish to match "/images/ /test", then you must use "/images/ /test" to determine the location.
Nginx 是对解码后的 URI 进行比较的。浏览器地址栏显示的 URL 一般会进行 URLEncode,例如"空格"会被编码为 %20,但 Nginx 的 URL 匹配针对的是 URLDecode 之后的结果。也就是说,如果要匹配 /images/ /test(其中包含一个空格),写 location 时匹配目标应写解码后的 /images/ /test。
5.7 @ 前缀:命名 location
原文:
The prefix "@" specifies a named location. Such locations are not used during normal processing of requests, they are intended only to process internally redirected requests (see error_page, try_files).
@ 前缀用来定义"Named Location"(命名 location)。可以把它理解为独立于"普通 location"和"正则 location"之外的第三种类型。这种命名 location 不用于处理普通的 HTTP 请求,而是专门用来处理"内部重定向(internally redirected)"请求。
这里的"内部重定向"说成 "forward" 可能更好理解:它不需要与浏览器交互,纯粹是服务端的一次转发行为(配合 error_page、try_files 使用)。
六、官方示例解析
6.1 四个 location 配置
官方给出的示例配置如下:
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 / { [configuration B] } 的注释需要特别澄清。原文注释:
matches any query, since all queries begin with /, but regular expressions and any longer conventional blocks will be matched first.
大意是:location / {} 能匹配所有 HTTP 请求,因为任何请求都以 / 开始(这半句没错);但是正则 location 和其他任何比 / 更长的普通 location(location / {} 是普通 location 里最短的,其他普通 location 都比它长,当然 location = / {} 和 location ^~ / {} 与它一样长)会"优先匹配(matched first)"。
原文作者说 "regular expressions will be matched first" 只是想表达"正则 location 会覆盖这里的 location / {}",但实际过程依然是普通 location / 先匹配,接着正则 location 再匹配;而其他更长的普通 location(any longer conventional blocks)的确会先于 location / {} 匹配。这个简化的措辞容易让人误以为"先检查正则 location,再检查普通 location"。
6.2 请求匹配结果
/ -> configuration A
/documents/document.html -> configuration B
/images/1.gif -> configuration C
/documents/1.jpg -> configuration D
逐一分析:
/:精确命中location = /,使用 configuration A,停止搜索;/documents/document.html:普通 location 最大前缀匹配到/(configuration B),且没有正则命中(.html结尾不匹配\.(gif|jpg|jpeg)$),使用 B;/images/1.gif:普通 location 最大前缀匹配到^~ /images/(configuration C),因带^~停止正则搜索,使用 C(虽然它也匹配\.(gif|jpg|jpeg)$,但正则不会被检查);/documents/1.jpg:普通 location 最大前缀匹配到/(configuration B),继续正则搜索,命中~* \.(gif|jpg|jpeg)$,正则覆盖普通结果,使用 D。
原文还提到:
Note that you could define these 4 configurations in any order and the results would remain the same.
需要提醒的是:这里说"可以按任意顺序定义且结果不变",是因为上面只有一个正则 location。前文已经强调,正则 location 的匹配与编辑顺序有关;如果存在多个正则 location,顺序就会影响结果。
6.3 关于 location 嵌套
原文:
While nested locations are allowed by the configuration file parser, their use is discouraged and may produce unexpected results.
Nginx 的配置文件解析程序允许 location 嵌套定义(如 location / { location /uri/ {} }),但平时很少见到这种配置,因为官方并不建议这样做——它可能导致很多意想不到的后果。
七、安装 Nginx
本节附带说明源码安装 Nginx 的过程(示例版本为 1.4.6)。
wget http://nginx.org/download/nginx-1.4.6.tar.gz
tar zxvf nginx-1.1.0.tar.gz
./configure
make
make install
在 ./configure 这一步可能遇到如下报错:
./configure: error: the HTTP rewrite module requires the PCRE library.
安装 Nginx 时,rewrite 模块默认会被安装,但 rewrite 模块所依赖的 PCRE 库需要额外安装。报错提示给出了三种解决思路:
You can either disable the module by using --without-http_rewrite_module
option, or install the PCRE library into the system, or build the PCRE library
statically from the source with nginx by using --with-pcre=<path> option.
即:使用 --without-http_rewrite_module 禁用该模块;或把 PCRE 库安装到系统中;或使用 --with-pcre=<path> 让 Nginx 从源码静态编译 PCRE。
通常的解决办法是先安装依赖:
yum -y install pcre-devel openssl openssl-devel
备注:PCRE(Perl Compatible Regular Expressions)是 Perl 兼容的正则表达式库。Nginx 的 rewrite 正则表达式采用的是 Perl 语法集(常识:正则表达式有不同的语法集,常见的
grep指令若需采用 Perl 语法集,需要用grep -P指定)。
八、location 实例演练
Nginx 的语法形式是 location [=|~|~*|^~|@] /uri/ { ... },即可以用 =、~*、~、^~、@ 作为前缀,也可以没有前缀([A] 表示可选的 A,A|B 表示 A、B 二选一),紧接着是 /uri/,再是 {...} 指令块,整体含义是:对满足条件的 /uri/ 适用指令块中的指令。
如前所述,location 分两大类:普通 location(以 =、^~ 为前缀或不带任何前缀)和正则 location(以 ~、~* 为前缀)。
当一个 server 中编写了多个 location 时,Nginx 如何为一个 HTTP 请求选定处理的 location?核心仍然是那句话:正则 location 让步普通 location 的严格精确匹配结果;但覆盖普通 location 的最大前缀匹配结果。 下面通过实例说明。
8.1 例题 1:先普通 location,再正则 location
很多人以为 Nginx 是"先匹配正则 location 再匹配普通 location",这是误区。Nginx 实际上是"先匹配普通 location,再匹配正则 location",但普通 location 的匹配结果又分两种:一种是"严格精确匹配(exact match)",另一种是"最大前缀匹配(the most specific match)"。
假设 Nginx 配置如下:
server {
listen 9090;
server_name localhost;
location / {
root html;
index index.html index.htm;
deny all;
}
location ~ \.html$ {
allow all;
}
}
附录:Nginx 的目录结构为 nginx/html/index.html。
配置含义:
location / { ... deny all; }:普通 location,以/开始的 URI 请求都拒绝访问(任何 HTTP 请求都必然以/开始,所以/能匹配所有请求);location ~ \.html$ { allow all; }:正则 location,以.html结尾的 URI 请求都允许访问。
测试结果:
[root@web108 ~]# curl http://localhost:9090/
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.1.0</center>
</body>
</html>
[root@web108 ~]# curl http://localhost:9090/index.html
<html>
<head>
<title>Welcome to nginx!</title>
</head>
<body bgcolor="white" text="black">
<center><h1>Welcome to nginx!</h1></center>
</body>
</html>
[root@web108 ~]# curl http://localhost:9090/index_notfound.html
<html>
<head><title>404 Not Found</title></head>
<body bgcolor="white">
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.1.0</center>
</body>
</html>
汇总如下:
| URI 请求 | HTTP 响应 |
|---|---|
curl http://localhost:9090/ |
403 Forbidden |
curl http://localhost:9090/index.html |
Welcome to nginx! |
curl http://localhost:9090/index_notfound.html |
404 Not Found |
推演:
curl http://localhost:9090/返回403 Forbidden,说明匹配到了location / { ... deny all; }。原因是请求GET /被普通 location/严格精确匹配,于是停止搜索正则 location;curl http://localhost:9090/index.html返回Welcome to nginx!,说明没有被deny all命中(否则会 403)。虽然/index.html也以/开头,但此时普通 location/的匹配结果是"最大前缀匹配",所以 Nginx 继续搜索正则 location;location ~ \.html$对以.html结尾的请求allow all,于是访问到实际存在的index.html页面;curl http://localhost:9090/index_notfound.html同理:先匹配location / {}(最大前缀匹配),随后被正则 location~ \.html$覆盖,最终allow all;但目录下并不存在index_notfound.html,因此返回 404 Not Found。
如果访问 http://localhost:9090/index.txt 会是什么结果?显然是 deny all:先匹配上 location / { ... deny all; }(最大前缀匹配),继续搜索正则 location,但 /index.txt 不以 .html 结尾,正则 location 匹配失败,最终采纳普通 location 的最大前缀匹配结果。
[root@web108 ~]# curl http://localhost:9090/index.txt
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.1.0</center>
</body>
</html>
8.2 例题 2:普通 location 的"隐式"严格匹配
在例题 1 的基础上增加一条精确配置:
server {
listen 9090;
server_name localhost;
location /exact/match.html {
allow all;
}
location / {
root html;
index index.html index.htm;
deny all;
}
location ~ \.html$ {
allow all;
}
}
测试请求:
[root@web108 ~]# curl http://localhost:9090/exact/match.html
<html>
<head><title>404 Not Found</title></head>
<body bgcolor="white">
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.1.0</center>
</body>
</html>
结果进一步验证了"普通 location 的严格精确匹配会终止对正则 location 的搜索"。
小结:先匹配普通 location,再匹配正则 location;但如果普通 location 的匹配结果恰好是"严格精确匹配(exact match)",Nginx 不再尝试后面的正则 location;如果普通 location 的匹配结果是"最大前缀匹配",则正则 location 的匹配覆盖普通 location 的匹配。即:正则 location 让步普通 location 的严格精确匹配结果,但覆盖普通 location 的最大前缀匹配结果。
8.3 例题 3:普通 location 的"显式"严格匹配与 ^~ 前缀
前面演示的普通 location 都不带任何前缀。实际上普通 location 也可以加 ^~ 和 = 前缀:
^~:意为"非正则,不需要继续正则匹配"。通常普通 location 还会继续搜索正则 location(恰好严格精确匹配除外),但^~告诉 Nginx:本条普通 location 无论是最大前缀匹配还是严格精确匹配,都终止继续搜索正则 location;=:表示普通 location 不接受"最大前缀"匹配结果,必须严格等于,即严格精确匹配。
例题 3:^~ 前缀的使用
server {
listen 9090;
server_name localhost;
location /exact/match.html {
allow all;
}
location ^~ / {
root html;
index index.html index.htm;
deny all;
}
location ~ \.html$ {
allow all;
}
}
把例题 2 中的 location / {} 修改为 location ^~ / {},测试结果对比如下:
| URI 请求 | 修改前 | 修改后 |
|---|---|---|
curl http://localhost:9090/ |
403 Forbidden | 403 Forbidden |
curl http://localhost:9090/index.html |
Welcome to nginx! | 403 Forbidden |
curl http://localhost:9090/index_notfound.html |
404 Not Found | 403 Forbidden |
curl http://localhost:9090/exact/match.html |
404 Not Found | 404 Not Found |
除了 GET /exact/match.html 是 404 Not Found,其余都是 403 Forbidden。原因很简单:所有请求都以 / 开头,因此都能匹配上 / 这个普通 location;但普通 location 的匹配原则是"最大前缀",所以只有 /exact/match.html 匹配到更具体的 location /exact/match.html { allow all; },其余都命中 location ^~ / { deny all; } 并终止正则搜索。
例题 4:= 前缀的使用
server {
listen 9090;
server_name localhost;
location /exact/match.html {
allow all;
}
location = / {
root html;
index index.html index.htm;
deny all;
}
location ~ \.html$ {
allow all;
}
}
例题 4 相对例题 2 把 location / {} 改成了 location = / {},测试结果对比如下:
| URI 请求 | 修改前 | 修改后 |
|---|---|---|
curl http://localhost:9090/ |
403 Forbidden | 403 Forbidden |
curl http://localhost:9090/index.html |
Welcome to nginx! | Welcome to nginx! |
curl http://localhost:9090/index_notfound.html |
404 Not Found | 404 Not Found |
curl http://localhost:9090/exact/match.html |
404 Not Found | 404 Not Found |
curl http://localhost:9090/test.jsp |
403 Forbidden | 404 Not Found |
最能说明问题的是 GET /test.jsp:/test.jsp 既没有匹配正则 location ~ \.html$,也没有匹配 location = / {}。如果是原来的 location / {},它会"最大前缀"匹配到普通 location /,结果是 deny all(403);而改成 location = / {} 后,/test.jsp 不满足精确匹配,也不命中正则,于是没有任何 location 命中对应指令,最终返回 404。
8.4 例题 5:正则 location 与编辑顺序
"location 的指令与编辑顺序无关"这句话不全对:
- 对普通 location:匹配规则是最大前缀匹配(与顺序无关),若恰好是严格精确匹配结果,或带有
^~、=前缀(=只能严格匹配),则停止搜索正则 location; - 对正则 location:匹配规则是按编辑顺序逐个匹配(与顺序有关),只要匹配上就立即停止后面的搜索。
配置 3.1 与 3.2:正则顺序影响结果
配置 3.1:
server {
listen 9090;
server_name localhost;
location ~ \.html$ {
allow all;
}
location ~ ^/prefix/.*\.html$ {
deny all;
}
}
配置 3.2:
server {
listen 9090;
server_name localhost;
location ~ ^/prefix/.*\.html$ {
deny all;
}
location ~ \.html$ {
allow all;
}
}
测试结果:
| URI 请求 | 配置 3.1 | 配置 3.2 |
|---|---|---|
curl http://localhost:9090/regextest.html |
404 Not Found | 404 Not Found |
curl http://localhost:9090/prefix/regextest.html |
404 Not Found | 403 Forbidden |
解释:
location ~ ^/prefix/.*\.html$ { deny all; }:对以/prefix/开头、.html结尾的所有 URI 请求拒绝访问;location ~ \.html$ { allow all; }:对以.html结尾的 URI 请求允许访问。
实际上,^/prefix/.*\.html$ 匹配的集合是 \.html$ 的子集。
- 在配置 3.1 下:两个请求都先匹配上
location ~ \.html$ { allow all; }并停止后续搜索,于是都允许访问(页面不存在故 404 Not Found); - 在配置 3.2 下:
/regextest.html不匹配 prefix,继续搜索~ \.html$,允许访问(404 Not Found);而/prefix/regextest.html先匹配到 prefix,deny all(403 Forbidden)。
配置 3.3 与 3.4:普通 location 与顺序无关
配置 3.3:
server {
listen 9090;
server_name localhost;
location /prefix/ {
deny all;
}
location /prefix/mid/ {
allow all;
}
}
配置 3.4:
server {
listen 9090;
server_name localhost;
location /prefix/mid/ {
allow all;
}
location /prefix/ {
deny all;
}
}
测试结果:
| URI 请求 | 配置 3.3 | 配置 3.4 |
|---|---|---|
curl http://localhost:9090/prefix/t.html |
403 Forbidden | 403 Forbidden |
curl http://localhost:9090/prefix/mid/t.html |
404 Not Found | 404 Not Found |
注:原文表头写作"配置 3.1 / 配置 3.2",但对应的数据实为配置 3.3 与 3.4 的结果,此处已按上下文订正。
测试结果表明:普通 location 的匹配规则是"最大前缀"匹配,而且与编辑顺序无关。两个配置只是交换了两条普通 location 的先后顺序,结果完全一致——/prefix/t.html 最大前缀匹配到 /prefix/(deny all,403),/prefix/mid/t.html 最大前缀匹配到更长的 /prefix/mid/(allow all,页面不存在故 404)。
8.5 例题 6:@ 前缀 Named Location 的使用
参考官方文档 http://wiki.nginx.org/HttpCoreModule#error_page。假设配置如下:
server {
listen 9090;
server_name localhost;
location / {
root html;
index index.html index.htm;
allow all;
}
# error_page 404 http://www.baidu.com; # 直接这样写是不允许的
error_page 404 = @fallback;
location @fallback {
proxy_pass http://www.baidu.com;
}
}
配置含义:如果请求的 URI 存在,本 Nginx 返回对应页面;如果不存在,则把请求代理到 baidu.com 上去做弥补(Nginx 发现 URI 对应页面不存在时,HTTP 状态码为 404,此时 error_page 404 指令能捕获它,并通过 @fallback 命名 location 完成内部转发)。
测试一:访问一个两边都不存在的页面
[root@web108 ~]# curl http://localhost:9090/nofound.html -i
HTTP/1.1 302 Found
Server: nginx/1.1.0
Date: Sat, 06 Aug 2011 08:17:21 GMT
Content-Type: text/html; charset=iso-8859-1
Location: http://localhost:9090/search/error.html
Connection: keep-alive
Cache-Control: max-age=86400
Expires: Sun, 07 Aug 2011 08:17:21 GMT
Content-Length: 222
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved <a href="http://www.baidu.com/search/error.html">here</a>.</p>
</body></html>
当 GET /nofound.html 发送给本 Nginx,Nginx 找不到对应页面,于是 error_page 404 = @fallback 生效,请求被代理到 http://www.baidu.com。Nginx 向百度发送了 GET /nofound.html,但该页面在百度也不存在,百度返回 302 跳转到错误页。
直接访问 http://www.baidu.com/nofound.html 的结果:
[root@web108 ~]# curl http://www.baidu.com/nofound.html -i
HTTP/1.1 302 Found
Date: Sat, 06 Aug 2011 08:20:05 GMT
Server: Apache
Location: http://www.baidu.com/search/error.html
Cache-Control: max-age=86400
Expires: Sun, 07 Aug 2011 08:20:05 GMT
Content-Length: 222
Connection: Keep-Alive
Content-Type: text/html; charset=iso-8859-1
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved <a href="http://www.baidu.com/search/error.html">here</a>.</p>
</body></html>
测试二:访问 Nginx 不存在但百度存在的页面
[root@web108 ~]# curl http://www.baidu.com/duty/ -i
HTTP/1.1 200 OK
Date: Sat, 06 Aug 2011 08:21:56 GMT
Server: Apache
P3P: CP=" OTI DSP COR IVA OUR IND COM "
Set-Cookie: BAIDUID=5C5D2B2FD083737A0C88CA7075A6601A:FG=1; expires=Sun, 05-Aug-12 08:21:56 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1
Last-Modified: Wed, 05 Jan 2011 06:44:53 GMT
ETag: "d66-49913b8efe340"
Accept-Ranges: bytes
Content-Length: 3430
Cache-Control: max-age=86400
Expires: Sun, 07 Aug 2011 08:21:56 GMT
Vary: Accept-Encoding,User-Agent
Connection: Keep-Alive
Content-Type: text/html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
...
</body>
</html>
可见百度这个页面确实存在。再通过本地 Nginx 访问:
[root@web108 ~]# curl http://localhost:9090/duty/ -i
HTTP/1.1 200 OK
Server: nginx/1.1.0
Date: Sat, 06 Aug 2011 08:23:23 GMT
Content-Type: text/html
Connection: keep-alive
P3P: CP=" OTI DSP COR IVA OUR IND COM "
Set-Cookie: BAIDUID=8FEF0A3A2C31D277DCB4CC5F80B7F457:FG=1; expires=Sun, 05-Aug-12 08:23:23 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1
Last-Modified: Wed, 05 Jan 2011 06:44:53 GMT
ETag: "d66-49913b8efe340"
Accept-Ranges: bytes
Content-Length: 3430
Cache-Control: max-age=86400
Expires: Sun, 07 Aug 2011 08:23:23 GMT
Vary: Accept-Encoding,User-Agent
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
...
</body>
</html>
当 curl http://localhost:9090/duty/ -i 时,Nginx 没找到对应页面,于是 error_page = @fallback 把请求代理到 baidu.com。需要特别注意:这里的 error_page = @fallback 不是靠重定向实现的,而是前文所说的 "internally redirected(forward)"——服务端内部转发,浏览器地址栏不会变化。
九、总结
-
匹配总顺序:先匹配普通 location,再匹配正则 location。正则 location 并非"先执行",它之所以看起来优先,是因为它会覆盖普通 location 的最大前缀匹配结果。
-
普通 location 内部:采用"最大前缀匹配"(the most specific match),与 location 在配置文件中的编辑顺序无关。
-
正则 location 内部:严格按照在配置文件中的物理(编辑)顺序逐个匹配,命中第一条即停止,与编辑顺序密切相关。因此,更具体、范围更小的正则应放在前面。
-
终止正则搜索的三种情形:
- 普通 location 带
=前缀,发生严格精确匹配; - 普通 location 带
^~前缀,命中最大前缀匹配; - 普通 location 不带
=或^~,但其最大前缀匹配恰好就是严格精确匹配(隐式精确匹配)。
- 普通 location 带
-
最终决策:若正则 location 有命中,使用正则结果(覆盖普通 location 的最大前缀匹配);若正则 location 无命中,回退使用普通 location 的最大前缀匹配结果。
-
@命名 location:不参与普通请求匹配,仅用于error_page、try_files等触发的内部重定向(forward)。 -
一句话记忆:正则 location 让步普通 location 的严格精确匹配结果;但覆盖普通 location 的最大前缀匹配结果。
此处评论已关闭