在建立连接过程中,对于nginx监听到的每个客户端连接,都会将它的读事件的handler设置为ngx_http_init_request函数,这个函数就是请求处理的入口。在处理请求时,主要就是要解析http请求,比如:uri,请求行等,然后再根据请求生成响应。下面看一下nginx处理的具体过程。
1. ngx_http_init_request
在ngx_http_init_connection函数中,将连接的读事件的handler设置为这个函数,在客户端发送请求时会被调用。
/* ngx_event_t的data域存放事件对应的连接句柄 */
c = rev->data;
/* 在ngx_init_connection中对读事件添加了timer,超时直接返回*/
if (rev->timedout) {
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
ngx_http_close_connection(c);
return;
}
/* 连接处理的request的计数 */
c->requests++;
/* ngx_http_connection_t */
hc = c->data;
if (hc == NULL) {
hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
if (hc == NULL) {
ngx_http_close_connection(c);
return;
}
}
r = hc->request;
if (r) {
ngx_memzero(r, sizeof(ngx_http_request_t));
r->pipeline = hc->pipeline;
if (hc->nbusy) {
r->header_in = hc->busy[0];
}
} else {
/* 为request分配空间 */
r = ngx_pcalloc(c->pool, sizeof(ngx_http_request_t));
if (r == NULL) {
ngx_http_close_connection(c);
return;
}
hc->request = r;
}
c->data = r;
r->http_connection = hc;
c->sent = 0;
r->signature = NGX_HTTP_MODULE;
上面一段代码完成获取连接、请求并进行一部分初始化工作,比如会为新的请求分配内存。
/**
* ngx_listening_t的servers存放监听同一端口的server,但它们的监听的地址可以不同。
*
* port
* / | \
* addr1 addr2 addr3
* | | |
* conf1 conf2 conf3
*/
port = c->listening->servers;
r->connection = c;
if (port->naddrs > 1) {
/*
* there are several addresses on this port and one of them
* is an "*:port" wildcard so getsockname() in ngx_http_server_addr()
* is required to determine a server address
*/
/* 获取连接c的socket绑定的本地地址 */
if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
ngx_http_close_connection(c);
return;
}
/* 根据连接c的socket地址匹配port->addrs,找到对应的address:port */
switch (c->local_sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
sin6 = (struct sockaddr_in6 *) c->local_sockaddr;
addr6 = port->addrs;
/* the last address is "*" */
for (i = 0; i < port->naddrs - 1; i++) {
if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) {
break;
}
}
addr_conf = &addr6[i].conf;
break;
#endif
default: /* AF_INET */
sin = (struct sockaddr_in *) c->local_sockaddr;
addr = port->addrs;
/* the last address is "*" */
for (i = 0; i < port->naddrs - 1; i++) {
if (addr[i].addr == sin->sin_addr.s_addr) {
break;
}
}
addr_conf = &addr[i].conf;
break;
}
} else {
switch (c->local_sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
addr6 = port->addrs;
addr_conf = &addr6[0].conf;
break;
#endif
default: /* AF_INET */
addr = port->addrs;
addr_conf = &addr[0].conf;
break;
}
}
/* virtual hosts based on the address:port */
r->virtual_names = addr_conf->virtual_names;
/* the default server configuration for the address:port */
cscf = addr_conf->default_server;
/* 初始化为default server的配置,后续虚拟主机匹配成功会采用对应的配置 */
r->main_conf = cscf->ctx->main_conf;
r->srv_conf = cscf->ctx->srv_conf;
r->loc_conf = cscf->ctx->loc_conf;
注释中解释的很清楚,这段代码是为地址addr:port匹配server config,从而确定该请求的配置。由于nginx支持虚拟主机,所以这里确定了r->virtual_names是该addr:port对应的虚拟主机数组,后面会根据请求的HOST匹配对应的虚拟主机从而确定最终的配置。nginx的每个请求都有执行环境,这个环境就是ngx_request_t请求的main_conf、srv_conf和loc_conf。请求的执行环境就是nginx各个模块的配置信息,根据这些信息的不同请求的处理效果是不一样的。待解释完虚拟主机匹配后,再详细说明执行环境查找。
rev->handler = ngx_http_process_request_line;
r->read_event_handler = ngx_http_block_reading;
将连接读事件的handler设置为ngx_http_process_request_line,在本方法的最后会直接调用rev->handler(rev)。为什么这么做?
我的猜测是,在客户端第一次请求时,该连接的读事件的handler是ngx_init_request,用于处理话请求,后续请求直接复用,所以不需要执行ngx_init_request,所以需要将rev->handler设置成ngx_http_process_request_line,最后直接调用handler是为了在ngx_init_request中处理第一个请求。不知道这样对不对?
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
c->log->file = clcf->error_log->file;
if (!(c->log->log_level & NGX_LOG_DEBUG_CONNECTION)) {
c->log->log_level = clcf->error_log->log_level;
}
/* initialise the temporary buffer for the request's connection */
if (c->buffer