插件模式 bug优化1

目前有个客户问题已经处理几周了没处理好,又得去救火,来帮忙处理问题了,目前花了一上午找到根本原因,并且给出解决方案;

但是在处理这些问题的过程中有些点值得总结!
目前waf插件模式部署方式为旁挂在ngx旁边,nginx发出子请求到waf来做检测,根据返回结果来决定ngx是否转发流量。

说一下ngx的子请求这一块。

  • 1、主请求和子请求的异同
  1. 主请求结构体 ngx_http_request,子请求结构体ngx_http_sub_request
  2. 都是根据配置文件(/list)调用对应的模块处理.
  3. 都是放入queue由http框架调用
  • 2、使用subrequest的方式只需完成以下4个步骤即可:
    1.在nginx.conf文件中配置好子请求的处理方式
    2.启动subrequest请求
    3.实现子请求执行结束时的回调方法
    4.实现父请求被激活时的回调方法

nginx的相关资料中有很多这些资料

  • 下图为启动subrequest的过程

    (1)NGX主循环会定期地调用事件模块,检查是否有网络事件发生;
    (2)事件模块发现这个请求的回调方法属于HTTP框架,交由HTTP框架来处理--->入口函数为ngx_http_process_request--->ngx_http_handler--->ngx_http_core_run_phases
    /* 1-->ngx_http_process_request-> 1.1--->ngx_http_handler -->1.1.1--->ngx_http_core_run_phases 1.2-->ngx_http_run_posted_requests */
    ps:请体读取(100%)
    nginx核心核心会读取请求体请求体,这个工作是交给请求阶段的模块处理体-ngx_http_discard_request_body();nginx核心核心处理处理的标准接口,如果接口希望配置文件中一些请求体相关的指令((如$ request_body和$ request_body_file),一般来说都必须接口来完成;nginx的content handler中,nginx内置模块中如 perxy模块,fastcgi模块,uwsgi模块,uwsgi模块,这些协议的后端进程转发数据。

ngx_http_read_client_request_body()接口接口接口的请求部分部分写入一个文件在两个块不同时存在于中,还可能全部存在于一个临时文件中,最后还可能一部分存在于内部,剩下的部分存在于临时文件中。下面先介绍一下和这些的不相同为储行
(3)根据解析完的URI来决定使用哪个location下的模块来处理这个请求;
(4)调用mytest模块的ngx_http_mytest_handler方法处理这个请求;
(5)设置subrequest子请求的URI及回调方法(ngx_http_mytest_handler);//5-9步请见subrequest使用方式一节介绍
(6)调用ngx_http_subrequest方法创建子请求;
(7)创建的子请求会添加到原始请求的posted_requests链表中;
(8)ngx_http_subrequest方法执行完毕,子请求创建成功;
(9)ngx_http_mytest_handler方法执行完毕,返回NGX_DONE,这样父请求不会被销毁,等待以后的再次激活;
(10)HTTP框架执行完当前请求(父请求)后,执行 ngx_http_run_posted_requests -->检查posted_requests链表中是否还有子请求,如果存在子请求,则调用子请求的write_event_handler方法;
ngx_http_run_posted_requests->ngx_http_handler逻辑 重新走子请求的http_hander逻辑
notes:本例中子请求只有handler,没有write_event_handler
(11)根据子请求的URI(本例是/list),检查nginx.conf所有的location配置,确定应有哪个模块执行子请求,比如可以用反向代理模块执行(本例是proxy_pass https://www.qq.com😉;
(12)调用模块入口方法来处理子请求,例如反向代理模块入口方法ngx_http_proxy_pass-->ngx_http_proxy_handler; //配置proxy_pass后,在 ngx_http_core_content_phase 里面指向该函数
其中ngx_http_proxy_handler 会//生成一个ngx_http_upstream_t结构,赋值到r->upstream
notes:本例没有贴出来,在nginx源码有
(13)由于反向代理模块使用了upstream机制,所以它也要通过很多次的异步调用才能完整的处理完子请求,这是它的入口方法返回的是NGX_DONE;
(14)再次检查是否还有子请求,这是已经发现没有子请求了,当然子请求可以继续创建新的子请求,只是这里的反向代理模块不会这样做;
(15)第二步中网络事件处理完毕后,将控制权交给事件模块;
(16)本轮网络事件处理完毕后,交还控制权给NGINX主循环。

3) 实现子请求处理完毕时的回调方法

Nginx在子请求正常或者异常结束时,都会调用ngx_http_post_subrequest_pt回调方法。
在ngx_http_post_subrequest_pt回调方法内必须设置父请求激活后的处理方法,
例如:r->parent->write_event_handler=mytest_post_handler;
即子请求结束,调用父请求回调函数,执行父请求

  • 下图是子请求激活父请求过程的序列图

    (1)nginx主循环定期地调用事件模块,检查是否有网络事件发生;
    (2)如果事件模块监测到连接关闭事件,而这个请求的处理方法属于upstream模块,则交由upstream模块来处理请求;
    (3) upstream模块开始调用ngx_http_upstream_finalize_request方法来结束upstream机制下的请求(4)调用HTTP框架提供的ngx_http_finalize_request方法来结束子请求;
    (5)ngx_http_finalize_request方法会检查当前的请求是否是子请求.如果是,则会回调post_suberquest成员中的handler方法,也就是会调用mytest_subrequest_post_handler方法;
    也就是ngx_http_finalize_request 会调用子请求的回调函数r->post_subrequest->handler(r, r->post_subrequest->data, rc); //r变量是子请求(不是父请求)
    (6)在实现的子请求回调方法中,解析子请求返回的响应包。
    注意,这时需要通过write_event_handler设置父请求被激活后的回调方法(因为此时父请求的回调方法已经被HTTP框架设置为,什么事都不做的ngx_http_request_empty_handler方法);
    (7)子请求的回调方法执行完毕后,交由HTTP框架的ngx_http_finalize_request方法继续向下执行
    (8)ngx_http_finalize_request方法执行完毕;
    (9)HTTP框架如果发现当前请求后还有父请求需要执行,则调用父请求的write_event_handler回调方法;
ngx_http_finalize_request --->调用ngx_http_post_request
  /* 将父请求加入posted_request队尾,获得一次运行机会,这样pr就会加入到posted_requests,
         在ngx_http_run_posted_requests中就可以调用pr->ngx_http_run_posted_requests */  
        if (ngx_http_post_request(pr, NULL) != NGX_OK) 

子请求为upstream模板 其中的ngx_http_upstream_handler --->--->ngx_http_run_posted_requests来处理链表,此时链表最后为parent 请求
(10)这里可以根据第6步中解析子请求响应后的结果来构造响应包;
(11)调用无阻塞的ngx_http_send_header,ngx_http_output_filter发送方法,向客户端发送响应包
(12)无阻塞发送方法会立刻返回,即使目前未发送完,nginx之后也会异步地发送完所有的响应包,然后再结束请求;
(13)父请求的回调方法执行完毕;
(14)当第2步中的上游服务器连接关闭事件处理完毕后,交还控制权给事件模块;
(15)本轮网络事件处理完毕后,交还控制权给nginx主循环。

  • 处理父请求被重新激活后的回调方法

mytest_post_handler是父请求重新激活后的回调方法。将respond发回给client

以上ngx内容转载自网上并修改部分内容

posted @ 2023-02-17 15:07  codestacklinuxer  阅读(18)  评论(0编辑  收藏  举报