nginx http框架执行流程(8) - 结束请求

2018-07-29 22:13:34

在处理 HTTP 请求的过程中可能会因为各种各样的原因结束请求,既有正常的发送完数据结束,也有权限检查不通过而结束,还有其他的很多情况(超时、读取错误、缓冲区满、内部异常等)导致的非正常结束。nginx 使用多个函数应用在不同的场合,其中最常用的就是 ngx_http_finalize_request()。

http 协议支持长连接,可以反复重用一个连接,所以在一个请求结束时需要记录访问日志,然后释放请求相关资源,但并不真正地关闭连接而是重用(归还到连接池)。

ngx_http_free_request() 负责释放请求的内存等资源。

void
ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc){
	//...
    // r->pool就是请求的内存池,也当做是请求释放的标志
    if (r->pool == NULL) {
        ngx_log_error(NGX_LOG_ALERT, log, 0, "http request already closed");
        return;
    }

    // 释放请求相关的资源,调用cleanup链表,相当于析构
    cln = r->cleanup;
    r->cleanup = NULL;

    while (cln) {
        if (cln->handler) {
            cln->handler(cln->data);
        }
        cln = cln->next;
    }

    // rc > 0 是http状态码
    if (rc > 0 && (r->headers_out.status == 0 || r->connection->sent == 0)) {
        r->headers_out.status = rc;
    }

    // 此时请求已经结束,调用log模块记录日志
    ngx_http_log_request(r);

    r->request_line.len = 0;
    r->connection->destroyed = 1;

    // 销毁请求的内存池
    // 但连接的内存池还在,可以用于长连接继续使用
    pool = r->pool;
    r->pool = NULL;

    ngx_destroy_pool(pool);
}

函数 ngx_http_close_request() 操作请求结构体里的引用计数 r->count,尝试 "关闭" http 请求。如果引用计数大于零,说明请求还有关联的其他操作(例如读取数据、访问后端服务、子请求等)没有完成,不能关闭。

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;

    // 引用计数至少为1,表示有一个操作,否则就是严重错误
    if (r->count == 0) {
        ngx_log_error(NGX_LOG_ALERT, c->log, 0, "http request count is zero");
    }

    // 减少引用计数,本次操作结束
    r->count--;

    // 如果还有引用计数,意味着此请求还有关联的epoll事件未完成
    // 不能关闭,直接返回
    // blocked表示有线程操作正在阻塞,也不能关闭
    // blocked必须有线程eventhandler处理
    if (r->count || r->blocked) {
        return;
    }

    // 引用计数为0,没有任何操作了,可以安全关闭

    // 释放请求相关的资源,调用cleanup链表,相当于析构
    ngx_http_free_request(r, rc);

    // 调用ngx_close_connection
    // 释放连接,加入空闲链表,可以再次使用
    // 销毁连接的内存池
    ngx_http_close_connection(c);
}
void
ngx_http_close_connection(ngx_connection_t *c)
{
    ngx_pool_t  *pool;

    c->destroyed = 1;

    pool = c->pool;

    // 关闭连接,删除epoll里的读写事件
    // 释放连接,加入空闲链表,可以再次使用
    ngx_close_connection(c);

    // 关闭连接,销毁连接的内存池
    ngx_destroy_pool(pool);
}
void
ngx_close_connection(ngx_connection_t *c)
{
    ngx_err_t     err;
    ngx_uint_t    log_error, level;
    ngx_socket_t  fd;

    // 读事件在定时器里,需要删除
    if (c->read->timer_set) {
        ngx_del_timer(c->read);
    }

    // 写事件在定时器里,需要删除
    if (c->write->timer_set) {
        ngx_del_timer(c->write);
    }

    if (!c->shared) {
        // in event/ngx_event.h
        // 宏,实际上是ngx_event_actions.del_conn
        if (ngx_del_conn) {
            ngx_del_conn(c, NGX_CLOSE_EVENT);

        } else {
            if (c->read->active || c->read->disabled) {
                // 宏,实际上是ngx_event_actions.del
                ngx_del_event(c->read, NGX_READ_EVENT, NGX_CLOSE_EVENT);
            }

            if (c->write->active || c->write->disabled) {
                // 宏,实际上是ngx_event_actions.del
                ngx_del_event(c->write, NGX_WRITE_EVENT, NGX_CLOSE_EVENT);
            }
        }
    }

    if (c->read->posted) {
        ngx_delete_posted_event(c->read);
    }

    if (c->write->posted) {
        ngx_delete_posted_event(c->write);
    }

    c->read->closed = 1;
    c->write->closed = 1;

    // 连接加入cycle的复用队列ngx_cycle->reusable_connections_queue
    ngx_reusable_connection(c, 0);

    // 释放连接,加入空闲链表
    ngx_free_connection(c);

    fd = c->fd;
    c->fd = (ngx_socket_t) -1;

    if (c->shared) {
        return;
    }

    // 关闭socket
    ngx_close_socket(fd);
}


