当我们发送响应的时候,意味着我们即将结束当前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关闭连接请求流程介绍完毕。自我感觉对于关闭连接请求函数,理解不是深刻,日后工作中若能加深印象,会再次编辑此页面,重新梳理流程。