nginx之Http模块

1. 概述

从事件模块中,隐约可以看出2块内容:1. 模块的初始化,这里边最主要的配置项的解析,以及相关内存的分配;2. 模块的执行流程,就是在进程中的运行逻辑。

这个在http框架中表现的更为突出一些。

http模块的配置比事件模块更复杂,它分成3个层级:

  • 直接隶属于http{}块内的配置项称为main配置项。

  • 直接隶属于server{}块内的配置项称为srv配置项。

    选择使用哪一个server虚拟主机块是取决于server_name的。

  • 直接隶属于location{}块内的配置项称为loc配置项。

    选择使用哪一个location块是将用户请求URI与合适的server块内的所有location表达式
    做匹配后决定的。

下边主要来看看http模块的context:ngx_http_module_t

typedef struct {
    // 解析http{...}内的配置项前回调
    ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);
    // // 解析完http{...}内的所有配置项后回调
    ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);

    // 创建用于存储HTTP全局配置项的结构体,该结构体中的成员将保存直属于http{}块的配置项参数。在解析main配置项前调用
    void       *(*create_main_conf)(ngx_conf_t *cf);
    // 解析完main配置项后回调
    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);

    // 创建用于存储可同时出现在main、srv级别配置项的结构体,该结构体中的成员与server配置是相关联的
    void       *(*create_srv_conf)(ngx_conf_t *cf);
    // merge_srv_conf方法可以把出现在main级别中的配置项值合并到srv级别配置项中
    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);

    // 创建用于存储可同时出现在main、srv、loc级别配置项的结构体,该结构体中的成员与location配置是相关联的
    void       *(*create_loc_conf)(ngx_conf_t *cf);
    // 可以分别把出现在main、srv级别的配置项值合并到loc级别的配置项中
    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;

2. Http模块的配置项

相关结构

我们按上文的思路来看看模块ngx_http_core_module

ngx_module_t  ngx_http_core_module = {
    NGX_MODULE_V1,
    &ngx_http_core_module_ctx,             /* module context */
    ngx_http_core_commands,                /* module directives */
    NGX_HTTP_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};

这里主要的context与commands,context结构就是上节的结构ngx_http_module_t,commands就比较丰富了。上节说,有三种配置项,也就对应着三种配置项的结构,分别是ngx_http_core_main_conf_tngx_http_core_srv_conf_tngx_http_core_loc_conf_t。下边分别来看看这3个配置项

main级别的配置项

在main之前,先来看看ngx_http_conf_ctx_t,它的定义如下:

typedef struct {
  	// 指向一个指针数组,数组中的每个成员都是由所有HTTP模块的create_main_conf方法创建的存放全局配置项的结构体, 在http{}块内的main级别的配置项参数
    void        **main_conf;
    
    // 指向一个指针数组,数组中的每个成员都是由所有HTTP模块的create_srv_conf方法创建的与server相关的结构体,在http{} 或者 server{}块内
    void        **srv_conf;
    
    // 指向一个指针数组,数组中的每个成员都是由所有HTTP模块的create_loc_conf方法创建的与location相关的结构体,在http{} 或 server{} 或location{}块内
    void        **loc_conf;
} ngx_http_conf_ctx_t;

在核心结构体ngx_cycle_t的conf_ctx成员指向的指针数组中,第7个指针由
ngx_http_module模块使用。

这个指针设置为指向解析http{}块时生成的ngx_http_conf_ctx_t结构体,而ngx_http_conf_ctx_t的3个成员则分别指向新分配的3个指针数组。

新的指针数组中成员的意义由每个HTTP模块的ctx_index序号指定(ctx_index在HTTP模块中表明它处于HTTP模块间的序号),例如,第6个HTTP模块的ctx_index是5(ctx_index同样由0开始计数),那么在ngx_http_conf_ctx_t的3个数组中,第6个成员就指向第6个HTTP模块的create_main_conf、create_srv_conf、create_loc_conf方法建立的结构体。

如下图所示:

Http框架main级别配置项

ngx_http_core_main_conf_t的结构如下:它的创建和初始化是在ngx_http_module中调用的ngx_http_core_module的ngx_http_core_create_main_confngx_http_core_init_main_conf

typedef struct {
    ngx_array_t                servers;         /* ngx_http_core_srv_conf_t */

    ngx_http_phase_engine_t    phase_engine;

    ngx_hash_t                 headers_in_hash;

    ngx_hash_t                 variables_hash;

    ngx_array_t                variables;         /* ngx_http_variable_t */
    ngx_array_t                prefix_variables;  /* ngx_http_variable_t */
    ngx_uint_t                 ncaptures;

    ngx_uint_t                 server_names_hash_max_size;
    ngx_uint_t                 server_names_hash_bucket_size;

    ngx_uint_t                 variables_hash_max_size;
    ngx_uint_t                 variables_hash_bucket_size;

    ngx_hash_keys_arrays_t    *variables_keys;

    ngx_array_t               *ports;

    ngx_http_phase_t           phases[NGX_HTTP_LOG_PHASE + 1];
} ngx_http_core_main_conf_t;

再来看看ngx_http_core_commands,它比较长,这里节选几个关注的

static ngx_command_t  ngx_http_core_commands[] = {
     ...
       { ngx_string("server"),
      NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
      ngx_http_core_server,
      0,
      0,
      NULL },
    ...
    { ngx_string("location"),
NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12,
      ngx_http_core_location,
      NGX_HTTP_SRV_CONF_OFFSET,
      0,
      NULL },
	...
}

这里在遇到server配置项时,调用了ngx_http_core_server()来进行解析的,解析的结构体是ngx_http_core_srv_conf_t,存在了ngx_http_core_main_conf_t的servers下,servers本身是一个动态数组,它存着多个server配置项。

