拿下奇怪的前端报错(四):1比特丢失导致的音视频播放时长无限增长-浅析http分片传输核心和一个坑点

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
云原生多模数据库 Lindorm,多引擎 多规格 0-4节点
简介: 在一个使用MongoDB GridFS存储文件的项目中,音频和视频文件在大部分设备上播放时长显示为无限,而单独播放则正常。经调查发现,问题源于HTTP Range请求的处理不当,导致最后一个字节未被正确返回。通过调整请求参数,使JavaScript/MongoDB的操作范围与HTTP Range一致,最终解决了这一问题。此案例强调了对HTTP协议深入理解及跨系统集成时注意细节的重要性。

问题背景

在一个使用MongoDB GridFS实现文件存储和分片读取的项目中,同事遇到了一个令人困惑的问题:音频文件总是丢失最后几秒,视频文件也出现类似情况。更奇怪的是,播放器显示的总时长为无限大。这个问题困扰了团队成员几天,都没有解决,因为架构这块我负责的,最后当然就需要我来深入调查了(本来是想蒙混过去的,毕竟影响其实不大,但因为刚好有点空闲)。

  • 在应用中,音频时长为无限,可以不停的快进也就是时长无限增长,大部分手机都如此,除了一部分手机正常
  • 在浏览器中单独打开音频文件,播放正常
  • 文件使用Mongodb的GridFS存储在数据库,通过http接口对外提供读取接口

HTTP分片传输简介

在深入问题之前,让我们先了解一下HTTP分片传输的基本概念,这对理解后续的调试过程至关重要。这部分内容很久之前看网络相关文档的时候,有初略的了解,大概就是前端请求时,后端支持分片可返回部分数据+总数据长度,然后前端就可以按需读取了。

HTTP分片传输(HTTP Range Requests)允许客户端请求资源的部分内容,而不是整个资源。这在处理大文件,特别是音频和视频流时非常有用。主要涉及以下HTTP头:

  1. Range:客户端在请求中使用此头来指定所需的字节范围。
    例如:Range: bytes=500-999表示请求从第500字节到第999字节的内容。

  2. Content-Range:服务器在响应中使用此头来指明实际返回的字节范围。
    例如:Content-Range: bytes 500-999/1234表示返回的是500-999字节,总文件大小为1234字节。

  3. Accept-Ranges:服务器使用此头来告诉客户端它支持范围请求。

重要的是,HTTP规范中定义的Range是包含性的(inclusive),这意味着Range: bytes=0-499实际上请求了500个字节。这里是一个非常容易出错的点,因为在大多数的编程语言中,对类似数组数据的操作,结尾位置的数据都是不包含的,这也是一个坑点,刚入行不久的小伙伴很可能就跳进去了,毕竟随能够记得那么细呢?而且是一个反常见模式的知识点!

调试过程

1. 初步分析

首先,我注意到播放器的duration显示为无穷大。为了进一步测试,我尝试将currentTime设置为一个非常大的值,但问题依然存在。但是在单独的页面播放和oppo的某手机播放没问题,就猜测是某个特殊点的兼容性问题,但部分尝试做了兼容,所以正常,但大部分的设备或者webview都没有兼容,导致这个问题没被处理,从而出现时长无限。这时候测试得到一个比较重要的信息:

  • 特别小的文件,时长在5秒内的不会出现无限时长,视频文件大概率都会出现

于是就进一步猜测,肯定和网络传输有关,而且和文件体积有关系,这时候想起了大文件分片,因为上传的时候就是分片上传的

2. 网络请求分析

观察到网络请求一直处于pending状态,我怀疑可能是由于socket没有正确关闭导致无限加载。我尝试在数据流关闭后结束请求,但这并没有解决问题。

  1. 而且发现浏览器在不同的发送包内容基本一样的请求

3. 请求头分析

仔细查看请求头,我发现了一些奇怪的Range请求,如Range=11333-11333/11334。起初,我认为可能是因为起始值和结束值相同导致的问题。我尝试在这种情况下重写Range,对于Range=11333-这样的请求则返回416状态码。但这些措施都没有解决问题。

也就是这里引起了我的警觉,因为是不停的在请求,一开始的时候,还以为是触发了某个浏览器的bug,但是分析请求头,有一点引起可我的注意,就是11333-11333/11334 ,为什么会请求这样的数据,第一反应是一个0字节的请求

