正文

1 Nginx启动流程 

2 HTTP 初始化 

新连接建立时的行为

在上次博客的最后可以看到,在ngx_event_accept方法建立连接的最后一步,将会调用ngx_listening_t监听结构体的handler方法。这时候HTTP框架就开始介入请求了。

HTTP框架在初始化时就会将每个监听ngx_listening_t结构体的handler方法设为ngx_http_init_connection方法,该方法执行流程如下图:

nginx 处理post add_header失败 nginx怎么处理http请求_nginx


其中定时器中的超时时间是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请求行

nginx 处理post add_header失败 nginx怎么处理http请求_nginx_02


6.读事件被触发,这是需要在用户态的进程空间分配内存,用来把内核缓冲区上的TCP流复制到用户态的内存中。

   这一步将在ngx_connection_t的内存池中分配一块内存,内存块的大小与nginx.conf文件中的client_header_buffer_size配置项参数一致。

header_in指针共同指向这块内存缓冲区。

整体流程

http模块作用:解析http 请求.

nginx 处理post add_header失败 nginx怎么处理http请求_回调方法_03

ngx_http_process_request:处理请求,设置读写事件的handler
ngx_http_handler:初始化phase_handler
ngx_http_core_run_phase:处理http 11个阶段

nginx 处理post add_header失败 nginx怎么处理http请求_nginx_04

ngx_http_read_client_request_body是首次接收http包体函数,如果一次没全部接收完包体,则调用ngx_http_read_client_request_body_handler继续接收。
发送http header filter和发送http body filter不同

上述两张图片来自网络,图片重点各有不同

流程图

nginx 处理post add_header失败 nginx怎么处理http请求_回调方法_05

nginx拥有众多http模块,如何将其整合并确保http请求会用到相应模块?

  • nginx将http请求划分11个阶段,每一阶段可包含多个http模块,每个模块handler运行结果决定下一个模块;
  • 每个http阶段由ngx_http_phase_handler_s描述,包含3个成员:checker,handler以及 next;
  • Nginx不允许直接调用http模块的handler,而是通过提供的checker封装调用,共有7个http请求阶段实现了checker(4个),也就是说只有这7个阶段允许第3方模块注册;
    Nginx初始化时,调用每个http模块的ngx_http_module_t->postconfiguration将自身的handler加入cmcf->phases(二维数组);
  • 然后通过ngx_http_init_phase_handlers()将cmcf->phases重组为一维数组cmcf->phase_engine.handlers,此时所有的ngx_http_phase_handler_s都位于其中;
  • 一个http请求经过解析请求行和请求头后,最终调用ngx_http_core_run_phases,其以http请求的phase_handler为下标,尝试遍历cmcf->phase_engine.handlers (可能因为处理结果提前返回)
ngx_http_core_run_phases(ngx_http_request_t *r)
{
    ......
    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
    ph = cmcf->phase_engine.handlers;
    while (ph[r->phase_handler].checker) {
        rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
    }
}

以上是接受客户端连接,并根据http请求行和请求头执行相应http模块,http请求可能还有包体,由http模块负责处理;
一般有两种方法: 1 接收包体并存入内存或文件; 2 接收包体后直接丢弃;

3 接收HTTP请求行(line)

在初始化请求之后,将调用ngx_http_process_request_line方法接收HTTP请求行。

因为请求行的长度是不定的,这意味着在读事件被触发时,内核套接字缓冲区的大小未必足够接收到全部的HTTP请求行。

因此调用一次ngx_http_process_request_line方法不一定能够接收完完整的HTTP请求行,该方法会被多次调度。下图展示了该方法的流程

nginx 处理post add_header失败 nginx怎么处理http请求_nginx_06


该方法会调用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头部

handler仅仅处理header(行,header),不处理body.需要处理body,说明是post情况,需要交互。都交给后面第三方模块处理

4 接收HTTP请求头部(header)

注意:上面是请求行,这里是请求头部。
跟HTTP请求行一样,HTTP头部也属于可变长度的字符串,它与HTTP请求行和包体间都是通过换行符来区分的。

下图展示了HTTP框架使用ngx_http_process_request_headers方法接收、解析HTTP头部的流程

