当我们发送响应的时候,意味着我们即将结束当前HTTP请求,注意这里并不是关闭TCP连接,因此TCP连接可能正用在其他请求上。Nginx定义了很多接口用关闭HTTP请求,但用的最多还是ngx_http_finalize_request。接下来分析一下它。
一、Nginx管理HTTP核心思想
众所周知,Nginx全异步框架,当一个流程可能需要较长时间占用进程,那么Nginx建议派生出子请求(subrequest),用于处理当前业务逻辑。这样无疑增加了Nginx复杂度,因为很有可能子请求又会创建新的子请求,那么Nginx是如何管理这些请求呢以及如何确定是否真正关闭请求呢?Nginx使用下面三个参数进行管理(主要参数):
struct ngx_http_request_s {
...
/* 原始请求 客户端发送过来的HTTP请求 原始请求下main指向自己 */
ngx_http_request_t *main;
/* 当前请求的父请求。未必是原始请求。原始请求的parent为NULL */
ngx_http_request_t *parent;
...
/**
* 表示当前请求引用计数.
* 1、当派生出一个子请求时,就会把原始请求count加1,当子请求结束的时候count
* 减1。当子请求再次派生出新的子请求,原始请求count也会加1
* 2、当我们接收body的时候,count也会加1,避免在count为0的场景下把请求回调掉。
* 3、这里所说的原始请求,就是客户端发送过来的http请求
*/
unsigned count:16;
...
}
说明:
1、通过main、parent用于管理父子请求间关系,注意main始终指向原始请求。
2、count参数是计数器,当count为0时,就要真正销毁请求。
二、函数ngx_http_finalize_request
/**
* 结束HTTP请求
* @param r http请求
* @param rc 一般是发送header或者body函数的返回值
*/
void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
{
ngx_connection_t *c;
ngx_http_request_t *pr;
ngx_http_core_loc_conf_t *clcf;
c = r->connection;
ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http finalize request: %i, \"%V?%V\" a:%d, c:%d",
rc, &r->uri, &r->args, r == c->data, r->main->count);
if (rc == NGX_DONE)
{
ngx_http_finalize_connection(r);//结束连接 只有count为0时才会真正关闭连接
return;
}
if (rc == NGX_OK && r->filter_finalize)
{
c->error = 1;
}
if (rc == NGX_DECLINED)
{//再次执行流水线
r->content_handler = NULL;
r->write_event_handler = ngx_http_core_run_phases;
ngx_http_core_run_phases(r);
return;
}
说明:
1)当返回码为NGX_DONE表示为所有流程都已经完成,正常关闭连接
2)当返回码为NGX_DECLINED表示流程还没有完全结束,例如发送body场景,还有一部分没有发送成功,需要重新进入流水线执行。
/* 如果是子请求 则进行子请求的handler处理 */
if (r != r->main && r->post_subrequest)
{
rc = r->post_subrequest->handler(r, r->post_subrequest->data, rc);
}
/* 错误、超时、客户端关闭连接 或者其他错误 */
if (rc == NGX_ERROR || rc == NGX_HTTP_REQUEST_TIME_OUT || rc == NGX_HTTP_CLIENT_CLOSED_REQUEST || c->error)
{
if (ngx_http_post_action(r) == NGX_OK)
{
return;
}
if (r->main->blocked)
{
r->write_event_handler = ngx_http_request_finalizer;
}
ngx_http_terminate_request(r, rc);//强制终止请求
return;
}
/* 处理特殊错误 */
if (rc >= NGX_HTTP_SPECIAL_RESPONSE || rc == NGX_HTTP_CREATED || rc == NGX_HTTP_NO_CONTENT)
{
if (rc == NGX_HTTP_CLOSE)
{
ngx_http_terminate_request(r, rc);//强制终止请求
return;
}
if (r == r->main)
{//当前请求为原始请求 删除定时器
if (c->read->timer_set)
{
ngx_del_timer(c->read);
}
if (c->write->timer_set)
{
ngx_del_timer(c->write);
}
}
c->read->handler = ngx_http_request_handler;
c->write->handler = ngx_http_request_handler;
ngx_http_finalize_request(r, ngx_http_special_response_handler(r, rc));
return;
}
以上代码流程主要是用于错误信息处理。
/* 表示当前请求不是原始请求 */
if (r != r->main)
{
if (r->buffered || r->postponed)
{
if (ngx_http_set_write_handler(r) != NGX_OK)
{
ngx_http_terminate_request(r, 0);//强制终止请求
}
return;
}
pr = r->parent;
if (r == c->data)
{
r->main->count--;
if (!r->logged)
{
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
if (clcf->log_subrequest)
{
ngx_http_log_request(r);
}
r->logged = 1;
}
else
{
ngx_log_error(NGX_LOG_ALERT, c->log, 0,
"subrequest: \"%V?%V\" logged again",
&r->uri, &r->args);
}
r->done = 1;
if (pr->postponed && pr->postponed->request == r)
{
pr->postponed = pr->postponed->next;
}
c->data = pr;
}
else
{
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http finalize non-active request: \"%V?%V\"",
&r->uri, &r->args);
r->write_event_handler = ngx_http_request_finalizer;
if (r->waited)
{
r->done = 1;
}
}
if (ngx_http_post_request(pr, NULL) != NGX_OK)
{
r->main->count++;
ngx_http_terminate_request(r, 0);
return;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http wake parent request: \"%V?%V\"",
&pr->uri, &pr->args);
return;
}
/* 当前请求是原始请求 */
if (r->buffered || c->buffered || r->postponed || r->blocked)
{
if (ngx_http_set_write_handler(r) != NGX_OK)
{
ngx_http_terminate_request(r, 0);
}
return;
}
if (r != c->data)
{
ngx_log_error(NGX_LOG_ALERT, c->log, 0,
"http finalize non-active request: \"%V?%V\"",
&r->uri, &r->args);
return;
}
r->done = 1;
r->write_event_handler = ngx_http_request_empty_handler;
if (!r->post_action)
{
r->request_complete = 1;
}
if (ngx_http_post_action(r) == NGX_OK)
{
return;
}
/* 删除定时器 */
if (c->read->timer_set)
{
ngx_del_timer(c->read);
}
if (c->write->timer_set)
{
c->write->delayed = 0;
ngx_del_timer(c->write);
}
if (c->read->eof)
{
ngx_http_close_request(r, 0); //判断count是否为0
return;
}
//关闭连接
ngx_http_finalize_connection(r);
}
三、遗留问题
1、在前面几篇一直提升成员参数count,当count为0时才会真正关闭请求对象,如果非0则不关闭,那么到底在哪个函数中进行逻辑判断呢?
2、在上面流程中,我们看到了关闭流程,那么对于HTTP请求长连接是如何处理的呢?
以上两个问题都在函数ngx_http_finalize_connection有体现,该函数在ngx_http_finalize_request函数中有调用。
/**
* 关闭连接
* @param r http请求
*/
static void
ngx_http_finalize_connection(ngx_http_request_t *r)
{
ngx_http_core_loc_conf_t *clcf;
#if (NGX_HTTP_V2)
if (r->stream)
{
ngx_http_close_request(r, 0);
return;
}
#endif
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
if (r->main->count != 1)
{
if (r->discard_body)
{
r->read_event_handler = ngx_http_discarded_request_body_handler;
ngx_add_timer(r->connection->read, clcf->lingering_timeout);
if (r->lingering_time == 0)
{
r->lingering_time = ngx_time() + (time_t)(clcf->lingering_time / 1000);
}
}
ngx_http_close_request(r, 0);
return;
}
if (r->reading_body)
{
r->keepalive = 0;
r->lingering_close = 1;
}
if (!ngx_terminate && !ngx_exiting && r->keepalive && clcf->keepalive_timeout > 0)
{//采用keepalive方式 管理连接
ngx_http_set_keepalive(r);
return;
}
if (clcf->lingering_close == NGX_HTTP_LINGERING_ALWAYS || (clcf->lingering_close == NGX_HTTP_LINGERING_ON && (r->lingering_close || r->header_in->pos < r->header_in->last || r->connection->read->ready)))
{
ngx_http_set_lingering_close(r);
return;
}
/* 判断count是否为0 如果为0则关闭请求以及tcp连接 */
ngx_http_close_request(r, 0);
}
3.1、判断count是否为0
static void
ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc)
{
ngx_connection_t *c;
r = r->main;
c = r->connection;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http request count:%d blk:%d", r->count, r->blocked);
if (r->count == 0)
{
ngx_log_error(NGX_LOG_ALERT, c->log, 0, "http request count is zero");
}
r->count--;
/**
* 如果count非0表示还有其他子请求或者动作需要处理 不能直接关闭
* 如果blocked非0表示阻塞,不能直接关闭 此参数用于aio(异步io)
*/
if (r->count || r->blocked)
{
return;
}
#if (NGX_HTTP_V2)
if (r->stream)
{
ngx_http_v2_close_stream(r->stream, rc);
return;
}
#endif
ngx_http_free_request(r, rc);
ngx_http_close_connection(c);
}
3.2、keep-alive长连接处理
对于长连接处理,实现函数为ngx_http_set_keepalive。在内部处理会关闭当前请求却不关闭tcp连接,但是会设置定时器,当定时器到期后会把tcp连接关闭掉。这里不深入介绍keepalive机制了。
四、总结
至此,Nginx关闭连接请求流程介绍完毕。自我感觉对于关闭连接请求函数,理解不是深刻,日后工作中若能加深印象,会再次编辑此页面,重新梳理流程。