可如何由ngx_cycle_t核心结构体中找到main级别的配置结构体呢?Nginx提供的
ngx_http_cycle_get_module_main_conf宏可以实现这个功能。

server级别的配置项

先来看看server配置项的解析,它是在上边提到的ngx_http_core_server()中完成的,其流程如下:

下边再来看看ngx_http_core_srv_conf_t结构

typedef struct {
    /* array of the ngx_http_server_name_t, "server_name" directive */
    ngx_array_t                 server_names;

    /* server ctx */
    ngx_http_conf_ctx_t        *ctx;

    u_char                     *file_name;
    ngx_uint_t                  line;

    ngx_str_t                   server_name;

    size_t                      connection_pool_size;
    size_t                      request_pool_size;
    size_t                      client_header_buffer_size;

    ngx_bufs_t                  large_client_header_buffers;

    ngx_msec_t                  client_header_timeout;

    ngx_flag_t                  ignore_invalid_headers;
    ngx_flag_t                  merge_slashes;
    ngx_flag_t                  underscores_in_headers;

    unsigned                    listen:1;
#if (NGX_PCRE)
    unsigned                    captures:1;
#endif

    ngx_http_core_loc_conf_t  **named_locations;
} ngx_http_core_srv_conf_t;

在这里它也有一个ngx_http_conf_ctx_t的ctx,下边来看看这个结构:

各个HTTP模块都有自己的ngx_http_core_srv_conf_t结构体,所以这里是一个数组。

HTTP框架使用散列表(HashMap)来存放虚拟主机,其中每个元素的关键字是server name字符串,而值则是ngx_http_core_srv_conf_t结构体的指针,来加速获取server配置项。

location级别的配置项

在ngx_http_core_module的commands中,看到对location的配置解析是用ngx_http_core_location()来完成的,它的流程如下:

下边来看看ngx_http_core_loc_conf_t,它的结构比较多,这里只看一部分

