11.10 结束HTTP请求

对于事件驱动的架构来说,结束请求是一项复杂的工作。因为一个请求可能会被许多个事件触发,这使得Nginx框架调度到某个请求的回调方法时,在当前业务内似乎需要结束HTTP请求,但如果真的结束了请求,销毁了与请求相关的内存,多半会造成重大错误,因为这个请求可能还有其他事件在定时器或者epoll中。当这些事件被回调时,请求却已经不存在了,这就是严重的内存访问越界错误!如果尝试在属于某个HTTP模块的回调方法中试图结束请求,先要把这个请求相关的所有事件(有些事件可能属于其他HTTP模块)都从定时器和epoll中取出并调用其handler方法,这又太复杂了,另外,不同HTTP模块上的代码耦合太紧密将会难以维护。

那HTTP框架又是怎样解决这个问题的呢?HTTP框架把一个请求分为多种动作,如果HTTP框架提供的方法会导致Nginx再次调度到请求(例如,在这个方法中产生了新的事件,或者重新将已有事件添加到epoll或者定时器中),那么可以认为这一步调用是一种独立的动作。例如,接收HTTP请求的包体、调用upstream机制提供的方法访问第三方服务、派生出subrequest子请求等。这些所谓独立的动作,都是在告诉Nginx,如果机会合适就再次调用它们处理请求,因为这个动作并不是Nginx调用一次它们的方法就可以处理完毕的。因此,每一种动作对于整个请求来说都是独立的,HTTP框架希望每个动作结束时仅维护自己的业务,不用去关心这个请求是否还做了其他动作。这种设计大大降低了复杂度。

这种设计具体又是怎么实现的呢?实际上,在11.8节中已经介绍过,每个HTTP请求都有一个引用计数,每派生出一种新的会独立向事件收集器注册事件的动作时(如ngx_http_read_client_request_body方法或者ngx_http_subrequest方法),都会把引用计数加1,这样每个动作结束时都通过调用ngx_http_finalize_request方法来结束请求,而ngx_http_finalize_request方法实际上却会在引用计数减1后先检查引用计数的值,如果不为0是不会真正销毁请求的。

也就是说,HTTP框架要求在请求的某个动作结束时,必须调用ngx_http_finalize_request方法来结束请求。ngx_http_finalize_request方法也设计得比较复杂,在第3章中曾经谈到过它最基本的用法,本节中将详细讨论ngx_http_finalize_request方法到底做了些什么。

在说明ngx_http_finalize_request方法前,先介绍一下HTTP框架提供的几个更低级别的结束请求方法。

11.10.1 ngx_http_close_connection

ngx_http_close_connection方法是HTTP框架提供的一个用于释放TCP连接的方法,它的目的很简单,就是关闭这个TCP连接,当且仅当HTTP请求真正结束时才会调用这个方法。图11-22列出了ngx_http_close_connection方法所做的工作。

11.10 结束HTTP请求 - 图1

图 11-22 ngx_http_close_connection方法的流程图

下面先来分析一下这个底层的方法ngx_http_close_connection究竟做了些什么。

1)首先将连接的读/写事件从定时器中取出。实际上就是检查读/写事件的time_set标志位,如果为1,则证明事件在定时器中,那么需要调用ngx_del_timer方法把事件从定时器中移除。

2)调用ngx_del_conn宏(或者ngx_del_event宏)将读/写事件从epoll中移除。实际上就是调用第9章重点介绍过的ngx_event_actions_t接口中的del_conn方法,当事件模块是epoll模块时,就是从epoll中移除这个连接的读/写事件。同时,如果这个事件在ngx_posted_accept_events或者ngx_posted_events队列中,还需要调用ngx_delete_posted_event宏把事件从post事件队列中移除。

3)调用ngx_free_connection方法把表示连接的ngx_connection_t结构体归还给ngx_cycle_t核心结构体的空闲连接池free_connections。

4)调用系统提供的close方法关闭这个TCP连接套接字。

5)销毁ngx_connection_t结构体中的pool内存池。

可见,这个ngx_http_close_connection方法主要是针对连接做了一些工作,它是非常基础的方法。