11.5 接收HTTP头部

本节将描述接收HTTP头部这一阶段,该阶段是通过ngx_http_process_request_headers方法实现的,该方法将被设置为连接的读事件回调方法,在接收较大的HTTP头部时,它有可能会被反复多次地调用。HTTP头部类似下面加了下划线的字符串,而ngx_http_process_request_headers方法的目的就在于接收到当前请求全部的HTTP头部。


GET/uri HTTP/1.1

cred:xxx

username:ttt

content-length:4

test


可以看出,HTTP头部也属于可变长度的字符串,它与HTTP请求行和包体间都是通过换行符来区分的。同时,它与解析HTTP请求行一样,都需要使用状态机来解析数据。既然HTTP请求行和头部都是变长的,对它们的总长度当然是有限制的。从图11-3的第6步可以看出,当最初分配的大小为client_header_buffer_size的缓冲区且无法容纳下完整的HTTP请求行或者头部时,会再次分配大小为large_client_header_buffers(这两个值皆为nginx.conf文件中指定的配置项)的缓冲区,同时会将原先缓冲区的内容复制到新的缓冲区中。所以,这意味着可变长度的HTTP请求行加上HTTP头部的长度总和不能超过large_client_header_buffers指定的字节数,否则Nginx将会报错。

先来看看图11-4中展示的HTTP框架使用ngx_http_process_request_headers方法接收、解析HTTP头部的流程。

11.5 接收HTTP头部 - 图1

图 11-4 ngx_http_process_request_headers方法接收HTTP头部的流程图

图11-4中分支较多,下面详细地解释一下图中的11个步骤。

1)如同接收http请求行一样,首先检查当前的读事件是否已经超时。检查方法仍然是检查事件的timeout标志位,如果为1,则表示接收请求已经超时,这时调用ngx_http_close_request方法关闭连接,同时退出ngx_http_process_request_headers方法。

2)检查接收HTTP请求头部的header_in缓冲区是否用尽,当header_in缓冲区的pos成员指向了end成员时,表示已经用尽,这时需要调用ngx_http_alloc_large_header_buffer方法分配更大、更多的缓冲区,如同图11-3中的第6步。如果缓冲区还没有用尽,则跳到第4步中执行。

3)事实上,ngx_http_alloc_large_header_buffer方法会有3种返回值,其中NGX_OK表示成功分配到更大的缓冲区,可以继续接收客户端发来的字符流;NGX_DECLINED表示已经达到缓冲区大小的上限,无法分配更大的缓冲区;NGX_ERROR表示出现错误。所以,当返回NGX_ERROR时,跳转到第1步执行;而当返回NGX_DECLINED时,需要向用户返回错误并且同时退出ngx_http_process_request_headers方法,错误码由宏NGX_HTTP_REQUEST_HEADER_TOO_LARGE表示,也就是494,实际上这一过程是通过调用ngx_http_finalize_request方法来实现的(参见11.10.6节);如果返回NGX_OK,则继续第4步执行。

4)接收客户端发来的字符流,即把内核套接字缓冲区上的字符流接收到header_in缓冲区中。这一过程是通过调用封装过的recv方法实现的,如果过程中出现错误,仍然跳转到第1步执行;如果没有接收到数据,但错误码表明仍然需要再次接收数据,则跳转到第5步执行;如果成功接收到数据,则跳转到第6步执行。

5)这个步骤将该读事件添加到epoll和定时器中,实际上就是图11-3中第3步和第4步的合并,不再赘述。

6)调用ngx_http_parse_header_line方法解析缓冲区中的字符流。这种方法有3个返回值:返回NGX_OK时,表示解析出一行HTTP头部,这时需要跳转到第7步设置这行已经解析出的HTTP头部;返回NGX_HTTP_PARSE_HEADER_DONE时,表示已经解析出了完整的HTTP头部,这时可以准备开始处理HTTP请求了(11.6节介绍);返回NGX_AGAIN时,表示还需要接收到更多的字符流才能继续解析,这时需要跳转到第2步去接收更多的字符流;除此之外的错误情况,将跳转到第8步发送400错误给客户端。

7)将解析出的HTTP头部设置到表示ngx_http_request_t结构体headers_in成员的headers链表中。从3.6.3节中可以看出,开发HTTP模块时获取到的HTTP头部就是在这个步骤中设置的。

8)当调用ngx_http_parse_header_line方法解析字符串构成的HTTP时,是有可能遇到非法的或者Nginx当前版本不支持的HTTP头部的,这时该方法会返回错误,于是调用ngx_http_finalize_request方法,向客户端发送NGX_HTTP_BAD_REQUEST宏对应的400错误码响应。

9)当ngx_http_parse_header_line方法认为已经解析到完整的HTTP头部时,将会根据HTTP头部中的host字段情况,调用ngx_http_find_virtual_server方法找到对应的虚拟主机配置块,也就是第10章中介绍过的ngx_http_core_srv_conf_t结构体。这一步会导致图11-2的第4步中ngx_http_request_t结构体里的srv_conf、loc_conf成员被重新设置,以指向正确的虚拟主机。

10)这一步骤将检查以上步骤中接收解析出的HTTP头部是否合法,主要包括以下几项:如果HTTP版本为1.1,则host头部不可以为空,否则返回400 Bad Request错误响应给客户端;如果传递了Content-Length头部,那么它必须是合法的数字,否则会返回400 Length Required错误响应给客户端;如果请求使用了PUT方法,那么必须传递Content-Length头部,否则会返回400 Length Required错误响应给客户端。

11)调用ngx_http_process_request方法开始使用各HTTP模块正式地在业务上处理HTTP请求。

以上11步骤仅专注于接收并解析出全部的HTTP头部,同时检查它们的合法性,并将解析出的HTTP头部设置到ngx_http_request_t结构体里的合适位置。接下来开始讨论如何使用以上两节中已经解析好的HTTP请求行和头部。