11.4 接收HTTP请求行

接收HTTP请求行这个行为必然是在初始化请求之后发生的。在图11-2的第11步表明已经调用了ngx_http_process_request_line方法来接收HTTP请求行。HTTP请求行的格式如下所示。


GET/uri HTTP/1.1


可以看出,这样的请求行长度是不定的,它与URI长度相关,这意味着在读事件被触发时,内核套接字缓冲区的大小未必足够接收到全部的HTTP请求行,由此可以得出结论:调用一次ngx_http_process_request_line方法不一定能够做完这项工作。所以,ngx_http_process_request_line方法也会作为读事件的回调方法,它可能会被epoll这个事件驱动机制多次调度,反复地接收TCP流并使用状态机解析它们,直到确认接收到了完整的HTTP请求行,这个阶段才算完成,才会进入下一个阶段接收HTTP头部。

因此,ngx_http_process_request_line方法与ngx_http_init_connection方法、ngx_http_init_request方法都不一样,后两种方法在一个请求中只会被调用一次,而ngx_http_process_request_line方法则至少会被调用一次,而到底会调用多少次则取决于客户端的行为及网络中IP包的转发等。图11-3展示了ngx_http_process_request_line方法的流程,需要注意其中对各个步骤的描述,其中有些步骤会导致ngx_http_process_request_line方法暂时结束,但会在下一次读事件来临时继续被调用。

11.4 接收HTTP请求行 - 图1

图 11-3 接收、解析HTTP请求行的流程图

图11-3描述了ngx_http_process_request_line方法的主要流程,由于它涉及了TCP字符流的接收、解析,因此会相对复杂一些,下面详细描述一下这12个步骤:

1)首先检查这个读事件是否已经超时,超时时间仍然是nginx.conf配置文件中指定的client_header_timeout。如果ngx_event_t事件的timeout标志为1,则认为接收HTTP请求已经超时,调用ngx_http_close_request方法(参见11.10.3节)关闭请求,同时由ngx_http_process_request_line方法中返回。

2)在当前读事件未超时的情况下,检查header_in接收缓冲区(参见图11-2的第6步)中是否还有未解析的字符流。第一次调用ngx_http_process_request_line方法时缓冲区里必然是空的,这时会调用封装的recv方法把Linux内核套接字缓冲区中的TCP流复制到header_in缓冲区中。header_in的类型是ngx_buf_t,它的pos成员和last成员指向的地址之间的内存就是接收到的还未解析的字符流。如果header_in接收缓冲区中还有未解析的字符流,则不会调用recv方法接收,而是跳到下面的第4步继续执行。

3)在第2步中曾经调用封装的recv方法,如果返回值表示连接出现错误或者客户端已经关闭连接,则跳转到第1步;如果返回值表示接收到客户端发送的字符流,则跳转到第5步中解析;如果返回值表示本次没有接收到TCP流,需要继续检测这个读事件,则开始本步骤的执行。

首先检查这个读事件是否在定时器中,如果已经在定时器,则跳转到第4步;反之,调用ngx_add_timer方法向定时器添加这个读事件。

4)调用ngx_handle_read_event方法把该读事件添加到epoll中,同时ngx_http_process_request_line方法结束。

5)在第2步接收到字符流后,将在这一步骤用状态机解析已经接收到的TCP字符流,确认其是否构成完整的HTTP请求行。这个状态机解析请求行的方法叫做ngx_http_parse_request_line,它使用ngx_http_request_t结构体中的state成员来保存解析状态,如下所示。


ngx_int_t ngx_http_parse_request_line(ngx_http_request_tr,ngx_buf_tb)


这里传入的参数b是header_in缓冲区,返回值主要有3类:返回NGX_OK表示成功地解析到完整的HTTP请求行;返回NGX_AGAIN表示目前接收到的字符流不足以构成完成的请求行,还需要接收更多的字符流;返回NGX_HTTP_PARSE_INVALID_REQUEST或者NGX_HTTP_PARSE_INVALID_09_METHOD等其他值时表示接收到非法的请求行。

6)如果ngx_http_parse_request_line方法返回NGX_OK,表示成功地接收到完整的请求行,这时跳转到第7步继续执行。

如果ngx_http_parse_request_line方法返回NGX_AGAIN,则表示需要接收更多的字符流,这时需要对header_in缓冲区做判断,检查是否还有空闲的内存,如果还有未使用的内存可以继续接收字符流,则跳转到第2步,检查缓冲区是否有未解析的字符流,否则调用ngx_http_alloc_large_header_buffer方法分配更大的接收缓冲区。到底分配多大呢?这由nginx.conf文件中的large_client_header_buffers配置项指定。

如果ngx_http_parse_request_line方法返回NGX_HTTP_PARSE_INVALID_REQUEST或者NGX_HTTP_PARSE_INVALID_09_METHOD等其他值,那么HTTP框架将不再处理非法请求,跳转到第1步关闭请求。

7)在接收到完整的HTTP请求行后,首先要把请求行中的信息如方法名、URI及其参数、HTTP版本等信息设置到ngx_http_request_t结构体的相应成员中(如request_line、uri、method_name、unparsed_uri、http_protocol、exten、args等),在3.6.2节开发HTTP模块时曾介绍过这些成员的用法,它们就是在这一步中被赋值的。

8)如果在第7步得到的http_version成员中显示用户请求的HTTP版本小于1.0(如HTTP 0.9版本),其处理过程将与HTTP 1.0和HTTP 1.1的完全不同,它不会有接收HTTP头部这一步骤。这时将会调用ngx_http_find_virtual_server方法寻找到相应的虚拟主机,回顾一下在10.4节中虚拟主机是使用散列表来进行管理的,ngx_http_find_virtual_server方法就是用于在散列表中检索出虚拟主机。

如果http_version成员中显示出用户请求的HTTP版本是1.0或者更高的版本,则直接跳到第10步中执行。

9)继续处理HTTP版本小于1.0的情形。由于不需要再次接收HTTP头部,调用ngx_http_process_request方法开始处理请求(参见11.6节)。

10)初始化ngx_http_request_t结构体中存放HTTP头部的一些容器,如headers_in结构体中ngx_list_t类型的headers链表容器、ngx_array_t类型的cookies动态数组容器等,为下一步接收HTTP头部做好准备(参见11.5节)。

11)由于已经接收完HTTP请求行,因此这时把读事件的回调方法由ngx_http_process_request_line改为ngx_http_process_request_headers,准备接收HTTP头部。

12)调用ngx_http_process_request_headers方法开始接收HTTP头部。

接收完HTTP请求行后,在下一节中我们将分析接收HTTP头部这一步骤。