一、Nginx介绍
Nginx的产生
没有听过Nginx?那么一定听过它的"同行"Apache吧!Nginx同Apache一样都是一种WEB服务器。基于REST架构风格,以统一资源描述符(Uniform Resources Identifier)URI或者统一资源定位符(Uniform Resources Locator)URL作为沟通依据,通过HTTP协议提供各种网络服务。
然而,这些服务器在设计之初受到当时环境的局限,例如当时的用户规模,网络带宽,产品特点等局限并且各自的定位和发展都不尽相同。这也使得各个WEB服务器有着各自鲜明的特点。
Apache的发展时期很长,而且是毫无争议的世界第一大服务器。它有着很多优点:稳定、开源、跨平台等等。它出现的时间太长了,它兴起的年代,互联网产业远远比不上现在。所以它被设计为一个重量级的。它不支持高并发的服务器。在Apache上运行数以万计的并发访问,会导致服务器消耗大量内存。操作系统对其进行进程或线程间的切换也消耗了大量的CPU资源,导致HTTP请求的平均响应速度降低。
这些都决定了Apache不可能成为高性能WEB服务器,轻量级高并发服务器Nginx就应运而生了。
俄罗斯的工程师Igor Sysoev,他在为Rambler Media工作期间,使用C语言开发了Nginx。Nginx作为WEB服务器一直为Rambler Media提供出色而又稳定的服务。
然后呢,Igor Sysoev将Nginx代码开源,并且赋予自由软件许可证。
由于:
- Nginx使用基于事件驱动架构,使得其可以支持数以百万级别的TCP连接
- 高度的模块化和自由软件许可证是的第三方模块层出不穷(这是个开源的时代啊~)
- Nginx是一个跨平台服务器,可以运行在Linux,Windows,FreeBSD,Solaris, AIX,Mac OS等操作系统上
- 这些优秀的设计带来的极大的稳定性
所以,Nginx火了!
Nginx的用武之地
Nginx是一款自由的、开源的、高性能的HTTP服务器和反向代理服务器;同时也是一个IMAP、POP3、SMTP代理服务器;Nginx可以作为一个HTTP服务器进行网站的发布处理,另外Nginx可以作为反向代理进行负载均衡的实现。
关于代理
说到代理,首先我们要明确一个概念,所谓代理就是一个代表、一个渠道;
此时就设计到两个角色,一个是被代理角色,一个是目标角色,被代理角色通过这个代理访问目标角色完成一些任务的过程称为代理操作过程;如同生活中的专卖店~客人到adidas专卖店买了一双鞋,这个专卖店就是代理,被代理角色就是adidas厂家,目标角色就是用户。
正向代理
说反向代理之前,我们先看看正向代理,正向代理也是大家最常接触的到的代理模式,我们会从两个方面来说关于正向代理的处理模式,分别从软件方面和生活方面来解释一下什么叫正向代理。
在如今的网络环境下,我们如果由于技术需要要去访问国外的某些网站,此时你会发现位于国外的某网站我们通过浏览器是没有办法访问的,此时大家可能都会用一个操作FQ进行访问,FQ的方式主要是找到一个可以访问国外网站的代理服务器,我们将请求发送给代理服务器,代理服务器去访问国外的网站,然后将访问到的数据传递给我们!
上述这样的代理模式称为正向代理,正向代理最大的特点是客户端非常明确要访问的服务器地址;服务器只清楚请求来自哪个代理服务器,而不清楚来自哪个具体的客户端;正向代理模式屏蔽或者隐藏了真实客户端信息。来看个示意图(我把客户端和正向代理框在一块,同属于一个环境,后面我有介绍):
客户端必须设置正向代理服务器,当然前提是要知道正向代理服务器的IP地址,还有代理程序的端口。如图。
总结来说:正向代理,"它代理的是客户端",是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端必须要进行一些特别的设置才能使用正向代理。
正向代理的用途:
(1)访问原来无法访问的资源,如Google
(2)可以做缓存,加速访问资源
(3)对客户端访问授权,上网进行认证
(4)代理可以记录用户访问记录(上网行为管理),对外隐藏用户信息
反向代理
明白了什么是正向代理,我们继续看关于反向代理的处理方式,举例如我大天朝的某宝网站,每天同时连接到网站的访问人数已经爆表,单个服务器远远不能满足人民日益增长的购买欲望了,此时就出现了一个大家耳熟能详的名词:分布式部署;也就是通过部署多台服务器来解决访问人数限制的问题;某宝网站中大部分功能也是直接使用Nginx进行反向代理实现的,并且通过封装Nginx和其他的组件之后起了个高大上的名字:Tengine,有兴趣的童鞋可以访问Tengine的官网查看具体的信息:http://tengine.taobao.org/。那么反向代理具体是通过什么样的方式实现的分布式的集群操作呢,我们先看一个示意图(我把服务器和反向代理框在一块,同属于一个环境,后面我有介绍):
通过上述的图解大家就可以看清楚了,多个客户端给服务器发送的请求,Nginx服务器接收到之后,按照一定的规则分发给了后端的业务处理服务器进行处理了。此时~请求的来源也就是客户端是明确的,但是请求具体由哪台服务器处理的并不明确了,Nginx扮演的就是一个反向代理角色。
客户端是无感知代理的存在的,反向代理对外都是透明的,访问者并不知道自己访问的是一个代理。因为客户端不需要任何配置就可以访问。反向代理,"它代理的是服务端",主要用于服务器集群分布式部署的情况下,反向代理隐藏了服务器的信息。
反向代理的作用:
(1)保证内网的安全,通常将反向代理作为公网访问地址,Web服务器是内网
(2)负载均衡,通过反向代理服务器来优化网站的负载
项目场景
通常情况下,我们在实际项目操作时,正向代理和反向代理很有可能会存在在一个应用场景中,正向代理代理客户端的请求去访问目标服务器,目标服务器是一个反向单利服务器,反向代理了多台真实的业务处理服务器。具体的拓扑图如下:
二者区别
截了一张图来说明正向代理和反向代理二者之间的区别,如图。
图解:
在正向代理中,Proxy和Client同属于一个LAN(图中方框内),隐藏了客户端信息;
在反向代理中,Proxy和Server同属于一个LAN(图中方框内),隐藏了服务端信息;
实际上,Proxy在两种代理中做的事情都是替服务器代为收发请求和响应,不过从结构上看正好左右互换了一下,所以把后出现的那种代理方式称为反向代理了。
负载均衡
我们已经明确了所谓代理服务器的概念,那么接下来,Nginx扮演了反向代理服务器的角色,它是以依据什么样的规则进行请求分发的呢?不用的项目应用场景,分发的规则是否可以控制呢?
这里提到的客户端发送的、Nginx反向代理服务器接收到的请求数量,就是我们说的负载量。
请求数量按照一定的规则进行分发到不同的服务器处理的规则,就是一种均衡规则。
所以~将服务器接收到的请求按照规则分发的过程,称为负载均衡。
负载均衡在实际项目操作过程中,有硬件负载均衡和软件负载均衡两种,硬件负载均衡也称为硬负载,如F5负载均衡,相对造价昂贵成本较高,但是数据的稳定性安全性等等有非常好的保障,如中国移动中国联通这样的公司才会选择硬负载进行操作;更多的公司考虑到成本原因,会选择使用软件负载均衡,软件负载均衡是利用现有的技术结合主机硬件实现的一种消息队列分发机制。
Nginx支持的负载均衡调度算法方式如下:
- weight轮询(默认):接收到的请求按照顺序逐一分配到不同的后端服务器,即使在使用过程中,某一台后端服务器宕机,Nginx会自动将该服务器剔除出队列,请求受理情况不会受到任何影响。 这种方式下,可以给不同的后端服务器设置一个权重值(weight),用于调整不同的服务器上请求的分配率;权重数据越大,被分配到请求的几率越大;该权重值,主要是针对实际工作环境中不同的后端服务器硬件配置进行调整的。
- ip_hash:每个请求按照发起客户端的ip的hash结果进行匹配,这样的算法下一个固定ip地址的客户端总会访问到同一个后端服务器,这也在一定程度上解决了集群部署环境下session共享的问题。
- fair:智能调整调度算法,动态的根据后端服务器的请求处理到响应的时间进行均衡分配,响应时间短处理效率高的服务器分配到请求的概率高,响应时间长处理效率低的服务器分配到的请求少;结合了前两者的优点的一种调度算法。但是需要注意的是Nginx默认不支持fair算法,如果要使用这种调度算法,请安装upstream_fair模块。
- url_hash:按照访问的url的hash结果分配请求,每个请求的url会指向后端固定的某个服务器,可以在Nginx作为静态服务器的情况下提高缓存效率。同样要注意Nginx默认不支持这种调度算法,要使用的话需要安装Nginx的hash软件包。
几种常用web服务器对比
二、Nginx的配置指令执行的顺序 11 个阶段
执行阶段前言
location /test {
set $a 32;
echo $a;
set $a 56;
echo $a;
}
两次都会输出56,因为set阶段始终在content阶段之前执行,跟代码的先后顺序无关。
Nginx的配置指令执行不是按照配置的先后顺序执行,二十分为11 个阶段:post-read、server-rewrite、find-config、rewrite、post-rewrite、preaccess、access、post-access、try-files、content 、log , Nginx配置文件中的所有指令是按照上面11个阶段的顺讯执行。
1阶段:post-read
该阶段是在Nginx读取并解析完请求头(request headers)之后就立即开始执行的,标准模块的ngx_realip就是在post-read阶段注册了的处理程序。它的功能是迫使 Nginx 认为当前请求的来源地址是指定的某一个请求头的值。
ngx_realip 主要有三个指令 set_real_ip_from,real_ip_header,real_ip_recursive 。这三个指令可以直接配置在server下,下面是ngx_realip官网 列举的一个配置的示例。
set_real_ip_from 192.168.1.0/24;
set_real_ip_from 192.168.2.1;
set_real_ip_from 2001:0db8::/32;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
set_real_ip_from的值可以是一个ip,也可以是一个ip段,表示当请求是某一个ip时,使用real_ip_header的值替换
remote_add中的值,并将之保存在realip_remote_addr 和 realip_remote_port中。
log_format main '"$remote_add" - "$remote_user" - "$realip_remote_addr" - "$realip_remote_port"'
server {
listen 9090;
access_log logs/access_9090.log main;
set_real_ip_from 127.0.0.1;
real_ip_header IP;
location /first {
rewrite ^(.*)$ /index.html break;
}
}
运行命令
curl -H"IP: 192.2.2.2" http://localhost:9090/first
查看日志其中的remote_addr 已经改为 192.2.2.2,而 $realip_remote_addr 是127.0.0.1。 当Header IP是非法的IP地址,则remote_addr 不改变。
** 注意 real_ip_header 的默认值为 X-Real-IP
下面我们着重看一下 real_ip_recursive(默认值为off)
1) real_ip_header 的http header中仅有一个ip时,则直接替换remote_addr。
2) real_ip_header 的http header 中有多个ip时
- real_ip_recursive 为off时, 则用第一个ip替换remote_addr
- real_ip_recursive 为on时, 则用第一个不在set_real_ip_from 的ip替换remote_addr
例如nginx配置如下:
set_real_ip_from 127.0.0.1;
set_real_ip_from 192.178.2.2;
real_ip_header IP;
real_ip_recursive on;
请求命令为
curl -H"IP: 192.168.2.3 192.178.2.2" http://localhost:8080/first
remote_addr 被替换为 192.168.2.3 (注意这里的顺序是从右至左)。当real_ip_recursive为off时 remote_addr被替换为 192.178.2.2
注意: ngx_realip的三个指令都放在 server下,不要放在location下,我在mac 1.15.5上进行测试时,location下的配置是不生效的。
2阶段:server-rewrite
由于 server-rewrite 阶段位于 post-read 阶段之后,所以 server 配置块中的 set 指令也就总是运行在 ngx_realip 模块改写请求的来源地址之后。
当 ngx_rewrite 模块的配置指令直接书写在 server 配置块中时,都是运行在 server-rewrite 阶段。
ngx_rewrite的指令主要有一下几个:
break
if
return
rewrite
rewrite_log
set
uninitialized_variable_warn
例如nginx有以下配置
log_format main '"$remote_addr" - "$b"';
server {
access_log logs/host_8080.log main;
listen 8080;
location /first {
set $a "$b world";
rewrite ^(.*)$ /index.html;
}
set $b "Hello";
}
访问/first之后,日志中输出 Hello world;
这里,配置语句 set $b "Hello"; 直接写在了 server 配置块中,因此它就运行在 server-rewrite 阶段。而 server-rewrite 阶段要早于 rewrite 阶段运行,因此写在 location 配置块中的语句 set $a "$b, world" 便晚于外面的 set $a hello 语句运行。该例的测试结果证明了这一点:
$ curl localhost:8080/test
hello, world
后面再rewrite阶段会详细讲解这几个命令。
3阶段:find-config
find-config 阶段不支持NGINX模块注册处理程序,而是由Nginx核心模块来完成当前请求的URL与location配置块之间的匹配工作。
location /hello {
echo "hello world";
}
在Nginx接收到请求到此阶段之前,location配置块中包括locaction指令本身的所有指令都没有执行。也就是说,post-read和server-rewrite阶段执行的仅仅是server配置块以及更外层作用域中的配置指令。
4阶段:rewrite
rewrite阶段又可以分为两个小的阶段
1> 在server配置块中,按照顺序执行ngx_rewrite模块的指令
2> 在find-config阶段,nginx内核根据server配置块中所有location的配置为请求匹配到合适的location配置块,
在rewrite阶段,location配置块中的配置的ngx_rewrite模块的指令将被执行。
下面我们详细看一下ngx_rewrite中的7个指令:
1) rewrite (基本语法: rewrite regex replacement [flag]; 可以配置在server,location和if配置块下)
regex是RCPE风格的,如果regex匹配URI,nameURI就会被替换成replacement,replacement就是新的URI,如果同一个配置块下有多个rewrite配置指令,匹配并不会终止,而是继续用新的URI(也就是上一次匹配到的replacement)继续进行匹配,直至返回最后一个匹配。如果在匹配到之后想要停止匹配直接返回,可是使用flag参数(将其设置为break)。
注意 : 如果匹配到replacement中有关于协议的东西,例如http://或者https://等,处理将终止,直接返回客户端302(Temporary redirect)。
如果Nginx返回给客户端30x,浏览器在接收到这个状态码时,会根据response中的Location响应头再发起一次请求;如果不是30x状态码,所有的跳转只是在nginx内部完成,有兴趣的可以将nginx的日志调为debug看一下整个跳转过程。
举一个例子
location /first {
rewrite ^(.*)$ /test;
rewrite /test /index.html;
rewrite /test /second.html;
}
当请求 curl -I http://localhost:8080/first
时,nginx接到请求之后,匹配到/first 根据第一个rewrite指令,将URI替换为/test, 然后继续执行第二个rewrite指令,将URI替换为/index.html,之后继续执行第三个指令,发现regex(/test)与新的URI(/index.html)并不匹配,所以最终我们将会看到index.html的返回结果。
开启debug日志,可以看到详细的跳转过程。
再举一例:
server {
rewrite ^(.*)$ https://$host:$server_port$request_uri;
location /first {
rewrite ^(.*)$ /test;
}
}
当请求curl -I http://localhost:8080/first
时,首先会执行server块下的rewrite,所有请求都返回302 (Location:https://127.0.0.1:8080/first),浏览器在接收到这个302 状态码时,会根据response中的Location响应头再发起一次请求。
下面讲一下rewrite的第三个参数flag:
1.1 ) last
如果有last参数,那么停止处理任何rewrite相关的指令,立即用替换后的新URI开始下一轮的location匹配。
1.2) break
停止处理任何rewrite的相关指令,就如同break 指令本身一样。
last的break的相同点在于,立即停止执行所有当前上下文的rewrite模块指令;不同点在于last参数接着用新的URI马上搜寻新的location,而break不会搜寻新的location,直接用这个新的URI来处理请求,这样能避免重复rewite。因此,在server上下文中使用last,而在location上下文中使用break。
1.3) redirect
replacement 如果不包含协议,仍然是一个新的的URI,那么就用新的URI匹配的location去处理请求,不会返回30x跳转。但是redirect参数可以让这种情况也返回30x(默认302)状态码,就像新的URI包含http://和https://等一样。这样的话,浏览器看到302,就会再发起一次请求,真正返回响应结果的就是这第二个请求
1.4) permanent
和redirect参数一样,只不过直接返回301永久重定向
虽说URI有了新的,但是要拼接成完整的URL还需要当前请求的scheme,以及由server_name_in_redirect和port_in_redirect指令决定的HOST和PORT.
还有一个比较有意思的应用,就是如果replacement中包含请求参数,那么默认情况下旧URI中的请求参数也会拼接在replacement后面作为新的URI,如果不想这么做,可以在replacement的最后面加上?。
2) break (基本语法: break; 可以配置在server,location 和if配置块下)
停止任何处理rewrite的相关指令。如果出现在location里面,name所有后面的rewrite模块都不会执行,也不发起内部重定向,而是直接用新的URI处理请求。
例如有如下配置
server {
rewrite ^/server/break$ /hello1 break;
rewrite ^/server/last$ /hello1 last;
location /hello {
rewrite ^(.*)$ /hello1;
break;
}
location hello1 {
rewrite ^(.*)$ /index.html last;
}
location / {
root html;
}
}
访问 curl -I http://localhost:8080/server/break
,将返回404;
请求curl -I http://localhost:8080/server/last
将返回index.html
访问 curl -I http://localhost:8080/hello
,将返回 404;
请求 curl -I http://localhost:8080/hello1
,将返回index.html。
综合以上的实验,可以肯定的是:
- 在server中配置块中如果使用break指令,下面所有的location将不会执行,而last会进行下一次内部跳转。
- 在location中的break则是直接退出,返回URI的结果; last 进入下一次内部跳转,该location内的rewrite配置指令不再执行。
3) if (基本语法:if(condition) {....}) 可以配置在server和location配置块中
根据condition是true和false决定是否加载 花括号中的配置,花括号中的配置可以继承外面的配置,也可以对外面的指令进行重写。
condition 可以是自定义变量或者系统变量本身,也可以是表达式:
a. condition 为变量本身时, 0: false 非0 : true
b. 变量可以通过"=" 、"!=" 与字符串比较
c. 匹配正则表达式 ~
!~
(大小写敏感) ~*
!~*
(大小写不敏感)
d. -f
-d
-e
-x
!-f
!-d
!-e
!-x
等检验文件或者目录存在或者文件属性
4) return (基本语法: return code [text]; return code URL; return URL; 可以配置在server,location和if配置块中)
停止任何的进一步处理,并且将指定状态码返回给客户端。如果状态码为444(此状态码是非标准的),那么直接关闭此TCP连接。
return的参数有四种形式:
return code
此时,响应内容就是nginx所默认的,比如503 Service Temporarily Unavailable; 如果是444那就直接关闭TCP连接,也可以是其他值(644等),但是没啥意义return code text
因为要带响应内容,因此code不能是具有跳转功能的30xreturn code URL
此时URI可以为URI做内部跳转,也可以是具有“[http://”或者“https://”等协议的绝对URL,直接返回客户端,而code是30x(301, 302, 303, 307,308)return URL
此时code默认为302,而URL必须是带“http://”等协议的绝对URL
5) set (基本语法: set $var value; 可以配置在server,location和if配置块中)
这个指令可以用来自定义变量,也可以改变系统变量的值。
6) rewrite_log (基本语法: rewrite_log on | off; 可以配置在http,server,location和if配置块中)
如果开启on,name当rewrite时,会产生一个notice基本的日志;否则不产生日志。可以在调试的时候将其设置为on。
5阶段:post-rewrite
post-rewrite 阶段,不接受 Nginx 模块注册处理程序,而是由 Nginx 核心完成 rewrite 阶段所要求的“内部跳转”操作
“内部跳转”的工作原理:
本质上其实就是把当前的请求处理阶段强行倒退到 find-config 阶段,以便重新进行请求 URI 与 location 配置块的配对。
比如例中,运行在 rewrite 阶段的 rewrite 指令就让当前请求的处理阶段倒退回了 find-config 阶段。由于此时当前请求的 URI 已经被 rewrite 指令修改为了 /bar,所以这一次换成了 location /bar 与当前请求相关联,然后再接着从 rewrite 阶段往下执行。
为什么不直接在 rewrite 指令执行时立即进行跳转呢?
为了在最初匹配的 location 块中支持多次反复地改写 URI
server {
listen 8080;
location /foo {
set $a hello;
rewrite ^ /bar;
}
location /bar {
echo "a = [$a]";
}
}
location /foo {
rewrite ^ /bar;
rewrite ^ /baz;
echo foo;
}
location /bar {
echo bar;
}
location /baz {
echo baz;
}
注意的:如果在 server 配置块中直接使用 rewrite 配置指令对请求 URI 进行改写,则不会涉及“内部跳转”
server {
listen 8080;
rewrite ^/foo /bar;
location /foo {
echo foo;
}
location /bar {
echo bar;
}
}
另外:
post-rewrite阶段和find-config阶段一样,没有Nginx模块注册处理程序,由nginx核心完成rewrite阶段所要求的内部跳转操作(当rewrite阶段有内部跳转的配置指令时)。内部跳转本质上是跳转到find-config阶段,使用新的URI重新匹配location。
例如nginx有如下配置:
server {
listen 8080;
location /first {
rewrite ^ /index.html;
}
location / {
root html;
}
}
请求 curl -I http://localhost:8080/first
时,先找到/first
location,在/first
location中rewrite URI 为/index.html进入post-rewrite阶段,进行一次内部跳转,回到find-config阶段,使用新的/index.html做为URI重新匹配location至 location /
。
这里有一个问题,为什么不在rewrite阶段执行内部跳转?答案就是为了在一个location中支持反复改写URI。试想一下如果有以下配置。
location /first {
rewrite ^ /second;
rewrite /second /third;
}
如果在rewrite阶段直接做内部跳转,那么 第二个rewrite将不能执行,我们必须再声明一个 /second
location进行URI 重写。
想证实这一点很简单,我们做一个简单的例子:
server {
listen 8080;
location /first {
rewrite ^ /second;
rewrite /second /third;
}
location /second {
return 200 "second content";
}
location /third {
return 200 "third content";
}
}
请求curl http://localhost:8080/first
之后将得到 "third content"相应值,同时可以在日志中看到如下日志。
using configuration /first
using configuration /third
注意一下,server配置块下的rewrite命令在server-rewrite阶段执行。
6阶段:preaccess
该阶段包含标准函数ngx_access-allow deny ngx_limit_req 和 ngx_limit_zone ngx_auth_request,以及openresty函数access_by_lua其中也包含了限频限流模块resty.limit.req resty.limit.conn
limit_req_zone 可以控制请求的访问频度,即速率限制,采用的漏桶算法 “leaky bucket”;limit_req_conn 可以限制访问的并发度,可以根据源IP限制单用户并发访问的连接数或连接到该服务的总并发连接数。
注意的是:标准模块 ngx_realip 其实也在这个阶段注册了处理程序
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 相关联。
建议是:尽量在 server 配置块中配置 ngx_realip 这样的模块
7阶段:access
标准模块 ngx_http_access_module、第三方模块 ngx_auth_request 、 ngx_lua 就运行在这个阶段。
ngx_http_access_module主要有两个指令:
- deny (基本语法deny address | CIDR | unix: | all; 可以配置在 http, server, location, limit_except)
- allow (基本语法allow address | CIDR | unix: | all; 可以配置在 http, server, location, limit_except)
8阶段:post-access
post-access 阶段主要用于配合 access 阶段实现标准 ngx_http_core 模块提供的配置指令 satisfy 的功能。
对于多个 Nginx 模块注册在 access 阶段的处理程序, satisfy 配置指令可以用于控制它们彼此之间的协作方式。
比如模块 A 和 B 都在 access 阶段注册了与访问控制相关的处理程序,那就有两种协作方式,一是模块 A 和模块 B 都得通过验证才算通过,二是模块 A 和模块 B 只要其中任一个通过验证就算通过。第一种协作方式称为 all 方式(或者说“与关系”),第二种方式则被称为 any 方式(或者说“或关系”)。默认情况下,Nginx 使用的是 all 方式。下面是一个例子:
location /post_access {
satisfy all;
deny all;
access_by_lua 'ngx.exit(ngx.OK)' ;
return 200 "hello world";
}
在接口/post_access 中同时配置了ngx_access 和ngx_lua两个模块,这样access阶段就由两个模块一起检查,其中deny all 会让ngx_access模块处理程序拒绝当前请求,而语句access_by_lua 'ngx.exit(ngx.OK)' ; 则总是允许访问。当satify指令为all时,当前的请求会被拒绝,返回403(forbidden)。
如果我们把上例中的 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 阶段只要有一个模块通过了验证,就会认为请求整体通过了验证。
9阶段: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.
location /test {
try_files /foo /bar/ /baz;
echo "uri: $uri";
}
location /foo {
echo foo;
}
location /bar/ {
echo bar;
}
location /baz {
echo baz;
}
我们在 location /test 中使用了 try_files 配置指令,并提供了三个参数,/foo、/bar/ 和 /baz. 根据前面对 try_files 指令的介绍,我们可以知道,它会在 try-files 阶段依次检查前两个参数 /foo 和 /bar/ 所对应的文件系统对象是否存在。
不妨先来做一组实验。假设现在 /var/www/ 路径下是空的,则第一个参数 /foo 映射成的文件 /var/www/foo 是不存在的;同样,对于第二个参数 /bar/ 所映射成的目录 /var/www/bar/ 也是不存在的。于是此时 Nginx 会在 try-files 阶段发起到最后一个参数所指定的 URI(即 /baz)的“内部跳转”。实际的请求结果证实了这一点:
$ curl localhost:8080/test
baz
接下来再做一组实验:在 /var/www/ 下创建一个名为 foo 的文件,其内容为 hello world(注意你需要有 /var/www/ 目录下的写权限):
$ echo 'hello world' > /var/www/foo
然后再请求 /test 接口:
$ curl localhost:8080/test
uri: /foo
这里发生了什么?我们来看, try_files 指令的第一个参数 /foo 可以映射为文件 /var/www/foo,而 Nginx 在 try-files 阶段发现此文件确实存在,于是立即把当前请求的 URI 改写为这个参数的值,即 /foo,并且不再继续检查后面的参数,而直接运行后面的请求处理阶段。
通过前面这几组实验不难看到, try_files 指令本质上只是有条件地改写当前请求的 URI,而这里说的“条件”其实就是文件系统上的对象是否存在。当“条件”都不满足时,它就会无条件地发起一个指定的“内部跳转”。当然,除了无条件地发起“内部跳转”之外,
try_files 指令还支持直接返回指定状态码的 HTTP 错误页,例如:
try_files /foo /bar/ =404;
这行配置是说,当 /foo 和 /bar/ 参数所对应的文件系统对象都不存在时,就直接返回 404 Not Found 错误页。注意这里它是如何使用等号字符前缀来标识 HTTP 状态码的。
10阶段:content
该阶段包含标准函数echo proxy_pass,以及openresty 函数content_by_lua balance_by_lua header_filter_by_lua body_filter_by_lua
content阶段主要是处理内容输出的阶段,是整个模型中比较靠后的阶段,但这个阶段是非常重要的一个阶段,有大量的模块与指令,例如proxy,三方的echo,content_by_lua等模块都是在这个阶段执行。注意Nginx 模块在向 content 阶段注册配置指令时,其实就是向location 配置块中注册所谓的“内容处理程序”(content handler)。
每一个 location 只能有一个“内容处理程序”,因此,当在 location 中同时使用多个模块的 content 阶段指令时,只有其中一个模块能成功注册“内容处理程序”。例如 echo 和 content_by_lua 如果同时注册,最终只会有一个生效,但具体是哪一个生效是不稳定的。
proxy模块是Nignx一个重要的功能,会在后面单独说明。
ngx_index 模块, ngx_autoindex 模块,以及 ngx_static 模块
Nginx 一般会在 content 阶段安排三个这样的静态资源服务模块。按照它们在 content 阶段的运行顺序,依次是 ngx_index 模块, ngx_autoindex 模块,以及 ngx_static 模块。
ngx_index 和 ngx_autoindex 模块都只会作用于那些 URI 以 / 结尾的请求,例如请求 GET /cats/,而对于不以 / 结尾的请求则会直接忽略,同时把处理权移交给 content 阶段的下一个模块。而 ngx_static 模块则刚好相反,直接忽略那些 URI 以 / 结尾的请求。
ngx_index 模块主要用于在文件系统目录中自动查找指定的首页文件,类似 index.html 和 index.htm 这样的,例如:
location / {
root /var/www/;
index index.htm index.html;
}
为了进一步确认 ngx_index 模块在找到文件时的“内部跳转”行为,我们不妨设计下面这个小例子:
location / {
root /var/www/;
index index.html;
}
location /index.html {
set $a 32;
echo "a = $a";
}
此时我们在本机的 /var/www/ 目录下创建一个空白的 index.html 文件,并确保该文件的权限设置对于运行 Nginx worker 进程的帐户可读
$ curl 'http://localhost:8080/'
a = 32
如果此时把 /var/www/index.html 文件删除,再访问 / 又会发生什么事情呢?答案是返回 403 Forbidden 出错页。为什么呢?因为 ngx_index 模块找不到 index 指令指定的文件(在这里就是 index.html),接着把处理权转给 content 阶段的后续模块,而后续的模块也都无法处理这个请求,于是 Nginx 只好放弃,输出了错误页
运行在 ngx_index 模块之后的 ngx_autoindex 模块就可以用于自动生成这样的“目录索引”网页。我们来把上例修改一下:
location / {
root /var/www/;
index index.html;
autoindex on;
}
此时仍然保持文件系统中的 /var/www/index.html 文件不存在。我们再访问 / 位置时,就会得到一张漂亮的网页:
$ curl 'http://localhost:8080/'
ngx_static 模块服务磁盘文件的例子。我们使用下面这个配置片段:
location / {
root /var/www/;
}
现在来通过 HTTP 协议请求一下这两个文件所对应的 URI:
$ curl 'http://localhost:8080/index.html'
this is my home
$ curl 'http://localhost:8080/hello.html'
hello world
location / 中没有使用运行在 content 阶段的模块指令,于是也就没有模块注册这个 location 的“内容处理程序”,处理权便自动落到了在 content 阶段“垫底”的那 3 个静态资源服务模块。首先运行的 ngx_index 和 ngx_autoindex 模块先后看到当前请求的 URI,/index.html 和 /hello.html,并不以 / 结尾,于是直接弃权,将处理权转给了最后运行的 ngx_static 模块。ngx_static 模块根据 root 指令指定的“文档根目录”(document root),分别将请求 URI /index.html 和 /hello.html 映射为文件系统路径 /var/www/index.html 和 /var/www/hello.html,在确认这两个文件存在后,将它们的内容分别作为响应体输出,并自动设置 Content-Type、Content-Length 以及 Last-Modified 等响应头。
很多初学者会想当然地把 404 错误理解为某个 location 不存在,其实上面这个例子表明,即使 location 存在并成功匹配,也是可能返回 404 错误页的。因为决定着 404 错误页的是抽象的“资源”是否存在,而非某个具体的 location 是否存在。
location /auth {
access_by_lua '
';
}
显然,这个 /auth 接口只定义了 access 阶段的配置指令,即 access_by_lua,并未定义任何 content 阶段的配置指令。于是当我们请求 /auth 接口时,在 access 阶段的 Lua 代码会如期执行,然后 content 阶段的那些静态文件服务会紧接着自动发生作用,直至 ngx_static 模块去文件系统上找名为 auth 的文件。而经常地,404 错误页会抛出,除非运气太好,在对应路径上确实存在一个叫做 auth 的文件。所以,一条经验是,当遇到意外的 404 错误并且又不涉及静态文件服务时,应当首先检查是否在对应的 location 配置块中恰当地配置了 content 阶段的模块指令,例如 content_by_lua、 echo 以及 proxy_pass 之类。
11阶段:log
改阶段包含ngx的acces_log error_log以及openresty函数log_by_lua
该阶段主要记录日志
其他
openresty请求处理顺序
set_by_lua: 流程分支处理判断变量初始化
rewrite_by_lua: 转发、重定向、缓存等功能(例如特定请求代理到外网)
access_by_lua: IP 准入、接口权限等情况集中处理(例如配合 iptable 完成简单防火墙)
content_by_lua: 内容生成
header_filter_by_lua: 响应头部过滤处理(例如添加头部信息)
body_filter_by_lua: 响应体过滤处理(例如完成应答内容统一成大写) log_by_lua*:会话完成后本地异步完成日志记录(日志可以记录在本地,还可以同步到其他机器)
参考:
https://www.jianshu.com/p/da7a6e628bb4
https://www.linuxidc.com/Linux/2019-03/157394.htm
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)