热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

nginx源码分析--多阶段请求处理

读取完请求头后,nginx进入请求的处理阶段(请求体的读取已经在多阶段处理中了)。简单的情况下,客户端发送过的统一资源定位符(url)对应服务器上某一路径上的资源,web服务器需要做的仅仅是将url映

读取完请求头后,nginx进入请求的处理阶段(请求体的读取已经在多阶段处理中了)。简单的情况下,客户端发送过的统一资源定位符(url)对应服务器上某一路径上的资源,web服务器需要做的仅仅是将url映射到本地文件系统的路径,然后读取相应文件并返回给客户端。但这仅仅是最初的互联网的需求,而如今互联网出现了各种各样复杂的需求,要求web服务器能够处理诸如安全及权限控制,多媒体内容和动态网页等等问题。这些复杂的需求导致web服务器不再是一个短小的程序,而变成了一个必须经过仔细设计,模块化的系统。nginx良好的模块化特性体现在其对请求处理流程的多阶段划分当中,多阶段处理流程就好像一条流水线,一个nginx进程可以并发的处理处于不同阶段的多个请求。nginx允许开发者在处理流程的任意阶段注册模块,在启动阶段,nginx会把各个阶段注册的所有模块处理函数按序的组织成一条执行链。


nginx实际把请求处理流程划分为了11个阶段,这样划分的原因是将请求的执行逻辑细分,各阶段按照处理时机定义了清晰的执行语义,开发者可以很容易分辨自己需要开发的模块应该定义在什么阶段,下面介绍一下各阶段:

NGX_HTTP_POST_READ_PHASE: 接收完请求头之后的第一个阶段,它位于uri重写之前,实际上很少有模块会注册在该阶段,默认的情况下,该阶段被跳过;

NGX_HTTP_SERVER_REWRITE_PHASE: server级别的uri重写阶段,也就是该阶段执行处于server块内,location块外的重写指令,前面的章节已经说明在读取请求头的过程中nginx会根据host及端口找到对应的虚拟主机配置;

NGX_HTTP_FIND_CONFIG_PHASE: 寻找location配置阶段,该阶段使用重写之后的uri来查找对应的location,值得注意的是该阶段可能会被执行多次,因为也可能有location级别的重写指令;

NGX_HTTP_REWRITE_PHASE: location级别的uri重写阶段,该阶段执行location基本的重写指令,也可能会被执行多次;

NGX_HTTP_POST_REWRITE_PHASE: location级别重写的后一阶段,用来检查上阶段是否有uri重写,并根据结果跳转到合适的阶段;

NGX_HTTP_PREACCESS_PHASE: 访问权限控制的前一阶段,该阶段在权限控制阶段之前,一般也用于访问控制,比如限制访问频率,链接数等;

NGX_HTTP_ACCESS_PHASE: 访问权限控制阶段,比如基于ip黑白名单的权限控制,基于用户名密码的权限控制等;

NGX_HTTP_POST_ACCESS_PHASE: 访问权限控制的后一阶段,该阶段根据权限控制阶段的执行结果进行相应处理;

NGX_HTTP_TRY_FILES_PHASE: try_files指令的处理阶段,如果没有配置try_files指令,则该阶段被跳过;

NGX_HTTP_CONTENT_PHASE:内容生成阶段,该阶段产生响应,并发送到客户端;

NGX_HTTP_LOG_PHASE: 日志记录阶段,该阶段记录访问日志;


多阶段执行链

nginx按请求处理的执行顺序将处理流程划分为多个阶段,一般每个阶段又可以注册多个模块处理函数,nginx按阶段将这些处理函数组织成了一个执行链,这个执行链保存在http主配置(ngx_http_core_main_conf_t)的phase_engine字段中,phase_engine字段的类型为ngx_http_phase_engine_t:

[cpp] view plaincopy
  1. typedef struct {  
  2.     ngx_http_phase_handler_t  *handlers;  
  3.     ngx_uint_t                 server_rewrite_index;  
  4.     ngx_uint_t                 location_rewrite_index;  
  5. } ngx_http_phase_engine_t;  
