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_t
、ngx_http_core_srv_conf_t
、ngx_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方法建立的结构体。
如下图所示:

ngx_http_core_main_conf_t的结构如下:它的创建和初始化是在ngx_http_module中调用的ngx_http_core_module的ngx_http_core_create_main_conf
与ngx_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_conf
与merge_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_s
、ngx_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头部。
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_PHASE | ngx_http_core_generic_phase |
NGX_HTTP_SERVER_REWRITE_PHASE | ngx_http_core_rewrite_phase |
NGX_HTTP_FIND_CONFIG_PHASE | ngx_http_core_find_config_phase |
NGX_HTTP_REWRITE_PHASE | ngx_http_core_rewrite_phase |
NGX_HTTP_POST_REWRITE_PHASE | ngx_http_core_post_rewrite_phase |
NGX_HTTP_PREACCESS_PHASE | ngx_http_core_generic_phase |
NGX_HTTP_ACCESS_PHASE | ngx_http_core_access_phase |
NGX_POST_ACCESS_PHASE | ngx_http_core_post_access_phase |
NGX_HTTP_PRECONTENT_PHASE | ngx_http_core_generic_phase |
NGX_HTTP_CONTENT_PHASE | ngx_http_core_content_phase |
NGX_HTTP_LOG_PHASE | ngx_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_request
和ngx_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种配置,到框架的初始化,最后是框架的整个执行流程。