函数功能:初始化一些数据结构,找到虚拟服务器配置。
ngx_http_init_request函数的主要工作即是初始化请求,由于它是一个事件处理函数,它只有唯一一个ngx_event_t *类型的参数,ngx_event_t 结构在nginx中表示一个事件,事件处理的上下文类似于一个中断处理的上下文,为了在这个上下文得到相关的信息,nginx中一般会将连接结构的引用保存在事件结构的data字段,请求结构的引用则保存在连接结构的data字段,这样在事件处理函数中可以方便的得到对应的连接结构和请求结构。
进入函数内部看一下,首先判断该事件是否是超时事件,如果是的话直接关闭连接并返回;反之则是指之前accept的连接上有请求过来需要处理,ngx_http_init_request函数首先在连接的内存池中为该请求分配一个ngx_http_request_t结构,这个结构将用来保存该请求所有的信息。分配完之后,这个结构的引用会被包存在连接的hc成员的request字段,以便于在长连接或pipelined请求中复用该请求结构。
在这个函数中,nginx根据该请求的接收端口和地址找到一个默认虚拟服务器配置,在nginx配置文件中可以设置多个监听在不同端口和地址的虚拟服务器(每个server块对应一个虚拟服务器),另外还根据域名(server_name指令可以配置该虚拟服务器对应的域名)来区分监听在相同端口和地址的虚拟服务器,每个虚拟服务器可以拥有不同的配置内容,而这些配置内容决定了nginx在接收到一个请求之后如何处理该请求。找到之后,相应的配置被保存在该请求对应的ngx_http_request_t结构中。注意这里根据端口和地址找到的默认配置只是临时使用一下,最终nginx会根据域名找到真正的虚拟服务器配置。
同样的nginx会为这个请求分配一个内存池(r->pool),后续所有与该请求相关的内存分配一般都会使用该内存池,默认大小为4096个字节,可以使用request_pool_size指令修改;为这个请求分配响应头链表,初始大小为20;创建所有模块的上下文ctx指针数组,变量数据;
将该请求的main字段设置为它本身,表示这是一个主请求,nginx中对应的还有子请求概念,将该请求的count字段设置为1,count字段表示请求的引用计数;将当前时间保持在start_sec和start_msec字段,这个时间是该请求的起始时刻,将被用来计算一个请求的处理时间(request time),nginx使用的这个起始点和apache略有差别,nginx中请求的起始点是接收到客户端的第一个数据包开始,而apache则是接收到客户端的整个request line后开始算起;
初始化请求的其他字段,比如将uri_changes设置为11,表示最多可以将该请求的uri改写10次,subrequests被设置为201,表示一个请求最多可以发起200个子请求;
做完所有这些初始化工作之后,ngx_http_init_request函数会调用读事件的处理函数来真正的解析客户端发过来的数据,也就是会进入ngx_http_process_request_line函数中处理。
static void
ngx_http_init_request(ngx_event_t *rev)
{
…
//超时事件
if (rev->timedout) {
。。。
}
…
r = hc->request; // r=hc->request=c->data=rev->data->data->request
if (r) {
。。。
} else {
r = ngx_pcalloc(c->pool, sizeof(ngx_http_request_t));
if (r == NULL) {
ngx_http_close_connection(c);
return;
}
hc->request = r;
}
。。。
/* find the server configuration for the address:port */
port = c->listening->servers;
r->connection = c;
if (port->naddrs > 1) {
…
} else {
switch (c->local_sockaddr->sa_family) {
…
default: /* AF_INET */
addr = port->addrs;
addr_conf = &addr;[0].conf;
break;
}
}
r->virtual_names = addr_conf->virtual_names;
/* the default server configuration for the address:port */
cscf = addr_conf->default_server;
r->main_conf = cscf->ctx->main_conf;
r->srv_conf = cscf->ctx->srv_conf;
r->loc_conf = cscf->ctx->loc_conf;
rev->handler = ngx_http_process_request_line;
r->read_event_handler = ngx_http_block_reading;
//SSL握手
#if (NGX_HTTP_SSL)
。。。
#endif
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
c->log->file = clcf->error_log->file; //”./log/error.log”
if (!(c->log->log_level & NGX_LOG_DEBUG_CONNECTION)) {
c->log->log_level = clcf->error_log->log_level;
}
…
r->pool = ngx_create_pool(cscf->request_pool_size, c->log);
if (r->pool == NULL) {
ngx_http_close_connection(c);
return;
}
rev->handler(rev); // ngx_http_process_request_line
}
函数功能:解析请求行。
将连接的读事件的处理函数设置为ngx_http_process_request_line函数,这个函数用来解析请求行,将请求的read_event_handler设置为ngx_http_block_reading函数,这个函数实际上什么都不做(当然在事件模型设置为水平触发时,唯一做的事情就是将事件从事件模型监听列表中删除,防止该事件一直被触发)。
ngx_http_process_request_line函数的主要作用即是解析请求行,同样由于涉及到网络IO操作,即使是很短的一行请求行可能也不能被一次读完,所以在之前的ngx_http_init_request函数中,ngx_http_process_request_line函数被设置为读事件的处理函数,它也只拥有一个唯一的ngx_event_t *类型参数,并且在函数的开头,同样需要判断是否是超时事件,如果是的话,则关闭这个请求和连接;否则开始正常的解析流程。先调用ngx_http_read_request_header函数读取数据。并为这个请求分配一个缓冲区用来保存它的请求头,地址保存在header_in字段,默认大小为1024个字节,可以使用client_header_buffer_size指令修改,这里需要注意一下,nginx用来保存请求头的缓冲区是在该请求所在连接的内存池中分配,而且会将地址保存一份在连接的buffer字段中,这样做的目的也是为了给该连接的下一次请求重用这个缓冲区,另外如果客户端发过来的请求头大于1024个字节,nginx会重新分配更大的缓存区,默认用于大请求的头的缓冲区最大为8K,最多4个,这2个值可以用large_client_header_buffers指令设置,后面还会说到请求行和一个请求头都不能超过一个最大缓冲区的大小。
由于可能多次进入ngx_http_process_request_line函数,ngx_http_read_request_header函数首先检查请求的header_in指向的缓冲区内是否有数据,有的话直接返回;否则从连接读取数据并保存在请求的header_in指向的缓存区,而且只要缓冲区有空间的话,会一次尽可能多的读数据,读到多少返回多少;如果客户端暂时没有发任何数据过来,并返回NGX_AGAIN,返回之前会做2件事情:
(1)设置一个定时器,时长默认为60s,可以通过指令client_header_timeout设置,如果定时事件到达之前没有任何可读事件,nginx将会关闭此请求;
(2)调用ngx_handle_read_event函数处理一下读事件,如果该连接尚未在事件处理模型上挂载读事件,则将其挂载上;如果客户端提前关闭了连接或者读取数据发生了其他错误,则给客户端返回一个400错误(当然这里并不保证客户端能够接收到响应数据,因为客户端可能都已经关闭了连接),最后函数返回NGX_ERROR;
如果ngx_http_read_request_header函数正常的读取到了数据,ngx_http_process_request_line函数将调用ngx_http_parse_request_line函数来解析,这个函数根据http协议规范中对请求行的定义实现了一个有限状态机,经过这个状态机,nginx会记录请求行中的请求方法(Method),请求uri以及http协议版本在缓冲区中的起始位置,在解析过程中还会记录一些其他有用的信息,以便后面的处理过程中使用。如果解析请求行的过程中没有产生任何问题,该函数会返回NGX_OK;如果请求行不满足协议规范,该函数会立即终止解析过程,并返回相应错误号;如果缓冲区数据不够,该函数返回NGX_AGAIN。
在整个解析http请求的状态机中始终遵循着两条重要的原则:减少内存拷贝和回溯。内存拷贝是一个相对比较昂贵的操作,大量的内存拷贝会带来较低的运行时效率。nginx在需要做内存拷贝的地方尽量只拷贝内存的起始和结束地址而不是内存本身,这样做的话仅仅只需要两个赋值操作而已,大大降低了开销,当然这样带来的影响是后续的操作不能修改内存本身,如果修改的话,会影响到所有引用到该内存区间的地方,所以必须很小心的管理,必要的时候需要拷贝一份。
这里不得不提到nginx中最能体现这一思想的数据结构,ngx_buf_t,它用来表示nginx中的缓存,在很多情况下,只需要将一块内存的起始地址和结束地址分别保存在它的pos和last成员中,再将它的memory标志置1,即可表示一块不能修改的内存区间,在另外的需要一块能够修改的缓存的情形中,则必须分配一块所需大小的内存并保存其起始地址,再将ngx_bug_t的temprary标志置1,表示这是一块能够被修改的内存区域。
再回到ngx_http_process_request_line函数中,如果ngx_http_parse_request_line函数返回了错误,则直接给客户端返回400错误;如果返回NGX_AGAIN,则需要判断一下是否是由于缓冲区空间不够,还是已读数据不够。如果是缓冲区大小不够了,nginx会调用ngx_http_alloc_large_header_buffer函数来分配另一块大缓冲区,如果大缓冲区还不够装下整个请求行,nginx则会返回414错误给客户端,否则分配了更大的缓冲区并拷贝之前的数据之后,继续调用ngx_http_read_request_header函数读取数据来进入请求行自动机处理,直到请求行解析结束;
如果返回了NGX_OK,则表示请求行被正确的解析出来了,这时先记录好请求行的起始地址以及长度,并将请求uri的path和参数部分保存在请求结构的uri字段,请求方法起始位置和长度保存在method_name字段,http版本起始位置和长度记录在http_protocol字段。还要从uri中解析出参数以及请求资源的拓展名,分别保存在args和exten字段。
static void
ngx_http_process_request_line(ngx_event_t *rev)
{
。。。
rc = NGX_AGAIN;
for ( ;; ) {
if (rc == NGX_AGAIN) { // NO.1
n = ngx_http_read_request_header(r);
//recv()读取请求头信息
if (n == NGX_AGAIN || n == NGX_ERROR) {
return;
}
}
//记录请求行中的请求方法(Method),请求uri以及http协议版本在缓冲区中的起始位置
rc = ngx_http_parse_request_line(r, r->header_in);
if (rc == NGX_OK) {
…
c->log->action = "reading client request headers";
rev->handler = ngx_http_process_request_headers;
ngx_http_process_request_headers(rev);
return;
}
。。。
}
}
函数功能:处理HTTP请求头。
ngx_http_process_request_headers会继续检查该事件是否为超时事件,如果不是,继续处理。在这里,需要检查存放client的header的空间,不够的话,需要用ngx_http_alloc_large_header_buffer申请一个更大的空间。紧接着,需要一步一步解析header的每一个部分,并以key/value的方式将其存放到r->headers_in.headers中。一切做完后,读取完请求头之后,nginx调用了ngx_http_process_request_header函数,这个函数主要做了两个方面的事情,一是调用ngx_http_find_virtual_server函数查找虚拟服务器配置;二是对一些请求头做一些协议的检查。比如对那些使用http1.1协议但是却没有发送Host头的请求,nginx给这些请求返回400错误。还有nginx现在的版本并不支持chunked格式的输入,如果某些请求申明自己使用了chunked格式的输入(请求带有值为chunked的transfer_encoding头部),nginx给这些请求返回411错误。最后转入ngx_http_process_request处理请求。
static void
ngx_http_process_request_headers(ngx_event_t *rev)
{
。。。
if (rev->timedout) { // 是否超时
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
c->timedout = 1;
ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
return;
}
。。。
rc = NGX_AGAIN;
for ( ;; ) {
if (rc == NGX_AGAIN) {
if (r->header_in->pos == r->header_in->end) { //一般不需要
rv = ngx_http_alloc_large_header_buffer(r, 0);
。。。
}
n = ngx_http_read_request_header(r); //快速返回了,因为已经读过
if (n == NGX_AGAIN || n == NGX_ERROR) {
return;
}
}
rc = ngx_http_parse_header_line(r, r->header_in,
cscf->underscores_in_headers); //解析HTTP头部
if (rc == NGX_OK) {
if (r->invalid_header && cscf->ignore_invalid_headers) {
。。。
//以key 和value的格式保存HTTP头
h = ngx_list_push(&r-;>headers_in.headers); //ngx_http_headers_in_t ,存放key,value
if (h == NULL) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
h->hash = r->header_hash;
h->key.len = r->header_name_end - r->header_name_start;
h->key.data = r->header_name_start; // "User-Agent",host
h->key.data[h->key.len] = '\0';
h->value.len = r->header_end - r->header_start;
h->value.data = r->header_start;
。。。
if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { //检验函数
return;
}
continue;
}
if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
/* a whole header has been parsed successfully */
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http header done");
r->request_length += r->header_in->pos - r->header_in->start;
r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;
rc = ngx_http_process_request_header(r); //检验头信息如1.1协议需要host信息?
if (rc != NGX_OK) {
return;
}
ngx_http_process_request(r);
return;
}
。。。。
}
}
函数功能:处理请求。
static void
ngx_http_process_request(ngx_http_request_t *r)
{
。。。
if (c->read->timer_set) { // 超时,过了一会儿,就在这里了
ngx_del_timer(c->read); //here
}
c->read->handler = ngx_http_request_handler;
c->write->handler = ngx_http_request_handler;
r->read_event_handler = ngx_http_block_reading;
ngx_http_handler(r); //核心是ngx_http_core_run_phases,进入各阶段处理函数
ngx_http_run_posted_requests(c);
}