第11章 HTTP框架的执行流程

本章将介绍动态的HTTP框架,主要探讨在请求的生命周期中,基于事件驱动的HTTP框架是怎样处理网络事件以及怎样集成各个HTTP模块来共同处理HTTP请求的,同时,还会介绍为了简化HTTP模块的开发难度而提供的多个非阻塞的异步方法。本章内容与第9章介绍的事件模块密切相关,同时还会使用到第10章介绍过的http配置项和11个阶段。另外,本书第二部分讲述了怎样开发HTTP模块,本章将会回答为什么可以这样开发HTTP模块。

HTTP框架存在的主要目的有两个:

❑Nginx事件框架主要是针对传输层的TCP的,作为Web服务器HTTP模块需要处理的则是HTTP,HTTP框架必须要针对基于TCP的事件框架解决好HTTP的网络传输、解析、组装等问题。

❑虽然事件驱动架构在性能上是不错的,但它的开发效率并不高,而HTTP模块的业务通常较复杂,我们希望HTTP模块在拥有事件框架的高性能优势的同时,尽量只关注业务。这样,HTTP框架就需要为HTTP模块屏蔽事件驱动架构,使得HTTP模块不需要关心网络事件的处理,同时又能灵活地介入那11个阶段中以处理请求。

根据以上HTTP框架的设计目的,我们再来看HTTP框架在动态执行中的大概流程:先与客户端建立TCP连接,接收HTTP请求行、头部并解析出它们的意义,再根据nginx.conf配置文件找到一些HTTP模块,使其依次合作着处理这个请求。同时为了简化HTTP模块的开发,HTTP框架还提供了接收HTTP包体、发送HTTP响应、派生子请求等工具和方法。

对于TCP网络事件,可粗略地分为可读事件和可写事件,然而可读事件中又可细分为收到SYN包带来的新连接事件、收到FIN包带来的连接关闭事件,以及套接字缓冲区上真正收到TCP流。可写事件虽然相对简单点,但Nginx提供限制速度功能,有时可写事件触发时未必可以去发送响应。同时,为了精确地控制超时,还需要把读/写事件放置到定时器中。这些事件的管理都需要依靠HTTP框架,这给HTTP框架带来了复杂性。在清楚了解这些设计后,我们将对HTTP模块的开发有一个非常透彻的认识,因为HTTP模块完全是由HTTP框架设计、定义的,它就像Android应用程序与Android操作系统间的关系。同时,深入了解HTTP框架后,读者会明白如何把复杂的事件驱动机制从关注于业务的模块中分离,这些设计方法都是值得读者学习的。

11.1 HTTP框架执行流程概述

本章在介绍HTTP框架的同时会说明它怎样使用事件模块提供的操作方法,在这之前,先来回顾一下第9章中关于事件驱动模式的内容。

每一个事件都是由ngx_event_t结构体表示的,而TCP连接则由ngx_connection_t结构体表示,HTTP请求毫无疑问是基于一个TCP连接实现的。每个TCP连接包括一个读事件和一个写事件,它们放在ngx_connection_t中的read成员和write成员中。通过事件模块提供的ngx_handle_read_event方法和ngx_handle_write_event方法,可以把相应的事件添加到epoll中,我们可以期待在满足事件触发条件时,Nginx进程会调用ngx_event_t事件的handler回调方法执行业务。而通过事件模块提供的ngx_add_timer方法可以将上面的读事件或者写事件添加到定时器中,在满足超时条件后,Nginx进程同样会调用ngx_event_t事件的handler回调方法执行业务。

在第3章开发HTTP模块时,并没有看到事件模块的影子,但HTTP框架确实是依靠事件驱动机制实现的。基于这一点,先来总结一下HTTP框架需要完成的最主要的4项工作。

HTTP框架需要完成的第一项工作是集成事件驱动机制,管理用户发起的TCP连接,处理网络读/写事件,并在定时器中处理请求超时的事件。这些内容将在11.2节~11.5节介绍,其中11.2节会讨论新连接建立成功后HTTP框架的行为,11.3节介绍第一个网络可读事件到达后HTTP框架的行为,11.4节介绍在没有接收到完整的HTTP请求行之前HTTP框架所要完成的工作,11.5节介绍在没有接收到完整的HTTP请求头部之前HTTP框架所要完成的工作。

HTTP框架需要完成的第二项工作是与各个HTTP模块共同处理请求。实际上,通过第3章的例子我们已经知道,只有请求的URI与location配置匹配后HTTP框架才会调度HTTP模块处理请求。而在第10章中也已看到,HTTP框架定义了11个阶段,其中4个基本的阶段只能由HTTP框架处理,其余的7个阶段可以让各HTTP模块介入来共同处理请求。因此,HTTP框架需要在这7个阶段中调度合适的HTTP模块处理请求。第11.6节中将介绍HTTP框架如何调度HTTP模块参与到请求的处理中。

第三项工作与第5章介绍过的subrequest功能有关。为了实现复杂的业务,HTTP框架允许将一个请求分解为多个子请求,当然,子请求还可以继续向下派生“孙子”请求,这样就可以把复杂的功能分散到多个子请求中,每个子请求仅专注于一个功能。这种设计也是一种平衡,使用事件驱动机制在提高性能的同时其实大大增加了程序的复杂度,特别是开发复杂功能时太多事件的处理会让代码混乱不堪,而子请求的派生则可以降低复杂度,使得Nginx可以提供多样化的功能。在第11.7节中,将讨论HTTP框架是如何设计、实现subrequest功能的。

HTTP框架的第四项工作则是提供基本的工具接口,供各HTTP模块使用,诸如接收HTTP包体,以及发送HTTP响应头部、响应包体等。在11.8节中将说明HTTP框架提供的接收HTTP包体功能,11.9节将说明发送HTTP响应是怎样实现的,在11.10节中将讨论如何结束HTTP请求。为什么要专门讨论请求的结束呢?因为在基于事件驱动的HTTP框架中,由于每个HTTP模块仅能在某一时刻介入到请求中,所以有时候它需要表达一种希望“延后”结束请求的意思,这一特性造成了结束请求的动作十分复杂,因而使用独立的一节来专门说明。

本章的全部内容就是在探讨如何完成以上四项工作。