发布于 

Nginx 多阶段 Http 请求处理流程

Nginx 通过将各个阶段的所有模块按序的组织成一条执行链,以流水线的形式依次进行处理。

执行链及流程

nginx执行链的定义如下:

1
2
3
4
5
typedef struct {
ngx_http_phase_handler_t *handlers; /*执行链*/
ngx_uint_t server_rewrite_index;
ngx_uint_t location_rewrite_index;
} ngx_http_phase_engine_t;

执行链节点的数据结构定义如下:

1
2
3
4
5
struct ngx_http_phase_handler_s {
ngx_http_phase_handler_pt checker;
ngx_http_handler_pt handler;
ngx_uint_t next;
};

对于执行链节点,相同阶段具有相同的checker函数,handler中则保存的是挂载至该阶段的模块处理函数,一般在checker函数中会执行当前节点的handler函数

执行链的执行流程是按照执行链顺序向前执行,但某个阶段需要回跳或跳过之后的某些执行阶段,next字段保存的就是跳跃的目的索引。

各个阶段如下:

NGX_HTTP_POST_READ_PHASE

接受完请求头之后的第一个阶段,位于uri重写之前,默认情况下该阶段被跳过;

nginx源码中仅有realip模块在POST_READ阶段对客户端ip进行了替换,且该模块并未被默认编译进nginx

POST_HEAD阶段的checker函数仅调用对应的handler函数,随后对返回值进行处理,当handler返回NGX_OK时进入下一阶段

NGX_HTTP_SERVER_REWRITE_PHASE

server级别的uri重写阶段,执行于server块内,location块外的重写指令。

nginx的rewrite模块在此阶段提供url重写指令rewrite和变量指令set,以及逻辑控制指令if、break和return以供用户完成一些简单的需求而不必注册handler

该阶段的请求未被匹配至具体的location中

SERVER_REWRITE阶段的checker函数同样调用handler函数,但处理方式稍有不同,当handler返回NGX_DECLINED时进入下一阶段

NGX_HTTP_FIND_CONFIG_PHASE

这一阶段根据重写过的uri查找对应的location,可能被执行多次

通过ngx_http_core_find_location函数完成对r->loc_conf的设置,然后调用ngx_http_update_location_config更新请求相关配置

NGX_HTTP_REWRITE_PHASE

location级别的重写阶段,该阶段执行location基本的重写指令,可能被执行多次。

与SERVER_REWRITE的逻辑基本相同,使用相同的handler函数,只是执行的时机不同。

NGX_HTTP_POST_REWRITE_PHASE

该阶段不能挂载handler,仅用于检查REWRITE阶段是否进行uri重写,若发生重写则返回再次执行REWRITE阶段,默认限制的重写次数为10次

介入该阶段的模块同样是rewrite模块

NGX_HTTP_PREACCESS_PHASE

访问权限控制的前一阶段,进入这一阶段时请求的loc_conf配置已经确定,在此阶段一般用于进行资源配置,限制连接数或请求速率。

ngx_http_limit_conn_module和ngx_http_limit_req_module等模块会在该阶段注册handler

NGX_HTTP_ACCESS_PHASE

访问权限控制阶段,例如基于ip黑白名单的权限控制,或者基于用户名密码的权限控制。

默认情况下nginx 的 ngx_http_access_module和ngx_http_auth_basic_module模块分别会在该阶段注册一个handler

需要注意的是在此阶段需要满足所有的handler验证,即所有handler返回NGX_OK时,该阶段的checker函数才会进入下一阶段。

NGX_HTTP_POST_ACCESS_PHASE

此阶段仅根据ACCESS阶段的执行结果进行相应处理,不能挂载handler。

当ACCESS阶段返回NGX_HTTP_FORBIDDEN或NGX_HTTP_UNAUTHORIZED时,会在此阶段结束请求

NGX_HTTP_TRY_FILES_PHASE

处理try_files指令,如果没有配置try_files指令,该阶段会被跳过。

try_file指令用于检查指定的一个或多个文件或目录是否存在,若存在则执行之后的阶段,否则返回指定的lcoation或指定的返回码

此阶段不能挂载handler

NGX_HTTP_CONTENT_PHASE

内容生成阶段,该阶段产生响应,并发送至客户端

在CONTENT阶段中,checker函数首先检查是否设置了content_handler,这里的content_handler不同于挂载在执行链上的handler,是每个location都可以独立拥有的,若存在则nginx在CONTENT阶段会直接执行content_handler,而不会再执行本阶段的handler。

在执行content_handler之前,nginx会将请求的write_event设置为ngx_http_request_empty_handler,这表示如果模块所设置的content_handler涉及到IO操作,则需要合理的设置读写 事件handler