其中handlers字段即为执行链,实际上它是一个数组,而每个元素之间又被串成链表,从而允许执行流程向前,或者向后的阶段跳转,执行链节点的数据结构定义如下:
[cpp] view plaincopy
  1. struct ngx_http_phase_handler_s {  
  2.     ngx_http_phase_handler_pt  checker;  
  3.     ngx_http_handler_pt        handler;  
  4.     ngx_uint_t                 next;  
  5. };  
其中checker和handler都是函数指针,相同阶段的节点具有相同的checker函数,handler字段保存的是模块处理函数,一般在checker函数中会执行当前节点的handler函数,但是例外的是NGX_HTTP_FIND_CONFIG_PHASE,NGX_HTTP_POST_REWRITE_PHASE,NGX_HTTP_POST_ACCESS_PHASE和NGX_HTTP_TRY_FILES_PHASE这4个阶段不能注册模块函数。next字段为快速跳跃索引,多数情况下,执行流程是按照执行链顺序的往前执行,但在某些执行阶段的checker函数中由于执行了某个逻辑可能需要回跳至之前的执行阶段,也可能需要跳过之后的某些执行阶段,next字段保存的就是跳跃的目的索引。

和建立执行链相关的数据结构都保存在http主配置中,一个是phases字段,另外一个是phase_engine字段。其中phases字段为一个数组,它的元素个数等于阶段数目,即每个元素对应一个阶段。而phases数组的每个元素又是动态数组(ngx_array_t),每次模块注册处理函数时只需要在对应阶段的动态数组增加一个元素用来保存处理函数的指针。由于在某些执行阶段可能需要向后,或者向前跳转,简单的使用2个数组并不方便,所以nginx又组织了一个执行链,保存在了phase_engine字段,其每个节点包含一个next域用来保存跳跃目的节点的索引,而执行链的建立则在nginx初始化的post config阶段之后调用ngx_http_init_phase_handlers函数完成,下面分析一下该函数:

