侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

Nginx丢弃http包体处理实例详解

linux  /  管理员 发布于 7年前   248

Nginx丢弃http包体处理实例详解

http框架丢弃http请求包体和上一篇文章http框架接收包体, 都是由http框架提供的两个方法,供http各个模块调用,从而决定对包体做什么处理。是选择丢弃还是接收,都是由模块决定的。例如静态资源模块,如果接收到来自浏览器的get请求,请求某个文件时,则直接返回这个文件内容给浏览器就可以了。没有必要再接收包体数据,get请求实际上也不会有包体。因此静态资源模块将调用http框架提供的丢弃包体函数进行丢包处理。

        相比接收包体过程, 丢弃包体操作就简单很多了,至少不需要把包体存放到http结构中的request_body缓冲区,也不需要考虑包体是否只存放到内存,或者只存放到文件中等问题, 框架接收完包体后就直接丢弃了。丢弃包体由三部分组成:

        (1) http模块首次调用框架提供的ngx_http_discard_request_body函数,做些初始化操作。例如如果一次操作无法丢弃所有包体 ,则需要重新把读事件注册到epoll中,这样再次调度执行时,能够继续执行丢包操作。再者,调用实际的丢包函数ngx_http_read_discarded_request_body进行丢弃包体操作。

        (2)如果一次操作无法丢弃所有包体,则在事件再次被调度时,继续接收剩余的包体数据,然后丢弃。

        (3)实际的丢包处理,也就是接收包体后,直接丢弃。

        从图中可以看出这三个过程中,丢包流程是一个公共的功能。也就是说不管http模块调用ngx_http_discard_request_body函数开始进行丢包处理,还是一次调度没有接收完全部包体时,由ngx_http_discarded_request_body_handler负责丢弃剩余的包体操作, 都会调用公共的丢包函数ngx_http_read_discarded_request_body进行接收包体后直接丢弃操作。

一、丢包初始化流程

        ngx_http_discard_request_body是被http模块调用,用于丢弃包体的函数。对于模块来讲是一个透明的操作。也就是说模块只需要调用这个接口就可以丢弃http请求包体,而不需要知道http框架是如何实现这个接口的。纵使框架一次调度没有丢弃完所有包体,下一次调度执行时会再次进行丢包操作,但对模块来说,他们是不知道的。

//功能: 丢弃http包体的首次回调函数,如果一次性不能全部接收完成并丢弃,则设置 //  读事件的回调为ngx_http_discarded_request_body_handler ngx_int_t ngx_http_discard_request_body(ngx_http_request_t *r) {   //需要丢弃的包体不用考虑超时问题  if (rev->timer_set)  {   ngx_del_timer(rev);  }   //包体长度小于等于0,则直接返回。表示丢弃包体  //如果已经接收过包体了,这时也不需要在接收。通常情况下get请求没有包体,因此包体长度为0  if (r->headers_in.content_length_n <= 0 || r->request_body)  {   return NGX_OK;  }   size = r->header_in->last - r->header_in->pos;  //已经预先接收了部分包体  if (size)  {   //包体未全部接收完成   if (r->headers_in.content_length_n > size)   {    r->header_in->pos += size;    r->headers_in.content_length_n -= size;    }   else   {    //包体已经全部接收    r->header_in->pos += (size_t) r->headers_in.content_length_n;    r->headers_in.content_length_n = 0;    return NGX_OK;   }  }   //设置后续读事件的回调  r->read_event_handler = ngx_http_discarded_request_body_handler;   //注册读事件回调,插入到epoll  ngx_handle_read_event(rev, 0));    //接收包体内容  if (ngx_http_read_discarded_request_body(r) == NGX_OK)  {   //表示已经接收到完整的包体了,将延迟关闭清0   r->lingering_close = 0;   }  else  {   //表示需要多次调度才能完成丢弃包体这个操作,于是把引用计数加1,防止这边在丢弃包体,而其他   //事件却已经让请求意外销毁   r->count++;   //标识为正在丢弃包体   r->discard_body = 1;  }   return NGX_OK; }  

         在接收http请求头部时,如果也顺便接收了http包体数据,这个时候就没有必要继续执行剩余的操作,丢弃包体成功,函数直接返回。如果一次调度没有丢弃完所有包体,则会设置http请求结构ngx_http_request_s的读事件read_event_handler为:ngx_http_discarded_request_body_handler, 下一次被调度时由这个函数负责丢弃剩余的包体。因此ngx_http_discard_request_body只会被http模块首次调用。

        函数也会调用实际的丢包函数ngx_http_read_discarded_request_body开始进行接收包体后直接丢弃处理。

二、丢包处理 

       ngx_http_read_discarded_request_body函数负责接收来自客户端的包体数据,然后再丢弃。因此对于模块而言,就是丢弃包体操作,但对于框架而言,丢弃包体操作其实就是接收包体操作, 只不过接收后的包体数据没有交给模块使用而已。为什么框架要接收包体后再直接丢弃呢? 岂不是多此一举。其实不然,之所以这样做是有原因的。假设某个不健壮的客户端浏览器使用阻塞的方法向nginx服务器发送了http包体数据, 如果nginx框架不接收的话,会导致客户端浏览器超时没有反应,从而导致客户端浏览器关闭这个连接。因此nginx的http框架要先从内核中接收来自客户端的包体数据, 但这些数据对于模块而言是没有用的,因此接收后的这些数据会直接被丢弃。

