Pages

Thursday, 23 January 2020

Nginx配置location、if以及return、rewrite和 try_files 指令

location指令的使用
location指令要放在server模块里面,语法如下:

server{
    location 修饰符 URI {
        [具体配置]
    }
}

location指令是干嘛用的?
根据官方文档,location的作用是:Sets configuration depending on a request URI.即:根据请求的URI来设置具体配置,比如:
location ^~ /images/ {
    [ configuration ]
}

该配置表示当uri中包括/images/时,就会被匹配,实际上不止要包括/images/,并且还要以/images/开头,因为它的前面有^~修饰符,该修饰符表示要以它后面指定的字符串开头。

location指令的规则
上面举了个栗子,这里详细说规则,首先说一下uri,uri是指url中的除去协议和域名及参数后,剩下的部分,比如请求的url为:http://www.test.com/test/index.php?page=1 ,则uri为:/test/index.php。

修饰符有以下四个,用于修饰它后面的URI:
=表示精确匹配,也就是uri要完全跟=号后面的值相同;
^~表示URI 以某个常规字符串开头,不是正则匹配;
~表示区分大小写正则匹配;
~*表示不区分大小写正则匹配。

location匹配原则:
1. 用所有的前缀字符串测试 URI;
2. 等号=定义了前缀字符串和 URI 的精确匹配关系。如果找到了这个精确匹配,则停止;
3. 查找。
4. 如果^~修饰符预先匹配到最长的前缀字符串,则不检查正则表达式;
5. 存储最长的匹配前缀字符串;
6. 用正则表达式测试 URI;
7. 匹配到第一个正则表达式后停止查找,使用对应的 location;
8. 如果没有匹配到正则表达式,则使用之前存储的前缀字符串对应的 location。

测试nginx配置:
server {
    listen 80;
    server_name www.test.com;

    charset utf-8;
    default_type text/html;
    root /Users/bruce/www/personal/test;

    access_log /usr/local/var/log/nginx/www.test.com.access.log;
    error_log /usr/local/var/log/nginx/www.test.com.error.log;

    autoindex on;
    autoindex_exact_size off;
    autoindex_format html;
    autoindex_localtime on;

    location = / {
       return 502 "规则A\n";
    }
    location = /login {
       return 502 "规则B\n";
    }
    location ^~ /static/ {
       return 502 "规则C\n";
    }
    location ^~ /static/files {
       return 502 "规则D\n";
    }
    location ~ \.(gif|jpg|png|js|css)$ {
       return 502 "规则E\n";
    }
    location ~* \.PNG$ {
       return 502 "规则F\n";
    }
    location /img {
       return 502 "规则G\n";
    }
    location / {
       return 502 "规则H\n";
    }

    location ~ \.php {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        include fastcgi.conf;
    }
}

在hosts文件中添加域名解析:

127.0.0.1 www.test.com

另外,保证root、access_log、error_log三个指令指向的路径存在并且具有可访问权限,只要目录存在即可,目录里无需放任何文件。

鉴于一般人可能没装echo模块,所以我使用了return 502方式,如果你编译了echo模块,那么你可以把return 502换成echo即可,并且用echo的时候后面的\n还可以去掉,而用return 502如果后面没有\n,用curl访问则会多一个%,我加\n就是为了去掉这个百分号。

测试时,建议使用curl测试,不要用浏览器,因为用浏览器的话,测试访问图片时,浏览器会自动显示图片,虽然图片并不存在,但浏览器还是会显示一个黑色界面表示这是显示图片,而用curl就没这个问题,curl不用参数,直接curl http://xxxx.html即可,比如:

curl http://www.test.com/a.png

以下为测试url及结果说明:
访问根目录/,比如 http://www.test.com/或http://www.test.com 将匹配规则A,实际上http://www.test.com/会跳转到http://www.test.com。

访问 http://www.test.com/login 将匹配规则B, http://www.test.com/register则匹配 规则H。

