NGINX.COM
Web Server Load Balancing with NGINX Plus

本文介绍了如何创建 NGINX 重写规则(这些方法同样适用于 NGINX Plus 和 NGINX 开源版软件)。重写规则会改变客户端请求中的部分或全部 URL,通常出于以下两个目的:

  • 告知客户端其请求的资源目前位于其他位置。例如:您网站的域名已更改;或是您希望客户端采用规范的 URL 格式(无论是否带有 www 前缀),又或是您希望发现并更正域名的常见拼写错误。在这些场景下,returnrewrite 指令都可以适用。
  • 控制 NGINX 和 NGINX Plus 内部的处理流,例如,当需要动态生成内容时,将请求转发到应用服务器。try_files 指令通常用于此类情形。

注:如欲了解如何将 Apache HTTP 服务器重写规则转换为 NGINX 重写规则,请参阅我们的随附博文:《将 Apache 重写规则转换为 NGINX 重写规则》。

本文假设您对 HTTP 响应代码和正则表达式(NGINX 和 NGINX Plus 使用 Perl 语法)有一定的了解。

 

比较 returnrewritetry_files 指令

两个通用的 NGINX 重写指令是 returnrewrite,而 try_files 指令是将请求定向到应用服务器的便捷方法。下面来看看这些指令的作用及其差异。

return 指令

return 指令是两个通用指令中较为简单的一个,因此我们建议尽可能使用该指令,而非 rewrite(稍后会详细说明原因和使用场景)。您可以将 return 添加到 serverlocation 上下文(指定要重写的 URL)中,它定义了客户端在后续资源请求中使用的 rewrite 重写后的 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 指令匹配域名为www.old‑name.com 的请求 URL。return 指令告知 NGINX 停止处理请求,并立即将 301 响应码(Moved Permanently)和指定的已重写的 URL 发送至客户端。重写的 URL 使用两个 NGINX 变量来延用原始请求 URL 中的值:$scheme 表示协议(httphttps),$request_uri 表示包含参数的完整 URI。

对于 3xx 响应码,url 参数用以定义新(重写)的 URL。

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

对于其他响应码,您可以选择自定义出现在响应正文中的文本字符串(404 Not Found 等 HTTP 代码的标准文本仍包含在响应头中)。该文本可包含 NGINX 变量。

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

例如,在拒绝不具备有效身份验证令牌的请求时,该指令可能适用:

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

您还可以使用一些语法快捷方式,例如,如果代码为 302,则忽略它;请参阅 return 指令的参考文档。

(在某些情况下,您可能希望返回比文本字符串更复杂或更精细的响应。使用 error_page 指令,您可为每个 HTTP 代码返回一个完整的自定义 HTML 页面,并更改响应代码或执行重定向。)

可以看到,return 指令便于使用,通常在满足以下两个条件时用来实现重定向:重写的 URL 适合每个匹配 serverlocation 块的请求;并且您能够使用标准 NGINX 变量构建重写的 URL。

rewrite 指令

若要测试 URL 之间更复杂的差异、捕获原始 URL 中没有相应 NGINX 变量的元素或者更改(或添加)路径中的元素,该怎么办呢?在这种情况下,您可以使用 rewrite 指令。

return 指令一样,在需要重写的 URL 的 serverlocation 上下文中添加 rewrite 指令。这两个指令虽然非常相似,但多有不同,而且正确使用 rewrite 指令的难度可能更大。它的语法很简单:

rewrite regex URL [flag];

第一个参数 regex 表示,NGINX Plus 和 NGINX 仅在与指定的正则表达式相匹配(除了匹配 serverlocation 指令以外)时才会重写 URL。额外的条件判断意味着 NGINX 必须执行更多处理。

第二个区别是 rewrite 指令只能返回 301302 响应码。若要返回其他响应码,您需要在 rewrite 指令后添加一个 return 指令(请参见以下示例)。

最后,rewrite 指令不一定会像 return 指令那样停止 NGINX 对请求的处理,也未必向客户端发送重定向。除非您明确表明(使用标记或 URL 语法)希望 NGINX 停止处理或发送重定向,否则它会在整个配置中查找 rewrite 模块中定义的指令(breakifreturnrewriteset),并依次对其进行处理。如果重写的 URL 与 rewrite 模块中的后续指令相匹配,NGINX 将对重写的 URL 执行指定的操作(通常会再次重写)。