nginx 处理post add_header失败 nginx怎么处理http请求_nginx_07


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请求了。

notes 接收http 头部如果buffer不够,会分配更大的buffer,如果大于最大的buffer则返回错误。header只用一个buffer接收,不能用多个buffer接收

5 处理HTTP请求

nginx 处理post add_header失败 nginx怎么处理http请求_HTTP_08

2.设置读、写事件的回调方法为ngx_http_request_handler方法,请求的后续处理都是通过ngx_http_request_handler方法进行的。

6.设置ngx_http_request_t结构体的write_event_handler成员为ngx_http_core_run_phases方法。该方法可能会被多次调度来完成HTTP请求的处理。

7.调用ngx_http_core_run_phases回调方法,该方法执行流程如下

nginx 处理post add_header失败 nginx怎么处理http请求_回调方法_09


ngx_http_request_t结构体中的phase_handler成员(处理流程的核心)将决定执行到哪一阶段,关于该成员跟其相关的数据结构可以查看。

  • HTTP请求11个阶段

nginx 处理post add_header失败 nginx怎么处理http请求_回调方法_10

NGX_HTTP_FIND_CONFIG_PHASE,NGX_HTTP_POST_REWIRTE_PHASE,NGX_HTTP_POST_ACESS_PHASE,NGX_HTTP_TRY_FILES_PHASE,这4个阶段用户不可介入.

  • 该回调方法的源码如下
void ngx_http_core_run_phases(ngx_http_request_t *r)
{
    ...
    cmcf=ngx_http_get_module_man_conf(r,ngx_http_core_module);
    ph=cmcf->phase_engine.handlers;
    
    while(ph[phase_handler].checker){
        rc=ph[r->phase_handler].checker(r,&ph[r->phase_hanlder]);
    }
}

 只有当相应的checker方法返回NGX_OK时才将控制权交还给Nginx的事件模块,否则将继续执行(checker方法中会修改phase_handler成员以向下执行)。

当这个请求上对应的事件再次触发时,HTTP框架将回调ngx_http_request_handler方法开始处理请求(上面已经设置了该方法为其读、写事件的回调方法)。

  • 下图是ngx_http_request_handler方法的流程

nginx 处理post add_header失败 nginx怎么处理http请求_运维_11

 2.如果当前事件可写,将调用ngx_http_reqeust_t结构体中的write_event_handler方法。该方法在上面已经设置为ngx_http_core_run_phases,可见该方法会被多次调用。

  • Checker方法

请对照上面ngx_http_core_run_phases 代码.
checker方法是HTTP框架定义的,每一个HTTP处理阶段对应着相应着一个checker方法(有的阶段的checker方法一样),handler方法只有在checker方法中调用。

checker方法的主要任务在于,根据phase_handler执行某个HTTP模块实现的回调方法,并根据方法的返回值决定:

1.当前阶段是否已经结束

2.下次要执行的回调方法是哪一个

3.是立刻执行下一个回调方法还是先把控制权交还给epoll

下面介绍HTTP框架中其中两个checker方法的介绍

  • ngx_http_core_generic_phase

在前面的下面有该方法的介绍,下面只给出该方法的流程图

nginx 处理post add_header失败 nginx怎么处理http请求_运维_12

可见,checker方法主要是根据handler方法的返回值来改变phase_handler的值以用来控制handler方法的执行顺序的。

  • ngx_http_core_content_phase

ngx_http_core_content_phase是NGX_HTTP_CONTENT_PHASE阶段的checker方法。是我们开发HTTP模块时最常用的一个阶段。

1) 两种挂载

content phrase handler默认调用 (按阶段挂载)

gx_http_index_handler---ngx_http_autoindex_handler---ngx_http_gzip_static_handler---ngx_http_static_handler

自定义content handler(按需挂载)


2) config

ngx_http_core_content_phase阶段的handler方法则可以放在ngx_http_core_conf_t结构体中,这就是按需挂载的基础,其余10个阶段中各HTTP模块的handler方法都是放在全局ngx_http_core_main_conf_t结构体中