//功能: 从内核中读取数据到nginx中,nginx不对收到的数据进行处理。相当于丢弃包体 static ngx_int_t ngx_http_read_discarded_request_body(ngx_http_request_t *r) {  //用于接收包体的临时缓冲区  u_char buffer[NGX_HTTP_DISCARD_BUFFER_SIZE];   for ( ;; )  {   //已经全部丢弃成功   if (r->headers_in.content_length_n == 0)   {    //设置丢弃后的读事件回调,再有读事件时,不做任何处理    r->read_event_handler = ngx_http_block_reading;    return NGX_OK;   }      //从内核中接收包体到临时缓冲区   n = r->connection->recv(r->connection, buffer, size);    //更新剩余需要接收的包体大小   r->headers_in.content_length_n -= n;  } } 

        函数内部只是使用一个临时的缓冲区变量存放每次接收来自内核的包体数据。并没有把这部分数据保存到http请求结构中的request_body缓冲区。因此包体数据没有交给http模块,相当于被丢弃了。在所有包体从内核中接收完成时,设置http请求结构ngx_http_request_s的读事件read_event_handler回调设置为: ngx_http_block_reading, 表示再收到来自客户端的数据,则不进行任何处理了。因为已经接收完所有的包体数据,也就不需要理会来自客户端浏览器的其它数据。

三、丢弃剩余的包体

        ngx_http_discarded_request_body_handler用于在一次调度中没有丢弃完所有包体,则该函数会表调用,用于丢弃剩余的包体。函数内部也会调用实际的丢弃包体函数,进行接收包体然后丢弃操作。nginx服务器做了一个优化处理,会设置一个总超时时间,如果超过这个时间都还没有丢弃完全部的包体,则会关闭这个连接。这是一种对服务器保护的措施,避免长时间的丢包操作占用服务器资源。

//功能: 第1次未能全部丢弃包体时,该函数被调用。之后有读事件时,该函数被调用 void ngx_http_discarded_request_body_handler(ngx_http_request_t *r) {  //检测延迟关闭时间,如果总时长超过了lingering_time,则不再接收任何包体,这是一个总时间。  //总超时后,将直接光比连接  if (r->lingering_time)  {   timer = (ngx_msec_t) (r->lingering_time - ngx_time());   //已经到达了延迟关闭时间   if (timer <= 0)   {    //清空丢弃包体标识,表示包体已经丢弃    r->discard_body = 0;     //延迟关闭开关清0    r->lingering_close = 0;    ngx_http_finalize_request(r, NGX_ERROR);    return;   }   }   //接收包体后丢弃  rc = ngx_http_read_discarded_request_body(r);  //表示包体已经全部丢弃  if (rc == NGX_OK)  {   r->discard_body = 0;  //包体已经全部接收完   r->lingering_close = 0;  //清空延迟关闭标志   ngx_http_finalize_request(r, NGX_DONE);   return;  } } 

        ngx_http_discarded_request_body_handler这个函数是怎么被事件对象调用的呢? 在前面的文章已经分析了,ngx_connection_s读事件的回调设置为ngx_http_request_handler。   因此在读事件发生时,会回调请求结构的读回调。如果还不是不清楚这个调用过程,可以参考: nginx处理http请求这篇文章

static void ngx_http_request_handler(ngx_event_t *ev) {  //如果同时发生读写事件,则只有写事件才会触发。写事件优先级更高  if (ev->write)   {   r->write_event_handler(r); //在函数ngx_http_handler设置为ngx_http_core_run_phases  }  else  {   r->read_event_handler(r); //在函数ngx_http_process_request设置为ngx_http_block_reading  } } 

        到此为止,http框架丢弃包体的流程已经分析完成了。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!


  • 上一条:
    nginx处理http请求实例详解
    下一条:
    Nginx反向代理实现支持长连接详解
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在Linux系统中使用Iptables实现流量转发功能流程步骤(0个评论)
    • vim学习笔记-入门级需要了解的一些快捷键(0个评论)
    • 在centos7系统中实现分区并格式化挂载一块硬盘到/data目录流程步骤(0个评论)
    • 在Linux系统种查看某一个进程所占用的内存命令(0个评论)
    • Linux中grep命令中的10种高级用法浅析(0个评论)
    • 近期文章
    • 智能合约Solidity学习CryptoZombie二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-11
    • 2017-07
    • 2017-10
    • 2017-11
    • 2018-01
    • 2018-02
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2021-02
    • 2021-03
    • 2021-04
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2021-12
    • 2022-01
    • 2022-03
    • 2022-04
    • 2022-08
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-06
    • 2023-07
    • 2023-10
    • 2023-12
    • 2024-01
    • 2024-04
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客