这正是复杂之处,您需要周密计划如何对指令进行排序以获得预期结果。例如,如果原始 location 块和其中的 NGINX 重写规则与重写的 URL 相匹配,NGINX 就会进入循环,重复地应用重写,直至达到 10 次内置上限。如欲了解全部详情,请参阅 rewrite 模块文档。如前所述,我们建议您尽可能使用 return 指令。

下例显示了使用 rewrite 指令的 NGINX 重写规则。它匹配以字符串 /download 开头且在路径靠后位置包含 /media//audio/ 目录的 URL,并用 /mp3/ 替换这些元素和添加相应的文件扩展名 .mp3.ra$1$2 变量捕获未更改的路径元素。例如,/download/cdn-west/media/file1 变为 /download/cdn-west/mp3/file1.mp3。如果文件名上有扩展名(例如 .flv),表达式会将其删除并替换为 .mp3

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

如上所述,您可以在 rewrite 指令中添加标记,以控制处理流。本例中的 last 标记就是其中之一:它指示 NGINX 跳过当前 serverlocation 块中的任何后续 rewrite 模块指令,并开始搜索与重写的 URL 相匹配的新 location

在本例中,最后的 return 指令表示,如果 URL 与任一 rewrite 指令均不匹配,则会向客户端返回 403 响应码。

try_files 指令

returnrewrite 指令一样,将 try_files 指令添加到 serverlocation 块中。作为参数,它需要一个包含一个或多个文件和目录的列表以及一个最终的 URI:

try_files file ... uri;

依次检查文件和目录是否存在(通过 rootalias 指令的设置构建每个文件的完整路径),并提供它找到的第一个文件和目录。若要表示一个目录,在元素名称的末尾添加斜杠即可。如果不存在任何文件或目录,NGINX 会执行内部重定向到最后一个元素(uri)定义的 URI。

若要 try_files 指令发挥作用,您还需要定义一个捕获内部重定向的 location 块,如下例所示。最后一个元素可以是命名位置,用符号 @ 开头。

try_files 指令通常使用 $uri 变量,它表示 URL 中域名后面的部分。

在下面的示例中,如果客户端请求的文件不存在,NGINX 会提供默认的 GIF 文件。当客户端请求(例如) http://www.domain.com/images/image1.gif 时,NGINX 会先在适用于该位置的 rootalias 指令指定的本地目录中查找 image1.gif (示例代码中未显示)。如果 image1.gif 不存在,NGINX 将查找 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.comold‑name.com 永久重定向到 www.new‑name.com,使用了两个 NGINX 变量从原始请求 URL 中捕获值 — $scheme 表示原始协议(httphttps),$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/aboutwww.old‑name.com/about 的基本内容相同),那么这种重写较为适用。如果除了更改域名以外还调整了网站结构,那么通过删除 $request_uri 将所有请求重定向到主页可能更安全。

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

就此类用例而言,其他有关在 NGINX 中重写 URL 的博文中使用了 rewrite 指令,如下所示:

# 不建议
rewrite ^ $scheme://www.new-name.com$request_uri permanent;

这不如 return 指令高效,因为它需要 NGINX 处理正则表达式,尽管比较简单(插入符 [ ^ ] 匹配完整的原始 URL)。但是相应的 return 指令对于人类读者来说也更容易理解:较之 rewrite permanent 指令,return 301 更清楚地表明 NGINX 返回代码 301

添加和删除 www 前缀

以下示例添加和删除了 www 前缀:

# 添加 “www”
server {
    listen 80;
    listen 443 ssl;
    server_name domain.com;
    return 301 $scheme://www.domain.com$request_uri;
}

# 删除 “www”
server {
    listen 80;
    listen 443 ssl;
    server_name www.domain.com;
    return 301 $scheme://domain.com$request_uri;
}

同样地,最好还是使用 return,而非 rewrite(如下)。rewrite 需要解释正则表达式 ^(.*)$ 并创建一个自定义变量($1)- 实际上相当于 $request_uri 固定变量。

