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

发布时间 - 2026-01-11 02:07:49    点击率:

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  # http  # 详解Nginx服务器和iOS的HTTPS安全通信  # 详解NGINX访问https跳转到http的解决方法  # 详解nginx服务器http重定向到https的正确写法  # 详解nginx如何配置HTTPS  # 详解nginx同一端口监听多个域名和同时监听http与https  # 详解阿里云LINUX服务器配置HTTPS(NGINX)  # 微信小程序Server端环境配置详解(SSL  # Nginx HTTPS  # TLS 1.2 升级)  # nginx处理http请求实例详解  # 回调  # 客户端  # 设置为  # 首次  # 是一个  # 也会  # 是由  # 不需  # 则会  # 清空  # 就可以  # 也就是说  # 也不  # 是有  # 也就  # 不需要  # 才会  # 是怎么  # 做什么  # 长时间 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Laravel如何生成API文档?(Swagger/OpenAPI教程)  google浏览器怎么清理缓存_谷歌浏览器清除缓存加速详细步骤  Windows10如何更改计算机工作组_Win10系统属性修改Workgroup  C#如何调用原生C++ COM对象详解  如何在阿里云服务器自主搭建网站?  香港代理服务器配置指南:高匿IP选择、跨境加速与SEO优化技巧  Claude怎样写结构化提示词_Claude结构化提示词写法【教程】  百度浏览器网页无法复制文字怎么办 百度浏览器复制修复  Laravel如何实现密码重置功能_Laravel密码找回与重置流程  iOS发送验证码倒计时应用  mc皮肤壁纸制作器,苹果平板怎么设置自己想要的壁纸我的世界?  LinuxCD持续部署教程_自动发布与回滚机制  WEB开发之注册页面验证码倒计时代码的实现  阿里云网站搭建费用解析:服务器价格与建站成本优化指南  Bootstrap整体框架之CSS12栅格系统  Python结构化数据采集_字段抽取解析【教程】  javascript事件捕获机制【深入分析IE和DOM中的事件模型】  制作公司内部网站有哪些,内网如何建网站?  微信小程序 五星评分(包括半颗星评分)实例代码  免费的流程图制作网站有哪些,2025年教师初级职称申报网上流程?  如何在IIS管理器中快速创建并配置网站?  Laravel如何创建自定义Artisan命令?(代码示例)  Laravel怎么实现验证码功能_Laravel集成验证码库防止机器人注册  PHP 实现电台节目表的智能时间匹配与今日/明日轮播逻辑  Laravel如何实现模型的全局作用域?(Global Scope示例)  Win11怎么关闭资讯和兴趣_Windows11任务栏设置隐藏小组件  Laravel如何为API生成Swagger或OpenAPI文档  EditPlus中的正则表达式 实战(1)  Angular 表单中正确绑定输入值以确保提交与验证正常工作  如何在万网ECS上快速搭建专属网站?  Laravel如何使用withoutEvents方法临时禁用模型事件  如何快速打造个性化非模板自助建站?  html5源代码发行怎么设置权限_访问权限控制方法与实践【指南】  如何快速搭建自助建站会员专属系统?  Laravel中间件起什么作用_Laravel Middleware请求生命周期与自定义详解  在线制作视频网站免费,都有哪些好的动漫网站?  浏览器如何快速切换搜索引擎_在地址栏使用不同搜索引擎【搜索】  php读取心率传感器数据怎么弄_php获取max30100的心率值【指南】  Win11应用商店下载慢怎么办 Win11更改DNS提速下载【修复】  Laravel如何使用Seeder填充数据_Laravel模型工厂Factory批量生成测试数据【方法】  Laravel Docker环境搭建教程_Laravel Sail使用指南  Laravel怎么配置不同环境的数据库_Laravel本地测试与生产环境动态切换【方法】  Python文件流缓冲机制_IO性能解析【教程】  邀请函制作网站有哪些,有没有做年会邀请函的网站啊?在线制作,模板很多的那种?  怎么用AI帮你为初创公司进行市场定位分析?  微信公众帐号开发教程之图文消息全攻略  Laravel如何获取当前登录用户信息_Laravel Auth门面使用与Session用户读取【技巧】  Laravel如何自定义分页视图?(Pagination示例)  Laravel如何实现多表关联模型定义_Laravel多对多关系及中间表数据存取【方法】  Python并发异常传播_错误处理解析【教程】