struct ngx_http_core_loc_conf_s {
// location的名称,即nginx.conf中location后的表达式
ngx_str_t name;
    ...
/*指向所属location块内
ngx_http_conf_ctx_t结构体中的loc_conf指针数组,它保存着当前location块内所有HTTP模块create_loc_conf方法产生的结构体指针
*/
void **loc_conf;
...
/*将同一个
server块内多个表达
location块的
ngx_http_core_loc_conf_t结构体以双向链表方式组织起来,该
locations指针将指向
ngx_http_location_queue_t结构体
*/
ngx_queue_t *locations;

与srv_conf相同,loc_conf指向了location下create_loc_conf生成的结构体指针。整体的结构如下:

在server配置项下的locaiton配置,是直接放到了ngx_http_conf_ctx_t的loc_conf中。

在ngx_http_core_loc_conf_t结构体中有一个成员locations,它表示属于当前块的所有location块通过ngx_http_location_queue_t结构体构成的双向链表。

typedef struct {
    // 双向列表容器将ngx_http_location_queue_t结构体连接起来
    ngx_queue_t                      queue;
    
    // 如果location中的字符串可以精确匹配(包括正则表达式),exact将指向对应的ngx_http_core_loc_conf_t结构体,否则值为NULL
    ngx_http_core_loc_conf_t        *exact;
    
    // 如果location中的字符串无法精确匹配(包括了自定义的通配符),inclusive将指向对应的ngx_http_core_loc_conf_t结构体,否则值为NULL
    ngx_http_core_loc_conf_t        *inclusive;
    
    // location的名称
    ngx_str_t                       *name;    
    u_char                          *file_name;
    ngx_uint_t                       line;
    ngx_queue_t                      list;
} ngx_http_location_queue_t;

ngx_http_location_queue_t中的queue成员将把所有相关的ngx_http_location_queue_t结构体串联起来。同时,ngx_http_location_queue_t将帮助用户把所有的location块与其所属的server块关联起来。

同一个server下的ngx_http_core_loc_conf_t是通过双向链表关联起来的,如下图所示:

每一批location块是通过双向链表与它的父配置块(要么属于server块,要么属于location块)关联起来的。由双向链表的查询效率可以知道,当一个请求根据10.4节中描述过的散列表快速查询到server块时,必须遍历其下的所有location组成的双向链表才能找到与其URI匹配的location配置块,这也是用户无法接受的。HTTP框架通过静态的二叉查找树来保存location.这个过程在ngx_http_init_static_location_trees()中完成

不同级别配置项的合并

ps:各种结构体只需要注意,main下可以有main、srv、loc;srv下可以有srv、loc;loc下只有loc就能理解它的结构了,因为你自己设置,也是如此,容器中套容器罢了。

考虑到HTTP模块可能需要合并不同级别的同名配置项,因此,HTTP框架为ngx_http_module_t接口提供了merge_srv_conf方法,用于合并main级别与srv级别的server相关的配置项,同时,它还提供了merge_loc_conf方法,用于合并main级别、srv级别、loc级别的location相关的配置项。

不同级别配置项内存合并前的示意图:

http模块的context定义了merge_srv_confmerge_loc_conf的接口函数,它们是在ngx_http_merge_servers()函数中调用的,这个过程就不细谈了。

3. 监听端口

监听端口属于server块,它是由listen配置项决定的,与ngx_http_core_srv_conf_t结构体密切相关。

每一个TCP端口,都有一个独立的ngx_http_conf_port_t结构体来表示,如下:

typedef struct {
    // 协议族
    ngx_int_t                  family;
    
    // 端口
    in_port_t                  port;
    
    // 地址的动态数组
    ngx_array_t                addrs;     /* array of ngx_http_conf_addr_t */
} ngx_http_conf_port_t;

它是用ngx_http_core_main_conf_t结构体的ports属性保存的。这里来看看addrs。

对同一个端口8000,我们可以同时监听127.0.0.1:8000、173.39.160.51:8000这两个地址,当一台物理机器具备多个IP地址时这是很有用的。Nginx使用ngx_http_conf_addr_t结构体来表示一个对应着具体地址的监听端口的,因此,一个ngx_http_conf_port_t将会对应多个ngx_http_conf_addr_t。

ngx_http_conf_addr_t如下:

typedef struct {
    // 套接字的各种属性
    ngx_http_listen_opt_t      opt;

    // 3个散列表用于加速寻找到对应监听端口上的新连接,确定到底使用哪个server{}虚拟主机下的配置来处理它。所以,散列表的值就是ngx_http_core_srv_conf_t结构体的地址
    ngx_hash_t                 hash;
    ngx_hash_wildcard_t       *wc_head;
    ngx_hash_wildcard_t       *wc_tail;

#if (NGX_PCRE)
    ngx_uint_t                 nregex;
    ngx_http_server_name_t    *regex;
#endif

    // 该监听端口下对应的默认server{}
    /* the default server configuration for this address:port */
    ngx_http_core_srv_conf_t  *default_server;
    
    // servers动态数组中的成员将指向ngx_http_core_srv_conf_t结构体
    ngx_array_t                servers;  /* array of ngx_http_core_srv_conf_t */
} ngx_http_conf_addr_t;

servers数组把监听的端口与server{}虚拟主机关联起来了。

对于每一个监听地址ngx_http_conf_addr_t,都会有ngx_listening_t与其相对应,而ngx_listening_t的handler回调方法设置为ngx_http_init_connection,所以,新的TCP连接成功建立后都会调用ngx_http_init_connection方法初始化HTTP相关的信息。这个过程是通过ngx_http_add_listening来完成的

4. 请求的11个阶段

先来看看ngx_http_phases枚举,如下:

typedef enum {
    
    // 在接收到完整的HTTP头部后处理的HTTP阶段
    NGX_HTTP_POST_READ_PHASE = 0,

    // 在将请求的URI与location表达式匹配前,修改请求的URI(所谓的重定向)是一个独立的HTTP阶段
    NGX_HTTP_SERVER_REWRITE_PHASE,

    // 框架:根据请求的URI寻找匹配的location表达式,这个阶段只能由ngx_http_core_module模块实现,不建议其他HTTP模块重新定义这一阶段的行为
    NGX_HTTP_FIND_CONFIG_PHASE,
    
    // 在NGX_HTTP_FIND_CONFIG_PHASE阶段寻找到匹配的location之后再修改请求的URI
    NGX_HTTP_REWRITE_PHASE,
    
    /* 框架:这一阶段是用于在rewrite重写URL后,防止错误的
        nginx.conf配置导致死循环(递归地修改URI),因此,这一阶段仅由
        ngx_http_core_module模块处理。
     */
    NGX_HTTP_POST_REWRITE_PHASE,

    // 表示在处理NGX_HTTP_ACCESS_PHASE阶段决定请求的访问权限前,HTTP模块可以介入的处理阶段
    NGX_HTTP_PREACCESS_PHASE,

    // 这个阶段用于让HTTP模块判断是否允许这个请求访问Nginx服务器
    NGX_HTTP_ACCESS_PHASE,
    
    /*框架: 在NGX_HTTP_ACCESS_PHASE阶段中,
   	 	当HTTP模块的handler处理函数返回不允许访问的错误码时(FORBIDDEN或者UNAUTHORIZED)
   	 	这里将负责向用户发送拒绝服务的错误响应。
    	因此,这个阶段实际上用于给NGX_HTTP_ACCESS_PHASE阶段收尾
    */
    NGX_HTTP_POST_ACCESS_PHASE,

    /**
      框架:这个在老的版本中是NGX_HTTP_TRY_FILES_PHASE,用于当HTTP请求访问静态文件资源时,
      try_files配置项可以使这个请求顺序地访问多个静态文件资源,
      如果某一次访问失败,则继续访问try_files中指定的下一个静态资源
    **/
    NGX_HTTP_PRECONTENT_PHASE,
	
    // 用于处理HTTP请求内容的阶段,这是大部分HTTP模块最愿意介入的阶段
    NGX_HTTP_CONTENT_PHASE,

    // 处理完请求后记录日志的阶段。例如,ngx_http_log_module模块就在这个阶段中加入了一个handler处理方法,使得每个HTTP请求处理完毕后会记录access_log访问日志
    NGX_HTTP_LOG_PHASE
} ngx_http_phases;

NGX_HTTP_FIND_CONFIG_PHASE、NGX_HTTP_POST_REWRITE_PHASE、NGX_HTTP_POST_ACCESS_PHASE、NGX_HTTP_PRECONTENT_PHASE这4个阶段则不允许HTTP模块加入自己的ngx_http_handler_pt方法处理用户请求,它们仅由HTTP框架实现。

这些阶段的处理的handler函数的定义:ngx_http_phase_handler_sngx_http_phase_engine_t

struct ngx_http_phase_handler_s {
    // 在处理到某一个HTTP阶段时,HTTP框架将会在checker方法已实现的前提下首先调用checker方法来处理请求,由框架中的ngx_http_core_module模块实现
    ngx_http_phase_handler_pt  checker;
    
    // 除ngx_http_core_module模块以外的HTTP模块,只能通过定义checker;handler方法才能介入某一个HTTP处理阶段以处理请求
    ngx_http_handler_pt        handler;
    
