今天我们讲 request,在 Nginx 中我们指的是 http 请求,具体到 Nginx 中的数据结构是ngx_http_request_t。ngx_http_request_t 是对一个 http 请求的封装。 我们知道,一个 http 请求,包含请求行、请求头、请求体、响应行、响应头、响应体。
http 请求是典型的请求-响应类型的的网络协议,而 http 是文本协议,所以我们在分析请求行与请求头,以及输出响应行与响应头,往往是一行一行的进行处理。 ( 推荐学习:nginx使用 )
如果我们自己来写一个 http 服务器,通常在一个连接建立好后,客户端会发送请求过来。然后我们读取一行数据,分析出请求行中包含的 method、uri、http_version 信息。
然后再一行一行处理请求头,并根据请求 method 与请求头的信息来决定是否有请求体以及请求体的长度,然后再去读取请求体。
得到请求后,我们处理请求产生需要输出的数据,然后再生成响应行,响应头以及响应体。
在将响应发送给客户端之后,一个完整的请求就处理完了。当然这是最简单的 webserver 的处理方式,其实 Nginx 也是这样做的,只是有一些小小的区别,比如,当请求头读取完成后,就开始进行请求的处理了。Nginx 通过 ngx_http_request_t 来保存解析请求与输出响应相关的数据。
那接下来,简要讲讲 Nginx 是如何处理一个完整的请求的。对于 Nginx 来说,一个请求是从ngx_http_init_request 开始的,在这个函数中,会设置读事件为 ngx_http_process_request_line,也就是说,接下来的网络事件,会由 ngx_http_process_request_line 来执行。
从ngx_http_process_request_line 的函数名,我们可以看到,这就是来处理请求行的,正好与之前讲的,处理请求的第一件事就是处理请求行是一致的。
通过 ngx_http_read_request_header 来读取请求数据。然后调用 ngx_http_parse_request_line 函数来解析请求行。Nginx 为提高效率,采用状态机来解析请求行,而且在进行 method 的比较时,没有直接使用字符串比较,而是将四个字符转换成一个整型,然后一次比较以减少 cpu 的指令数,这个前面有说过。
很多人可能很清楚一个请求行包含请求的方法,uri,版本,却不知道其实在请求行中,也是可以包含有 host 的。比如一个请求 GET http://www.taobao.com/uri HTTP/1.0 这样一个请求行也是合法的,而且 host 是 www.taobao.com,这个时候,Nginx 会忽略请求头中的 host 域,而以请求行中的这个为准来查找虚拟主机。
另外,对于对于 http0.9 版来说,是不支持请求头的,所以这里也是要特别的处理。所以,在后面解析请求头时,协议版本都是 1.0 或 1.1。整个请求行解析到的参数,会保存到 ngx_http_request_t 结构当中。
在解析完请求行后,Nginx 会设置读事件的 handler 为 ngx_http_process_request_headers,然后后续的请求就在 ngx_http_process_request_headers 中进行读取与解析。
ngx_http_process_request_headers 函数用来读取请求头,跟请求行一样,还是调用 ngx_http_read_request_header 来读取请求头,调用 ngx_http_parse_header_line 来解析一行请求头,解析到的请求头会保存到 ngx_http_request_t 的域 headers_in 中,headers_in 是一个链表结构,保存所有的请求头。
而 HTTP 中有些请求是需要特别处理的,这些请求头与请求处理函数存放在一个映射表里面,即 ngx_http_headers_in,在初始化时,会生成一个 hash 表,当每解析到一个请求头后,就会先在这个 hash 表中查找,如果有找到,则调用相应的处理函数来处理这个请求头。比如:Host 头的处理函数是 ngx_http_process_host。
当 Nginx 解析到两个回车换行符时,就表示请求头的结束,此时就会调用 ngx_http_process_request 来处理请求了。
ngx_http_process_request 会设置当前的连接的读写事件处理函数为 ngx_http_request_handler,然后再调用 ngx_http_handler 来真正开始处理一个完整的http请求。
这里可能比较奇怪,读写事件处理函数都是ngx_http_request_handler,其实在这个函数中,会根据当前事件是读事件还是写事件,分别调用 ngx_http_request_t 中的 read_event_handler 或者是 write_event_handler。
由于此时,我们的请求头已经读取完成了,之前有说过,Nginx 的做法是先不读取请求 body,所以这里面我们设置 read_event_handler 为 ngx_http_block_reading,即不读取数据了。
刚才说到,真正开始处理数据,是在 ngx_http_handler 这个函数里面,这个函数会设置 write_event_handler 为 ngx_http_core_run_phases,并执行 ngx_http_core_run_phases 函数。
ngx_http_core_run_phases 这个函数将执行多阶段请求处理,Nginx 将一个 http 请求的处理分为多个阶段,那么这个函数就是执行这些阶段来产生数据。
因为 ngx_http_core_run_phases 最后会产生数据,所以我们就很容易理解,为什么设置写事件的处理函数为 ngx_http_core_run_phases 了。
在这里,我简要说明了一下函数的调用逻辑,我们需要明白最终是调用 ngx_http_core_run_phases 来处理请求,产生的响应头会放在 ngx_http_request_t 的 headers_out 中,这一部分内容,我会放在请求处理流程里面去讲。Nginx 的各种阶段会对请求进行处理,最后会调用 filter 来过滤数据,对数据进行加工,如 truncked 传输、gzip 压缩等。
这里的 filter 包括 header filter 与 body filter,即对响应头或响应体进行处理。filter 是一个链表结构,分别有 header filter 与 body filter,先执行 header filter 中的所有 filter,然后再执行 body filter 中的所有 filter。
在 header filter 中的最后一个 filter,即 ngx_http_header_filter,这个 filter 将会遍历所有的响应头,最后需要输出的响应头在一个连续的内存,然后调用 ngx_http_write_filter 进行输出。
ngx_http_write_filter 是 body filter 中的最后一个,所以 Nginx 首先的 body 信息,在经过一系列的 body filter 之后,最后也会调用 ngx_http_write_filter 来进行输出(有图来说明)。
这里要注意的是,Nginx 会将整个请求头都放在一个 buffer 里面,这个 buffer 的大小通过配置项 client_header_buffer_size 来设置,如果用户的请求头太大,这个 buffer 装不下,那 Nginx 就会重新分配一个新的更大的 buffer 来装请求头,这个大 buffer 可以通过 large_client_header_buffers 来设置,这个 large_buffer 这一组 buffer,比如配置 48k,就是表示有四个 8k 大小的 buffer 可以用。
注意,为了保存请求行或请求头的完整性,一个完整的请求行或请求头,需要放在一个连续的内存里面,所以,一个完整的请求行或请求头,只会保存在一个 buffer 里面。
这样,如果请求行大于一个 buffer 的大小,就会返回 414 错误,如果一个请求头大小大于一个 buffer 大小,就会返回 400 错误。在了解了这些参数的值,以及 Nginx 实际的做法之后,在应用场景,我们就需要根据实际的需求来调整这些参数,来优化我们的程序了。
处理流程图:
以上这些,就是 Nginx 中一个 http 请求的生命周期了。
Atas ialah kandungan terperinci nginx的请求如何处理?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!