然后我就开始读HTTP1.1-rfc2616规范文档,发现这了这个反直觉的坑,那就是范围的结尾值是包含的。

这里基本上我猜测到了问题出现的原因,下一步就是验证了

附rfc的说明 - inclusive,开始还不确定这个单词的意思
在这里插入图片描述

4. 深入源码

分析第三方库的源码后,我发现了问题的根源:

  • HTTP的Content-Range头中,start和end都是包含的(inclusive)。
  • 而在JavaScript和MongoDB中,类似slice()这样的操作,end是不包含的(exclusive)。

这种不一致性导致了在处理最后一个字节时出现问题。具体来说:

  1. 当客户端请求最后一个字节时(例如Range: bytes=11333-11333),服务器正确地解释了这个请求。
  2. 但是,当服务器使用JavaScript或MongoDB的API来获取这个范围的数据时,由于end是exclusive的,实际上没有返回任何数据。
  3. 这导致客户端认为还有更多数据需要获取,因此会继续发送请求,造成无限加载的情况。

解决方案

为了解决这个问题,便对getDownloadStream方法进行了修改-重写,类似于下面的方式-先缓存老方法,再修改请求参数,再调用老方法:

const oldFun = obj.a;
obj.a = function(a1, a2) {
   
  if (a2.end) {
   
    a2.end += 1;
  }
  return oldFun(a1, a2);
}

这个简单的修改确保了JavaScript/MongoDB的操作范围与HTTP请求的Range一致,解决了缺少最后一个字节的问题。通过将end值增加1,确保了包含了请求范围的最后一个字节。

经验总结

  1. HTTP协议的深入理解很重要:尤其是请求头参数的精确含义,对于解决复杂问题至关重要。在这个案例中,理解Range请求的包含性本质是解决问题的关键。

  2. 框架设计的重要性:这次调试过程让我意识到了Koa等框架设计的优势。能够在一个中间件中同时处理请求和响应,大大方便了调试过程。

  3. 细节决定成败:在这个案例中,仅仅一个字节的差异就导致了严重的用户体验问题。这提醒我们在处理底层数据时必须格外小心,特别是在处理不同系统间的数据交换时。

  4. 开源贡献的价值:通过这次经历,我不仅解决了自己的问题,还有机会通过PR为开源社区做出贡献,提升了项目的整体质量。这展示了开源社区如何通过集体智慧来解决复杂问题。

  5. 跨系统集成的挑战:这个问题突出了在集成不同系统(如HTTP服务器、JavaScript运行时和MongoDB)时可能遇到的细微差异。在设计跨系统解决方案时,需要特别注意这些潜在的不一致性。

结语

这个看似简单的问题背后隐藏着HTTP协议、JavaScript特性和数据库操作之间的微妙差异。通过耐心的调试和深入的分析,我不仅解决了问题,还加深了对各种技术细节的理解。这再次证明,在软件开发中,"魔鬼藏在细节里"。

对于其他可能遇到类似问题的开发者,建议深入理解所使用的每个组件的特性,特别是在处理底层数据传输时。同时,这个案例也强调了全面测试的重要性,尤其是在处理边界情况时。通过分享这种经验,可以共同提高整个开发社区的技术水平和问题解决能力。

本文主要是复盘之前工作中遇到的一个奇怪的前端问题,并且是我个人的第一次开源代码贡献,还提交了两次,第一次因为格式不对没写注释-个人仓库管理员没接受,第二次说明了情况并详细说明和备注了解释等才通过

