Nginx 配置 location 以及 return、rewrite 和 try_files 指令
正则表达式
Nginx 使用 perl 语法的正则表达式。
正则表达式的用法可以参考 这里。
Nginx 内置的全局变量
https://moonbingbing.gitbooks.io/openresty-best-practices/openresty/inline_var.html
location
在 Nginx 的配置文件中,通过 location 匹配用户请求中的 URI。格式如下:
location 前缀字符串 URL {
[ 配置 ]
}
前缀字符串及优先级
其中,前缀字符串部分支持 5 种:
=
:精确匹配,优先级最高。如果找到了这个精确匹配,则停止查找。^~
:URI 以某个常规字符串开头,不是正则匹配~
:区分大小写的正则匹配~*
:不区分大小写的正则匹配/
:通用匹配, 优先级最低。任何请求都会匹配到这个规则
优先级为: =
> 完整路径 > ^~
> ~
、~*
> 部分起始路径 > /
示例
# 精确匹配 / ,域名后面不能带任何字符串。匹配到后,停止继续匹配
location = / {
}
# 匹配到所有请求
location / {
if (-f $request_filename) {
expires max;
break;
}
if (!-e $request_filename) {
rewrite ^/(.*)$ /index.php/$1 break;
}
index index.php;
autoindex off;
}
# 匹配任何以 /documents/ 开头的 URI。优先级低于正则表达式,匹配到后还会继续往下匹配,当后面没有正则匹配或正则匹配失败时,使用这里代码
location /documents/ {
}
# 最长字符匹配到 /images/abc,继续往下,会发现 ^~ 存在
location /images/abc {
}
# 匹配任何以 /images/ 开头的 URI。优先级高于正则表达式,匹配成功后,停止往下搜索正则。
location ^~ /images/ {
}
# 正则匹配,区分大小写。匹配任何以 /documents/Abc 开头的地址,匹配符合以后,还要继续往下搜索
location ~ /documents/Abc {
}
# 正则匹配,忽略大小写。匹配所有以 gif、jpg 或 jpeg 结尾的请求
location ~* \.(gif|jpg|jpeg)$ {
}
location 匹配原则
可以 参考这篇译文。
每个请求的处理逻辑顺序如下:
- 用所有的前缀字符串测试 URI。
- 等号
=
定义了前缀字符串和 URI 的精确匹配关系。如果找到了这个精确匹配,则停止查找。 - 如果
^~
修饰符预先匹配到最长的前缀字符串,则不检查正则表达式。 - 存储最长的匹配前缀字符串。
- 用正则表达式测试 URI。
- 匹配到第一个正则表达式后停止查找,使用对应的 location。
- 如果没有匹配到正则表达式,则使用之前存储的前缀字符串对应的 location。
if 和 break 指令
可以参考 Nginx 模块 - ngx_http_rewrite_module。
if
if 的可用上下文有:server、location。if 的条件可能是以下任何一种情况:
变量名;如果变量值是空字符串或“0”则为 FALSE。注意,在 1.0.1 版本之前,任何以“0”开头的字符串都会被当做 FALSE。
使用“=”和“!=”的变量跟字符串的比较
使用“~”(区分大小写匹配)和“~*”(不区分大小写匹配)运算符将变量与正则表达式匹配。正则表达式可以包含捕获,之后可以通过 $1
到 $9
这几个变量名重复使用。“!~”和“!~*”用作不匹配运算符。如果正则表达式包含“}”或“;”字符,则整个表达式应该用单引号或双引号括起来。
用“-f”和“!-f”运算符检查文件是否存在
用“-d”和“!-d”运算符检查目录是否存在
用“-e”和“!-e”运算符检查文件、目录或符号链接的存在性
用“-x”和“!-x”运算符检查可执行文件
示例:
# 如果用户代理 User-Agent 包含"MSIE",rewrite 请求到 /msie/ 目录下。通过正则匹配的捕获可以用 $1 $2 等使用
if ($http_user_agent ~ MSIE) {
rewrite ^(.*)$ /msie/$1 break;
}
# 如果 cookie 匹配正则,设置变量 $id 等于匹配到的正则部分
if ($http_cookie ~* "id=([^;]+)(?:;|$)") {
set $id $1;
}
# 如果请求方法为 POST,则返回状态 405(Method not allowed)
if ($request_method = POST) {
return 405;
}
# 如果通过 set 指令设置了 $slow,限速
if ($slow) {
limit_rate 10k;
}
# 如果请求的文件存在,则开启缓存,并通过 break 停止后面的检查
if (-f $request_filename) {
expires max;
break;
}
# 如果请求的文件、目录或符号链接都不存在,则用 rewrite 在 URI 头部添加 /index.php
if (!-e $request_filename) {
rewrite ^/(.*)$ /index.php/$1 break;
}
break
break 的可用上下文有:server、location、if。用于停止处理当前的 ngx_http_rewrite_module 指令集合。
if ($slow) {
limit_rate 10k;
break;
}
return、rewrite 和 try_files 指令
NGINX rewrite 的两个通用指令是 return
和 rewrite
,而 try_files
指令可以将请求定向到应用程序服务器。
return 指令
在重定向满足两个条件时适用:
- 重写的 URL 适用于每个匹配的
server
或location
的请求 - 可以使用标准的 NGINX 变量构建重写的 URL
return
指令简单高效,建议尽量使用 return
,而不是 rewrite
。
return
指令放在 server
或 location
上下文中。语法很简单:
return code [text];
return code URL;
return 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’ 的请求。return
指令告诉 Nginx 停止处理请求,直接返回 301 (Moved Permanently)
代码和指定的重写过的 URL 到客户端。$scheme
是协议(HTTP 或 HTTPS),$request_uri
是包含参数的完整的 URI。
对于 3xx 系列响应码,url
参数定义了新的(重写过的)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";
通过 error_page 指令,可以为每个 HTTP 代码返回一个完整的自定义 HTML 页面,也可以更改响应代码或执行重定向。
rewrite 指令
NGINX Rewrite 规则官方文档
Rewrite 模块手册
HTTP 响应码
rewrite 规则会改变部分或整个用户请求中的 URL,主要有两个用途:
- 通知客户端,请求的资源已经换地方了。例如网站改版后添加了 www 前缀,通过 rewrite 规则可以将所有请求导向新站点。
- 控制 Nginx 中的处理流程。例如当需要动态生成内容时,将请求转发到应用程序服务器。
try_files
指令经常用于这个目的。
但是,如果需要测试 URL 之间更复杂的区别,或者要从原始 URL 中捕获的元素没有对应的 NGINX 变量,或者更改或添加路径中的元素(例如各大 PHP 框架常用的 index.php
入口文件),该怎么办? 可以使用 rewrite
指令。
rewrite
指令放在 server
或 location
上下文中。语法很简单:
rewrite regex URL [flag];
第一个参数 regex 是正则表达式。
flag 标志位
flag 支持以下 4 个选项:
- last:停止处理当前的 ngx_http_rewrite_module 指令集,并开始对匹配更改后的 URI 的新 location 进行搜索(再从 server 走一遍匹配流程)。此时对于当前
server
或location
上下文,不再处理 ngx_http_rewrite_module 重写模块的指令。 - break:停止处理当前的 ngx_http_rewrite_module 指令集
- redirect:返回包含 302 代码的临时重定向,在替换字符串不以“http://”,“https://”或“$scheme”开头时使用
- permanent:返回包含 301 代码的永久重定向。
last 和 break 的区别及共同处:
- last 重写 url 后,会再从 server 走一遍匹配流程,而 break 终止重写后的匹配
- break 和 last 都能阻止后面的 rewrite 指令再次执行
rewrite
指令只能返回代码 301 或 302。要返回其他代码,需要在 rewrite
指令后面包含 return
指令。
rewrite
指令不一定会暂停 NGINX 对请求的处理,因为它不一定会发送重定向到客户端。除非明确指出(使用 flag 或 URL 的语法)你希望 NGINX 停止处理或发送重定向,否则它将在整个配置中运行,查找在重写模块中定义的指令(break、if、return、rewrite 和 set),并按顺序处理。如果重写的 URL 与 Rewrite 模块中的后续指令匹配,NGINX 会对重写的 URL 执行指定的操作(通常会重新写入)。
这是复杂的地方,必须仔细计划指令顺序以获得期望的结果。例如,如果原始 location 块和其中的 NGINX 重写规则与重写的 URL 匹配,NGINX 可以进入一个循环,Nginx 默认限制循序最大 10 次。
示例
下面是使用 rewrite
指令的 NGINX 重写规则的示例。它匹配以字符串 /download
开头的 URL,然后用 /mp3/
替换在路径稍后的某个位置包含的 /media/
或 /audio/
目录,并添加适当的文件扩展名 .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;
# ...
}
可以将 flag 添加到重写指令来控制处理流程。示例中的 last 告诉 NGINX 跳过当前服务器或位置块中的任何后续 ngx_http_rewrite_module 重写模块的指令,并开始搜索与重写的 URL 匹配的新位置。
这个例子中的最后一个 return
指令意味着如果 URL 不匹配任何一个 rewrite
指令,将返回给客户端 403 代码。
try_files 指令
try_files
指令也放在 server
或 location
上下文中。语法很简单:
try_files file ... uri;
try_files
指令的参数是一个或多个文件或目录的列表,以及最后面的 URI 参数。
Nginx 会按顺序检查文件及目录是否存在(根据 root
和 alias
指令设置的参数构造完整的文件路径),并用找到的第一个文件提供服务。在元素名后面添加斜杠 /
表示这个是目录。如果文件和目录都不存在,Nginx 会执行内部重定向,跳转到命令的最后一个 uri 参数定义的 URI 中。
要想 try_files
指令工作,必须定义一个 location 块捕捉内部重定向。最后一个参数可以是命名过的 location,由初始符号(@)指示。
try_files
指令通常使用 $uri
变量,表示 URL 中域名之后的部分。
下面示例中,如果客户端请求的文件不存在,Nginx 会响应一个默认的 GIF 文件。假设客户请求“http://www.domain.com/images/image1.gif”,Nginx 会首先通过用于这个 location 的 root
和 alias
指令,在本地目录中查找这个文件。如果“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;
}