[cpp] view plaincopy
  1. static ngx_int_t  
  2. ngx_http_init_phase_handlers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)  
  3. {  
  4.     ngx_int_t                   j;  
  5.     ngx_uint_t                  i, n;  
  6.     ngx_uint_t                  find_config_index, use_rewrite, use_access;  
  7.     ngx_http_handler_pt        *h;  
  8.     ngx_http_phase_handler_t   *ph;  
  9.     ngx_http_phase_handler_pt   checker;  
  10.   
  11.     cmcf->phase_engine.server_rewrite_index = (ngx_uint_t) -1;  
  12.     cmcf->phase_engine.location_rewrite_index = (ngx_uint_t) -1;  
  13.     find_config_index = 0;  
  14.     use_rewrite = cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers.nelts ? 1 : 0;  
  15.     use_access = cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers.nelts ? 1 : 0;  
  16.   
  17.     n = use_rewrite + use_access + cmcf->try_files + 1 /* find config phase */;  
  18.   
  19.     for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) {  
  20.         n += cmcf->phases[i].handlers.nelts;  
  21.     }  
  22.   
  23.     ph = ngx_pcalloc(cf->pool,  
  24.                      n * sizeof(ngx_http_phase_handler_t) + sizeof(void *));  
  25.     if (ph == NULL) {  
  26.         return NGX_ERROR;  
  27.     }  
  28.   
  29.     cmcf->phase_engine.handlers = ph;  
  30.     n = 0;  
  31.   
  32.     for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) {  
  33.         h = cmcf->phases[i].handlers.elts;  
  34.   
  35.         switch (i) {  
  36.   
  37.         case NGX_HTTP_SERVER_REWRITE_PHASE:  
  38.             if (cmcf->phase_engine.server_rewrite_index == (ngx_uint_t) -1) {  
  39.                 cmcf->phase_engine.server_rewrite_index = n;  
  40.             }  
  41.             checker = ngx_http_core_rewrite_phase;  
  42.   
  43.             break;  
  44.   
  45.         case NGX_HTTP_FIND_CONFIG_PHASE:  
  46.             find_config_index = n;  
  47.   
  48.             ph->checker = ngx_http_core_find_config_phase;  
  49.             n++;  
  50.             ph++;  
  51.   
  52.             continue;  
  53.   
  54.         case NGX_HTTP_REWRITE_PHASE:  
  55.             if (cmcf->phase_engine.location_rewrite_index == (ngx_uint_t) -1) {  
  56.                 cmcf->phase_engine.location_rewrite_index = n;  
  57.             }  
  58.             checker = ngx_http_core_rewrite_phase;  
  59.   
  60.             break;  
  61.   
  62.         case NGX_HTTP_POST_REWRITE_PHASE:  
  63.             if (use_rewrite) {  
  64.                 ph->checker = ngx_http_core_post_rewrite_phase;  
  65.                 ph->next = find_config_index;  
  66.                 n++;  
  67.                 ph++;  
  68.             }  
  69.   
  70.             continue;  
  71.  &
推荐阅读
  • 31.项目部署
    目录1一些概念1.1项目部署1.2WSGI1.3uWSGI1.4Nginx2安装环境与迁移项目2.1项目内容2.2项目配置2.2.1DEBUG2.2.2STAT ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文讲述了如何通过代码在Android中更改Recycler视图项的背景颜色。通过在onBindViewHolder方法中设置条件判断,可以实现根据条件改变背景颜色的效果。同时,还介绍了如何修改底部边框颜色以及提供了RecyclerView Fragment layout.xml和项目布局文件的示例代码。 ... [详细]
  • 解决Cydia数据库错误:could not open file /var/lib/dpkg/status 的方法
    本文介绍了解决iOS系统中Cydia数据库错误的方法。通过使用苹果电脑上的Impactor工具和NewTerm软件,以及ifunbox工具和终端命令,可以解决该问题。具体步骤包括下载所需工具、连接手机到电脑、安装NewTerm、下载ifunbox并注册Dropbox账号、下载并解压lib.zip文件、将lib文件夹拖入Books文件夹中,并将lib文件夹拷贝到/var/目录下。以上方法适用于已经越狱且出现Cydia数据库错误的iPhone手机。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • PDO MySQL
    PDOMySQL如果文章有成千上万篇,该怎样保存?数据保存有多种方式,比如单机文件、单机数据库(SQLite)、网络数据库(MySQL、MariaDB)等等。根据项目来选择,做We ... [详细]
  • 本文介绍了如何在Mac上使用Pillow库加载不同于默认字体和大小的字体,并提供了一个简单的示例代码。通过该示例,读者可以了解如何在Python中使用Pillow库来写入不同字体的文本。同时,本文也解决了在Mac上使用Pillow库加载字体时可能遇到的问题。读者可以根据本文提供的示例代码,轻松实现在Mac上使用Pillow库加载不同字体的功能。 ... [详细]
  • 如何在php中将mysql查询结果赋值给变量
    本文介绍了在php中将mysql查询结果赋值给变量的方法,包括从mysql表中查询count(学号)并赋值给一个变量,以及如何将sql中查询单条结果赋值给php页面的一个变量。同时还讨论了php调用mysql查询结果到变量的方法,并提供了示例代码。 ... [详细]
  • Python SQLAlchemy库的使用方法详解
    本文详细介绍了Python中使用SQLAlchemy库的方法。首先对SQLAlchemy进行了简介,包括其定义、适用的数据库类型等。然后讨论了SQLAlchemy提供的两种主要使用模式,即SQL表达式语言和ORM。针对不同的需求,给出了选择哪种模式的建议。最后,介绍了连接数据库的方法,包括创建SQLAlchemy引擎和执行SQL语句的接口。 ... [详细]
author-avatar
真理难辩_175
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有