背景
Location和Rewrite规则的写法在日常的Nginx配置中很常见,有必要对其有一个了解。在学习这些知识之前,我们首先需要对正则表达式有一个简单的了解,可以参考之前的博文,这里就不再详细赘述了。
location
location指令用于匹配client请求uri的path部分,然后对不同的请求提供不同的静态内容,或者通过反向代理重定向到内部的server。
对于从client过来的request, nginx会进行预处理,nginx首先对采用 ’%XX’(uri采用%+十六进制格式用于在浏览器和插件中显示非标准的字母和字符) 格式文本编码的uri进行解码。然后处理path中的相对路径符号’.’和‘..’,然后对于含有相邻的两个及以上的’/’压缩成一个’/’。 这样处理完后会得到一个干净的path,然后会用这个path会在server的location指令的参数进行匹配。
关于它的配置详情,我们可以参考对应的Nginx官方文档,这里简单叙述。
location的正则匹配主要包括如下几种:
1 2 3 4 5 |
= 开头表示精确匹配 ^~ 开头表示uri以某个常规字符串开头,不是正则匹配。如果该选项匹配,只匹配该选项,不匹配别的选项,一般用来匹配目录 ~ 开头表示区分大小写的正则匹配; ~* 开头表示不区分大小写的正则匹配 / 通用匹配, 如果没有其它匹配,任何请求都会匹配到 |
Nginx是按照如下顺序进行规则匹配的:
- 精确匹配 = 对应的字符串,如果匹配到,则停止匹配。
- 匹配 ^~ 开头的字符串前缀,如果匹配到,则停止匹配。
- 按照最长原则匹配剩下所有的常规字符串,如果有完全匹配,则直接使用终止。否则,将匹配记下来,继续下一步。
- 匹配所有正则表达,按照它们在配置文件中定义的顺序。 如果匹配到了,那就使用它,否则就使用第三步记下的匹配规则。
这个匹配过程翻译成优先级后则顺序如下(自上而下,优先级从高到低):
1 2 3 4 5 6 |
(location =) (location ^~ 路径) (location 常规字符串,但是完全匹配) (location ~,~* 正则顺序) (location 部分常规字符串前缀起始路径) (location /) |
我们举个实际的例子来验证研究一下:
首先,我在我的MAC笔记本上安装Nginx,并配置如下配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
localhost:/usr/local/var/www root$ cat /usr/local/etc/nginx/servers/test.conf server { listen 80; server_name test.location.com; root /usr/local/var/www; location = / { # matching config A # 精确匹配 / ,访问的主机名或者域名后面不能带任何字符串 } location / { root /usr/local/var/www/all; # matching config B # 通用匹配,所有的地址都以 / 开头,所以这条规则将匹配到所有请求 } location /forever/ { # matching config C # 常规字符串开头匹配 } location /forever/lakers/ { # matching config D # 常规字符串开头匹配 } location ^~ /kobe/ { # matching config E # 常规字符串开头匹配,但是以 ^~ 开头 } location /kobe/abc/ { # matching config F # 常规字符串开头匹配 } location /wang { # matching config G # 常规字符串开头匹配 } location ~ /wang/xinyu { # matching config H # 正则匹配字符串 } location ~* \.(gif|jpg|jpeg)$ { root /usr/local/var/www/tupian; # matching config I # 不区分大小写的正则匹配 } } |
如上,然后我建立每个location配置对应的root目录结构,方便验证测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
localhost:/usr/local/var/www root$ tree . ├── 50x.html ├── all │ └── hello │ └── index.html ├── forever │ ├── index.html │ └── lakers │ └── index.html ├── index.html ├── kobe │ ├── 1.gif │ ├── index.html │ └── test │ └── abc │ └── index.html └── tupian └── forever └── 1.jpg 9 directories, 9 files |
每个index.html里面的内容,主要是输出标识他所属的目录,比如:
1 2 3 4 5 6 7 8 9 10 11 |
/usr/local/var/www wangxinyu$ pwd /usr/local/var/www localhost$ cat kobe/index.html <html> <head> <title>This is test</title> </head> <body> <h1>This is kobe location!</h1> </body> </html> |
接下来启动Nginx并且绑定host就可以验证了,比如:
这样就可以在浏览器输入网址来测试location匹配了。 经过测试,我们得出如下结论:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/ -> config A # 精确匹配,优先级最高 /hello -> config B # 匹配不到其他的规则,就只能匹配通用规则了 /kobe/123.gif -> config E # 因为 ^~ 的优先级高于 ~* ,所以没有到 config F /kobe/abc/def -> config E # 同理,因为 ^~ 的存在,所以没有匹配 config F /forever/forever.html -> config C # 只有C匹配到 /forever/1.jpg -> config I # 正则表达式的优先级比常规字符串的优先级高(先匹配到C,但是继续匹配的时候匹配到了正则I,所以走I了) /wang/xinyu.gif -> config H # 首先匹配到G,然后继续匹配正则,匹配到了H和I,但是他是按照在配置文件中定义的顺序去匹配的,所以走了H |
最后,我们附上一个日常的基本Nginx的location写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#第一部分,精确匹配网站根,通过域名访问网站首页比较频繁,使用这个会加速处理。 #这里是直接转发给后端应用服务器了,也可以是一个静态首页 location = / { proxy_pass http://helloword.com/ } # 第二部分,处理静态文件请求,两种匹配模式搭配使用,目录匹配和后缀匹配 location ^~ /static/ { root /var/www/html/static/; } location ~* \.(gif|jpg|jpeg|png|css|js|ico)$ { root /var/www/html/res/; } #第三部分,通用规则。 location / { proxy_pass http://helloword:8080/ } |
Rewrite
Nginx的Rewrite规则主要实现url重写以及重定向,在这个过程中可以使用nginx提供的全局变量或自己设置的变量。Rewrite只能对域名后边的除去传递的参数外的字符串起作用。
比如下面这个URL:
1 |
http://www.foreverlakers.com/wp-admin/post.php?post=1085&action=edit |
Rewrite只对 wp-admin/post.php 重写。 如果你想对域名或参数字符串起作用,可以使用全局变量匹配,也可以使用proxy_pass反向代理。当然如果URI中含有参数(post=1085&action=edit),那么默认情况下参数会被自动附加到替换厚的串上。如果你不想让那些参数跟在你改写后的uri后面,你可以通过在替换串的末尾加上?标记来解决这一问题。比如:
rewrite ^/users/(.*)$ /show?user=$1? last;
这里我们要区分Location和Rewrite的区别,虽然他俩貌似都能实现跳转功能,但是本质上是不同的。 location是对路径做控制访问或反向代理,也可以proxy_pass到其他机器。rewrite是更改某一域名获取资源的路径。很多情况下rewrite也会写在location里,它们的执行顺序是(其实很简单,从外到里):
- 执行server块的rewrite指令
- 执行location匹配
- 执行选定的location中的rewrite指令
Rewrite的基本语法我们可以从对应的官方网站上看到,这里简单介绍一下:
Syntax: | rewrite regex replacement [flag]; |
---|---|
Default: | — |
Context: | server , location , if |
在 regex 中匹配小括号()
之间匹配的内容,可以在后面的表达式中通过$1
来引用,$2
表示的是前面第二个()
里的内容,依次类推。
flag 标志位
Rewrite指令的最后一项参数为flag标记,主要包括:
- last :表示完成本次rewrite,用重写后的URI继续进行 Location 匹配。
- break:表示完成本次rewrite,并且直接应用,不再继续进行 Location 匹配。
- redirect:返回一个302的临时重定向,地址栏会显示跳转后的地址。
- permanent:返回一个301的永久重定向,地址栏会显示跳转后的地址。
这里我们举个例子来说明一下last和break的区别:
1 2 3 4 5 6 7 8 9 |
server { ... location /download/ { rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 last; rewrite ^(/download/.*)/audio/(.*)\..*$ $1/mp3/$2.ra last; return 403; } ... } |
如果我在Nginx的配置文件中这样写,那么最终会造成死循环匹配,最后超过10次后就返回500错误码了。 因为我们将URI改写后last指令并不能限制它继续进行Location匹配,它还是会匹配中这个规则。 如果我们把last换成break就不会存在这个问题了。
if指令
在使用Rewrite规则时,我们可以对给定的条件condition进行判断。如果为真,大括号内的rewrite指令将被执行。他的语法规则特别简单,就和C语言一样:if(condition){…} ,if条件(conditon)可以是如下任何内容:
- 当表达式只是一个变量时,如果值为空或任何以0开头的字符串都会当做false
- 直接比较变量和内容时,使用
=
或!=
~
正则表达式匹配,~*
不区分大小写的匹配,!~
区分大小写的不匹配
-f
和!-f
用来判断是否存在文件
-d
和!-d
用来判断是否存在目录
-e
和!-e
用来判断是否存在文件或目录
-x
和!-x
用来判断文件是否可执行
我们来看一些常见的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
if ($http_user_agent ~* "WordPress|Verifying pingback from") { return 403; # 这个是用来防止典型的Wordpress放大攻击的 } set $if_rewrite 1; if ($request_method != GET) { set $if_rewrite 0; } if ($mobile_rewrite = perform) { set $if_rewrite 0; } if ($request_uri ~* ^/check_nginx$) { set $if_rewrite 0; } if ($if_rewrite = 1) { rewrite ^/(.*) http://www.foreverlakers.com/$1 permanent; # 一个典型的rewrite } location = /check_nginx { # 如果是Nginx监控检测,则直接给他返回200 access_log off; return 200; } if ($http_cookie ~* "id=([^;]+)(?:;|$)") { set $id $1; } //如果cookie匹配正则,设置变量$id等于正则引用部分 location ~* \.(gif|jpg|png|swf|flv)$ { valid_referers none blocked www.aaa.com www.bbb.com; if ($invalid_referer) { return 404; } //防盗链 } |
下面是可以用作if判断的全局变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
$args : #这个变量等于请求行中的参数,同$query_string $content_length : 请求头中的Content-length字段。 $content_type : 请求头中的Content-Type字段。 $document_root : 当前请求在root指令中指定的值。 $host : 请求主机头字段,否则为服务器名称。 $http_user_agent : 客户端agent信息 $http_cookie : 客户端cookie信息 $limit_rate : 这个变量可以限制连接速率。 $request_method : 客户端请求的动作,通常为GET或POST。 $remote_addr : 客户端的IP地址。 $remote_port : 客户端的端口。 $remote_user : 已经经过Auth Basic Module验证的用户名。 $request_filename : 当前请求的文件路径,由root或alias指令与URI请求生成。 $scheme : HTTP方法(如http,https)。 $server_protocol : 请求使用的协议,通常是HTTP/1.0或HTTP/1.1。 $server_addr : 服务器地址,在完成一次系统调用后可以确定这个值。 $server_name : 服务器名称。 $server_port : 请求到达服务器的端口号。 $request_uri : 包含请求参数的原始URI,不包含主机名,如:”/foo/bar.php?arg=baz”。 $uri : 不带请求参数的当前URI,$uri不包含主机名,如”/foo/bar.html”。 $document_uri : 与$uri相同。 |
比如,我们的这个网址:
http://www.foreverlakers.com/wp-admin/post.php?post=1085&action=edit
- $host:www.foreverlakers.com
- $server_port:80
- $request_uri:http://www.foreverlakers.com/wp-admin/post.php
- $document_uri:wp-admin/post.php
- $document_root:/var/www/html/wordpress
- $request_filename:/var/www/html/wordpress/wp-admin/post.php
参考
http://nginx.org/en/docs/http/ngx_http_core_module.html#location
http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#rewrite
https://segmentfault.com/a/1190000002797606
发表评论