标题:【Nginx06】Nginx学习:HTTP核心模块(三)Location
Nginx学习:HTTP核心模块(三)Location
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 进入到 204 中,访问 /zyblog/ 进入 205
如果我们访问 /zyblogaaa 就会进入到 203 的配置中
访问 /zyblog/1.html 进入的是 205
访问 /zyblogaaa/1.html 进入的是203
如果访问一个没有配置的 /new 这样的路径,则全部是进入到 / 这个 201 的配置中
如果注释掉 /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$2,其实取得就是正则中每一个括号的里面的值。
最后,还有一点,正则是按先后顺序匹配的,在上面的两个正则条件中,都有 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/2.txt 会返回 210 。
匹配优先级
从上面的学习中,我们其实可以看出,几种不同的匹配规则的优先级顺序。
= 号精确匹配是绝对的 No.1 ,但是灵活性也最低
^~ 非正则最大前缀是其次,前两个匹配规则会减少匹配次数,不会继续向下匹配
正则优先级老三,它的灵活性是最高的,同样的正则匹配,还要看正则匹配的前后顺序(比如上面测试的 gif 那个)
不带任何符号的普通规则优先级最低
没有任何匹配成功的,都会到
location /
下面进行最终的处理
除了优先级之外,还有个匹配顺序的问题,比如说面试的时候给出上面的一堆配置,然后问这些问题:
访问 /zyblog/aa/1.jpg ,返回的会是哪个呢?是 206
而如果是 /zyblog/aa/1.txt 呢?返回的是 209
直接 /zyblog/1.txt 呢?同样还是 209
/zyblog/aa 呢?203
/zyblog/r/ 还是 203
/r/zyblog/ 会是多少呢?208
晕头转向吧?那就对了,这东西就是容易让人晕头转向,重点还是把握住上面的优先级规则,另外,在现实的生产环境中,尽量别配太复杂的正则,懂得都懂。在进行配置的时候,本地或者测试机多测试一下才是王道。
嵌套
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 这类的指令进行操作的。可以当做内部使用的一些预备命名路径。
结尾 / 问题
location 中的字符有没有 / 都没有影响。也就是说 /user/ 和 /user 是一样的(精确匹配和普通匹配同时存在时会有不一样的情况)。注意,这里有问题,官网这个地方说明的应该是访问请求时,客户端发来的请求有无 / 的问题。而 location 后面定义的 uri 是否有结尾的 / 却是很多人没有注意到的,这个后面我们也会演示到。
如果 URI 结构是 https://www.zyblog.com.cn/ 的形式,尾部有没有 / 都不会造成重定向。因为浏览器在发起请求的时候,默认加上了 / 。虽然很多浏览器在地址栏里也不会显示 / 。这一点,可以访问baidu验证一下。
如果 URI 的结构是 https://www.zyblog.com.cn/some-dir/ 。尾部如果缺少 / 将导致重定向。因为根据约定,URL 尾部的 / 表示目录,没有 / 表示文件。所以访问 /some-dir/ 时,服务器会自动去该目录下找对应的默认文件。如果访问 /some-dir 的话,服务器会先去找 some-dir 文件,找不到的话会将 some-dir 当成目录,301重定向到 /some-dir/ ,去该目录下找默认文件。可以去测试一下你的网站是不是这样的。
root根目录
为请求设置根目录。
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 文件。以下是访问的情况说明,顺带演示结尾 / 的问题:
访问 /root_test1/ 和 /root_test2/ ,root_test1 正常返回 index.html ,root_test2 返回 403 ,错误日志报错
directory index of "/home/www/html1/root_test2/" is forbidden
访问 /root_test1 和 /root_test2 都返回404
指定文件访问均正常,比如 /root_test1/index.html 和 /root_test2/1.html
将 location 中的末尾斜杠去掉。
location /root_test1 {
root /home/www/html1;
}
location /root_test2 {
root /home/www/html1/;
}
访问 /root_test1/ 和 /root_test2/ ,效果和上面的一样
访问 /root_test1 和 /root_test2 ,产生302,就是上面末尾斜杠问题第三个解释
综上所测,location 路径的末尾斜杠对访问结果有影响的,有斜杠会认定为是目录,客户端不带斜杠访问会404,而没有斜杠的配置在客户端访问时会进行301跳转到目录再进行访问
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 的区别。
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之间的区别,一开始我也是懵圈的,而且很容易搞混。
内部访问 Location
内部访问的意思就是只能通过 Nginx 内部访问,无法从外部直接访问的 URI 。它的配置非常简单,在 location 中添加一个配置指令即可。
internal
就这么简单的一个指令,可以指定一个路径是否只能用于内部访问。如果是外部访问,客户端将收到 404 (Not Found) 错误。 下面的这些请求被看作是内部请求:
由 error_page 指令、index 指令、 random_index 指令和 try_files 指令引起的重定向请求
由后端服务器返回的 “X-Accel-Redirect” 响应头引起的重定向请求
由 ngx_http_ssi_module 模块和 ngx_http_addition_module 模块的 “include virtual” 指令产生的子请求
用 rewrite 指令对请求进行修改
明白啥意思了没?其实就是我们直接使用浏览器访问的话,这个有 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” 的报错信息了,赶紧检查一下是不是有循环重定向的问题吧。
无目录或者无index.html
如果我们 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