第四十三讲:处理HTTP请求头部的流程
在HTTP模块开始处理用户请求之前尼,首先我们需要nginx的框架先对客户端建立好连接,然后接收用户发来的http的line;比如说方法,url等;然后再去接收到所有的header;那么根据这些header信息,我们才能决定使用哪些配置块;才能决定让http模块怎么样去处理请求;
所以我们先来看一看nginx的框架是怎么样建立连接以及接收http请求的?

在上图中,分为三个层次来讲这个过程,
(1):是操作系统的内核;比如三次握手,当用户发来SYN的时候,内核会发送一个SYN|ACK表示确认了,要求这边打开,然后当客户端再发来ACK的时候尼,内核认为这个连接已经建立成功了,这里可能会有很多worker进程,那么每个worker进程可能都监听了80和43端口;这个时候尼操作系统会根据它的负载均衡算法会选中CPU上的worker进程,这个worker进程尼,会通过我们之前介绍到的epoll_wait的方法去返回到刚刚建立好的这个连接的句柄;那么我拿到这个刚刚建立好的句柄以后尼,这其实是一个读事件,因为我读到了ACK这样的一个报文,根据这个读事件尼,我们找到原来它是我们监听的80或者443;我们就可以调用这个accept方法;当调用accept方法的时候尼,就会分配连接内存池,内存池在内存中主要分为连接内存池和请求内存池,那么这里就开始分配连接内存池了,很多同学都应该看过这样的一个配置项,叫connection_pool_size:512;那么这就是连接内存池吃的分配大小,到了这一步的时候,Nginx会为这个新建立的连接分配512字节的内存池,这个内存池会怎么用尼?马上就会看到,那么分配完这个内存池建立好连接以后尼,我们所有的http模块开始从事件模块手中接入请求的处理过程了;那么http模块在启动的时候尼就会定义一个ngx_http_init_connection 设置回调方法;也就是说当accept一个新连接的时候,我们这个方法就被回调执行了;这个时候我需要把新建立的这个事件的读事件添加到epoll中,通过epoll_ctl这个函数;然后还要加一个定时器 client_header_timeout: 60s 如果60s钟没有收到请求就超时了,那么这个流程走完以后尼.可能nginx的事件模块就切换到其它的fd去处理了,当用户真的把http请求,一个get请求或者post请求发来的时候尼,其实它是发来data;那么在tcp层也就是操作系统内核层它会回一个ACK;但是同时事件模块的epoll_wait又拿到了一个请求;那么这个请求的回调方法ngx_http_wait_request_handler 这个时候收到这个请求以后尼,我需要把内核中的DATA读到我nginx的用户态中,要读到用户态中需要分配内存,那么这一段内存我需要分配多大尼?我是从哪里分的尼?首先我是从连接内存池中来分的;这个连接内存池中初始分配默认512字节;这个时候我需要从内存池中再分配1k,因为内存池是可以扩展大小的,它只是初始分配了512字节;现在我分配了1k,那么这个1k可不可以修改尼?client_header_buffer_size:1k 可以修改;但是它并不是修改的越大越好,从这我们可以看到,只要用户有一个字节发过来,我们Nginx的worker进程就会分配1k的内存出来;所以修改的很大并不是合适的;但有可能同学会问,如果这个url或者header非常的大已经超过1k了怎么办尼?我们可以看一看,如果已经超过1k了,会有怎么样的变化?

刚刚我们分配完1k以后尼,那么这个时候,收到了小于等于1k的请求内容;那么处理请求和处理连接是不一样的;处理连接我可能只需要把连接收到我的nginx的内存中就可以了,但是处理请求的时候尼,我可能需要去做大量的上下文分析,去分析它的http协议,去分析每一个header;所以这个时候尼我需要分配一个请求内存池,请求内存池会默认分配多大尼?request_pool_size:4k 默认为4k;基本上是connection_pool_size的8倍,为什么要有八倍的这么一个大的大小尼?因为请求的上下文它涉及到业务,通常4k是一个比较合适的一个数字,如果你分配的很少的时候尼,我们的请求内存池在不断的扩充,当我们分配内存的次数变的多的时候,性能肯定会下降的;所以request_pool_size:4k要不要改是其实是根据我们的业务状况来的,有很多同学会问nginx提供的默认值要不要修改,其实我们理解了这个内存池后面怎么会用到我们就可以根据我们的业务情况去决定是否修改它,那么分配完这个内存池以后尼,我们会用一个状态机去解析请求的行,在解析请求行的过程中可能会发现有的url特别大,已经超过了我们刚刚所分配的1k的大小,那怎么办尼?我们会分配一个更大的内存,这个更大的内存就是来解决有一些url太长了,那么分配的这个大内存有多大尼?large_client_header_buffers:4 8k 这个也是我们经常遇到的配置,我们注意到它是4 空格 8k;什么意思尼?我们这一块分配大内存的时候,它并不是分配4*8=32k; 它是先分配一个8k,然后把刚刚那个1k 的内容,拷贝到这个8k的第一部分,也就是说我们好剩7k,我们用剩下的7k再去接收httpd的url;然后发现我这个url是不是解完了?通过状态机再继续做这样的事情,如果8k都没有接收完,我们就会分配第二个8k,也就是分配了16k,那么最多我分配了32k,当我状态机完整的解析请求行了,我就可以标识url;那么什么叫标识url尼?因为我们在后面会介绍nginx会有很多变量,这些变量尼并不是会复制一份,它只是有一个指针去指向我们接收到的请求行,所以nginx的性能如此强大,标识完url尼,接下来我们看第二步,接收header,http请求中的header可能会非常的长,因为它可能含有cookie,有很多的host 那些字段;状态机开始解析header;header是非常有可能超过1k的,那这个时候我还是需要分配大内存,large_client_header_buffers:4 8k; 而接收url时候的large_client_header_buffers:4 8k 和这里的 large_client_header_buffers:4 8k这两个大内存共用了;也就是url我用掉了两个8k; 那么在这里我只需要再分配两个8k来给header使用;所以这个4*8k是针对所有的;那么分配完大内存以后,我接收到完整的header以后,我就开始标识header了;标识header的过程中尼还会涉及到我们下面所说的比如说确定哪个server块开始处理这个请求,当我收到host这样一个header的时候, 当我收到全部的header的以后,标识完header以后,我就会移除我的超时间定时器client_header_timeout: 60s,所以大家也就可以确定什么时候需要去修改这个60s;也就是说当我们收完完整的header,两次接收之间的时间差是多少?接下来我们需要开始我们的核心阶段:11个阶段的http请求处理;
以上所说的流程全是Nginx的框架执行的;接下来我们将会进入每一个http模块处理的流程,那么http模块其实是非常多样化的;我们必须要有一个流程按照一条主线,把他们串起来;这样方便我们理解和记忆;

浙公网安备 33010602011771号