关于IT运维技术的
最佳实践博客网站

Rewrite规则 在Nginx中的使用

Rewrite规则

rewrite规则 -『伪静态』规则,本文将讨论如何创建Nginx的 rewrite规则 (规则对于Nginx社区版和Nginx Plus版通用)。Rewrite规则 修改客户请求的部分或全部URL,通常为了下述两个目的中的一个:

  • 为了告知客户端请求的资源已经处于新的位置。通常用于当你网站的域名发生变化时、当你希望用户使用规范的URL格式是(比如加或不加www前缀)、当你希望捕获拼写错误的域名并将之纠正到正确的域名上时(译者注:如www.taobso.com这样子的域名)。`return`和`rewrite`命令很适合满足这种目的。
  • 为了控制请求在Nginx中处理的流向。例如当请求的内容需要动态生成时将其转发到某个应用服务器。`try_files`通常用于处理这种情况。

注意:如果想要将已有的Apache HTTP Server的 Rewrite规则 转换为 Nginx规则 ,请参考我们的另外一篇博客:[Converting Apache Rewrite Rules to NGINX Rewrite Rules](https://www.nginx.com/blog/converting-apache-to-nginx-rewrite-rules/)

使用 Nginx Rewrite规则 需要了解HTTP状态码和正则表达式(Nginx使用Perl语法的正则),这里假设大家都已经了解。

对比return、rewrite和try_files命令

通常用于做Nginx地址重写的命令为`return`和`rewrite`这两个,`try_files`命令是一个将请求转发到后端应用服务器的好用的方式。让我们一起看一看这三个命令的作用和不同之处。

return命令

`return`命令相比`rewrite`来说更简单一些,因此我们也更推荐尽可能的使用`return`而不是`rewrite`(下文会讲到为什么和在什么时候用)。将`return`放在将要重写的URL所在的`server`或`location`的上下文中,它也定义了客户端未来请求资源使用的重写后正确的URL。

这里有一个很简单的例子,将客户请求重定向到一个新的域名:

server {
listen 80;
listen 443 ssl;
server_name www.old-name.com;
return 301 $scheme://www.new-name.com$request_uri;
}

`listen`命令意味着此`server`块提供HTTP和HTTPS两种访问方式。`server_name`命令匹配客户端的URL请求中包含www.old-name.com的请求。`return`命令告诉Nginx停止处理请求并立即返回301(永久重定向)和新的URL给客户端。重写后的URL使用了两个Nginx变量来获取和替换初始请求中的值:`$scheme`是指请求的协议(HTTP或HTTPS),`$request_uri`指包含参数的全部URI。

你可以在3xx系列的HTTP状态码中指定重写后的URL:

return (301 | 302 | 303 | 307) url;

对于其他的HTTP状态码,你可以在响应的页面中自定义一个文本(标准的状态码文本信息依然会显示,比如『404 Not Found』)。文本中可包含Nginx变量。

return (1xx | 2xx | 4xx | 5xx) ["text"];

比如,这个命令就很适合在拒绝一个没有包含验证令牌的请求时给出一些提示:

return 401 "Access denied because token is expired or invalid";

也有一些语法的快捷方式可以使用,比如当使用302临时重定向时,可以去掉`302`状态码(即`return URL`默认采用302重定向)。具体可以查看Nginx参考文档中关于`return`的部分。

有些情况下我们可能会期望返回比HTTP状态码文本信息更复杂更多彩的内容,这时可以考虑`error_page`。通过这个命令,我们可以针对每一种HTTP状态码返回完整的自定义HTML页面。

`return`命令使用比较简单,比较适合以下两种情况的重定向:对访问这个`server`或`location`的所有请求重定向后的URL是一致的;可以使用标准Nginx变量重写URL。

但是,当我们需要探测URL间更复杂的差别、捕获无法使用Nginx参数从原始URL获取的元素,或者改变、修改URL中的元素时,`return`命令就无法处理了,我们需要用到`rewrite`命令。

rewrite命令

与`return`一样,也是将`rewrite`命令放到需要重写的URL所在的`server`或`location`上下文中。两个命令有更多的不同之处,`rewrite`使用上要复杂的多,虽然它的语法很简单:

rewrite regex URL [flag];
  • 第一个参数`regex`,意味着在匹配`server`、`location`之后Nginx重写URL之前需要先经过另外一个检查:URL需要匹配特定的正则表达式。这也意味着Nginx需要进行更多的处理(影响性能)。
  • 另外一个不同之处是,`rewrite`命令只能返回301或302状态码,如果需要返回其他的状态码,需要在`rewrite`之后附加`return`命令(详见下面的例子)。
  • 最后,`rewrite`不像`return`那样必须停止Nginx对请求的处理,也不一定必须返回客户端一个重定向URL。除非特别指明需要Nginx中止请求或者返回重定向,Nginx将查找`rewrite`模块中定义的命令(`break`、`if`、`return`和`set`)并按顺序执行。一旦重写后的URL匹配`rewrite`模块中的其他命令,Nginx也会对重写后的URL执行指定的动作(通常是再次重写)。

由于这些特性,使用`rewrite`命令就变的很复杂,我们需要小心的计划命令的执行顺序以达到期望的效果。如果出现设计不当的情况,比如重写后的URL匹配原始的`location`块,Nginx将陷入循环中,不断的重写直到默认的循环限制次数10次。请查看Nginx文档中关于`[rewrite]`模块的部分,来详细的了解`rewrite`命令。如之前所说,在`return`可以实现的情况下尽可能的使用`return`命令。

接下来是一个使用`rewrite`重写的例子,它匹配以/download开始并且在路径中包含/media或/audio,重写规则将他们替换成/mp3并添加了统一的文件后缀.mp3或.ra。`$1`和`$2`参数获取路径中不会改变的部分(译者注:详看正则表达式)。

比如,/download/cdn-west/media/fil1 根据规则会被重写为 /download/cdn-west/mp3/file1.mp3。

server {
...
rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 last;
rewrite ^(/download/.*)/audio/(.*)\..*$ $1/mp3/$2.ra last;
return 403;
...
}

我们之前提到过可以在`rewrite`命令后添加标识来控制处理流程。上例中的`last`标识就是一个:它告诉Nginx跳出当前`server`或`location`块中的其他匹配重写规则,并根据重写后的URL继续匹配其他的`location`。

译者注:常用的还有`break`,它与`last`不同,也是跳出当前`server`或`location`的匹配,并不再匹配其他的规则或`location`,直接处理。

上例中最后的`return`命令意味着当请求无法匹配上边两个重写规则后将返回403状态码。

try_files命令

与前两个命令一样`try_files`也是放在`server`或`location`中使用。`try_files`的参数是一个包含一个或多个文件、目录的列表,并以URI结束:

try_files file … uri;

Nginx按顺序检查文件后缀和目录(通过配置中的`root`和`alias`来构建文件的完整路径),并返回第一个找到的文件。为了标识一个目录,需要在元素的后面加一个「/」。如果没有文件和目录匹配到,Nginx会执行一个内部重定向到最后一个元素中指定的URI。

为保证`try_files`正常工作,我们需要定义一个`location`以匹配其内部重定向的URI。最后一个元素有也可以是一个命名`location`(named location),以前缀@标识。

`try_files`命令一般使用`$uri`参数,URI相当于域名后边的URL部分。

在接下来这个例子中,当客户端的请求的文件不存在时Nginx将返回一个默认的GIF文件。比如客户端请求 http://www.domain.com/images/image1.gif,Nginx首先在根据`root`或`alias`命令构建的完整路径中查找image1.gif,如果不存在就继续查找image1.gif/,如果仍然不存在,就重定向到/images/default.gif。请求会完全匹配到第二个`location`命令,处理就会结束,然后Nginx返回这个文件并将起标记为缓存30秒。

location /images/ {
try_files $uri $uri/ /images/default.gif;
}

location = /images/default.gif {
expires 30s;
}

实例:域名标准化

Nginx重写规则的一个非常常见的用法就是捕获废弃的、非标准的网站域名并将他们重定向到当前使用的域名上。接下来我们展示几个相关的具体实例。

重定向一个旧域名到新域名

下例中Nginx将到旧域名www.old-name.com、old-name.com的请求永久重定向到新的域名www.new-name.com,同时使用了两个Nginx参数来获取初始请求中的值:`$scheme`是指初始请求使用的协议(HTTP或HTTPS),`$request_uri`是域名后边包含参数在内的完整URI。

server {
listen 80;
listen 443 ssl;
server_name www.old-name.com old-name.com;
return 301 $scheme://www.new-name.com$request_uri;
}

由于`$request_uri`包含了域名后面的URL部分,因此这种重写只适合新旧域名的页面可以一一对应的情况(比如www.new-name.com/about与www.old-name.com/about相同)。如果已经重构了网站,去掉`return`命令中的`$request_uri`,将所有旧域名的请求重定向到主页会更安全一些

server {
listen 80;
listen 443 ssl;
server_name www.old-name.com old-name.com;
return 301 $scheme://www.new-name.com;
}

有一些博客上使用`rewrite`命令来处理这种情况,配置如下:

# NOT RECOMMENDED
rewrite ^ $scheme://www.new-name.com$request_uri permanent;

这种方式相比`return`来说比较低效,因为`rewrite`命令即使非常简单的重写需要Nginx先处理正则匹配(^会匹配完整的初始URL)。同时,`return`命令也更便于人们理解:`return 301`相比`rewrite … permanent`会更加清楚的显示Nginx返回301状态码。

添加和删除www前缀

接下来的例子是添加和删除www前缀:

# add 'www'
server {
listen 80;
listen 443 ssl;
server_name domain.com;
return 301 $scheme://www.domain.com$request_uri;
}
# remove 'www'
server {
listen 80;
listen 443 ssl;
server_name www.domain.com;
return 301 $scheme://domain.com$request_uri;
}

再说一次,`return`比`rewrite`更适合这种处理,如下例子,Nginx需要先处理^(.*)$这个正则,同时也声明了一个自定义变量`$1`,其实与`$request_uri`参数的作用一样。

# NOT RECOMMENDED
rewrite ^(.*)$ $scheme://www.domain.com$1 permanent;

重定向所有的流量到一个正确的域名

有一些特殊的情况,比如访问请求的域名拼写错误,无法匹配到到任何的`server`和`location`块,这时可以将所有的入站流量重定向到网站主页。此外需要配合的修改`server_name`命令为一个『_』,同时在`listen`命令上增加`default_server`参数。

server {
listen 80 default_server;
listen 443 ssl default_server;
server_name _;
return 301 $scheme://www.domain.com;
}

我们使用_来避免不经意间匹配到真实的域名–假设不会有网站使用_来做域名。当请求无法匹配到配置文件中的`server`时,请求就会流向这里。我们默认去掉了`$request_uri`,将所有的请求都重定向到网站的主页,因为通常情况下错误域名请求的URI也是错误的。

实例:强制所有的请求使用HTTPS(TLS/SSL加密)

下例中的`server`将强制所有的访客使用加密连接访问我们的站点:

server {
listen 80;
server_name www.domain.com;
return 301 https://www.domain.com$request_uri;
}

有一些博客使用Nginx的`if`检测并使用`rewrite`来实现这一目的,但这种方式会增加Nginx额外的处理,因为Nginx需要先处理`if`的结果并处理`rewrite`中的正则表达式,不建议使用这种方式。

# NOT RECOMMENDED
if ($scheme != "https") {
rewrite ^ https://www.mydomain.com$uri permanent;
}

实例:为Wordpress网站启用漂亮的固定链接

在很多Wordpress网站中都使用Nginx作为应用分发的平台,下面这个例子中,`try_files`命令告诉Nginx按顺序检查`$uri`文件、`$uri/`目录是否存在,如果都不存在,就会将请求重定向到/index.php并在后边附加请求的参数。

location / {
try_files $uri $uri/ /index.php?$args;
}

实例:丢掉不支持的文件后缀的请求

会有各种各样的原因,网站总会收到各种包含服务器不支持的文件后缀的请求。下面这个例子来自[Engine Yard blog,](https://blog.engineyard.com/2011/useful-rewrites-for-nginx),应用服务器使用Ruby On Rails,所以其他应用服务器的文件格式(比如ASP、PHP、CGI等等)都无法支持,应该拒绝掉。在一个将所有非静态元素的请求都传递给应用的`server`块中,这个`location`命令会在请求到达Rails的请求队列之前将所有非Rails支持的文件格式的请求丢弃掉。

location ~ \.(aspx|php|jsp|cgi)$ {
return 410;
}

严格的说,410状态码用于请求资源的URL之前可用,但当前失效了,服务器也不知道它现在的位置。相比404状态码的优点是,410明确的告知客户端请求的资源永久不可用,客户端就不会再发送请求。

如果你想给客户端一些更明确的请求失败的原因,可以返回403状态码(禁止访问),并附加一个解释文本『服务器仅响应Ruby请求』。另外,`deny all`命令也会返回一个403状态码,但没有解释性文字。

location ~ \.(aspx|php|jsp|cgi)$ {
deny all;
}

返回403状态码隐含着文件实际是存在的意思,因此如果想尽可能少的给客户端信息以求获取安全的话,返回404状态码是一个更好的选择。缺点就是客户端会不断的重试请求,因为404并不会说明文件是临时失效还是永久失效。

实例:自定义重新路由

这个例子来自[MODXCloud](https://modxcloud.com/userguide/using-modx-cloud/tools-and-configuration/web-rules-rewrites-redirects-tweaks.html),有一台服务器作为一系列URL的控制器。用户使用比较易读的方式请求资源,我们通过重写(非重定向)URL,使其可以通过控制器`listing.html`来处理。比如http://mysite.com/listings/123会被重写为http://mysite.com/listing.html?listing=123:

rewrite ^/listings/(.*)$ /listing.html?listing=$1 last;

文章比想象中长多了,有错误或不严谨之处请帮忙指出,谢谢。

赞(1)
未经允许不得转载:菜鸟HOW站长 » Rewrite规则 在Nginx中的使用
分享到: 更多 (0)

留下你的脚印

  订阅  
关注动态