Location 是整个 HTTP 模块中非常重要的一个子模块,它是为某个请求URI(路径)建立配置。这个模块又是属于 Server 模块的子模块,同时它还可以嵌套在另一个 Location 模块下面,因此,它的作用范围是 server 和 location 。其实,说白了,也就是我们可以为指定的一些路径去做一些额外的配置。
location [ = | ~ | ~* | ^~ ] uri { ... }
看着就复杂吧?最主要的就是 [] 中的选项,因为它可以有多种匹配模式。不过我们先讲一下不配置 Location 是什么情况。
如果我们不配置 Location ,那么根据请求中的 URL 的 Path 部分,比如:/,它就会找到 root 指定的目录下的 index 配置指定的文件,比如 index.html ,如果找不到文件,就返回 404 。
假如我们在浏览器客户端指定了访问路径,比如 /aaa/aaa.html ,那么就是找 root 指定的目录下的 aaa 目录下的 aaa.html 文件。比如在我这里就是 /usr/local/nginx/html/aaa/ 这个目录下的 aaa.html 文件。
这就是默认的,最正常的一个 Nginx 中 Server 的访问形式。
而 Location 的作用,就是能够让我们访问 /aaa/aaa.html 时,可以让他不直接打开 aaa.html ,或者在打开 aaa.html 的时候再做一些别的操作。比如我们先简单配一个,就用这个 /aaa/aaa.html 。
默认不配的情况下,我们 root 指定的目录 /aaa 并不存在,更没有下面的 aaa.html 文件,所以它会直接返回 404 。现在我们就简单配置一个。
location /aaa/aaa.html {
try_files '' /index.html;
}
现在你再重载一下配置文件,然后访问试试,看看是不是访问这个路径,它把首页打开了。可以说,这玩意就是整个 Nginx 的灵魂,或者说,所有的服务器应用中,类似的操作 URI 及访问路径的功能,都是灵魂,是服务器类型应用中最重要的部分之一。
路径匹配会在 URI 规范化以后进行。所谓规范化,就是先将 URI 中形如 “%XX” 的编码字符进行解码, 再解析 URI 中的相对路径 “.” 和 “..” 部分, 另外还可能会压缩相邻的两个或多个斜线成为一个斜线。
可以使用前缀字符串或者正则表达式定义路径。使用正则表达式需要在路径开始添加 “~*” 前缀 (不区分大小写),或者 “~” 前缀(区分大小写)。为了根据请求 URI 查找路径,Nginx 先检查前缀字符串定义的路径 (前缀路径),在这些路径中找到能最精确匹配请求 URI 的路径。然后 Nginx 按在配置文件中的出现顺序检查正则表达式路径, 匹配上某个路径后即停止匹配并使用该路径的配置,否则使用最大前缀匹配的路径的配置。
路径可以嵌套,但有例外,后面将提到。
在不区分大小写的操作系统(诸如Mac OS X和Cygwin)上,前缀匹配忽略大小写 (0.7.7) 。但是,比较仅限于单字节的编码区域(one-byte locale)。
正则表达式中可以包含匹配组 (0.7.40) ,结果可以被后面的其他指令使用。
如果最大前缀匹配的路径以 “^~” 开始,那么 Nginx 不再检查正则表达式。
而且,使用“=”前缀可以定义URI和路径的精确匹配。如果发现匹配,则终止路径查找。比如,如果请求 “/” 出现频繁,定义“location = /”可以提高这些请求的处理速度, 因为查找过程在第一次比较以后即结束。这样的路径明显不可能包含嵌套路径。
在 0.7.1 到 0.8.41 的所有nginx版本中,如果请求匹配的前缀字符串路径并没有 “=” 或 “^~” 前缀, 路径查找过程仍然会停止,而不进行正则表达式匹配。
没错,上面全是官方文档中的说明。能坚持看完吧?看不完咱们就来一个一个的试。
先试试最简单的,也是我们最常见的,那就是啥都不加的,以及加上一个 = 号进行精确匹配的。
location / {
return 200 201;
}
location = / {
return 200 202;
}
location /zyblog {
return 200 203;
}
location = /zyblog {
return 200 204;
}
location /zyblog/ {
return 200 205;
}
不同的 URI 给了不同的状态码返回,便于我们测试。
直接访问 http://192.168.56.88 或者 http://192.168.56.88/ ,都会进入 202 ,这里是 = 号在起作用,定位到精确匹配。访问 http://192.168.56.88/index.html ,或者 http://192.168.56.88/xxx (xxx 表示任意其它字符)都会进入 201 。其实 = 号的意思就是,只要访问的 URI 和我这里是完全对应的,就不进行其它匹配了,直接走当前这个 location 下面的内容。
普通匹配遵循的是前缀匹配法,这里会比较复杂,我们列表看一下。
如果注释掉 /zyblog/ 的配置,我们再测试访问 /zyblog、/zyblog/ ,这时你会发现,结尾带 / 的,走的是 203 ,而不带 / 的走的是 204 。现在将 = 号对应的 /zyblog 修改为 /zyblog/ ,那么上面的测试结果又会反过来。这里在平常进行配置的时候一定要注意,非常容易绕晕。
总结一下,普通匹配中,如果结尾没有 / ,是类似于正则中 /zyblog* 的意思,如果有 / 则是 /zyblog/* 的意思。而 = 号是完全匹配,和等号后面完全一样的才可以。
正则匹配就是可以使用正则表达式来进行复杂的 URI 定义的匹配。
location ~* \.(mp4|avi|gif)$ {
return 200 205;
}
location ~ \.(jpg|JPEG|gif)$ {
return 200 206;
}
上面的两个例子中,~* 表示忽略大小写,~ 表示区分大小写。我们可以直接这样来测试,http://192.168.56.88/1.mp4 和 http://192.168.56.88/1.MP4 都是走的 205 ,http://192.168.56.88/1.JPEG 走的是 206 ,但 http://192.168.56.88/1.jpeg 则会走 201 ,因为大小写没匹配到 206。
最后那个 $ 符号表示要以指定正则的内容结尾,比如我们现在尝试 http://192.168.56.88/1.mp4/123123,返回的是201,去掉符号之后,再次请求,返回的就是 205 了。除此之外,还有一个 ^ ,表示要以正则里的内容开头,其实这两个符号和普通的正则规则都是一样的。
location ~ ^/a(.*)\.(flv|mp5)$ {
return 200 207$1$2;
}
像上面这个配置,就只有 a 开头,以 flv 或 mp5 结尾的可以匹配到,其它 URI 都不会匹配到这个 location 。注意,~ 和 ^ , 和 { 之间都要有空格的哦。另外这个 207 的返回值我们还加上了一个 1
最后,还有一点,正则是按先后顺序匹配的,在上面的两个正则条件中,都有 gif 这个条件,当我们访问 http://192.168.56.88/1.gif 时,会走哪个呢?很明显,它会走第一个 205 ,正则和正则之间的优先级按配置文件中的顺序确定。
非正则匹配使用的是 ^~ ,啥意思呢?
location ^~ /r {
return 200 208;
}
location ~ \.(txt) {
return 200 209;
}
location /r/3.txt {
return 200 209;
}
location = /r/2.txt {
return 200 210;
}
其实呀,就是只要是访问 /r 目录下的内容,都会返回 208 ,不会走后面的其它正则匹配了(可以尝试去掉 ^~ 会发现会走后面.txt的正则匹配)。现在你可以访问 http://192.168.56.88/r/ 、http://192.168.56.88/r、http://192.168.56.88/r/1.jpg ,返回的都是 208 。即使 1.jpg 其实是匹配到了正则中的那个 jpg 相关的配置,但还是会走 ^~ 的配置。除非,在它底下再使用 普通规则 或者 精确规则 。http://192.168.56.88/r/3.txt 会返回 209 ,http://192.168.56.88/r/2.txt 会返回 210 。
从上面的学习中,我们其实可以看出,几种不同的匹配规则的优先级顺序。
location /
下面进行最终的处理除了优先级之外,还有个匹配顺序的问题,比如说面试的时候给出上面的一堆配置,然后问这些问题:
晕头转向吧?那就对了,这东西就是容易让人晕头转向,重点还是把握住上面的优先级规则,另外,在现实的生产环境中,尽量别配太复杂的正则,懂得都懂。在进行配置的时候,本地或者测试机多测试一下才是王道。
location 是可以嵌套在另一个 location 中的。
location /zy {
location /zy/bar {
location /zy/bar/baz {
return 200 211;
}
return 200 212;
}
return 200 213;
}
有点乱吧,不过应该也比较清晰,/zy/xxx、/zy/bar/xxx 会返回 213 和 212 ,而 /zy/bar/baz 及这个 URI 下面的所有内容会返回 211 ,/zy/bar 会返回 212 。它们三个层级组成了三层嵌套的关系。
这个 @ 符号,表示一个命名路径,有这个符号的 location 不参与路径解析。
location @cc {
return 200 214;
}
location /test/at {
try_files $uri @cc;
}
比如上面的配置,我们直接访问 /cc 是没有效果的,而访问 /test/at 才会返回 214 的内容,也就是说,@ 符号定义的 location 是需要配合 try_files 这类的指令进行操作的。可以当做内部使用的一些预备命名路径。
为请求设置根目录。
root path;
默认就是当前运行环境下的 html 目录,比如我的 Nginx 的安装目录是 /usr/local/nginx ,默认情况下就是这个目录的相对路径 ,它的默认值就是 /usr/local/nginx/html ,也可以配置成绝对路径。大家在正式开发以及线上环境使用时,通常都会设置一个项目的基础路径,往往就需要通过 root 来指定。而且这个配置指令其实更多的会配置到 Server 下面,只不过我们将它放到了 Location 一起讲解,另外它在 http 下也可以进行全局的配置。顺序是 location 没配就找 server 的,server 也没配就找 http 的,http 也没配呢?就是上面说的默认值嘛。要是这个文件夹还没有呢?404 或者 403 或者什么错误呗,反正我没试过,大家自己试试吧。
location /i/ {
root /data/w;
}
访问 /i/top.gif 的话,将使用的是 /data/w/i/top.gif 文件。文件路径的构造仅仅是将 URI 拼在 root 指令的值后面。
location /root_test1/ {
root /home/www/html1;
}
location /root_test2/ {
root /home/www/html1/;
}
对于这两个配置,我们设置的目录都是 /home/www/html1 这个目录,但稍有不同,/root_test2 的配置中,root 的最后多了一个斜杠。然后在实际的目录中,/home/www/html1/root_test1 目录下有一个 index.html 文件,/home/www/html1/root_test2 目录下有一个 1.html 文件。以下是访问的情况说明,顺带演示结尾 / 的问题:
directory index of "/home/www/html1/root_test2/" is forbidden
将 location 中的末尾斜杠去掉。
location /root_test1 {
root /home/www/html1;
}
location /root_test2 {
root /home/www/html1/;
}
root 目录末尾的斜杠和上面测试的报错没有关系,将 root_test1 中的 index.html 改名或者删除,使用 /root_test1/ 访问,一样会报 403 错误。也就是说,目录末尾那个斜杠基本没啥影响,上面两种写法基本是一样的。但是在没有默认 index 的情况下,访问路径带不带末尾的斜杠则会产生不同的效果,不带的会正常返回 404 ,而带的则会报 403 错误。这一点大家在配置的时候是需要注意的。403 错误的问题我们在文章最后会说到。
配置中 path 参数的值中可以包含除 document_root 和realpath_root 以外的变量。如果需要修改URI,应该使用alias指令。alias 是让当前这个 location 完全走 alias 中配置的内容,不会再将 location 的 URI 中 path 部分拼接到 alias 后面,只拼接实际的文件,比如之前我们在 location 中学习过的:
location /i/ {
alias /home/www/html1/;
}
那么访问 /i/1.gif ,访问的目录路径就是 /home/www/html1/1.gif 。一定要区分清楚 root 和 alias 的区别。
这个别名替换的意思其实是替换的 root 这个配置指令。先看下这它的配置指令。
alias path;
它主要用于定义指定路径的替换路径。啥意思?如果我们在当前这个 location 中,没有指定 root ,那么 root 会向上寻找,比如我们在 server 定义的 root 。
// nginx.conf
……
server {
root html;
………………
location /i {
alias /home/www/html1/;
}
……
}
……
如果我们不使用 alias ,那么 /i 将会访问 /usr/local/nginx/html/i 这个目录,但是现在,它将替换成 /home/www/html1这个目录。在 /home/www/html1 目录下新建一个 index.html ,里面就直接写一行字,然后访问一下 http://192.168.56.88/i 看看是不是你写的那行字。
注意 alias 结尾的斜杠,alias 是将请求中除 /i 路径之外的路径直接拼接到后面。因此,如果没有结尾的斜杠的话,访问 /i/1.gif ,将会查找的是 /home/www/html11.gif 这样一个路径。同理,如果我们只是访问路径,也需要注意最后的斜杠问题。这一点大家可以自己试一下哦,location 上的斜杠和 alias 的斜杠也会有相互影响。
对于图片、视频或者子目录来说,都是一样的效果。另外,它还可以针对正则进行操作实现类似于文件名重写的功能。
location ~* /i/(.+)\.htm {
alias /home/www/html1/new_$1.html;
}
看出来是什么意思了嘛,Location 的正则规则是 /i/xxxx.htm 都可以匹配。然后,我们需要拿到 xxxx 的内容,这里就使用了一个括号。一般的正则表达式,括号都可以通过类似于 0-N 这样的方式拿到,一般 0 是整个正则的匹配内容,而 1 就是第一个括号里面的匹配内容。因此,我们在现在就通过 1 获取到括号中的内容。
接下来我在 /home/www/html1 目录下建立两个文件,分别是 new_a.html 和 new_b.html 文件。内容随便自己写,能区分开就好了。
最后访问 http://192.168.56.88/i/a.htm 或 http://192.168.56.88/i/b.htm ,结果会显示 new_a.html 和 new_b.html 的内容。
是不是很好玩!!这里需要注意的是,如果配置成目录并且访问目录的话,那么只会找别名目录下的 index.html 文件。
最后,如果路径需要对应指令 path 值的最后一部分,比如说
location /images/ {
alias /data/w3/images/;
}
那么其实不如直接就使用 root 还好些。
location /images/ {
root /data/w3;
}
一定要注意它和root之间的区别,一开始我也是懵圈的,而且很容易搞混。
内部访问的意思就是只能通过 Nginx 内部访问,无法从外部直接访问的 URI 。它的配置非常简单,在 location 中添加一个配置指令即可。
internal
就这么简单的一个指令,可以指定一个路径是否只能用于内部访问。如果是外部访问,客户端将收到 404 (Not Found) 错误。下面的这些请求被看作是内部请求:
明白啥意思了没?其实就是我们直接使用浏览器访问的话,这个有 internal 配置的 location 就会返回 404 。来试下吧。
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
上面的配置是默认情况下 nginx.conf 中对于 500 系列相关错误的处理,直接跳转到 Nginx 运行目录下的 html 文件夹中的 50x.html 。现在直接访问 http://192.168.56.88/50x.html ,是可以直接看到那个 50x.html 静态页面的。而如果加上 internal ,就像下面这样。
location = /50x.html {
internal;
root html;
}
现在直接访问 http://192.168.56.88/50x.html ,会返回 404 ,我们再添加一个 location 。
location = /to500 {
rewrite .* /50x.html last;
}
这个 location 的意思是访问 /to500 这个 URI 后,直接通过重写指令转换给另一个 location 处理。然后进行测试,会发现我们可以正常看到 50x.html 页面的内容了。
Nginx 限制每个请求只能最多进行 10 次内部重定向,以防配置错误引起请求处理出现问题。如果内部重定向次数已达到 10 次,Nginx 将返回 500 (Internal Server Error) 错误。同时,错误日志中将有 “rewrite or internal redirection cycle” 的信息。这一点也比较好测,不停地向自己跳转就好了。
location = /to500 {
rewrite .* /to500 last;
}
还是使用 /to500 这个路径,然后访问一下,就会发现出现了 500 错误。接着我们看下 error.log 里面的内容。
2022/08/02 22:02:39 [error] 1470#0: *12 rewrite or internal redirection cycle while processing "/to500", client: 192.168.56.1, server: core.nginx.test, request: "GET /to500 HTTP/1.1", host: "192.168.56.88"
很明确的报错信息吧,以后如果看到 “rewrite or internal redirection cycle” 的报错信息了,赶紧检查一下是不是有循环重定向的问题吧。
如果我们 location 指定的目录在 root 路径下不存在会是什么情况?
location /nodic/ {
}
不管怎么访问,都会是 404 的错误页面。那么如果只有一个目录,里面是空的呢?也就是说,没有默认的 index.html 文件存在。
location /noindex/ {
}
// /usr/local/nginx/html
mkdir noindex
直接访问 /noindex/ ,返回的是 403 ,直接访问 /noindex ,会 301 ,访问 /noindex/xxx.html 返回的是 404 。Nginx 将目录访问会定位到 index 指定的文件,默认就是 index.html ,如果找不到这个文件,就统一报 403 。注意,它和权限没关系,即使你把 noindex 的目录权限改为 www 也是没用的,还是报 403 错误。
好了,HTTP 模块中,最最核心的两个部分:Server 和 Location 子模块都学习完了,剩下的,就是一大堆大大小小的配置项,根据功能的不同,也进行了一些拆分组合,尽量将相同类似的功能配置放在了一起。后续的内容还非常多,咱们只是刚刚起了个小步而已,大家要跟上哦。
参考文档:
http://nginx.org/en/docs/http/ngx_http_core_module.html#location