Nginx源代码分析之HTTP请求响应基本流程(十四)
2016-08-29 14:45
946 查看
HTTP的处理请求流程我们从ngx_http_init_connection开始论述
里面注册了一个处理函数
rev->handler = ngx_http_wait_request_handler;
ngx_http_wait_request_handler的参数是ngx_event_t rev,一旦有请求到达,数据已经被复制到rev->data中,这时会调用ngx_http_process_request_line来处理请求数据
但这里存在一个keepalive的问题,如果浏览器端设置了keepalive头部,那么r->keepalive标志为真,这样ngx在调用ngx_http_finalize_connection的时候,会调用ngx_http_set_keepalive设置keepalive的回调ngx_http_keepalive_handler,并保留当前的ngx_connection_t,然后直接返回。这样,在超时时间内,当同一个url请求再次到来的时候,i/o框架调用的是ngx_http_keepalive_handler而不是ngx_http_wait_request_handler。
还要补充的是,在http2中,因为socket默认是keepalive状态,而且多流并发,ngx_http_finalize_connection关闭的只是这个连接上的stream,因此会直接调用ngx_http_close_request然后返回:
#if (NGX_HTTP_V2)
if (r->stream) {
ngx_http_close_request(r, 0);
return;
}
#endif
而在ngx_http_close_request中,也不会关闭连接,而是关闭当前流:
#if (NGX_HTTP_V2)
if (r->stream) {
ngx_http_v2_close_stream(r->stream, rc);
return;
}
#endif
ngx_http_process_request_line的主要代码如下:
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);
if (rc == NGX_OK) {
if (ngx_http_process_request_uri(r) != NGX_OK) {
return;
}
if (r->host_start && r->host_end) {
host.len = r->host_end - r->host_start;
host.data = r->host_start;
rc = ngx_http_validate_host(&host, r->pool, 0);
if (rc == NGX_DECLINED) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent invalid host in request line");
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return;
}
if (rc == NGX_ERROR) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) {
return;
}
r->headers_in.server = host;
}
if (r->http_version < NGX_HTTP_VERSION_10) {
if (r->headers_in.server.len == 0
&& ngx_http_set_virtual_server(r, &r->headers_in.server)
== NGX_ERROR)
{
return;
}
ngx_http_process_request(r);
return;
}
c->log->action = "reading client request headers";
rev->handler = ngx_http_process_request_headers;
ngx_http_process_request_headers(rev);
return;
}
if (r->header_in->pos == r->header_in->end) {
rv = ngx_http_alloc_large_header_buffer(r, 1);
if (rv == NGX_ERROR) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
if (rv == NGX_DECLINED) {
r->request_line.len = r->header_in->end - r->request_start;
r->request_line.data = r->request_start;
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent too long URI");
ngx_http_finalize_request(r, NGX_HTTP_REQUEST_URI_TOO_LARGE);
return;
}
}
}
该函数的主要处理逻辑是按行读取r->connection->read中的数据,每成功读取到一行头部数据,再调用ngx_http_parse_request_line,根据http协议解析成http的头部,该函数的返回值如果是NGX_AGAIN,说明头部数据不完整,需要继续解析,说过返回NGX_OK,说明已经得到一个完整的头部,进入下一个处理流程。
首先调用ngx_http_process_request_uri处理url,接着调用ngx_http_set_virtual_server,该函数会根据已得到的host头部,在服务器初始化过程中配置的http{}中匹配一个server,其中关键代码如下:
rc = ngx_http_find_virtual_server(r->connection,
hc->addr_conf->virtual_names,
host, r, &cscf);
if (rc == NGX_ERROR) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}
if (rc == NGX_DECLINED) {
return NGX_OK;
}
r->srv_conf = cscf->ctx->srv_conf;
r->loc_conf = cscf->ctx->loc_conf;
重点是ngx_http_find_virtual_server,检索当前配置的server表,找到对应的server。最后2行,把匹配的svr_conf,以及server里面的location表赋给ngx_http_request_t *r,这样,后面的执行流程就能通过ngx_http_request_t *r 调用相应模块的handler来执行正确的处理。
然后涉及到一个HTTP版本的问题,如果低于1.0.直接调用ngx_http_process_request,对于高于1.0请求,则调用ngx_http_process_request_headers,该函数的过程主要是循环调用ngx_http_parse_header_line来处理请求的所有头部,当ngx_http_parse_header_line返回NGX_HTTP_PARSE_HEADER_DONE的时候,再调用ngx_http_process_request。
ngx_http_process_request的主要功能则是调用ngx_http_handler,ngx_http_handler判断当前http请求是否是内部跳转,然后设置r->phase_handler。最后调用
ngx_http_core_run_phases,这个函数的功能是循环检查已经注册的处理阶段,并调用每个阶段的checker来执行本阶段需要完成的任务。
这里要回头解释一下nginx的阶段的的具体含义和原理,在ngx_http_core_module.h中定义了所有的阶段
typedef enum {
NGX_HTTP_POST_READ_PHASE = 0,
NGX_HTTP_SERVER_REWRITE_PHASE,
NGX_HTTP_FIND_CONFIG_PHASE,
NGX_HTTP_REWRITE_PHASE,
NGX_HTTP_POST_REWRITE_PHASE,
NGX_HTTP_PREACCESS_PHASE,
NGX_HTTP_ACCESS_PHASE,
NGX_HTTP_POST_ACCESS_PHASE,
NGX_HTTP_TRY_FILES_PHASE,
NGX_HTTP_CONTENT_PHASE,
NGX_HTTP_LOG_PHASE
} ngx_http_phases;
nginx把整个请求响应划分为11个阶段,主要包括地址重定向,权限检查,请求文件,内容处理,日志记录等几个大的阶段,每个大的阶段分类下面可能有几个细阶段,比如重定向又分成NGX_HTTP_SERVER_REWRITE_PHASE,NGX_HTTP_FIND_CONFIG_PHASE,NGX_HTTP_REWRITE_PHASE,NGX_HTTP_POST_REWRITE_PHASE这几个阶段。我们知道,nginx有一个基本的核心模块ngx_http_core_module,还有其他许多功能模块,比如ngx_http_proxy_module,ngx_http_mp4_module等,以及第三方的可选模块。每个模块可以吧自己的处理函数注册到以上的阶段中。
阶段的初始化是在ngx_http.c的ngx_http_init_phases里面实现的,函数会初始化ngx_http_core_main_conf_t下面的ngx_http_phase_t的handlers数组:
if (ngx_array_init(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers,
cf->pool, 4, sizeof(ngx_http_handler_pt))
!= NGX_OK)
{
return NGX_ERROR;
}
接着,循环检查已注册的所有模块,调用模块的postconfiguration方法,该方法会把本模块的handler指针写到handlers数组里面。比如static module的postconfiguration是这样的
static ngx_int_t
ngx_http_static_init(ngx_conf_t *cf)
{
ngx_http_handler_pt *h;
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_static_handler;
return NGX_OK;
}
这些都准备好之后,会调用ngx_http_init_phase_handlers,他的作用是把已经注册的各阶段的处理函数和模块的处理函数写入到ngx_http_core_main_conf_t里面的phase_engine.handlers数据结构中,这样ngx_http_core_run_phases函数可以循环检查cmcf->phase_engine.handlers。然后判断是否执行这些阶段的处理函数:
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;
}
}
在前面的我们已经介绍过r->phase_handler是一个编号,其值一般是0,这样调用会从第一个注册的阶段的checker开始执行,每个checker内部会调用ph->handler,然后根据返回值决定r->phase_handler的值是递增还是直接指向ph-next。这样当前的checker调用结束后,会继续调用本阶段的handler或者下一个阶段的handler。一切都由phase_handler这个编号来的值来决定
当执行到NGX_HTTP_CONTENT_PHASE后,这个阶段的模块的函数一般会开始向客户端返回结果,这样HTTP流程就来到了响应阶段,如果是一个静态模块,响应比较简单,主要是根据请求的uri来读取本地文件或者缓存文件,然后填充响应包的头部和数据段,然后调用ngx_http_output_filter把数据发送出去。细节可以查看ngx_http_static_handler函数的代码。
里面注册了一个处理函数
rev->handler = ngx_http_wait_request_handler;
ngx_http_wait_request_handler的参数是ngx_event_t rev,一旦有请求到达,数据已经被复制到rev->data中,这时会调用ngx_http_process_request_line来处理请求数据
但这里存在一个keepalive的问题,如果浏览器端设置了keepalive头部,那么r->keepalive标志为真,这样ngx在调用ngx_http_finalize_connection的时候,会调用ngx_http_set_keepalive设置keepalive的回调ngx_http_keepalive_handler,并保留当前的ngx_connection_t,然后直接返回。这样,在超时时间内,当同一个url请求再次到来的时候,i/o框架调用的是ngx_http_keepalive_handler而不是ngx_http_wait_request_handler。
还要补充的是,在http2中,因为socket默认是keepalive状态,而且多流并发,ngx_http_finalize_connection关闭的只是这个连接上的stream,因此会直接调用ngx_http_close_request然后返回:
#if (NGX_HTTP_V2)
if (r->stream) {
ngx_http_close_request(r, 0);
return;
}
#endif
而在ngx_http_close_request中,也不会关闭连接,而是关闭当前流:
#if (NGX_HTTP_V2)
if (r->stream) {
ngx_http_v2_close_stream(r->stream, rc);
return;
}
#endif
ngx_http_process_request_line的主要代码如下:
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);
if (rc == NGX_OK) {
if (ngx_http_process_request_uri(r) != NGX_OK) {
return;
}
if (r->host_start && r->host_end) {
host.len = r->host_end - r->host_start;
host.data = r->host_start;
rc = ngx_http_validate_host(&host, r->pool, 0);
if (rc == NGX_DECLINED) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent invalid host in request line");
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return;
}
if (rc == NGX_ERROR) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) {
return;
}
r->headers_in.server = host;
}
if (r->http_version < NGX_HTTP_VERSION_10) {
if (r->headers_in.server.len == 0
&& ngx_http_set_virtual_server(r, &r->headers_in.server)
== NGX_ERROR)
{
return;
}
ngx_http_process_request(r);
return;
}
c->log->action = "reading client request headers";
rev->handler = ngx_http_process_request_headers;
ngx_http_process_request_headers(rev);
return;
}
if (r->header_in->pos == r->header_in->end) {
rv = ngx_http_alloc_large_header_buffer(r, 1);
if (rv == NGX_ERROR) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
if (rv == NGX_DECLINED) {
r->request_line.len = r->header_in->end - r->request_start;
r->request_line.data = r->request_start;
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent too long URI");
ngx_http_finalize_request(r, NGX_HTTP_REQUEST_URI_TOO_LARGE);
return;
}
}
}
该函数的主要处理逻辑是按行读取r->connection->read中的数据,每成功读取到一行头部数据,再调用ngx_http_parse_request_line,根据http协议解析成http的头部,该函数的返回值如果是NGX_AGAIN,说明头部数据不完整,需要继续解析,说过返回NGX_OK,说明已经得到一个完整的头部,进入下一个处理流程。
首先调用ngx_http_process_request_uri处理url,接着调用ngx_http_set_virtual_server,该函数会根据已得到的host头部,在服务器初始化过程中配置的http{}中匹配一个server,其中关键代码如下:
rc = ngx_http_find_virtual_server(r->connection,
hc->addr_conf->virtual_names,
host, r, &cscf);
if (rc == NGX_ERROR) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}
if (rc == NGX_DECLINED) {
return NGX_OK;
}
r->srv_conf = cscf->ctx->srv_conf;
r->loc_conf = cscf->ctx->loc_conf;
重点是ngx_http_find_virtual_server,检索当前配置的server表,找到对应的server。最后2行,把匹配的svr_conf,以及server里面的location表赋给ngx_http_request_t *r,这样,后面的执行流程就能通过ngx_http_request_t *r 调用相应模块的handler来执行正确的处理。
然后涉及到一个HTTP版本的问题,如果低于1.0.直接调用ngx_http_process_request,对于高于1.0请求,则调用ngx_http_process_request_headers,该函数的过程主要是循环调用ngx_http_parse_header_line来处理请求的所有头部,当ngx_http_parse_header_line返回NGX_HTTP_PARSE_HEADER_DONE的时候,再调用ngx_http_process_request。
ngx_http_process_request的主要功能则是调用ngx_http_handler,ngx_http_handler判断当前http请求是否是内部跳转,然后设置r->phase_handler。最后调用
ngx_http_core_run_phases,这个函数的功能是循环检查已经注册的处理阶段,并调用每个阶段的checker来执行本阶段需要完成的任务。
这里要回头解释一下nginx的阶段的的具体含义和原理,在ngx_http_core_module.h中定义了所有的阶段
typedef enum {
NGX_HTTP_POST_READ_PHASE = 0,
NGX_HTTP_SERVER_REWRITE_PHASE,
NGX_HTTP_FIND_CONFIG_PHASE,
NGX_HTTP_REWRITE_PHASE,
NGX_HTTP_POST_REWRITE_PHASE,
NGX_HTTP_PREACCESS_PHASE,
NGX_HTTP_ACCESS_PHASE,
NGX_HTTP_POST_ACCESS_PHASE,
NGX_HTTP_TRY_FILES_PHASE,
NGX_HTTP_CONTENT_PHASE,
NGX_HTTP_LOG_PHASE
} ngx_http_phases;
nginx把整个请求响应划分为11个阶段,主要包括地址重定向,权限检查,请求文件,内容处理,日志记录等几个大的阶段,每个大的阶段分类下面可能有几个细阶段,比如重定向又分成NGX_HTTP_SERVER_REWRITE_PHASE,NGX_HTTP_FIND_CONFIG_PHASE,NGX_HTTP_REWRITE_PHASE,NGX_HTTP_POST_REWRITE_PHASE这几个阶段。我们知道,nginx有一个基本的核心模块ngx_http_core_module,还有其他许多功能模块,比如ngx_http_proxy_module,ngx_http_mp4_module等,以及第三方的可选模块。每个模块可以吧自己的处理函数注册到以上的阶段中。
阶段的初始化是在ngx_http.c的ngx_http_init_phases里面实现的,函数会初始化ngx_http_core_main_conf_t下面的ngx_http_phase_t的handlers数组:
if (ngx_array_init(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers,
cf->pool, 4, sizeof(ngx_http_handler_pt))
!= NGX_OK)
{
return NGX_ERROR;
}
接着,循环检查已注册的所有模块,调用模块的postconfiguration方法,该方法会把本模块的handler指针写到handlers数组里面。比如static module的postconfiguration是这样的
static ngx_int_t
ngx_http_static_init(ngx_conf_t *cf)
{
ngx_http_handler_pt *h;
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_static_handler;
return NGX_OK;
}
这些都准备好之后,会调用ngx_http_init_phase_handlers,他的作用是把已经注册的各阶段的处理函数和模块的处理函数写入到ngx_http_core_main_conf_t里面的phase_engine.handlers数据结构中,这样ngx_http_core_run_phases函数可以循环检查cmcf->phase_engine.handlers。然后判断是否执行这些阶段的处理函数:
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;
}
}
在前面的我们已经介绍过r->phase_handler是一个编号,其值一般是0,这样调用会从第一个注册的阶段的checker开始执行,每个checker内部会调用ph->handler,然后根据返回值决定r->phase_handler的值是递增还是直接指向ph-next。这样当前的checker调用结束后,会继续调用本阶段的handler或者下一个阶段的handler。一切都由phase_handler这个编号来的值来决定
当执行到NGX_HTTP_CONTENT_PHASE后,这个阶段的模块的函数一般会开始向客户端返回结果,这样HTTP流程就来到了响应阶段,如果是一个静态模块,响应比较简单,主要是根据请求的uri来读取本地文件或者缓存文件,然后填充响应包的头部和数据段,然后调用ngx_http_output_filter把数据发送出去。细节可以查看ngx_http_static_handler函数的代码。
相关文章推荐
- IMSDroid Http Stack分析 -- 基本流程
- Zend framework处理一个http请求的流程分析
- Android中Http网络请求响应基本知识
- HttpServlet容器响应Web客户请求流程
- OkHttp3 HTTP请求执行流程分析
- IMSDroid Http Stack分析 -- 基本流程
- nginx记录分析网站响应慢的请求(ngx_http_log_request_speed)
- http协议5---响应信息的基本格式(实现对请求信息的修改)
- nginx记录分析网站响应慢的请求(ngx_http_log_request_speed)
- OKHttp网络框架源码解析(一)okHttp框架同步异步请求流程和源码分析
- IMSDroid Http Stack分析 -- 基本流程
- iOS开发工具-如何使用网络封包分析工具Charles,通过配置proxy对http、https、tcp、udp 等协议的请求响应过程交互信息进行分析、判断、解决我们移动开发中的遇到的各种实际问题。
- HTTP请求的基本概念 HTTP请求头和响应头的含义
- http请求响应码分析
- Zend framework处理一个http请求的流程分析
- HTTP协议深入学习之请求头域与响应头域详细分析
- 使用 firefox 的开发者工具与 DIG 命令行工具,来分析访问站点时的网络连接与 HTTP 请求和响应
- nginx源代码分析--监听套接字的创建 套接字的监听 HTTP请求创建连接
- HTTP发送请求和接收响应的整个流程
- 3.OkHttp请求流程分析