访问http://www.test.com/static/a.html将匹配规则C。

访问 http://www.test.com/static/files/a.html 将匹配规则D ,虽然规则C也能匹配到,但因为最大匹配原则,最终选中了规则D。你可以测试下,去掉规则D,则当前URL会匹配上 规则C 。

访问 http://www.test.com/a.png , 将匹配规则E和规则F,但是规则E顺序优先,规则F不起作用,而http://www.test.com/static/c.png则优先匹配到规则C。

访问http://www.test.com/a.PNG 则匹配规则E,而不会匹配规则D,因为规则E不区分大小写。

访问http://www.test.com/img/a.gif会匹配上规则E ,虽然规则G也可以匹配上,但是因为正则匹配优先,而忽略了规则G 。

访问http://www.test.com/img/a.tiff 会匹配上规则G。

访问http://www.test.com/category/id/1111因为以上规则都不匹配,则最终匹配到规则H 。

if指令的使用规则
if指令属于ngx_http_rewrite_module(即rewrite模块),if必须写在serve里面r或location里面。if 的条件可能是以下任何一种情况:

变量名:如果变量值是空字符串或“0”则为 FALSE。注意,在 1.0.1 版本之前,任何以“0”开头的字符串都会被当做 FALSE。
使用=和!=的变量跟字符串的比较(注意,它并不像编程语言那样用两个==号来判断是否相等,而是用一个=号,加个感叹号就是不等于,并且字符串不需要用引号引起来,当然引起来也可以)。
使用~(区分大小写匹配)和~*(不区分大小写匹配)运算符将变量与正则表达式匹配。正则表达式可以包含捕获,之后可以通过$1到$9这几个变量名重复使用。!~和!~*用作不匹配运算符。如果正则表达式包含}或;字符,则整个表达式应该用单引号或双引号括起来。

-f和!-f用来判断是否存在文件(f:file)
-d和!-d用来判断是否存在目录(d:directory)
-e和!-e用来判断是否存在文件或目录(e:exists)
-x和!-x用来判断文件是否可执行(x:execute)