# 不建议
rewrite ^(.*)$ $scheme://www.domain.com$1 permanent;

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

下面是一种特殊情形,即当请求 URL 与任何 serverlocation 块都不匹配时(可能是因为域名拼写错误),便会将传入流量重定向到网站的主页。它的工作原理是在 listen 指令中使用 default_server 参数,并在 server_name 指令中将下划线用作参数。

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

server_name 指令中将下划线用作参数可避免无意中匹配真实域名 — 因为没有网站会将下划线用作其域名。不过,与配置中其他任何 server 块都不匹配的请求会重定向到此处,并且 listen 指令中的 default_server 参数将指示 NGINX 使用该块处理这些请求。最好从 rewrite 的 URL 中删除 $request_uri 变量,从而将所有请求重定向到主页,因为带有错误域名的请求很可能会使用网站上不存在的 URI。

 

示例 – 强制所有请求使用 SSL/TLS

server 块强制所有访问者使用安全(SSL/TLS)连接访问您的网站。

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

就此用例而言,其他有关 NGINX 重写规则的博文中使用 if 测试和 rewrite 指令,如下所示:

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

但这种方法需要执行额外的处理,因为 NGINX 必须评估 if 条件并处理 rewrite 指令中的正则表达式。

 

对于使用 WordPress 的网站而言,NGINX 和 NGINX Plus 是常用的应用交付平台。以下 try_files 指令指示 NGINX 依次检查文件 $uri 和目录 $uri/ 是否存在。如果文件和目录都不存在,NGINX 将返回一个重定向到 /index.php,并传递查询字符串参数(通过 $args 参数捕获)。

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

 

示例 – 丢弃对不支持的文件扩展名的请求

由于各种原因,您的网站可能会收到以文件扩展名(对应于未运行的应用服务器)结尾的请求 URL。在 Engine Yard 博客的这个示例中,应用服务器是 Ruby on Rails,因此无法处理由其他应用服务器(Active Server Pages、PHP、CGI 等)处理的文件类型的请求,需要拒绝。在将动态资产请求传递给应用的 server 块中,此 location 指令会在非 Rails 文件类型的请求进入 Rails 队列之前将其丢弃。

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

严格来说,响应代码 410 (Gone) 适用于以下情况:请求的资源曾在此 URL 上提供,但现已不复存在,而且服务器不知道其当前位置(如有)。较之响应代码 404,它的优点是,明确表明该资源永久不可用,这样客户端不会再次发送请求。

您可能希望通过返回响应代码 403 (Forbidden) 和诸如 “Server handles only Ruby requests” 之类的说明作为文本字符串,向客户端更准确地指明失败原因。作为替代方法,deny all 指令返回响应码 403 而不作任何解释:

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

响应码 403 暗示请求的资源存在,因此如果您希望通过向客户端提供尽可能少的信息来实现“隐晦式安全”,代码 404 可能是更好的选择。其缺点是,客户端可能会反复重试请求,因为代码 404 并未表明失败是暂时的还是永久的。

 

示例 – 配置自定义重路由

MODXCloud 的这个示例中,有一个资源充当一组 URL 的控制器。您的用户可使用更易读的名称来命名资源,您能够对其进行重写(而非重定向),以便 listing.html 上的控制器执行处理。

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

例如,用户友好型 URL http://mysite.com/listings/123 被重写为由 listing.html 控制器处理的 URL http://mysite.com/listing.html?listing=123

 

将 NGINX 和 NGINX Plus 配置为 Web 服务器

Hero image
免费 O'Reilly 电子书:
《NGINX 完全指南》

更新于 2022 年,一本书了解关于 NGINX 的一切

关于作者

Tony Mauro

高级内容营销经理

关于 F5 NGINX

F5, Inc. 是备受欢迎的开源软件 NGINX 背后的商业公司。我们为现代应用的开发和交付提供一整套技术。我们的联合解决方案弥合了 NetOps 和 DevOps 之间的横沟,提供从代码到用户的多云应用服务。访问 nginx-cn.net 了解更多相关信息。