typedef struct ngx_http_core_loc_conf_s ngx_http_core_loc_conf_t
struct ngx_http_core_loc_conf_s {
    ...
    ngx_http_handler_pt handler;
    ...
}

3)实例分析

在我们最初的开发的HTTP模块中:

当检测到mytest配置项后,调用ngx_http_mytest方法将上面ngx_http_core_loc_conf_t的handler成员设置为自己的handler函数,实现了按需挂载。

事实上,在NGX_HTTP_FIND_CONFIG_PHASE阶段就会把ngx_http_request_t结构体的content_handler成员设置为匹配请求URI的location下的ngx_http_core_loc_conf_t结构体的handler成员。

下面是该checker方法的流程图,可以看出是如何实现按需挂载的

nginx 处理post add_header失败 nginx怎么处理http请求_HTTP_13

6 处理HTTP包体 ngx_http_read_client_request_body

        请求体的读取一般发生在nginx的content handler中,一些nginx内置的模块,比如proxy模块,fastcgi模块,uwsgi模块等,这些模块的行为必须将客户端过来的请求体(如果有的话)以相应协议完整的转发到后端服务进程,所有的这些模块都是调用了ngx_http_read_client_request_body
接口来完成请求体读取.
        值得注意的是这些模块会把客户端的请求体完整的读取后才开始往后端转发数据。
        第一次读取通过ngx_http_read_client_request_body.第二次及以后通过ngx_http_do_read_client_request_body函数,进行消息体的相关处理 .

1).接收包体

ngx_int_t ngx_http_read_client_request_body(ngx_http_request *r,ngx_http_client_body_handler_pt post_handler);
typedef void (*ngx_http_client_body_handler_pt) (ngx_http_request_t *r);

接收到的包体保存在ngx_http_reqeust_body_t中,一般调用该函数的handler函数会接着返回NGX_DONE。

在完成接收动作之后,会调用post_handler函数继续处理请求。

2).放弃接收包体(Nginx必须接收包体,但不做处理)


ngx_int ngx_http_discard_reqeust_body(ngx_http_request_t *r);


调用该函数的handler函数继续处理请求,Nginx会异步地接收跟丢弃包体

nginx 处理post add_header失败 nginx怎么处理http请求_运维_14

为什么不主动读取HTTP BODY数据?

为什么HTTP的核心模块只读取了HTTP协议的请求行和请求头,而没有读取HTTP的请求body内容

因为大部分情况下,HTTP协议是不需要用到HTTP body中的数据的,例如你返回一张图片或者一个静态页面。

只有在需要将body数据传输到后端的例如JAVA、PHP等的时候,才会需要将HTTP的body数据带过去(POST请求、文件上传等).所以Nginx只有在特殊的模块下(比如proxy模块,fastcgi模块,uwsgi模块等),才会去主动调用ngx_http_read_client_request_body方法,将body传递到后端。

ngx_http_read_client_request_body 不会主动调用,是第三方fastcgi等模块调用的。
注册handler read request body 实例:

request body 的读取详解:请参考

 与read header只要一个buffer不同,body可能很大。由于内存的限制,ngx_http_read_client_request_body()接口读取的请求体会部分或者全部写入一个临时文件中。

7 发送 HTTP 响应头部

发送函数 ngx_http_send_header,该函数调用 ngx_http_top_header_filter 方法开始顺序遍历过滤链表的每一个元素处理方法,直到最后一个把响应头部发送出去为止。
在Nginx中,过滤链表的顺序如下:

+----------------------------+
  |ngx_http_not_modified_filter|
  +----------+-----------------+
             |
             v
  +----------+------------+
  |ngx_http_headers_filter|
  +----------+------------+
             |
             v
  +----------+-----------+
  |ngx_http_userid_filter|
  +----------+-----------+
             |
             v
  +----------+-------------------+
  |ngx_http_charset_header_filter|
  +----------+-------------------+
             |
             v
  +----------+---------------+
  |ngx_http_ssi_header_filter|
  +----------+---------------+
             |
             v
  +----------+----------------+
  |ngx_http_gzip_header_filter|
  +----------+----------------+
             |
             v
  +----------+-----------------+
  |ngx_http_range_header_filter|
  +----------+-----------------+
             |
             v
  +----------+-------------------+
  |ngx_http_chunked_header_filter|
  +----------+-------------------+
             |
             v
  +----------+-----------+
  |ngx_http_header_filter|
  +----------------------+

