11.9 发送HTTP响应

本节开始讨论第3章中已出现过的发送HTTP响应的两个方法:ngx_http_send_header方法和ngx_http_output_filter方法。这两个方法将负责把HTTP响应中的应答行、头部、包体发送给客户端。Nginx是一个全异步的事件驱动架构,那么仅仅调用ngx_http_send_header方法和ngx_http_output_filter方法,就可以把响应全部发送给客户端吗?当然不是,当响应过大无法一次发送完时(TCP的滑动窗口也是有限的,一次非阻塞的发送多半是无法发送完整的HTTP响应的),就需要向epoll以及定时器中添加写事件了,当连接再次可写时,就调用ngx_http_writer方法继续发送响应,直到全部的响应都发送到客户端为止。

以上大致说了一下HTTP框架为发送响应所要做的工作,然而,对于各个HTTP模块而言,绝大多数情况下发送HTTP响应时就是这个请求结束的时候,难道说还要像接收包体那样,传递一个post_handler回调方法,等所有的响应都发送完时再回调HTTP模块的post_handler方法来关闭请求吗?这个设计显然是不好的,根据HTTP的特点,只要开始发送响应基本上可以确定请求就要结束了。因此,HTTP采用的设计是,使用ngx_http_output_filter方法发送响应时,必须与结束请求的ngx_http_finalize_request方法配合使用(ngx_http_finalize_request方法会把请求的write_event_handler设置为ngx_http_writer方法,并将写事件添加到epoll和定时器中),这样就使得真正负责在后台异步地发送响应的ngx_http_writer方法对HTTP模块而言也是透明的。

11.9.1节中将介绍发送HTTP响应行、头部的ngx_http_send_header方法,11.9.2节将介绍发送响应包体的ngx_http_output_filter方法,同时在这两节中还会穿插介绍如何配合ngx_http_finalize_request方法使用,实现异步的发送机制。最后在11.9.3节会介绍在后台发送响应的ngx_http_writer方法。

11.9.1 ngx_http_send_header

ngx_http_send_header方法负责构造HTTP响应行、头部,同时会把它们发送给客户端。发送响应头部使用了第6章所述的流水线式的过滤模块思想,即通过提供统一的接口,让各个感兴趣的HTTP模块加入到ngx_http_send_header方法中,然后通过每个过滤模块C源文件中独有的ngx_http_next_header_filter指针将各个过滤头部的方法连接起来,这样,在调用ngx_http_send_header方法时,实际就是依次调用了所有头部过滤模块的方法,其中,链表里的最后一个头部过滤方法将负责发送头部。因此,这些过滤模块组成的链表顺序是非常重要的,我们在第6章的6.2.1节和6.2.2节已经介绍过这部分内容,这里不再赘述。

调用ngx_http_send_header方法时,最后一个头部过滤模块叫做ngx_http_header_filter_module模块,之前的头部过滤模块会根据特性去修改表示请求的ngx_http_request_t结构体中headers_out成员里的内容,而最后一个头部过滤模块ngx_http_header_filter_module提供的ngx_http_header_filter方法则会根据HTTP规则把headers_out中的成员变量序列化为字符流,并发送出去,而本节的重点就在于说明ngx_http_header_filter方法所做的工作。

在了解ngx_http_header_filter方法之前,我们还是得先回顾一下事件驱动机制,因为它要求任何操作都不可以阻塞进程,ngx_http_header_filter方法当然也不能例外。那么,如果要发送的响应头部大于套接字可写的缓存,无法一次把响应头部发送出去怎么办?这就需要使用ngx_http_request_t结构体中ngx_chain_t类型的成员out了,它将会保存没有发送完的(剩余的)响应头部。那么,什么时候发送请求out成员中保存的剩余响应头部呢?这就要结合用于结束请求的ngx_http_finalize_request方法来说了。

当ngx_http_header_filter方法无法一次性发送HTTP头部时,将会有以下两个现象同时发生。

❑请求的out成员中将会保存剩余的响应头部。

❑ngx_http_header_filter方法返回NGX_AGAIN。

如果这个响应没有包体,那么这时通常已经可以调用ngx_http_finalize_request方法来结束请求了,参见11.10.6节中ngx_http_finalize_request方法的原型,它的第2个参数很关键,我们需要把NGX_AGAIN传进去,这样ngx_http_finalize_request方法就理解了实际上还需要HTTP框架继续发送请求out成员中保存的剩余响应字符流。ngx_http_finalize_request方法会设置请求的write_event_handler成员为ngx_http_writer方法,这样,当连接上有可写事件时,就会调用11.9.3节描述的ngx_http_writer方法继续发送剩余的HTTP响应。下面先来看看ngx_http_header_filter方法的流程图,如图11-19所示。

11.9 发送HTTP响应 - 图1

图 11-19 ngx_http_header_filter方法的流程图

下面描述一下图11-19中的6个步骤。

1)首先检查请求ngx_http_request_t结构体的header_sent标志位,如果header_sent为1,则表示这个请求的响应头部已经发送过了,不需要再向下执行,直接返回NGX_OK即可。

2)正式进入发送响应头部阶段,为防止反复地发送响应头部,将header_sent标志位置为1。同时需要检查当前请求是否是客户端发来的原始请求,如果当前请求只是一个子请求,它是不存在发送HTTP响应头部这个概念的,因此,如果当前请求不是main成员指向的原始请求时,跳到第1步直接返回NGX_OK。如果HTTP版本小于1.0,同样不需要发送响应头部,仍然跳到第1步返回NGX_OK。

3)根据请求headers_out结构体中的错误码、HTTP头部字符串,计算出如果把响应头部序列化为一个字符串共需要多少字节。

4)在请求的内存池中分配第3步计算出的缓冲区。

5)将响应行、头部按照HTTP的规范序列化地复制到缓冲区中。

6)将第4步中分配的缓冲区作为参数调用ngx_http_write_filter方法,将响应头部发送出去。

注意,第6步是通过调用ngx_http_write_filter方法来发送响应头部的。事实上,这个方法是包体过滤模块链表中的最后一个模块ngx_http_write_filter_module的处理方法,当HTTP模块调用ngx_http_output_filter方法发送包体时,最终也是通过该方法发送响应的(在11.9.2节中将详细地介绍这一方法)。当一次无法发送全部的缓冲区内容时,ngx_http_write_filter方法是会返回NGX_AGAIN的(同时将未发送完成的缓冲区放到请求的out成员中),也就是说,发送响应头部的ngx_http_header_filter方法会返回NGX_AGAIN。如果不需要再发送包体,那么这时就需要调用ngx_http_finalize_request方法来结束请求,其中第2个参数务必要传递NGX_AGAIN,这样HTTP框架才会继续将可写事件注册到epoll,并持续地把请求的out成员中缓冲区里的HTTP响应发送完毕才会结束请求。