参考资料 <深入理解Nginx>(陶辉)
Nginx事件模块博客地址:http://www.cnblogs.com/runnyu/p/4914698.html
Nginx是一个事件驱动构架的Web服务器,在上次的博客中我们可以看到Nginx是如何驱动事件的处理的。
本次将介绍HTTP框架是如何介入跟处理HTTP网络事件的。因为书上的思路已经足够清晰而且内容比较独立,因此本次基本上就把本章的重点记录一下。
HTTP框架存在的目的
1.Nginx事件框架主要是针对传输层的TCP的,作为Web服务器HTTP模块需要处理的则是HTTP,HTTP框架必须要针对基于TCP的事件框架解决好HTTP的网络传输、解析、组装等问题。
2.虽然事件驱动构架在性能上是不错的,但是HTTP模块的业务通常较复杂,HTTP框架的存在则可以让我们屏蔽事件驱动架构,尽量只关注业务,提供开发效率。
新连接建立时的行为
在上次博客的最后可以看到,在ngx_event_accept方法建立连接的最后一步,将会调用ngx_listening_t监听结构体的handler方法。这时候HTTP框架就开始介入请求了。
HTTP框架在初始化时就会将每个监听ngx_listening_t结构体的handler方法设为ngx_http_init_connection方法,该方法执行流程如下图:
其中定时器中的超时时间是nginx.conf配置文件中指定的client_header_timeout,后面的超时时间设定也是这个值。
第一次可读事件的处理
当TCP连接上第一次出现可读事件时,将会调用ngx_http_init_request方法初始化这个HTTP请求
该方法主要做了3件事情:
1.对请求构造ngx_http_request_t结构体并初始化部分参数;
2.修改读事件的回调方法为ngx_http_process_request_line
3.调用上面的回调方法解析HTTP请求行
6.读事件被触发,这是需要在用户态的进程空间分配内存,用来把内核缓冲区上的TCP流复制到用户态的内存中。
这一步将在ngx_connection_t的内存池中分配一块内存,内存块的大小与nginx.conf文件中的client_header_buffer_size配置项参数一致。
ngx_connection_t结构体的buffer指针以及ngx_http_request_t结构体的header_in指针共同指向这块内存缓冲区。
接收HTTP请求行
在初始化请求之后,将调用ngx_http_process_request_line方法接收HTTP请求行。
因为请求行的长度是不定的,这意味着在读事件被触发时,内核套接字缓冲区的大小未必足够接收到全部的HTTP请求行。
因此调用一次ngx_http_process_request_line方法不一定能够接收完完整的HTTP请求行,该方法会被多次调度。下图展示了该方法的流程
该方法会调用recv方法把Linux内核套接字缓冲区中的TCP流复制到header_in缓冲区中。
header_in的类型是ngx_buf_t,它的pos成员和last成员指向的地址之间的内存就是收到的未解析的字符流。
4.在本次没有接收到TCP流的时候,告诉事件驱动程序继续检测这个读事件,然后该方法就结束。在该读事件准备好的时候,该方法将被再次调度。
5.在接收到TCP流后,用状态机(ngx_http_parse_request_line方法)解析已经接收到的TCP字符流,确认其是否构成完整的HTTP请求行。
7.如果ngx_http_parse_request_line方法返回NGX_OK,表示已经成功地接收到完整的请求行。这一步将把请求行的的信息设置到ngx_http_request_t结构体的相应成员中
(request_line、uri、method_name、http_protocol、args等)。
11.接收完HTTP请求行后,把读事件的回调方法更改为ngx_http_request_headers准备接收HTTP头部。
接收HTTP头部
跟HTTP请求行一样,HTTP头部也属于可变长度的字符串,它与HTTP请求行和包体间都是通过换行符来区分的。
下图展示了HTTP框架使用ngx_http_process_request_headers方法接收、解析HTTP头部的流程
6.调用ngx_http_parse_header_line方法解析缓冲区的字符流。这个方法有3个返回值:
返回NGX_OK时,表示解析出一行HTTP头部;返回NGX_HTTP_PARSE_HEADER_DONE时,表示已经解析出了完整的HTTP头部;
返回NGX_AGAIN时,表示还需要接收到更多的字符流才能继续解析;除此之外的错误情况,将发送400错误给客户端。
7.解析出的HTTP头部信息设置到ngx_http_request_t结构体headers_in成员的headers链表中。
9.当ngx_http_parse_header_line方法返回NGX_HTTP_PARSE_HEADER_DONE时,将会根据HTTP头部中的host字段情况,
调用ngx_http_find_virtual_server方法找到对应的虚拟主机配置块。ngx_http_request_t结构体里的srv_conf、loc_conf成员被重新设置,以指向正确的虚拟主机。
在接收到完整的HTTP头部后,已经有足够的必要信息开始在业务上处理HTTP请求了。下一节将说明HTTP框架是如何召集负责具体功能的各HTTP模块合作处理请求的。