    // next的设计使得处理阶段不必按顺序依次执行,既可以向后跳跃数个阶段继续执行,也可以跳跃到之前曾经执行过的某个阶段重新执行
    ngx_uint_t                 next;
};
typedef struct {
    // 由ngx_http_phase_handler_t构成的数组首地址,它表示一个请求可能经历的所有ngx_http_handler_pt处理方法
    ngx_http_phase_handler_t  *handlers;
    
    // NGX_HTTP_SERVER_REWRITE_PHASE阶段第1个ngx_http_phase_handler_t处理方法在handlers数组中的序号
    ngx_uint_t                 server_rewrite_index;
    
    // 同上
    ngx_uint_t                 location_rewrite_index;
} ngx_http_phase_engine_t;

一个用户请求可能经历的所有ngx_http_handler_pt处理方法,这是所有HTTP模块可以合作处理用户请求的关
键!这个ngx_http_phase_engine_t结构体是保存在全局的ngx_http_core_main_conf_t结构体中的。

在ngx_http_core_main_conf_t中关于HTTP阶段有两个成员:phase_engine和phases,其中phase_engine控制运行过程中一个HTTP请求所要经过的HTTP处理函数,它将配合ngx_http_request_t结构体中的phase_handler成员使用(phase_handler指定了当前请求应当执行哪一个HTTP阶段);

而phases数组更像一个临时变量,它实际上仅会在Nginx启动过程中用到,它的唯一使命是按照11个阶段的概念初始化phase_engine中的handlers数组。

typedef struct {
    ...
    ngx_http_phase_engine_t    phase_engine;
	...
    ngx_http_phase_t           phases[NGX_HTTP_LOG_PHASE + 1];
} ngx_http_core_main_conf_t;

在HTTP框架的初始化过程中,任何HTTP模块都可以在ngx_http_module_t接口的postconfiguration方法中将自定义的方法添加到handler动态数组中。

3. Http框架初始化

ngx_http_module模块中,当配置文件中出现了http{}配置块时就回调ngx_http_block方法,而这个方法就包括了HTTP框架的完整初始化流程,其流程如下:

4. Http框架执行流程

Http框架主要完成4项基本工作:

  • HTTP框架需要完成的第一项工作是集成事件驱动机制,管理用户发起的TCP连接,处理网络读/写事件,并在定时器中处理请求超时的事件。
  • HTTP框架需要完成的第二项工作是与各个HTTP模块共同处理请求。只有请求的URI与location配置匹配后HTTP框架才会调度HTTP模块处理请求。
  • 为了实现复杂的业务,HTTP框架允许将一个请求分解为多个子请求,当然,子请求还可以继续向下派生“孙子”请求,这样就可以把复杂的功能分散到多个子请求中,每个子请求仅专注于一个功能。
  • 提供基本的工具接口,供各HTTP模块使用,诸如接收HTTP包体,以及发送HTTP响应头部、响应包体等。

下边分别来看看

ngx_http_request_s结构体

在继续处理之前,先来看看ngx_http_request_s结构体:


struct ngx_http_request_s {
    uint32_t                          signature;         /* "HTTP" */

    // 这个请求对应的客户端连接
    ngx_connection_t                 *connection;

    // 指向存放所有HTTP模块的上下文结构体的指针数组
    void                            **ctx;
    
    // 指向请求对应的存放main级别配置结构体的指针数组
    void                            **main_conf;
    
    // 指向请求对应的存放srv级别配置结构体的指针数组
    void                            **srv_conf;
    
    // 指向请求对应的存放loc级别配置结构体的指针数组
    void                            **loc_conf;

    // 两个回调
    ngx_http_event_handler_pt         read_event_handler;
    ngx_http_event_handler_pt         write_event_handler;

#if (NGX_HTTP_CACHE)
    ngx_http_cache_t                 *cache;
#endif

    // upstream机制用到的结构体
    ngx_http_upstream_t              *upstream;
    ngx_array_t                      *upstream_states;
                                         /* of ngx_http_upstream_state_t */

    // 表示这个请求的内存池,在ngx_http_free_request方法中销毁。
    ngx_pool_t                       *pool;
    
    // 用于接收HTTP请求内容的缓冲区,主要用于接收HTTP头部
    ngx_buf_t                        *header_in;

    /* ngx_http_process_request_headers方法在接收、解析完HTTP请求的头部后,
    会把解析完的每一个HTTP头部加入到headers_in的headers链表中*/
    ngx_http_headers_in_t             headers_in;
    
    /*HTTP模块会把想要发送的HTTP响应信息放到headers_out中,
		HTTP框架将headers_out中的成员序列化为HTTP响应包发送给用户**/
    ngx_http_headers_out_t            headers_out;

    // 接收HTTP请求中包体的数据结构
    ngx_http_request_body_t          *request_body;

    // 延迟关闭连接的时间
    time_t                            lingering_time;
    
    // 当前请求初始化时的时间
    time_t                            start_sec;
    ngx_msec_t                        start_msec;

    ngx_uint_t                        method;
    ngx_uint_t                        http_version;

    ngx_str_t                         request_line;
    ngx_str_t                         uri;
    ngx_str_t                         args;
    ngx_str_t                         exten;
    ngx_str_t                         unparsed_uri;

    ngx_str_t                         method_name;
    ngx_str_t                         http_protocol;
    ngx_str_t                         schema;
    
    // 表示需要发送给客户端的HTTP响应。
    ngx_chain_t                      *out;
    
    /* 当前请求既可能是用户发来的请求,也可能是派生出的子请求;
	main则标识一系列相关的派生子请求的原始请求,
	可通过main和当前请求的地址是否相等来判断是否为用户发来的原始请求*/
    ngx_http_request_t               *main;
    
    // 当前请求的父请求
    ngx_http_request_t               *parent;
    // 与subrequest子请求相关的功能
    ngx_http_postponed_request_t     *postponed;
    ngx_http_post_subrequest_t       *post_subrequest;
    ngx_http_posted_request_t        *posted_requests;

    /* 全局的ngx_http_phase_engine_t结构体中定义了
    一个ngx_http_phase_handler_t回调方法组成的数组。
    phase_handler成员则与该数组配合使用,表示请求下次应当执行以
    phase_handler作为序号的回调方法
    */
    ngx_int_t                         phase_handler;
    ngx_http_handler_pt               content_handler;
    ngx_uint_t                        access_code;

