linuxsir首页 LinuxSir.Org | Linux、BSD、Solaris、Unix | 开源传万世,因有我参与欢迎您!
网站首页 | 设为首页 | 加入收藏
您所在的位置:主页 > Linux基础建设 >

Nginx执行阶段详细解析

时间:2019-03-11  来源:未知  作者:admin666

Nginx 介绍
Nginx (engine x) 是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器。
Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行。其特点是占有内存少,并发能力强
OpenResty介绍
OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关

执行阶段前言

location /test {
set $a 32;
echo $a;

set $a 56;
echo $a;
}

两次都会输出56,因为set阶段始终在content阶段之前执行,跟代码的先后顺序无关。

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 认为当前请求的来源地址是指定的某一个请求头的值。下面这个例子就使用了 ngx_realip 模块提供的 set_real_ip_from 和 real_ip_header

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

这里的配置是让 Nginx 把那些来自 127.0.0.1 的所有请求的来源地址,都改写为请求头 X-My-IP 所指定的值。同时该例使用了标准内建变量 $remote_addr 来输出当前请求的来源地址,以确认是否被成功改写。

$ curl -H 'X-My-IP: 1.2.3.4' localhost:8080/test
    from: 1.2.3.4

server-rewrite阶段

该阶段包含标准函数ngx_rewrite、set 以及openresty函数set_by_lua、rewrite_by_lua
post-read 阶段之后便是 server-rewrite 阶段。当 ngx_rewrite 模块的配置指令直接书写在 server 配置块中时,基本上都是运行在 server-rewrite 阶段。

server {
    listen 8080;

    location /test {
        set $b "$a, world";
        echo $b;
    }

    set $a hello;
}

这里,配置语句 set $a hello 直接写在了 server 配置块中,因此它就运行在 server-rewrite 阶段。而 server-rewrite 阶段要早于 rewrite 阶段运行,因此写在 location 配置块中的语句 set $b "$a, world" 便晚于外面的 set $a hello 语句运行。该例的测试结果证明了这一点:

$ curl localhost:8080/test
hello, world

find-config 阶段

这个阶段并不支持 Nginx 模块注册处理程序,而是由 Nginx 核心来完成当前请求与 location 配置块之间的配对工作。

location /hello {
    echo "hello world";
}

rewrite 阶段

该阶段包含标准函数set_unescape_uri、rewrite以及openresty函数set_by_lua、 rewrite_by_lua

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

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 其实也在这个阶段注册了处理程序

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 这样的模块

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.

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 状态码的。

content阶段

该阶段包含标准函数echo proxy_pass 以及openresty 函数content_by_lua balance_by_lua header_filter_by_lua body_filter_by_lua
log

所有请求的标准输出都在改阶段。几乎所有的逻辑代码也在改阶段执行。这个阶段比较常见

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 方式。

location /test {
    satisfy all;

    deny all;
    access_by_lua 'ngx.exit(ngx.OK)';

    echo something important;
}

如果我们把上例中的 satisfy all 语句更改为 satisfy any,

location /test {
    satisfy any;

    deny all;
    access_by_lua 'ngx.exit(ngx.O扎金花游戏K)';

    echo something important;
}

结果则会完全不同:

$ curl localhost:8080/test
something important

在 any 方式下,access 阶段只要有一个模块通过了验证,就会认为请求整体通过了验证,而在上例中, ngx_lua 模块的 access_by_lua 语句总是会通过验证的。

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 之类。

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*:会话完成后本地异步完成日志记录(日志可以记录在本地,还可以同步到其他机器)

在这里插入图片描述

Linux公社的RSS地址:https://www.linuxidc.com/rssFeed.aspx

友情链接
  • Mozilla发布Firefox 67.0.4,修复沙箱逃逸漏洞
  • 蚂蚁金服正式成为CNCF云原生计算基金会黄金会员
  • Firefox 68将采用Microsoft BITS安装更新
  • OpenSSH增加对存储在RAM中的私钥的保护
  • 谷歌想实现自己的curl,为什么?
  • Raspberry Pi 4发布:更快的CPU、更大的内存
  • Firefox的UA将移除CPU架构信息
  • Ubuntu放弃支持32位应用程序实属乌龙,Steam会否重回Ubuntu怀抱
  • Qt 5.13稳定版发布:引入glTF 2.0、改进Wayland以及支持Lottie动
  • 红帽企业Linux 7现已内置Redis 5最新版
  • Slack进入微软内部禁用服务清单,GitHub也在其列?
  • 安全的全新编程语言V发布首个可用版本
  • Windows Terminal已上架,快尝鲜
  • 阿里巴巴微服务开源生态报告No.1
  • 面世两年,Google地球将支持所有基于Chromium的浏览器
  • 推进企业容器化持续创新,Rancher ECIC千人盛典完美收官
  • CentOS 8.0最新构建状态公布,或于数周后发布
  • Debian移植RISC
  • 微软拆分操作系统的计划初现雏形
  • Oracle发布基于VS Code的开发者工具,轻松使用Oracle数据库
  • Ubuntu 19.10停止支持32位的x86架构
  • 微软为Windows Terminal推出全新logo
  • 联想ThinkPad P系列笔记本预装Ubuntu系统
  • 微软发布适用于Win7/8的Microsoft Edge预览版
  • 启智平台发布联邦学习开源数据协作项目OpenI纵横
  • 经过六个多月的延迟,微软终于推出Hyper
  • ZFS On Linux 0.8.1 发布,Python可移植性工作
  • DragonFly BSD 5.6.0 发布,HAMMER2状态良好
  • Linux Kernel 5.2
  • CentOS 8.0 看起来还需要几周的时间
  • 百度网盘Linux版正式发布
  • PCIe 6.0宣布:带宽翻倍 狂飙至256GB/s
  • PHP 7.4 Alpha 发布,FFI扩展,预加载Opcache以获得更好的性能
  • Canonical将在未来的Ubuntu版本中放弃对32位架构的支持
  • Scala 2.13 发布,改进的编译器性能
  • 微软的GitHub收购了Pull Panda,并且使所有订阅完全免费
  • Windows Subsystem for Linux 2 (WSL 2)现在适用于Windows 10用
  • Debian 10 “Buster”的RISC
  • MariaDB宣布发布MariaDB Enterprise Server 10.4
  • DXVK 1.2.2 发布,带来微小的CPU开销优化
  • DragonFlyBSD 5.6 RC1 发布,VM优化,默认为HAMMER2
  • PrimeNG 8.0.0 发布,支持Angular 8,FocusTrap等
  • GIMP 2.10.12 发布,一些有用的改进
  • 清华大学Anaconda 镜像服务即将恢复
  • Debian GNU/Linux 10 “Buster” 操作系统将于2019年7月6日发布
  • 时时彩论坛
  • 五星体育斯诺克
  • 北单比分直播
  • 河北11选5走势图
  • 福建体彩36选7开奖结果
  • 九龙图库下载