前端压缩包下载遇到解压失败问题
最近在跟后端对接压缩包下载遇到了下载OK,解压报错inappropriate file type or format,尝试使用postman却可以正常下载解压,所以很是奇怪。毕竟postman可以,后端不改的基础上前端一定是有解的,于是继续摸索,最终因为进度所赶,改用base64编码传输,解决了这个问题,但没有根本搞清root cause。
借着周末,研究输出下。
报错
报错如上
代码
这里贴出问题相关前后端代码
后端
1 | router.get('/download-binary', function (req, res) { |
前端
1 |
|
解决
一开始以为是blob转存文件问题,尝试改写blob构造时type指定及增加编码等,发现还是不行。
于是翻看MDN,发现,XHR还是可以明确指定responseType于是加入了下
1 | xhr.responseType = 'blob'; |
发现,下载解压经常正常了,原来问题在这里。
于是,结论就是后端返回二进制数据的前提下,前端需要明确responseType即可,当然如果是新开标签同步方式下载不存在该问题,因为浏览器自己会指定。
问题虽然解决,但为什么这样就行?这里整体分析下。
分析
xhr.responseType
MDN文档上关于responseType这样描述
XMLHttpRequest.responseType 属性是一个枚举类型的属性,返回响应数据的类型。它允许我们手动的设置返回数据的类型。如果我们将它设置为一个空字符串,它将使用默认的”text”类型。
断点查看下xhr.responseType设定对于返回response体的影响
默认文本
blob
可以看出response类型有区别
因此可以说后端返回的response[content-type]在xhr中并没有用,仍然需要手动指定。但response[content-type]一点用都没有?错。
Response Headers[Content-Type]
尝试修改response[content-type]来对比测试
chrome下预览返回结果,
- 对于二进制还是文本,预览是chrome都会文本形式进行预览,所以直观来看内容一样
- 但是在JS中仍然会有影响,如果后端返回文本形式的内容,最终下载解压仍然失败。
- Content-Length在文本形式返回及二进制返回大小并不相同,比如这里如果是Text,长度是
350
,而Blob是318
所以结论就是前端xhr要明确responseType同时后端需要response[content-type]设置二进制返回,缺一不可。
Postman
使用postman测试,确保后端代码返回二进制数据,前端代码不指定responseType下解压报错。而postman却解压正常。
原因就容易解释了,浏览器异步请求需要指定XHR来确保返回数据正确进行格式解析,而postman请求的内部实现无论是利用的浏览器异步还是别的程序编程,总之正确的解析了返回的二进制数据,因此没问题。
DataURL
当然除了上述办法外还有一个办法即DataURL,这也是在一开始我还没解决找到解压根本原因前的解决方案。
后端
1 | router.get('/download-base64', function (req, res) { |
前端
1 | function downloadFileBase64Click() { |
如上即可解决。
需要注意的是,采用base64,后端返回的内容长度是438
,相较二进制的318
,长度会变大。
查资料了解:Base64编码的数据体积通常是原数据的体积4/3
Binary vs DataURL
搞清楚了问题所在,且了解到有两种方案,那哪种方案更好呢。首先了解下利弊
- Base64编码后数据体积变大
- 对于文件,采用Base64编码一般是用于降低HTTP会话请求数量
个人觉得如果是异步下载,优先二进制流即可,没必要造成后端编码成本及请求体积增大。
DataURL适用于比如网页加载部分图片等资源,不希望占用HTTP会话,且有些简单的图片资源需要动态生成,那么可以使用DataURL
延伸
utf8 vs utf-8
利用Node来编写下载支持的后端代码时注意到fs.readFileSync的编码有两个类似的值utf8,utf-8。这样傻傻分不清。查了下资料,这里两个值使用哪个均可,准确来说
utf8是utf-8的别名
。
WHY?因为有些框架下,对于编码常量上,不支持中线,因此才有了utf8这个值,node是跨平台运行环境,对此做了兼容处理。
Wireshark抓包
Chrome下看到的请求无法以16进制/二进制形式查看数据,肉眼看到的只是文本形式,因此觉得类似,但毕竟内容不同,那么具体怎么不同,这里可以使用抓包工具
Wireshark
来真正查看HTTP响应报文数据。
- 截图仔细对比16进制的数据,可以看出HTTP请求响应包-文本/二进制数据的内容是不同的。当然如果看右侧的文本形式还是会觉得相同。这点也与chrome中看到的一致
- 留意红框选中的length值,并不是前面讲到的内容长度350,这是因为Wireshark在包列表页展示的length为以太网帧的长度,这其中当然包含了在chrome看到的请求响应中的content-length即应用层HTTP-请求响应体的内容长度
- 虽然有返回不同的文本/二进制,但实际的数据传输最终都会是二进制01,这里只是显示为16进制格式而已,在Wireshark下也可以切换为二进制展示
写在最后
关于文中提到的代码demo,可以在这里找到
问题到此为止,结论也清晰了,Mark。