    ngx_http_variable_value_t        *variables;
	...
}

新连接

当Nginx接收到用户发起TCP连接的请求时,事件框架将会负责把TCP连接建立起来,如果TCP连接成功建立,HTTP框架就会介入请求的处理了,在ngx_event_accept方法建立新连接的最后1步,将会调用ngx_listening_t监听结构体的handler方法。HTTP框架在初始化时就会将每个监听ngx_listening_t结构体的handler方法设为ngx_http_init_connection方法。这也是Http框架的起始,下边来看看:

void
ngx_http_init_connection(ngx_connection_t *c)
{
    ngx_uint_t                 i;
    ngx_event_t               *rev;
    struct sockaddr_in        *sin;
    ngx_http_port_t           *port;
    ngx_http_in_addr_t        *addr;
    ngx_http_log_ctx_t        *ctx;
    ngx_http_connection_t     *hc;
    ngx_http_core_srv_conf_t  *cscf;
	...
        
    ctx->connection = c;
    ctx->request = NULL;
    ctx->current_request = NULL;

    c->log->connection = c->number;
    c->log->handler = ngx_http_log_error;
    c->log->data = ctx;
    c->log->action = "waiting for request";

    c->log_error = NGX_ERROR_INFO;

    rev = c->read;
    rev->handler = ngx_http_wait_request_handler;
    c->write->handler = ngx_http_empty_handler;

	...
        
    if (rev->ready) {
        /* the deferred accept(), iocp */

        if (ngx_use_accept_mutex) {
            ngx_post_event(rev, &ngx_posted_events);
            return;
        }

        rev->handler(rev);
        return;
    }

    cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);

    ngx_add_timer(rev, cscf->client_header_timeout);
    ngx_reusable_connection(c, 1);

    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
        ngx_http_close_connection(c);
        return;
    }
}

注意:将可读事件初始化为 rev->handler = ngx_http_wait_request_handler;本版本的中ngx_http_init_request已经变成了ngx_http_wait_request_handler

第一次可读事件

下边来看看ngx_http_wait_request_handler

static void
ngx_http_wait_request_handler(ngx_event_t *rev)
{
    u_char                    *p;
    size_t                     size;
    ssize_t                    n;
    ngx_buf_t                 *b;
    ngx_connection_t          *c;
    ngx_http_connection_t     *hc;
    ngx_http_core_srv_conf_t  *cscf;

    c = rev->data;

    ...

    // 获取srv配置项
    hc = c->data;
    cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);

    size = cscf->client_header_buffer_size;

    b = c->buffer;

    // 创建接收缓冲区
    if (b == NULL) {
        b = ngx_create_temp_buf(c->pool, size);
        if (b == NULL) {
            ngx_http_close_connection(c);
            return;
        }

        c->buffer = b;

    } else if (b->start == NULL) {

        b->start = ngx_palloc(c->pool, size);
        if (b->start == NULL) {
            ngx_http_close_connection(c);
            return;
        }

        b->pos = b->start;
        b->last = b->start;
        b->end = b->last + size;
    }

    // 接收数据
    n = c->recv(c, b->last, size);

    // 根据不同的结构进行处理
    if (n == NGX_AGAIN) {

        // 继续接收,加入定时器事件
        if (!rev->timer_set) {
            ngx_add_timer(rev, c->listening->post_accept_timeout);
            ngx_reusable_connection(c, 1);
        }

        // 加入读事件
        if (ngx_handle_read_event(rev, 0) != NGX_OK) {
            ngx_http_close_connection(c);
            return;
        }

        /*
         * We are trying to not hold c->buffer's memory for an idle connection.
         */

        if (ngx_pfree(c->pool, b->start) == NGX_OK) {
            b->start = NULL;
        }

        return;
    }

    if (n == NGX_ERROR) {
        ngx_http_close_connection(c);
        return;
    }

    if (n == 0) {
        ngx_log_error(NGX_LOG_INFO, c->log, 0,
                      "client closed connection");
        ngx_http_close_connection(c);
        return;
    }

    b->last += n;

   ...
	// 接收完成,对事件进行处理
    // 创建ngx_http_request_t结构体
    c->data = ngx_http_create_request(c);
    if (c->data == NULL) {
        ngx_http_close_connection(c);
        return;
    }

    // 处理事件
    rev->handler = ngx_http_process_request_line;
    ngx_http_process_request_line(rev);
}

第一次的事件的回调函数是ngx_http_wait_request_handler,它仅用于初始化请求,之后的读事件意味着接收到请求内容,显而易见,它的回调方法是需要改变一下的,即在这一步中将把这个读事件的回调方法设为ngx_http_process_request_line,这个方法将会负责接收并解析出完整的HTTP请求行。

接收请求行request line

请求行的格式:GET uri HTTP1.1

转过来接着看ngx_http_process_request_line,它与第一次读事件ngx_http_wait_request_handler不同,前者至少被执行一次,后者只执行一次。它主要是接收Header并解析Header,其过程如下:

第5步与第7步之间是通过ngx_http_parse_request_line方法来完成,

  • 如果ngx_http_parse_request_line方法返回NGX_OK,表示成功地接收到完整的请求行,这时跳转到第7步继续执行。
  • 如果ngx_http_parse_request_line方法返回NGX_AGAIN,则表示需要接收更多的字符流,这时需要对header_in缓冲区做判断,检查是否还有空闲的内存,如果还有未使用的内存可以继续接收字符流。

第11步于已经接收完HTTP请求行,因此这时把读事件的回调方法由ngx_http_process_request_line改为ngx_http_process_request_headers,准备接收HTTP头部。

第12步调用ngx_http_process_request_headers方法开始接收HTTP头部。