if指令测试配置:
注意if跟后面的括号一定要有一个空格,否则报错,但右括号(与左花括号{之间可以不用空格。

server {
    listen 80;
    server_name www.test.com;

    charset utf-8;
    default_type text/html;
    root /Users/bruce/www/personal/test;

    access_log /usr/local/var/log/nginx/www.test.com.access.log;
    error_log /usr/local/var/log/nginx/www.test.com.error.log;

    autoindex on;
    autoindex_exact_size off;
    autoindex_format html;
    autoindex_localtime on;

    location / {
        # 如果用户代理 User-Agent 包含"chrome",rewrite 请求到/chrome/目录下。通过正则匹配的捕获可以用 $1/$2 等使用
        if ($http_user_agent ~ Chrome) {
            rewrite ^([^/]*)$ /chrome$1 break;
        }

        # 如果cookie匹配正则,设置变量$id等于匹配到的正则部分
        if ($http_cookie ~* "id=([^;]+)(?:;|$)") {
            set $id $1;
            #为了证明是否设置成功,这里打印一下
            #return 502 $id;
        }

        # 如果请求方法为 POST,则返回状态 405(Method not allowed)
        if ($request_method = POST) {
            return 405;
        }

        # 如果请求的文件存在,则开启缓存,并通过 break 停止后面的检查
        if (-f $request_filename) {
            expires max;
            break;
        }

        # 如果请求的文件、目录或符号链接都不存在,则用 rewrite 在 URI 头部添加 /index.php
        if (!-e $request_filename) {
            echo $request_filename;
            rewrite ^/(.*)$ /index.php break;
        }
    }

    location ~ \.php {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        include fastcgi.conf;
    }

}

以上配置不需要先注释其他if只留一个,全部一起测试就可以,注意如果你没有cookie,可以用Edit ThiscookieChrome扩展自己添加一个cookieid=10010用于测试,测试cookie那一块可以把return 502那个注释打开,否则无法测试出是否进入。

test目录的结构如下:

test
├── chrome
│   └── index.html
└── index.php
return、rewrite 和 try_files 指令
NGINX rewrite的两个通用指令是return和rewrite,而try_files指令可以将请求定向到应用程序服务器。

return指令
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";

rewrite指令
rewrite 规则会改变部分或整个用户请求中的 URL,主要有两个用途:
– 通知客户端,请求的资源已经换地方了。例如网站改版后添加了 www 前缀,通过 rewrite 规则可以将所有请求导向新站点。
– 控制 Nginx 中的处理流程。例如当需要动态生成内容时,将请求转发到应用程序服务器。try_files 指令经常用于这个目的。

但是,如果需要测试 URL 之间更复杂的区别,或者要从原始 URL 中捕获的元素没有对应的 NGINX 变量,或者更改或添加路径中的元素(例如各大 PHP 框架常用的 index.php 入口文件),该怎么办? 可以使用 rewrite 指令。

rewrite 指令放在 server 或 location 上下文中。语法很简单:
rewrite regex URL [flag];
第一个参数 regex 是正则表达式。

flag 标志位
last 停止处理当前的ngx_http_rewrite_module指令集,并开始对匹配更改后的 URI 的新 location 进行搜索(再从 server 走一遍匹配流程)。此时对于当前 server 或 location 上下文,不再处理 ngx_http_rewrite_module 重写模块的指令。相当于Apache里的[L]标记。
break 终止匹配, 不再匹配后面的规则
redirect 返回包含 302 代码的临时重定向,地址栏会显示跳转后的地址,在替换字符串不以http://、https://或$scheme开头时使用。
permanent 返回包含 301 代码的永久重定向。
last和break就相当于php里的continue和break。

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;
}

try_files常用到的变量:
$args 请求url中?后面的参数(不包括问号本身)
$is_args 如果$args不为空,则$is_args就是一个?号,如果$is_args为空,则$is_args也为空字符串,所以你会经常看见有这样的写法:
try_files $uri $uri/ /index.php$is_args$args;

而有些人会写成这样:
try_files $uri $uri/ /index.php?$args;

其实第二种写法肯定是不好的,因为万一$args为空,那么就留下了一个无用的问号,而用$is_args则能避免这个问题。
$query_string 与$args相同。
$document_root root指令指定的值。
$request_filename 当前连接请求的文件路径,由root或alias指令与URI请求生成(不包住参数)。
$request_uri 原始请求uri(即url中除去协议和域名的部分,比如:http://127.0.0.1/index.php的uri即为/index.php)。

nginx官方文档中说明的内置变量:http://nginx.org/en/docs/http/ngx_http_core_module.html#location

在nginx.org点击右侧的documentation

然后搜索:Modules reference,找到ngx_http_core_module,点进去(因为我们配置的是http服务器,所以我们需要的所有指令都在这个模块里面)

然后搜索:Embedded Variables,点进去,然后你就可以看到所有nginx http模块的的内置变量了:

Embedded Variables
The ngx_http_core_module module supports embedded variables with names matching the Apache Server variables. First of all, these are variables representing client request header fields, such as $http_user_agent, $http_cookie, and so on. Also there are other variables:

$arg_name argument name in the request line
$args arguments in the request line
$binary_remote_addr client address in a binary form, value’s length is always 4 bytes for IPv4 addresses or 16 bytes for IPv6 addresses
$body_bytes_sent number of bytes sent to a client, not counting the response header; this variable is compatible with the “%B” parameter of the mod_log_config Apache module
$bytes_sent number of bytes sent to a client (1.3.8, 1.2.5)
$connection connection serial number (1.3.8, 1.2.5)
$connection_requests current number of requests made through a connection (1.3.8, 1.2.5)
$content_length “Content-Length” request header field
$content_type “Content-Type” request header field
$cookie_name the name cookie
$document_root root or alias directive’s value for the current request
$document_uri same as $uri
$host in this order of precedence: host name from the request line, or host name from the “Host” request header field, or the server name matching a request
$hostname host name
$http_name arbitrary request header field; the last part of a variable name is the field name converted to lower case with dashes replaced by underscores
$https “on” if connection operates in SSL mode, or an empty string otherwise
$is_args “?” if a request line has arguments, or an empty string otherwise
$limit_rate setting this variable enables response rate limiting; see limit_rate
$msec current time in seconds with the milliseconds resolution (1.3.9, 1.2.6)
$nginx_version nginx version
$pid PID of the worker process
$pipe “p” if request was pipelined, “.” otherwise (1.3.12, 1.2.7)
$proxy_protocol_addr client address from the PROXY protocol header, or an empty string otherwise (1.5.12) .The PROXY protocol must be previously enabled by setting the proxy_protocol parameter in the listen directive.
$proxy_protocol_port client port from the PROXY protocol header, or an empty string otherwise (1.11.0)
The PROXY protocol must be previously enabled by setting the proxy_protocol parameter in the listen directive.
$query_string same as $args
$realpath_root an absolute pathname corresponding to the root or alias directive’s value for the current request, with all symbolic links resolved to real paths
$remote_addr client address
$remote_port client port
$remote_user user name supplied with the Basic authentication
$request full original request line
$request_body request body
The variable’s value is made available in locations processed by the proxy_pass, fastcgi_pass, uwsgi_pass, and scgi_pass directives when the request body was read to a memory buffer.

$request_body_file name of a temporary file with the request body
At the end of processing, the file needs to be removed. To always write the request body to a file, client_body_in_file_only needs to be enabled. When the name of a temporary file is passed in a proxied request or in a request to a FastCGI/uwsgi/SCGI server, passing the request body should be disabled by the proxy_pass_request_body off, fastcgi_pass_request_body off, uwsgi_pass_request_body off, or scgi_pass_request_body off directives, respectively.

$request_completion “OK” if a request has completed, or an empty string otherwise
$request_filename file path for the current request, based on the root or alias directives, and the request URI
$request_id unique request identifier generated from 16 random bytes, in hexadecimal (1.11.0)
$request_length request length (including request line, header, and request body) (1.3.12, 1.2.7)
$request_method request method, usually “GET” or “POST”
$request_time request processing time in seconds with a milliseconds resolution (1.3.9, 1.2.6); time elapsed since the first bytes were read from the client
$request_uri full original request URI (with arguments)
$scheme request scheme, “http” or “https”
$sent_http_name arbitrary response header field; the last part of a variable name is the field name converted to lower case with dashes replaced by underscores
$sent_trailer_name arbitrary field sent at the end of the response (1.13.2); the last part of a variable name is the field name converted to lower case with dashes replaced by underscores
$server_addr an address of the server which accepted a request
Computing a value of this variable usually requires one system call. To avoid a system call, the listen directives must specify addresses and use the bind parameter.

$server_name name of the server which accepted a request
$server_port port of the server which accepted a request
$server_protocol request protocol, usually “HTTP/1.0”, “HTTP/1.1”, or “HTTP/2.0”
$status response status (1.3.2, 1.2.2)
$tcpinfo_rtt,$tcpinfo_rttvar, $tcpinfo_snd_cwnd, $tcpinfo_rcv_space
information about the client TCP connection; available on systems that support the TCP_INFO socket option
$time_iso8601 local time in the ISO 8601 standard format (1.3.12, 1.2.7)
$time_local local time in the Common Log Format (1.3.12, 1.2.7)
$uri current URI in request, normalized
The value of $uri may change during request processing, e.g. when doing internal redirects, or when using index files.

其他的可参考:Nginx 基本功能 – 将 Nginx 配置为 Web 服务器(HTTP Server)