文章目录
- ngx_http_wait_request_handler
- ngx_http_create_reques
- ngx_http_process_request_line
- ngx_http_process_request_headers
- ngx_http_process_host
- ngx_http_set_virtual_server
- ngx_http_process_request_headers
ngx_http_wait_request_handler
当客户端发送的请求到达时,epoll 监测到了一个读事件,然后调用读事件的 handler ngx_http_wait_request_handler 函数处理
。它的原型如下:
static void ngx_http_wait_request_handler(ngx_event_t *rev);
函数开始
c = rev->data;
if (rev->timedout) {
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
ngx_http_close_connection(c);
return;
}
如果是由定时器触发的该读事件,说明客户端没有发送请求导致超时,那么直接调用 ngx_http_close_connection
关闭连接。后面很多 handler 函数里开头都是这样,后面我就不提了。
if (c->close) {
ngx_http_close_connection(c);
return;
}
如果该连接被要求强制关闭,则会关闭连接。
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;
}
b = c->buffer 指向连接的缓冲区结构 ngx_buf_t
,此时为NULL 。这结构体定义如下:
struct ngx_buf_s {
u_char *pos;
u_char *last;
off_t file_pos;
off_t file_last;
u_char *start; /* start of buffer */
u_char *end; /* end of buffer */
ngx_buf_tag_t tag;
ngx_file_t *file;
ngx_buf_t *shadow;
/* the buf's content could be changed */
unsigned temporary:1;
/*
* the buf's content is in a memory cache or in a read only memory
* and must not be changed
*/
unsigned memory:1;
/* the buf's content is mmap()ed and must not be changed */
unsigned mmap:1;
unsigned recycled:1;
unsigned in_file:1;
unsigned flush:1;
unsigned sync:1;
unsigned last_buf:1;
unsigned last_in_chain:1;
unsigned last_shadow:1;
unsigned temp_file:1;
/* STUB */ int num;
};
这个结构体很重要。成员 start 和 end 表示缓冲区的起始和结束位置。而 post 和 last 则指向缓冲区中有效数据的起始和结束位置。start 和 post 之间的数据是被处理(已经分析过了)过的,可以认为是无效的。end 和 post 的差值表示缓冲区剩余空间大小。
ngx_create_temp_buf
函数创建了一个缓冲区,它的标志位 temporary 置1,表示缓冲区中的数据可以被修改。
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;
}
调用 c->recv 指向的函数读取数据到缓冲区,这个函数我们上篇文章中说过了就是 ngx_unix_recv
函数。当它返回 NGX_AGAIN
函数时,表示暂时无法读取数据,那么再把定时器添加上,然后把分配的缓冲区释放掉。这就是为什么上文有 else if (b->start == NULL) 分支的原因。
当返回 NGX_ERROR
时,表示读取数据过程中出现错误,那么直接关闭连接。
当返回为 0 时,表示客户端主动关闭连接,那么我们也关闭连接。
当返回的 n 大于 0 时,表示读取到的数据长度。
其他的读取函数的返回值都是这几种情况,后面遇到类似的就不提了。
b->last += n;
更新 last 指针,让他指向读取到的数据末尾。ngx_unix_recv
函数只是把数据读取到了缓冲区,并没有更新指针,所以这里要更新下。现在 post 和 last 之间就是读取到的数据了。
ngx_reusable_connection(c, 0);
因为现在读取到了请求,所以将连接置为不可重用的状态。
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_create_request
函数创建了一个表示请求的 ngx_http_request_t
结构,用 data 指向它。
然后将读事件的 handler 设置为 ngx_http_process_request_line
函数。
ngx_http_create_reques
这个函数的原型如下:
ngx_http_request_t *ngx_http_create_request(ngx_connection_t *c);
起始代码如下:
hc = c->data;
cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);
pool = ngx_create_pool(cscf->request_pool_size, c->log);
if (pool == NULL) {
return NULL;
}
r = ngx_pcalloc(pool, sizeof(ngx_http_request_t));
if (r == NULL) {
ngx_destroy_pool(pool);
return NULL;
}
r->pool = pool;
hc 指向 ngx_http_init_connection
函数中创建的 ngx_http_connection_t
结构。
然后调用 ngx_create_pool
创建一个内存池(请求使用的内存池),并调用 ngx_pcalloc
从内存池中分配一个 ngx_http_request_t
结构。其中 pool 成员指向内存池。
r->http_connection = hc;
r->signature = NGX_HTTP_MODULE;
r->connection = c;
r->main_conf = hc->conf_ctx->main_conf;
r->srv_conf = hc->conf_ctx->srv_conf;
r->loc_conf = hc->conf_ctx->loc_conf;
r->read_event_handler = ngx_http_block_reading;
然后做一些初始化。重要的是设置了 main_conf、srv_conf、loc_conf 成员。它们分别指向默认 server{} 配置上下文的 main、srv、loc 级别的配置结构数组。
r->header_in = hc->busy ? hc->busy->buf : c->buffer;
header_in 指向保存请求的缓冲区。
if (ngx_list_init(&r->headers_out.headers, r->pool, 20,
sizeof(ngx_table_elt_t))
!= NGX_OK)
{
ngx_destroy_pool(r->pool);
return NULL;
}
初始化保存响应头部的链表。
r->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module);
if (r->ctx == NULL) {
ngx_destroy_pool(r->pool);
return NULL;
}
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
r->variables = ngx_pcalloc(r->pool, cmcf->variables.nelts
* sizeof(ngx_http_variable_value_t));
if (r->variables == NULL) {
ngx_destroy_pool(r->pool);
return NULL;
}
分配保存所有模块上下文的数组。
分配保存所有变量的数组。
r->main = r;
r->count = 1;
tp = ngx_timeofday();
r->start_sec = tp->sec;
r->start_msec = tp->msec;
请求的主请求指向自身。引用计数初始化为1。记录接受到请求的时间戳。
r->method = NGX_HTTP_UNKNOWN;
r->http_version = NGX_HTTP_VERSION_10;
r->headers_in.content_length_n = -1;
r->headers_in.keep_alive_n = -1;
r->headers_out.content_length_n = -1;
r->headers_out.last_modified_time = -1;
r->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1;
r->subrequests = NGX_HTTP_MAX_SUBREQUESTS + 1;
r->http_state = NGX_HTTP_READING_REQUEST_STATE;
ctx = c->log->data;
ctx->request = r;
ctx->current_request = r;
r->log_handler = ngx_http_log_error_handler;
return r;
初始化一些成员。设置错误日志 handler 为 ngx_http_log_error_handler
。
ngx_http_process_request_line
ngx_http_wait_request_handler
函数最后调用 ngx_http_process_request_line
函数来读取请求行,它的定义如下:
static void ngx_http_process_request_line(ngx_event_t *rev);
这个函数里面是一个 for 循环:
rc = NGX_AGAIN;
for ( ;; ) {
if (rc == NGX_AGAIN) {
n = ngx_http_read_request_header(r);
if (n == NGX_AGAIN || n == NGX_ERROR) {
return;
}
}
rc = ngx_http_parse_request_line(r, r->header_in);
循环调用 ngx_http_read_request_header
函数读请求行数据。然后调用 ngx_http_parse_request_line
函数解析请求行(它会一边解析数据时,一边更新缓冲区的 pos 指针)。当它返回 NGX_OK
时,说明请求行解析完成。
if (rc == NGX_OK) {
/* the request line has been parsed successfully */
r->request_line.len = r->request_end - r->request_start;
r->request_line.data = r->request_start;
r->request_length = r->header_in->pos - r->request_start;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http request line: \"%V\"", &r->request_line);
r->method_name.len = r->method_end - r->request_start + 1;
r->method_name.data = r->request_line.data;
if (r->http_protocol.data) {
r->http_protocol.len = r->request_end - r->http_protocol.data;
}
if (ngx_http_process_request_uri(r) != NGX_OK) {
return;
}
r->request_line 就是缓冲区中请求行。
r->request_length 时目前已知的请求行长度。
r->method_name 就是请求方法。
r->http_protocol 就是HTTP协议版本号。
然后调用 ngx_http_process_request_uri
函数处理 url,分离出 uri,参数,请求的文件扩展名。
if (ngx_list_init(&r->headers_in.headers, r->pool, 20,
sizeof(ngx_table_elt_t))
!= NGX_OK)
{
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
c->log->action = "reading client request headers";
rev->handler = ngx_http_process_request_headers;
ngx_http_process_request_headers(rev);
return;
}
接着初始化保存请求头部的链表,每个请求头用 ngx_table_elt_t
结构表示。
typedef struct {
ngx_uint_t hash;
ngx_str_t key;
ngx_str_t value;
u_char *lowcase_key;
} ngx_table_elt_t;
key 是请求头部名称,value 是请求头部的值,lowcase_key 指向小写的头部名称,hash 是小写头部名称计算的 hash 值。
然后将读事件的 handler 设置为 ngx_http_process_request_headers
,并调用。
ngx_http_process_request_headers
这个函数与 ngx_http_process_request_line
类似,函数体里是一个大循环。
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
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);
如果缓冲区满了,则调用 ngx_http_alloc_large_header_buffer
分配一个更大的缓冲区。
n = ngx_http_read_request_header(r);
if (n == NGX_AGAIN || n == NGX_ERROR) {
return;
}
它返回 NGX_OK
,说明大的缓冲区分配成功。还是调用 ngx_http_read_request_header
读取请求头部,n 是读取到的数据长度。
rc = ngx_http_parse_header_line(r, r->header_in,
cscf->underscores_in_headers);
if (rc == NGX_OK) {
r->request_length += r->header_in->pos - r->header_name_start;
/* a header line has been parsed successfully */
h = ngx_list_push(&r->headers_in.headers);
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;
h->key.data[h->key.len] = '\0';
h->value.len = r->header_end - r->header_start;
h->value.data = r->header_start;
h->value.data[h->value.len] = '\0';
h->lowcase_key = ngx_pnalloc(r->pool, h->key.len);
if (h->lowcase_key == NULL) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
if (h->key.len == r->lowcase_index) {
ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);
} else {
ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
}
调用 ngx_http_parse_header_line
函数解析一个请求头部。当它返回 NGX_OK
时,说明成功解析到一个请求头部。那么更新 r->request_length 长度。填充好 ngx_table_elt_t
结构,将它加入 r->headers_in.headers 链表中。
hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
h->lowcase_key, h->key.len);
if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
return;
}
continue;
}
接下来,根据请求头部名称,找到处理它的 handler。ngx_hash_find
在这里返回一个 ngx_http_header_t
结构,这些结构体定义在全局变量 ngx_http_headers_in
中。
ngx_http_header_t ngx_http_headers_in[] = {
{ ngx_string("Host"), offsetof(ngx_http_headers_in_t, host),
ngx_http_process_host },
...
在解析完 http{} 配置时,ngx_http_init_headers_in_hash
函数会被调用将它们组织成一个 hash 表(cmcf->headers_in_hash)。
这里就讲一个最关键的 handler ,就是处理 Host 头部的函数 ngx_http_process_host
。
ngx_http_process_host
host = h->value;
rc = ngx_http_validate_host(&host, r->pool, 0);
它先调用 ngx_http_validate_host
检查 Host 的头部的值是否是一个合法的值。
if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) {
return NGX_ERROR;
}
r->headers_in.server = host;
return NGX_OK;
然后调用 ngx_http_set_virtual_server
函数,根据 Host 头部的值,查找匹配的虚拟服务。
ngx_http_set_virtual_server
hc = r->http_connection;
rc = ngx_http_find_virtual_server(r->connection,
hc->addr_conf->virtual_names,
host, r, &cscf);
它调用 ngx_http_find_virtual_server
函数查找匹配的虚拟服务。监听的IP和端口号相关的所有服务都被组织成了一个 hash 表,这个函数里就是调用 ngx_hash_find_combined
函数查找 hash 表,而这个 hash 表支持通配匹配,它的查找规则是:
- 先匹配精确的形式。
- 再匹配最长的前缀统配的形式。
- 再匹配最长的后缀通配的形式。
如果都不匹配,ngx_http_find_virtual_server
函数再查找是否有匹配的正则表达式的形式。
if (rc == NGX_DECLINED) {
return NGX_OK;
}
r->srv_conf = cscf->ctx->srv_conf;
r->loc_conf = cscf->ctx->loc_conf;
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
ngx_set_connection_log(r->connection, clcf->error_log);
return NGX_OK;
如果 ngx_http_find_virtual_server
返回 NGX_DECLINED
,表示没有匹配的服务,直接返回。这样就会使用默认服务。
如果返回 NGX_OK
,说明找到了匹配的服务。那么将 srv_conf 和 loc_conf 设置为解析这个匹配的 server{} 时,创建的 srv 和 loc 级别的配置结构数组。也就是,之后处理这个请求时都使用这个 server {} 里的配置项。
到这里,我们就看到了一个完整的匹配虚拟服务的流程。
ngx_http_process_request_headers
让我们又回到这个函数。当 ngx_http_parse_header_line
返回 NGX_HTTP_PARSE_HEADER_DONE
时,表示所有的请求头部都已经解析完。
if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
/* a whole header has been parsed successfully */
r->request_length += r->header_in->pos - r->header_name_start;
r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;
rc = ngx_http_process_request_header(r);
if (rc != NGX_OK) {
return;
}
ngx_http_process_request(r);
return;
}
那么它会调用 ngx_http_process_request_header
函数来检查头部的使用是否符合 HTTP 协议规范。如果是的话就调用 ngx_http_process_request
开始处理请求。
下篇文章继续。