接收头部request headers

ngx_http_process_request_headers,设置为连接的读事件回调方法,在接收较大的HTTP头部时,它有可
能会被反复多次地调用。目的在于接收到当前请求全部的HTTP头部。

头部格式如下:

cred: xxx
username: ttt
content-length: 4

HTTP头部也属于可变长度的字符串,它与HTTP请求行和包体间都是通过换行符来区分的。

同时,它与解析HTTP请求行一样,都需要使用状态机来解析数据。既然HTTP请求行和头部都是变长的,对它们的总长度当然是有限制的。

与请求行类似,核心是第6步,调用ngx_http_parse_header_line方法解析缓冲区中的字符流。这种方法有3个返回值:

  • 返回NGX_OK时,表示解析出一行HTTP头部,这时需要跳转到第7步设置这行已经解析出的HTTP头部;
  • 返回NGX_HTTP_PARSE_HEADER_DONE时,表示已经解析出了完整的HTTP头部,这时可以准备开始处理HTTP请求了
  • 返回NGX_AGAIN时,表示还需要接收到更多的字符流才能继续解析,这时需要跳转到第2步去接收更多的字符流
  • 除此之外的错误情况,将跳转到第8步发送400错误给客户端。

得到完整的header之后,将会根据HTTP头部中的host字段情况,调用ngx_http_find_virtual_server方法找到对应的虚拟主机配置块,也就是前边的ngx_http_core_srv_conf_t结构体。

最后调用ngx_http_process_request方法开始使用各HTTP模块正式地在业务上处理HTTP请求,进入下一节。

处理Http请求request

ngx_http_process_request方法只是处理请求的开始,对于基于事件驱动的异步HTTP框架来说,处理请求并不是一步可以完
成的。下边来看看ngx_http_process_request,它的流程如下:

主要介绍一下第4步:ngx_http_phase_engine_t结构体中的handlers动态数组中保存了请求需要经历的所有回调方法,而server_rewrite_index则是handlers数组中NGX_HTTP_SERVER_REWRITE_PHASE处理阶段的第一个回调方法所处的位置。
在这一步骤中,把phase_handler序号设为server_rewrite_index,这意味着无论之前执行到哪一个阶段,马上都要重新从NGX_HTTP_SERVER_REWRITE_PHASE阶段开始再次执行,这是Nginx的请求可以反复rewrite重定向的基础。

这里来看看ngx_http_core_run_phases:

void
ngx_http_core_run_phases(ngx_http_request_t *r)
{
    ngx_int_t                   rc;
    ngx_http_phase_handler_t   *ph;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    ph = cmcf->phase_engine.handlers;

    while (ph[r->phase_handler].checker) {

        rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);

        if (rc == NGX_OK) {
            return;
        }
    }
}

执行每个ngx_http_phase_handler_t处理阶段的checker方法,而不会执行
handler方法,而handler方法其实仅能在checker方法中被调用。checker方法由HTTP框架实现,所以可以控制各HTTP模块实现的处理方法在不同的阶段中采用不同的调用行为。

ngx_http_request_t结构体中的phase_handler成员将决定执行到哪一阶段,以及下一阶段应当执行哪个HTTP模块实现的内容。

各个checker见下表:

阶段名称checker方法
NGX_HTTP_POST_READ_PHASEngx_http_core_generic_phase
NGX_HTTP_SERVER_REWRITE_PHASEngx_http_core_rewrite_phase
NGX_HTTP_FIND_CONFIG_PHASEngx_http_core_find_config_phase
NGX_HTTP_REWRITE_PHASEngx_http_core_rewrite_phase
NGX_HTTP_POST_REWRITE_PHASEngx_http_core_post_rewrite_phase
NGX_HTTP_PREACCESS_PHASEngx_http_core_generic_phase
NGX_HTTP_ACCESS_PHASEngx_http_core_access_phase
NGX_POST_ACCESS_PHASEngx_http_core_post_access_phase
NGX_HTTP_PRECONTENT_PHASEngx_http_core_generic_phase
NGX_HTTP_CONTENT_PHASEngx_http_core_content_phase
NGX_HTTP_LOG_PHASEngx_http_core_generic_phase

在11个阶段中其中7个是允许各个HTTP模块向阶段中任意添加自己实现的handler处理方法的,但同一个阶段中的所有handler处理方法都拥有相同的checker方法。

我们知道,每个阶段中处理方法的返回值都会以不同的
方式影响HTTP框架的行为,checker方法在返回NGX_OK和其他值时也会导致不同的结果。

  • 当checker方法的返回值非NGX_OK时,意味着向下执行phase_engine中的各处理方法;

  • 反之,当任何一个checker方法返回NGX_OK时,意味着把控制权交还给Nginx的事件模块,由它根据事件(网络事件、定时器事件、异步I/O事件等)再次调度请求。

通常来说,在接收完HTTP头部后,是无法在一次Nginx框架的调度中处理完一个请求的。

在第一次接收完HTTP头部后,HTTP框架将调度ngx_http_process_request方法开始处理请求,如果某个checker方法返回了NGX_OK,则将会把控制权交还给Nginx框架。

当这个请求上对应的事件再次触发时,HTTP框架将不会再调度ngx_http_process_request方法处理请求,而是由ngx_http_request_handler方法开始处理请求,下边来看看它。

static void
ngx_http_request_handler(ngx_event_t *ev)
{
    ngx_connection_t    *c;
    ngx_http_request_t  *r;

    c = ev->data;
    r = c->data;

    ...

    if (c->close) {
        r->main->count++;
        ngx_http_terminate_request(r, 0);
        ngx_http_run_posted_requests(c);
        return;
    }

    if (ev->delayed && ev->timedout) {
        ev->delayed = 0;
        ev->timedout = 0;
    }

    if (ev->write) {
        r->write_event_handler(r);

    } else {
        r->read_event_handler(r);
    }

    ngx_http_run_posted_requests(c);
}

