Nginx 配置指令的执行顺序(十)
运行在 post-rewrite
阶段之后的是所谓的 preaccess
阶段。该阶段在 access
阶段之前执行,故名preaccess
.
标准模块 ngx_limit_req 和 ngx_limit_zone 就运行在此阶段,前者可以控制请求的访问频度,而后者可以限制访问的并发度。这里我们仅仅和它们打个照面,后面还会有机会专门接触到这两个模块。
前面反复提到的标准模块 ngx_realip 其实也在这个阶段注册了处理程序。有些读者可能会问:“这是为什么呢?它不是已经在 post-read
阶段注册处理程序了吗?”我们不妨通过下面这个例子来揭晓答案:
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
相关联。在这个例子中,因为ngx_realip 的配置指令都写在了 location
配置块中,所以在 post-read
阶段,ngx_realip 模块的处理程序没有看到任何可用的配置信息,便不会执行来源地址的改写工作了。
为了解决这个难题,ngx_realip 模块便又特意在 preaccess
阶段注册了处理程序,这样它才有机会运行location
块中的配置指令。正是因为这个缘故,上面这个例子的运行结果才符合直觉预期:
$ curl -H 'X-Real-IP: 1.2.3.4' localhost:8080/test
from: 1.2.3.4
不幸的是,ngx_realip 模块的这个解决方案还是存在漏洞的,比如下面这个例子:
server {
listen 8080;
location /test {
set_real_ip_from 127.0.0.1;
real_ip_header X-Real-IP;
set $addr $remote_addr;
echo "from: $addr";
}
}
这里,我们在 rewrite
阶段将 $remote_addr 的值保存到了用户变量 $addr
中,然后再输出。因为 rewrite
阶段先于 preaccess
阶段执行,所以当 ngx_realip 模块尚未在 preaccess
阶段改写来源地址时,最初的来源地址就已经在 rewrite
阶段被读取了。上例的实际请求结果证明了我们的结论:
$ curl -H 'X-Real-IP: 1.2.3.4' localhost:8080/test
from: 127.0.0.1
输出的地址确实是未经改写过的。Nginx 的“调试日志”可以进一步确认这一点:
$ grep -E 'http script (var|set)|realip' logs/error.log
[debug] 32488#0: *1 http script var: "127.0.0.1"
[debug] 32488#0: *1 http script set $addr
[debug] 32488#0: *1 realip: "1.2.3.4"
[debug] 32488#0: *1 realip: 0100007F FFFFFFFF 0100007F
[debug] 32488#0: *1 http script var: "127.0.0.1"
其中第一行调试信息
[debug] 32488#0: *1 http script var: "127.0.0.1"
是 set 语句读取 $remote_addr 变量时产生的。信息中的字符串 "127.0.0.1"
便是 $remote_addr 当时读出来的值。
而第二行调试信息
[debug] 32488#0: *1 http script set $addr
则显示我们对变量 $addr
进行了赋值操作。
后面两行信息
[debug] 32488#0: *1 realip: "1.2.3.4"
[debug] 32488#0: *1 realip: 0100007F FFFFFFFF 0100007F
是 ngx_realip 模块在 preaccess
阶段改写当前请求的来源地址。我们看到,改写后的新地址确实是期望的1.2.3.4
. 但很明显这个操作发生在 $addr
变量赋值之后,所以已经太迟了。
而最后一行信息
[debug] 32488#0: *1 http script var: "127.0.0.1"
则是 echo 配置指令在输出时读取变量 $addr
时产生的,我们看到它的值是改写前的来源地址。
看到这里,有的读者可能会问:“如果 ngx_realip 模块不在 preaccess
阶段注册处理程序,而在rewrite
阶段注册,那么上例不就可以工作了?”答案是:不一定。因为 ngx_rewrite 模块的处理程序也同样注册在 rewrite
阶段,而前面我们在 (二) 中特别提到,在这种情况下,不同模块之间的执行顺序一般是不确定的,所以 ngx_realip 的处理程序可能仍然在 set 语句之后执行。
一个建议是:尽量在 server
配置块中配置 ngx_realip 这样的模块,以避免上面介绍的这种棘手的例外情况。
运行在 preaccess
阶段之后的则是我们的另一个老朋友,access
阶段。前面我们已经知道了,标准模块ngx_access、第三方模块 ngx_auth_request 以及第三方模块 ngx_lua 的 access_by_lua 指令就运行在这个阶段。
access
阶段之后便是 post-access
阶段。从这个阶段的名字,我们也能一眼看出它是紧跟在 access
阶段后面执行的。这个阶段也和 post-rewrite
阶段类似,并不支持 Nginx 模块注册处理程序,而是由 Nginx 核心自己完成一些处理工作。post-access
阶段主要用于配合 access
阶段实现标准 ngx_http_core 模块提供的配置指令 satisfy 的功能。
对于多个 Nginx 模块注册在 access
阶段的处理程序,satisfy 配置指令可以用于控制它们彼此之间的协作方式。比如模块 A 和 B 都在 access
阶段注册了与访问控制相关的处理程序,那就有两种协作方式,一是模块 A 和模块 B 都得通过验证才算通过,二是模块 A 和模块 B 只要其中任一个通过验证就算通过。第一种协作方式称为 all
方式(或者说“与关系”),第二种方式则被称为 any
方式(或者说“或关系”)。默认情况下,Nginx 使用的是 all
方式。下面是一个例子:
location /test {
satisfy all;
deny all;
access_by_lua 'ngx.exit(ngx.OK)';
echo something important;
}
这里,我们在 /test
接口中同时配置了 ngx_access 模块和 ngx_lua 模块,这样 access
阶段就由这两个模块一起来做检验工作。其中,语句 deny all
会让 ngx_access 模块的处理程序总是拒绝当前请求,而语句access_by_lua 'ngx.exit(ngx.OK)'
则总是允许访问。当我们通过 satisfy 指令配置了 all
方式时,就需要access
阶段的所有模块都通过验证,但不幸的是,这里 ngx_access 模块总是会拒绝访问,所以整个请求就会被拒:
$ curl localhost:8080/test
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx</center>
</body>
</html>
细心的读者会在 Nginx 错误日志文件中看到类似下面这一行的出错信息:
[error] 6549\#0: *1 access forbidden by rule
然而,如果我们把上例中的 satisfy all
语句更改为 satisfy any
,
location /test {
satisfy any;
deny all;
access_by_lua 'ngx.exit(ngx.OK)';
echo something important;
}
结果则会完全不同:
$ curl localhost:8080/test
something important
即请求反而最终通过了验证。这是因为在 any
方式下,access
阶段只要有一个模块通过了验证,就会认为请求整体通过了验证,而在上例中,ngx_lua 模块的 access_by_lua 语句总是会通过验证的。
在配置了 satisfy any
的情况下,只有当 access
阶段的所有模块的处理程序都拒绝访问时,整个请求才会被拒,例如:
location /test {
satisfy any;
deny all;
access_by_lua 'ngx.exit(ngx.HTTP_FORBIDDEN)';
echo something important;
}
此时访问 /test
接口才会得到 403 Forbidden
错误页。这里,post-access
阶段参与了 access
阶段各模块处理程序的“或关系”的实现。
值得一提的是,上面这几个的例子需要 ngx_lua 0.5.0rc19 或以上版本;之前的版本是不能和 satisfy any
配置语句一起工作的。
Nginx 配置指令的执行顺序(十)的更多相关文章
- Nginx 配置指令的执行顺序(八)
前面我们详细讨论了 rewrite.access 和 content 这三个最为常见的 Nginx 请求处理阶段,在此过程中,也顺便介绍了运行在这三个阶段的众多 Nginx 模块及其配置指令.同时可以 ...
- Nginx 配置指令的执行顺序(五)
Nginx 的 content 阶段是所有请求处理阶段中最为重要的一个,因为运行在这个阶段的配置指令一般都肩负着生成“内容”(content)并输出 HTTP 响应的使命.正因为其重要性,这个阶段的配 ...
- Nginx 配置指令的执行顺序(一)
大多数 Nginx 新手都会频繁遇到这样一个困惑,那就是当同一个 location 配置块使用了多个 Nginx 模块的配置指令时,这些指令的执行顺序很可能会跟它们的书写顺序大相径庭.于是许多人选择了 ...
- Nginx配置指令的执行顺序
rewrite阶段 rewrite阶段是一个比较早的请求处理阶段,这个阶段的配置指令一般用来对当前请求进行各种修改(比如对URI和URL参数进行改写),或者创建并初始化一系列后续处理阶段可能需要的Ng ...
- Nginx 配置指令的执行顺序(六)
前面我们在 (五) 中提到,在一个 location 中使用 content 阶段指令时,通常情况下就是对应的 Nginx 模块注册该 location 中的“内容处理程序”.那么当一个 locati ...
- Nginx 配置指令的执行顺序(四)
ngx_lua 模块提供了配置指令 access_by_lua,用于在 access 请求处理阶段插入用户 Lua 代码.这条指令运行于 access 阶段的末尾,因此总是在 allow 和 deny ...
- Nginx 配置指令的执行顺序(三)
如前文所述,除非像 ngx_set_misc 模块那样使用特殊技术,其他模块的配置指令即使是在 rewrite 阶段运行,也不能和 ngx_rewrite 模块的指令混合使用.不妨来看几个这样的例子. ...
- Nginx 配置指令的执行顺序(二)
我们前面已经知道,当 set 指令用在 location 配置块中时,都是在当前请求的 rewrite 阶段运行的.事实上,在此上下文中,ngx_rewrite 模块中的几乎全部指令,都运行在 rew ...
- Nginx 配置指令的执行顺序
在一个 location 中使用 content 阶段指令时,通常情况下就是对应的 Nginx 模块注册该 location 中的“内容处理程序”.那么当一个 location 中未使用任何 cont ...
随机推荐
- java类中的static成员变量和static方法简单介绍,持续补充
一.静态成员变量 1.属于整个类而不是某个对象实例,所以可以直接通过类名和对象名去调用. 2.静态成员属于整个类,当系统第一次使用该类时,就会为其分配内存空间直到该类被卸载才会进行资源回收 二.静态方 ...
- H.数7(模拟)
1212: H.数7 时间限制: 1 Sec 内存限制: 64 MB 提交: 8 解决: 5 标签提交统计讨论版 题目描述 数7是一个简单的饭桌游戏,有很多人围成一桌,先从任意一人开始数数,1.2 ...
- CSS开发经验
1.尽量用class来定义样式.尽量少使用 .div1 ul li{}这样的样式下去,因为如果li里面还有<div><ul><li>这些元素的话会造成干扰,应该给 ...
- linux命令行常用快捷键
方向 <-前 后 ->删除ctrl + d 删除光标所在位置上的字符相当于VIM里x或者dlctrl + h 删除光标所在 ...
- Linux系统编程(20)——信号基本概念
信号及信号来源 信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的.信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知 ...
- 2014.8.4我出的模拟赛【NTR酋长】
NTR酋长 (ntr.pas/.c/.cpp) 黄巨大终于如愿以偿的进入了czy的后宫中……但是czy很生气……他要在黄巨大走到他面前的必经之路上放上几个NTR酋长来阻挡黄巨大. 众所周知,NTR酋长 ...
- oracle数据库时间转换
select * from TAB where 时间 BETWEEN to_date('2011-02-01 22:03:40','yyyy-mm-dd hh24:mi:ss') and to_dat ...
- ActionForward
一.只有登录才能显示的页面 这是一个很平常的问题,在访问某些网页的时候,只有登录才可以访问,以此保证安全. 实现原理也很简单,就是将一个属性设置在session中.在访问的时候进行判断即可. 例:re ...
- Old Sorting(转化成单调序列的最小次数,置换群思想)
Old Sorting Time Limit:2000MS Memory Limit:32768KB 64bit IO Format:%lld & %llu Submit S ...
- C# 数据的序列化存取
1,什么是序列化? 序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程.在序列化期间,对象将其当前状态写入到临时或持久性存储区.以后,可以通过从存储区中读取或反序列 ...