当前位置: 首页 > 科技观察

文件下载难倒了我们的CTO...

时间:2023-03-16 19:04:39 科技观察

今天给大家分享两个有用的现象,浏览器的行为与预期不一致。这两个问题其实都不是什么难的问题,但是我在工作中发现了很多问题。人们被难住了。图片来自宝途网。在我的印象中,群里至少有3位同事在问这样的问题。上周又有同事被这个现象困住了,所以我觉得这应该是一个普遍的问题。我将在这里与您分享。希望对大家有所帮助。现象一:点击按钮无法实现文件下载。前端同事反馈无法在浏览器中下载完成的“下载产品图片”按钮。(预计应该是下载zip文件)但是如果在浏览器地址栏输入这个下载地址却可以直接从浏览器下载,为什么呢?我们可以打开调试工具“网络部分”,然后点击上面的“下载产品图片”,首先检查网络请求是否正常。①首先看请求头,可以看到状态码是200,响应头有两个:content-disposition和Content-Type:voiceover:Content-Type:application/octet-stream告诉客户端这是二进制文件,content-disposition告诉客户端这是一个需要下载的附件,并告诉浏览器附件的默认文件名。②看这个请求的responsebody,是否和步骤1中的application/octet-stream一致:可以看到response是一堆乱码,也就是文件的二进制流表示,所以有其实从请求来看是没有问题的,文件是正常返回的,但是为什么文件下载不下来。下载的文件去哪里了?注意上图中另一个红框XHR。它的全称是XMLHttpRequest,是ajax请求的一种形式。Ajax本身无法触发浏览器的下载功能。它的响应将由JavaScript处理。使用ajax下载完成后,response会以字符串的形式存在内存中,所以不能使用ajax下载?不行,我们看看为什么浏览器可以下载。我们发现可以使用浏览器的GET请求(主要以frame加载,标签点击触发)或者POST请求(以表单形式存在)来下载文件。因为这是浏览器的内置事件,下载的响应将由浏览器自己处理。如果浏览器识别出它是二进制流数据,它就会下载它。如果它识别出是可以打开的文件,比如xml、image等,则不会下载。,将存在于预览样式中。那么为什么ajax默认不能下载文件呢?这是受浏览器的安全策略限制的。试想一下,如果ajax可以下载文件,就意味着ajax可以直接与磁盘进行交互,这会带来严重的安全隐患。根据以上分析,我们有了一个使用ajax下载文件的想法,因为使用标签(或框架)的点击事件可以触发浏览器内置的下载行为。我们用ajax下载完response后,可以用js创建一个隐藏的a标签(标签的href指向文件的链接),执行它的点击事件。这样就触发了浏览器内置的download事件,文件就可以下载了,但是需要注意的是必须在createda标签中加上download属性。这个下载属性有什么用?对于html、xml等浏览器可以打开的文件,如果不添加下载,点击a标签不会下载,而是打开。(注意download属性目前只兼容Firefox和Google)使用ajax下载文件的代码示例如下:constfilename=response.headers['content-disposition'].match(/filename=(.*)/)[1]//首先创建一个Blob对象(代表不可变的原始数据的类文件对象)constblob=newBlob([response.data],{type:'application/zip'});if(typeofwindow.navigator.msSaveBlob!=='undefined'){//兼容IE,window.navigator.msSaveBlob:将文件保存到本地window.navigator.msSaveBlob(blob,decodeURI(filename))}else{letelink=document.createElement("a");//创建一个标签elink.style.display="none";//隐藏标签elink.href=window.URL.createObjectURL(blob);//配置href指向内存地址thelocalfileelink.download=filename;elink.click();URL.revokeObjectURL(elink.href);//释放URL对象document.body.removeChild(elink);//移除标签}现象2:在浏览器中输入图片链接,想预览,但是原来是下载图片的问题。其实经过上面的分析,相信大家也很容易猜到是怎么回事了。我们先看一下数据包:可以看到返回的Content-Type是octet-stream。上面我们提到过,它指的是任何类型的二进制流数据,一般是下载的文件返回的,浏览器会因为无法识别打开的流数据而下载。那为什么大部分图片都可以在浏览器上预览,因为它返回的Content-Type是image/png或者image/jpeg等其他浏览器可以直接识别打开的文件,所以不会执行下载事件。总结以上两个问题需要我们对浏览器的工作机制和HTTP协议有一定的了解,所以基础真的很重要,否则很可能查了半天无从下手,但是如果知道这些原理,抓个包分析一下他们的Content-Type,瞬间就明白了!另外,对于一些疑难杂症,了解HTTP协议和浏览器的工作机制,也有助于快速定位和解决问题。比如上图的方案中,我们使用content-disposition获取文件名:constfilename=response.headers['content-disposition'].match(/filename=(.*)/)[1]但是一开始就发现这段代码有问题,日志显示response.headers['content-disposition']其实是空的。但是当你打开浏览器的网络,你会发现content-disposition明明是存在的:为什么在响应的header中获取不到content-disposition?检查后发现还是HTTP协议的问题。默认情况下,只有七种类型的标头。简单响应头(simpleresponseheaders)可以对外暴露:Cache-ControlContent-LanguageContent-LengthContent-TypeExpiresLast-ModifiedPragma这里是对外暴露的,也就是说客户端(比如Chrome)可以访问,可以是在网络中看到,在代码中也可以获取到它们的值。并且不包含content-disposition,所以即使服务器在协议回复包中添加了这个字段,如下:response.setHeader("content-disposition","attachment;filename="+filename);但因为它没有“暴露”在外面,客户可以“看到它,但不能吃它”。响应头Access-Control-Expose-Headers是控制“暴露”的开关,它列出了哪些头可以作为响应的一部分暴露给外部。所以如果想让客户端访问其他的header信息,服务端不仅要在header中添加header,还要在Access-Control-Expose-Headers中列出,如下:response.setHeader("Access-Control-Expose-Headers","Content-Disposition");response.setHeader("content-disposition","attachment;filename="+filename);这样JS响应头就有了content-disposition的值。参考链接:Access-Control-Expose-Headers:http://1il58.cn/AptUz作者:鲲哥,前独角兽技术专家,现创业者编辑:陶家龙来源:码海(ID:seofcode)