简单说,根据读写事件,调用不同的回调,最后调用了ngx_http_run_posted_request()。

ngx_http_process_requestngx_http_request_handler这两个方法对比:

  • ngx_http_process_request方法负责在接收完HTTP头部后,第一次与各个HTTP模块共同按阶段处理请求,
  • 如果ngx_http_process_request没能处理完请求,这个请求上的事件再次被触发,那就将ngx_http_request_handler方法,方法继续处理了。
  • 这两个方法的共通之处在于,它们都会先按阶段调用各个HTTP模块处理请求,再处理post请求。

PS: 这11个阶段大体可分成3种:rewrite、access、content。每个阶段都有pre、post。rewrite包括server与local2种,对于应用而言,可能后两种更可用一些。

从流程完整性上,我们只看NGX_HTTP_CONTENT_PHASE阶段。其流程如下:

首先检测ngx_http_request_t结构体的content_handler成员是否为空,其实就是看在NGX_HTTP_FIND_CONFIG_PHASE阶段匹配了URI请求的location内,是否有HTTP模块把处理方法设置到了ngx_http_core_loc_conf_t结构体的handler成员中。如果content_handler为空,则跳到第2步开始执行全局有效的handler方法;否则仅执行content_handler方法。

子请求与post请求

如果一个请求同时需要与多个上游服务器打交道,同时处理多个TCP连接,那么它需要处理的事件就太多了,这种复杂度会使得模块难以维护。Nginx解决这个问题的手段就是subrequest机制。

subrequest机制有以下两个特点:
·从业务上把一个复杂的请求拆分成多个子请求,由这些子请求共同合作完成实际的用户请求。
·每一个HTTP模块通常只需要关心一个请求,而不用试图掌握派生出的所有子请求,这极大地降低了模块的开发复杂度。

post请求的设计就是用于实现subrequest子请求机制的。

这里的post请求并不是HTTP的POST命令的请求,而是指的有后续请求的意思。

从数据上看,在ngx_http_request_s结构中,有

struct ngx_http_request_s {    
    ...
	ngx_http_request_t               *main;
    ngx_http_request_t               *parent;
    ngx_http_posted_request_t        *posted_requests;
    ...
}

等几个变量来存储与父、子请求相关的结构,main指向原始请求,parent是指向父请求,posted_requests是所有的子请求以单链表来连接起来的。其结构如下如下:

struct ngx_http_posted_request_s {
    ngx_http_request_t               *request;
    ngx_http_posted_request_t        *next;
};

在来看看子请求的处理,主要是在ngx_http_run_posted_requests中完成:

遍历posted_requests中的子请求,分别调用write_event_handler来处理它们。

void
ngx_http_run_posted_requests(ngx_connection_t *c)
{
    ngx_http_request_t         *r;
    ngx_http_posted_request_t  *pr;

    for ( ;; ) {

        if (c->destroyed) {
            return;
        }

        r = c->data;
        pr = r->main->posted_requests;

        if (pr == NULL) {
            return;
        }

        r->main->posted_requests = pr->next;

        r = pr->request;

        ngx_http_set_log_request(c->log, r);

        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
                       "http posted request: \"%V?%V\"", &r->uri, &r->args);

        r->write_event_handler(r);
    }
}

处理body

body体本身也是存在ngx_http_request_s中的

struct ngx_http_request_s {    
    ...
    ngx_http_request_body_t          *request_body;
    ...
   	unsigned                          count:16;
    ...
}

这里对count说一下,这是一个对ngx_http_request_s使用的引用计数,防止在多个事件中,有一个事件将它销毁而造成其他事件失败问题。

来看看这个body体

typedef struct {
    ngx_temp_file_t                  *temp_file;
    ngx_chain_t                      *bufs;
    ngx_buf_t                        *buf;
    off_t                             rest;
    off_t                             received;
    ngx_chain_t                      *free;
    ngx_chain_t                      *busy;
    ngx_http_chunked_t               *chunked;
    ngx_http_client_body_handler_pt   post_handler;
    unsigned                          filter_need_buffering:1;
    unsigned                          last_sent:1;
    unsigned                          last_saved:1;
} ngx_http_request_body_t;

body体可能存在临时文件中,也可能直接存在内存中。对于body体的请求有2种情况,一种的处理,在ngx_http_read_client_request_body中完成,第二种是抛弃,在ngx_http_discard_request_body中完成。这里我们主要来看看前者。它的流程如下:

这里只做了分配内存的工作,以及设置后续事件的回调操作,真正的处理是放在ngx_http_do_read_client_request_body中来完成的。ngx_http_do_read_client_request_body的过程如下:

这里对数据进行了读取,最后调用了在body上的回调函数。

发送Http响应