综合处理结束请求

ngx_http_finalize_request() 是 http 机制里的关键函数,在执行引擎、收发数据时反复出现,每当一个请求相关操作完成时就会调用此函数。

ngx_http_finalize_request() 可以在入口里传递一个错误码,它依据错误码再调用 ngx_http_finalize_connection()、ngx_http_close_request()等函数,以适当的方式结束请求--减少引用计数,或者真正的关闭连接。

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;

    // handler返回done,例如调用read body
    // 因为count已经增加,所以不会关闭请求
    if (rc == NGX_DONE) {
        // 检查请求相关的异步事件,尝试关闭请求
        //
        // 有多个引用计数,表示有其他异步事件在处理
        // 那么就不能真正结束请求
        // 调用ngx_http_close_request尝试关闭请求,引用计数减1
        ngx_http_finalize_connection(r);
        return;
    }

    // ok处理成功,但过滤链表出错了
    if (rc == NGX_OK && r->filter_finalize) {
        c->error = 1;
    }

    // 请求被拒绝处理,那么就重新设置r->write_event_handler
    // 继续走ngx_http_core_run_phases
    // 使用引擎数组里的content handler处理
    if (rc == NGX_DECLINED) {

        // content_handler设置为空指针,不再使用location专用handler
        r->content_handler = NULL;
        r->write_event_handler = ngx_http_core_run_phases;
        ngx_http_core_run_phases(r);
        return;
    }

    // 不是done、declined,可能是ok、error、again

    // 返回错误,或者是http超时等错误
    if (rc == NGX_ERROR
        || rc == NGX_HTTP_REQUEST_TIME_OUT
        || rc == NGX_HTTP_CLIENT_CLOSED_REQUEST
        || c->error)
    {

        // 释放主请求相关的资源,调用cleanup链表,相当于析构
        // 如果主请求有多线程任务阻塞,那么不能结束请求
        // 否则调用ngx_http_close_request尝试关闭请求,引用计数减1
        ngx_http_terminate_request(r, rc);
        return;
    }

    // 返回了300以上的http错误码
    if (rc >= NGX_HTTP_SPECIAL_RESPONSE
        || rc == NGX_HTTP_CREATED
        || rc == NGX_HTTP_NO_CONTENT)
    {
        // NGX_HTTP_CLOSE是Nginx自己定义的错误码
        // #define NGX_HTTP_CLOSE                     444
        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;
    }

    // 不是done、declined,可能是ok、error、again

    // 省略子请求。。。。

    // c->buffered,有数据在r->out里还没有发送
    // r->blocked,有线程task正在阻塞运行
    if (r->buffered || c->buffered || r->postponed) {

        // 设置发送数据的handler,即写事件的回调handler为write_event_handler
        if (ngx_http_set_write_handler(r) != NGX_OK) {
            ngx_http_terminate_request(r, 0);
        }

        return;
    }

    // 到这里,请求应该基本可以确定要结束了,设置done
    r->done = 1;

    r->read_event_handler = ngx_http_block_reading;

    // 不需要再关注写事件,因为数据已经发送完了
    r->write_event_handler = ngx_http_request_empty_handler;

    // 准备结束请求

    // 删除读事件超时,不再需要了
    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) {
        // 尝试关闭请求,引用计数减1,表示本操作完成
        ngx_http_close_request(r, 0);
        return;
    }

    // 检查请求相关的异步事件,尝试关闭请求
    //
    // 有多个引用计数,表示有其他异步事件在处理
    // 那么就不能真正结束请求
    // 调用ngx_http_close_request尝试关闭请求,引用计数减1
    ngx_http_finalize_connection(r);
}

流程图大致如下


备注

1.测试环境centos7 64位,nginx版本为 1.14.0。
2..原文地址http://www.freecls.com/a/2712/e8


©著作权归作者所有
收藏
推荐阅读
简介
天降大任于斯人也,必先苦其心志。