Nginx指令阶段
因为nginx执行是按阶段执行的,我们一般的程序都是按顺序执行的。nginx指令一般是分布在不同阶段的;
Nginx 处理请求的过程一共划分为 11 个阶段,按照执行顺序依次是 post-read、server-rewrite、find-config、rewrite、post-rewrite、preaccess、access、post-access、try-files、content 以及 log.
- post-read阶段
该阶段Nginx标准函数 set_real_ip_from、real_ip_header
最先执行的 post-read 阶段在 Nginx 读取并解析完请求头(request headers)之后就立即开始运行。标准模块 ngx_realip 就在 post-read 阶段注册了处理程序,它的功能是迫使 Nginx 认为当前请求的来源地址是指定的某一个请求头的值。
// eg:
server {
listen 8080;
set_real_ip_from 127.0.0.1;
real_ip_header X-My-IP;
location /test {
set $addr $remote_addr;
echo "from: $addr";
}
}
- server-rewrite阶段
该阶段包含标准函数ngx_rewrite、set 以及openresty函数set_by_lua、rewrite_by_lua
post-read 阶段之后便是 server-rewrite 阶段。当 ngx_rewrite 模块的配置指令直接书写在 server 配置块中时,基本上都是运行在 server-rewrite 阶段。
// eg:
server {
listen 8080;
location /test {
set $b "$a, world";
echo $b;
}
set $a hello;
}
- find-config 阶段
这个阶段并不支持 Nginx 模块注册处理程序,而是由 Nginx 核心来完成当前请求与 location 配置块之间的配对工作。
location /hello {
echo "hello world";
}
rewrite 阶段
该阶段包含标准函数set_unescape_uri、rewrite以及openresty函数set_by_lua、 rewrite_by_luapost-rewrite 阶段,
不接受 Nginx 模块注册处理程序,而是由 Nginx 核心完成 rewrite 阶段所要求的“内部跳转”操作
“内部跳转”的工作原理:本质上其实就是把当前的请求处理阶段强行倒退到 find-config 阶段,以便重新进行请求 URI 与 location 配置块的配对。比如例中,运行在 rewrite 阶段的 rewrite 指令就让当前请求的处理阶段倒退回了 find-config 阶段。由于此时当前请求的 URI 已经被 rewrite 指令修改为了 /bar,所以这一次换成了 location /bar 与当前请求相关联,然后再接着从 rewrite 阶段往下执行。
注意的:如果在 server 配置块中直接使用 rewrite 配置指令对请求 URI 进行改写,则不会涉及“内部跳转”preaccess 阶段
该阶段包含标准函数ngx_access-allow deny ngx_limit_req 和 ngx_limit_zone ngx_auth_request 以及openresty函数access_by_lua其中也包含了限频限流模块resty.limit.req resty.limit.conn
注意的是:标准模块 ngx_realip 其实也在这个阶段注册了处理程序
// eg
server {
listen 8080;
location /test {
set_real_ip_from 127.0.0.1;
real_ip_header X-Real-IP;
echo "from: $remote_addr";
}
}
与先看前到的例子相比,此例最重要的区别在于把 ngx_realip 的配置指令放在了 location 配置块中。前面我们介绍过,Nginx 匹配 location 的动作发生在 find-config 阶段,而 find-config 阶段远远晚于 post-read 阶段执行,所以在 post-read 阶段,当前请求还没有和任何 location 相关联。
- post-access阶段
该阶段不支持 Nginx 模块注册处理程序,而是由 Nginx 核心自己完成一些处理工作 - try-files 阶段
实现标准配置指令 try_files 的功能,并不支持 Nginx 模块注册处理程序。
try_files 指令接受两个以上任意数量的参数,每个参数都指定了一个 URI. 这里假设配置了 N 个参数,则 Nginx 会在 try-files 阶段,依次把前 N-1 个参数映射为文件系统上的对象(文件或者目录),然后检查这些对象是否存在。一旦 Nginx 发现某个文件系统对象存在,就会在 try-files 阶段把当前请求的 URI 改写为该对象所对应的参数 URI(但不会包含末尾的斜杠字符,也不会发生 “内部跳转”)。如果前 N-1 个参数所对应的文件系统对象都不存在,try-files 阶段就会立即发起“内部跳转”到最后一个参数(即第 N 个参数)所指定的 URI.
// eg1
location /test {
try_files /foo /bar/ /baz;
echo "uri: $uri";
}
// eg2
location /test {
try_files /foo /bar/ =404;
echo "uri: $uri";
}
content阶段
该阶段包含标准函数echo proxy_pass 以及openresty 函数content_by_lua balance_by_lua header_filter_by_lua body_filter_by_lua
所有请求的标准输出都在该阶段。几乎所有的逻辑代码也在该阶段执行。这个阶段比较常见log阶段
改阶段包含ngx的acces_log error_log以及openresty函数log_by_lua
该阶段主要记录日志satisfy指令
对于多个 Nginx 模块注册在 access 阶段的处理程序, satisfy 配置指令可以用于控制它们彼此之间的协作方式。比如模块 A 和 B 都在 access 阶段注册了与访问控制相关的处理程序,那就有两种协作方式,一是模块 A 和模块 B 都得通过验证才算通过,二是模块 A 和模块 B 只要其中任一个通过验证就算通过。第一种协作方式称为 all 方式(或者说“与关系”),第二种方式则被称为 any 方式(或者说“或关系”)。默认情况下,Nginx 使用的是 all 方式。
// eg1
location /test {
satisfy all;
deny all;
access_by_lua 'ngx.exit(ngx.OK)';
echo something important;
}
// eg2
location /test {
satisfy any;
deny all;
access_by_lua 'ngx.exit(ngx.OK)';
echo something important;
}
nginx中邪恶的if指令
if指令用来判断条件为true时要执行的指令,条件false时不执行相应的指令,if指令只能用在server、location内。
因为nginx执行是按阶段执行的,我们一般的程序都是按顺序执行的。nginx指令一般是分布在不同阶段的;
- 用 if 判断 Server Name
// 不推荐配置
server {
server_name example.com *.example.com;
if ($host ~* ^www\.(.+)) {
set $raw_domain $1;
rewrite ^/(.*)$ $raw_domain/$1 permanent;
}
# [...]
}
}
// 推荐
server {
server_name www.example.com;
return 301 $scheme://example.com$request_uri;
}
server {
server_name example.com;
}
- 用 if 检查文件是否存在
// 不推荐配置
server {
root /var/www/example.com;
location / {
if (!-f $request_filename) {
break;
}
}
}
// 推荐配置
server {
root /var/www/example.com;
location / {
try_files $uri $uri/ /index.html;
}
}
费力的 rewrites
rewrite 很容易和正则表达式混为一谈。 实际上,rewrite 是很容易的,我们应该努力去保持它们的整洁。
// 不推荐
rewrite ^/(.*)$ http://example.com/$1 permanent;
// 好一些
rewrite ^ http://example.com$request_uri? permanent;
// 更好些
return 301 http://example.com$request_uri;
忽略 http:// 的 rewrite
// 不推荐
rewrite ^ example.com permanent;
// 推荐
rewrite ^ http://example.com permanent;
没有使用标准的 Document Root Location
server {
root /;
location / {
try_files /web/$uri $uri @php;
}
location @php {
}
}
nginx配置的常见陷阱
- 把 root 放在 location 区块内
错误案例
server {
server_name www.example.com;
location / {
root /var/www/Nginx -default/;
# [...]
}
location /foo {
root /var/www/Nginx -default/;
# [...]
}
}
正确案例
server {
server_name www.example.com;
root /var/www/Nginx -default/;
location / {
# [...]
}
location /foo {
# [...]
}
}
- 重复index指令
错误案例
http {
index index.php index.htm index.html;
server {
server_name www.example.com;
location / {
index index.php index.htm index.html;
}
}
server {
server_name example.com;
location / {
index index.php index.htm index.html;
}
location /foo {
index index.php;
}
}
}
正确案例
http {
index index.php index.htm index.html;
server {
server_name www.example.com;
location / {
}
}
server {
server_name example.com;
location / {
}
location /foo {
}
}
}