相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。   相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
相关文章
|
17天前
|
前端开发 JavaScript 安全
前端性能调优:HTTP/2与HTTPS在Web加速中的应用
【10月更文挑战第27天】本文介绍了HTTP/2和HTTPS在前端性能调优中的应用。通过多路复用、服务器推送和头部压缩等特性,HTTP/2显著提升了Web性能。同时,HTTPS确保了数据传输的安全性。文章提供了示例代码,展示了如何使用Node.js创建一个HTTP/2服务器。
31 3
|
5天前
|
网络协议 网络安全 网络虚拟化
本文介绍了十个重要的网络技术术语,包括IP地址、子网掩码、域名系统(DNS)、防火墙、虚拟专用网络(VPN)、路由器、交换机、超文本传输协议(HTTP)、传输控制协议/网际协议(TCP/IP)和云计算
本文介绍了十个重要的网络技术术语,包括IP地址、子网掩码、域名系统(DNS)、防火墙、虚拟专用网络(VPN)、路由器、交换机、超文本传输协议(HTTP)、传输控制协议/网际协议(TCP/IP)和云计算。通过这些术语的详细解释,帮助读者更好地理解和应用网络技术,应对数字化时代的挑战和机遇。
31 3
|
18天前
|
前端开发 安全 应用服务中间件
前端性能调优:HTTP/2与HTTPS在Web加速中的应用
【10月更文挑战第26天】随着互联网的快速发展,前端性能调优成为开发者的重要任务。本文探讨了HTTP/2与HTTPS在前端性能优化中的应用,介绍了二进制分帧、多路复用和服务器推送等特性,并通过Nginx配置示例展示了如何启用HTTP/2和HTTPS,以提升Web应用的性能和安全性。
17 3
|
25天前
|
前端开发 JavaScript 中间件
前端全栈之路Deno篇(四):Deno2.0如何快速创建http一个 restfulapi/静态文件托管应用及oak框架介绍
Deno 是由 Node.js 创始人 Ryan Dahl 开发的新一代 JavaScript 和 TypeScript 运行时,旨在解决 Node.js 的设计缺陷,具备更强的安全性和内置的 TypeScript 支持。本文介绍了如何使用 Deno 内置的 `Deno.serve` 快速创建 HTTP 服务,并详细讲解了 Oak 框架的安装和使用方法,包括中间件、路由和静态文件服务等功能。Deno 和 Oak 的结合使得创建 RESTful API 变得高效且简便,非常适合快速开发和部署现代 Web 应用程序。
|
25天前
|
JSON 前端开发 数据格式
前端的全栈之路Meteor篇(五):自定义对象序列化的EJSON介绍 - 跨设备的对象传输
EJSON是Meteor框架中扩展了标准JSON的库,支持更多数据类型如`Date`、`Binary`等。它提供了序列化和反序列化功能,使客户端和服务器之间的复杂数据传输更加便捷高效。EJSON还支持自定义对象的定义和传输,通过`EJSON.addType`注册自定义类型,确保数据在两端无缝传递。
|
26天前
|
Web App开发 缓存 前端开发
拿下奇怪的前端报错(六):多摄手机webrtc拉取视频流会导致应用崩溃,从而无法进行人像扫描
本文介绍了一种解决手机摄像头切换导致应用崩溃的问题的方法。针对不支持facingMode配置的四摄手机,通过缓存和序号切换的方式,确保应用在特定设备上不会频繁崩溃,提升用户体验。
|
26天前
|
前端开发 JavaScript Docker
拿下奇怪的前端报错(五):SyntaxError: Unexpected token ‘??=‘或‘xxx‘ - 基于容器搭建开发环境或许是更好的选择
在前端开发中,同时维护多个项目时可能会遇到不同Node.js版本的问题。低版本Node.js可能导致依赖无法安装或启动失败,而高版本Node.js则可能引起第三方库的兼容性问题。推荐使用Docker搭建独立的开发环境,以避免版本不一致带来的困扰。
482 1
|
1月前
|
缓存 JavaScript 前端开发
拿下奇怪的前端报错(三):npm install卡住了一个钟- 从原理搞定安装的全链路问题
本文详细分析了 `npm install` 过程中可能出现的卡顿问题及解决方法,包括网络问题、Node.js 版本不兼容、缓存问题、权限问题、包冲突、过时的 npm 版本、系统资源不足和脚本问题等,并提供了相应的解决策略。同时,还介绍了开启全部日志、使用替代工具和使用 Docker 提供 Node 环境等其他处理方法。
422 0
|
1月前
|
JavaScript 前端开发 Docker
拿下奇怪的前端报错(二):nvm不可用报错`GLIBC_2.27‘‘GLIBCXX_3.4.20‘not Found?+ 使用docker构建多个前端项目实践
本文介绍了在多版本Node.js环境中使用nvm进行版本管理和遇到的问题,以及通过Docker化构建流程来解决兼容性问题的方法。文中详细描述了构建Docker镜像、启动临时容器复制构建产物的具体步骤,有效解决了不同项目对Node.js版本的不同需求。