同样的nginx将r->content_handler(r)的返回值直接传入ngx_http_finalize_request中,因此如果content_handler并未完成整个请求的处理,就需要设置合适的返回值并将请求的引用 计数加1以防请求被释放

如果没有注册content_handler,则与之前的阶段类似,checker会调用handler函数,而由于CONTENT是ngx_http_core_run_phases的最后一个阶段,因此若handler未返回NGX_DECLINED,checker将会结束请求并返回NGX_OK,否则将会返回NGX_FORBIDDEN或NGX_HTTP_NOT_FOUND

NGX_HTTP_LOG_PHASE

日志记录阶段,该阶段记录访问日志,进入该阶段标明该请求的响应已经发送到系统的发送缓冲区中。

LOG阶段的执行位于ngx_http_free_request中,在这个阶段中会遍历LOG阶段的所有handler并执行


HTTP proxy模块

HttpProxy模块用于将请求导向其他服务

Nginx与客户端使用HTTP/1.1通信,而在后台服务使用HTTP/1.0通信

在Nginx中, HTTP proxy模块介入于HTTP处理流程的CONTENT阶段,proxy模块通过”proxy_pass”配置的ngx_http_proxy_handler(位于ngx_http_proxy_module.c)中

rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);

设置处理请求的post_hadnler为ngx_http_upstream_init

而在 ngx_http_read_client_request_body 中

1
2
3
4
5
if (r != r->main || r->request_body || r->discard_body) {
r->request_body_no_buffering = 0;
post_handler(r);
return NGX_OK;
}

将请求交由upstream处理

部分proxy模块配置

proxy_pass:用于指定方向代理服务器的服务器池
proxy_set_header:用于添加一些请求头,传递至代理服务器。
例如 proxy_set_header Host $http_host 用于区分后端主机
proxy_set_header X-Real_IP $remote_addr 以便后端获得客户端的真实IP
proxy_body_buffer_size:指定缓冲区大小
proxy_connect_timeout:指定与后端连接的超时时间
proxy_send_timeout:指定后端服务器的数据回传超时时间

upstream模块

Upstream模块与Handler相似,区别在于upstream模块不产生内容,而是通过请求后端服务器得到内容,在使用中upstream模块只需开发若干回调函数,完成构造请求和解析响应等工作

upstream模块的处理流程

1.创建upstream数据结构
2.设置模块的tag和schema,其中schema用于日志,tag用于buf_chain管理
3.设置upstream的后端服务器列表
4.设置upstream回调函数
5.创建并设置upstream环境
6.完成初始化并进行收尾工作

此upstream的实际行为主要有两点,同上游主机建立连接并获取资源,以及将从上游主机中获取到的资源转发至下游客户端

而在同上游主机建立连接之前,nginx首先需要决定应当同 upstream 配置的后端主机列表中的哪一个建立连接

负载均衡模块用于从 upstream 指令所定义的后端主机列表中选取一台主机,以便进行之后建立连接以及获取相应的资源。

nginx内置的负载均衡模块主要有两种,默认为轮询模式,另一种则是ip_hash模式

ip_hash的主要逻辑位于 ngx_http_upstream_ip_hash 中,在这里这个函数主要做了两件事,对uscf->flags进行设置,以及设置init_upstream的回调函数为 ngx_http_upstream_init_ip_hash

在 ngx_http_upstream_init_ip_hash 中upstream将peer.init 设置为 ngx_http_upstream_init_ip_hash_peer(这个函数将会在 upstream 初始化请求时被调用)

通过调用peer.init,nginx会为每一个请求构造一张包含所有可用的upstream服务器的表,用于负载均衡计算以及提供当服务器宕机时的储备

同样在 ip_hash_peer 中,将upstream->peer.get的回调函数设置为 ngx_http_upstream_get_ip_hash_peer该函数负责从服务器表中取出某个服务器,通过get函数的返回值,nginx可以了解是否存在可用连接以及连接是否被建立

可能的返回值如下:

1
2
3
NGX_DONE: 得到连接地址,且连接已被建立
NGX_OK: 得到连接地址,但连接并未建立
NGX_BUSY: 所有连接均不可用

得到并建立一个连接之后,upstream就可以尝试向此连接中发送请求头和请求体了,upstream使用 ngx_http_upstream_send_request 向后端发送请求

而在得到来自上游服务器的响应之后,upstream通过 ngx_http_upstream_process_header 将来自上游服务器的响应内容进行处理,并通过 ngx_http_upstream_send_response 返回至客户端

最后,upstream通过 peer.free 和 ngx_http_upstream_finalize 释放资源和连接

以上就是 Nginx 多阶段处理 Http 请求的全部过程