Nginx 多阶段 Http 请求处理流程
Nginx 通过将各个阶段的所有模块按序的组织成一条执行链,以流水线的形式依次进行处理。
执行链及流程
nginx执行链的定义如下:
1 | typedef struct { |
执行链节点的数据结构定义如下:
1 | struct ngx_http_phase_handler_s { |
对于执行链节点,相同阶段具有相同的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 | if (r != r->main || r->request_body || r->discard_body) { |
将请求交由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 | NGX_DONE: 得到连接地址,且连接已被建立 |
得到并建立一个连接之后,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 请求的全部过程