Filter作用,比如处理respond做类似css,加粗变色等效果

发送header和body:
当执行ngx_http_send_header发送HTTP头部时,就从ngx_http_top_header_filter指针开始遍历所有的HTTP头部过滤模块,
而在执行ngx_http_output_filter发送HTTP包体时,就从ngx_http_top_body_filter指针开始遍历所有的HTTP包体过滤模块 

8 发送 HTTP 响应包体

注意:发送HTTP 响应报头和包体使用的filter不同

发送函数:ngx_int_t ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in),
然后调用下面的filter

+--------------------------+
  |ngx_http_range_body_filter|
  +----------+---------------+
             |
             v
  +----------+---------+
  |ngx_http_copy_filter|
  +----------+---------+
             |
             v
  +----------+-----------------+
  |ngx_http_charset_body_filter|
  +----------+-----------------+
             |
             v
  +----------+-------------+
  |ngx_http_ssi_body_filter|
  +----------+-------------+
             |
             v
  +----------+-------------+
  |ngx_http_postpone_filter|
  +----------+-------------+
             |
             v
  +----------+--------------+
  |ngx_http_gzip_body_filter|
  +----------+--------------+
             |
             v
  +----------+-----------------+
  |ngx_http_chunked_body_filter|
  +----------+-----------------+
             |
             v
  +---------------------+
  |ngx_http_write_filter|
  +---------------------+

根据发送响应包体的过滤链表顺序可以知道,除了最后一个模块是真正发送响应包体给客户端之外,其他模块都只是对响应包体进行修改,最后一个过来模块是ngx_http_write_filter_module,该模块提供的处理方法是ngx_http_write_filter;

总结:真正发送响应的是 ngx_http_write_filter 函数,但是该函数不能保证一次性把响应发送完毕,若发送不完毕,把剩余的响应保存在out 链表缓冲区中,继而调用ngx_http_writer 把剩余的响应发送出去,函数ngx_http_writer 最终调用的是ngx_http_output_filter 函数发送响应.

9 关闭连接请求

ngx_http_finalize_request 函数执行流

10 HTTP DEMO

小结:

nginx 处理post add_header失败 nginx怎么处理http请求_回调方法_03

Request流程
1) 请求头读取完成了,nginx的做法是先不读取请求body,所以这里面我们设置read_event_handler为ngx_http_block_reading,即不读取数据了.
2) 调用ngx_http_core_run_phases来处理请求,产生的响应头会放在ngx_http_request_t的headers_out中
3) nginx的各种阶段会对请求进行处理,会调用filter来过滤数据,对数据进行加工,如truncked传输、gzip压缩等。最后也会调用ngx_http_write_filter来进行输出。

 关键函数:ngx_http_core_run_phase:处理http 11个阶段

ngx_http_core_run_phases(ngx_http_request_t *r)
{
    ......
    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
    ph = cmcf->phase_engine.handlers;
    while (ph[r->phase_handler].checker) {
        rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
    }
}

ngx_http_core_content_phase 是NGX_HTTP_CONTENT_PHASE阶段的checker方法。是我们开发HTTP模块时最常用的一个阶段。

两种挂载:content phrase handler默认调用 (按阶段挂载)和自定义content handler(按需挂载).
挂载使用方法:push_event :

nginx http 模块主要是处理 http header,处理之后,发送函数 ngx_http_send_header,该函数调用 ngx_http_top_header_filter .
Nginx只有在特殊的模块下(比如proxy模块,fastcgi模块,uwsgi模块等),才会去主动调用ngx_http_read_client_request_body方法,将body传递到后端.
发送 HTTP 响应包体.注意:发送HTTP 响应报头和包体使用的filter不同

nginx 处理post add_header失败 nginx怎么处理http请求_nginx_16

 更多handler模块示例分析

http static module

本模块的作用就是读取磁盘上的静态文件,并把文件内容作为产生的输出。

nginx 处理post add_header失败 nginx怎么处理http请求_http_17