发送Http响应主要分成3部分,发送响应头,发送响应body体,以及一次发送不完时设置的后续发送,下边分别来看看这3个部分。发送响应使用了过滤器思想,通过提供统一的接口,让各个感兴趣的HTTP模块加入到其中。

  • 发送响应头

    ngx_http_send_header中完成发送相应头,其代码如下:

    ngx_int_t
    ngx_http_send_header(ngx_http_request_t *r)
    {
        if (r->post_action) {
            return NGX_OK;
        }
    
        if (r->header_sent) {
            ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                          "header already sent");
            return NGX_ERROR;
        }
    
        if (r->err_status) {
            r->headers_out.status = r->err_status;
            r->headers_out.status_line.len = 0;
        }
    
        return ngx_http_top_header_filter(r);
    }
    
    

    这里可以看到它其实主要就是调用了ngx_http_top_header_filter(),那它又是如何调用各个模块的相关函数的呢?

    nginx全局定义了一个ngx_http_top_header_filter变量,各个模块定义自己的ngx_http_next_header_filter变量。

    然后在各个模块的初始化阶段(postconfiguration),不停的将Next指向前一个的Top,而将top指向自己的处理函数,例如ngx_http_chunked_filter_module模块:

    static ngx_int_t
    ngx_http_chunked_filter_init(ngx_conf_t *cf)
    {
        ngx_http_next_header_filter = ngx_http_top_header_filter;
        ngx_http_top_header_filter = ngx_http_chunked_header_filter;
    
        ngx_http_next_body_filter = ngx_http_top_body_filter;
        ngx_http_top_body_filter = ngx_http_chunked_body_filter;
    
        return NGX_OK;
    }
    

    最后在自己的处理函数最后阶段调用Next函数,如下

    static ngx_int_t
    ngx_http_chunked_header_filter(ngx_http_request_t *r)
    {
       ...
        return ngx_http_next_header_filter(r);
    }
    

    这样就完成了对所有模块的处理函数的调用,body的调用类似,下边就不解释了。

    最先出现化的应该在最后调用,这个最后的是ngx_http_header_filter_module这个模块,之前的头部过滤模块会根据特性去修改表示请求的ngx_http_request_t结构体中headers_out成员里的内容,它负责把headers_out中的成员变量序列化为字符流,并发送出去。

    来看看它ngx_http_header_filter的工作。

    从源码看,在ngx_http_write_filter调用的发送的函数chain = c->send_chain(c, r->out, limit);是ngx_connection_s中的发送回调,其余各部都是组织数据与控制。

  • 发送响应body体

    发送body是在ngx_http_output_filter中完成的,其代码如下:

    ngx_int_t
    ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
    {
        ngx_int_t          rc;
        ngx_connection_t  *c;
    
        c = r->connection;
    
        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
                       "http output filter \"%V?%V\"", &r->uri, &r->args);
    
        rc = ngx_http_top_body_filter(r, in);
    
        if (rc == NGX_ERROR) {
            /* NGX_ERROR may be returned by any filter */
            c->error = 1;
        }
        return rc;
    }
    

    这个过程与发送响应头是相同的,也是经过各个模块的filter,而body体最后的发送是在ngx_http_write_filter_module模块中,具体的函数是正是前边header的最后一个调用:ngx_http_write_filter

    它的过程是控制剩余长度与总长度,判断是否限速,进行最后的chain = c->send_chain(c, r->out, limit);,过程不再详述了。

  • 后续发送

    这个后续发送是通过回到来完成的,其具体的方法是ngx_http_writer

    过程如下:

    
    static void
    ngx_http_writer(ngx_http_request_t *r)
    {
        ngx_int_t                  rc;
        ngx_event_t               *wev;
        ngx_connection_t          *c;
        ngx_http_core_loc_conf_t  *clcf;
    
        c = r->connection;
        wev = c->write;
    	...
        clcf = ngx_http_get_module_loc_conf(r->main, ngx_http_core_module);
    
        // 超时控制
        if (wev->timedout) {
            ...
            c->timedout = 1;
            ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT);
            return;
        }
    
        // 延期控制
        if (wev->delayed || r->aio) {
    		...
            if (!wev->delayed) {
                ngx_add_timer(wev, clcf->send_timeout);
            }
            if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) {
                ngx_http_close_request(r, 0);
            }
            return;
        }
    
        // 调用ngx_http_output_filer继续发送
        rc = ngx_http_output_filter(r, NULL);
      	...
    	// 是否还有剩余
        if (r->buffered || r->postponed || (r == r->main && c->buffered)) {
            // 写事件加入定时器
            if (!wev->delayed) {
                ngx_add_timer(wev, clcf->send_timeout);
            }
            // 写事件加入epoll
            if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) {
                ngx_http_close_request(r, 0);
            }
            return;
        }
    	// 没有剩余情况调用ngx_http_finalize_request结束请求。
    	...
        r->write_event_handler = ngx_http_request_empty_handler;
        ngx_http_finalize_request(r, rc);
    }
    

    结束Http请求

    结束请求是一项复杂的工作。因为一个请求可能会被许多个事件触发,这使得Nginx框架调度到某个请求的回调方法时,在当前业务内似乎需要结束HTTP请求,但如果真的结束了请求,销毁了与请求相关的内存,多半会造成重大错误,因为这个请求可能还有其他事件在定时器或者epoll中。这个过程其实就与ngx_http_request_t的引用计数相关了,具体的结束是在ngx_http_finalize_request中来完成的,这里只看看关闭请求所用的方法吧,具体过程不看了。

    • ngx_http_close_connection

      HTTP框架提供的一个用于释放TCP连接的方法,它的目的很简单,就是关闭这个TCP连接。

    • ngx_http_free_request

      将会释放请求对应的ngx_http_request_t数据结构,它并不会像ngx_http_close_connection方法一样去释放承载请求的TCP连接,每一个TCP连接可以反复地承载多个HTTP请求。

    • ngx_http_close_request

      ngx_http_close_request方法是更高层的用于关闭请求的方法。在上面提到的引用计数,就是由ngx_http_close_request方法负责检测的,同时它会在引用计数清零时正式调用ngx_http_free_request方法和ngx_http_close_connection方法来释放请求、关闭连接。

    • ngx_http_finalize_connection

      ngx_http_finalize_connection方法在结束请求时,解决了keepalive特性和子请求的问题。

    • ngx_http_terminate_request

      ngx_http_terminate_request方法是提供给HTTP模块使用的结束请求方法,但它属于非正
      常结束的场景,可以理解为强制关闭请求。

    • ngx_http_finalize_request

      ngx_http_finalize_request方法是开发HTTP模块时最常使用的结束请求方法。

5. 小结

本文对Http框架的学习,从3种配置,到框架的初始化,最后是框架的整个执行流程。

# nginx 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×