为什么浏览器中有些文件点击后是预览,有些是下载

2023-11-13

今天给大家分享两个比较有用的浏览器行为与预期不一致的现象,这两个问题其实并不是什么难题,但在工作中发现不少人被难住了,在我的印象中至少有三位同事在群里问这样的问题,上周又有同事被此现象困住了,所以我觉得这应该是个共性问题,在这里分享给大家,希望对大家有帮助

现象一、点击按钮无法实现文件下载
前端同事反馈在浏览器里点击实现好的「下载商品图片」按钮却无法下载(预期应该下载 zip 文件)

图片

img
但如果你在浏览器的地址栏里输入此下载地址却又能直接从浏览器里下载,这是为何?

我们可以打开调试工具「网络部分」,然后点击一下上面的「下载商品图片」,首先看一下网络请求是否正常。

1、 首先看请求头,可以看出状态码是 200,另外还有 content-disposition 与 Content-Type 这两个 response header

图片

img
画外音:Content-Type: application/octet-stream 告诉客户端这是一个二进制文件,content-disposition 告诉客户端这是一个需要下载的附件并告诉浏览器该附件默认的文件名。

2、再看此请求的 response body,是否和步骤一的 application/octet-stream 相符:

图片

img
可以看到 response 就是一堆乱码,即文件的二进制流表现形式,所以从请求来看其实是没有问题的,文件是正常的返回的,但为啥文件却没有下载下来,下载下来的文件去哪里了呢,注意看上图的另一个红框 XHR ,它的全称是 XMLHttpRequest,是 ajax 请求的一种表现形式。

ajax 本身无法触发浏览器的下载功能, 它的 response 会交由 JavaScript 处理,使用 ajax 下载完成后,response 以字符串的形式存储在内存中,那使用 ajax 就没法下载了吗?不是的,我们看下浏览器为啥能下载

我们发现使用浏览器的 GET 请求(主要以 frame 加载, a 标签点击触发)或 POST请求(以 form 的形式存在)是可以下载文件的,因为这是浏览器的内置事件,下载的 response 会交由浏览器自己处理,浏览器如果识别到是二进制流数据则下载,如果识别到是可以打开的文件,如 xml, image 等则不会下载,会以预览的样式存在。

那么为啥 ajax 不能默认实现文件下载呢,这是浏览器的安全策略限制的,试想如果 ajax 可以下载文件,那就意味着 ajax 可以直接与磁盘交互,这会存在严重的安全隐患。

根据以上分析,要使用 ajax 下载文件我们也就有思路了,既然使用 a 标签(或 frame)的点击事件可以触发浏览器的内置下载行为,那我们在用 ajax 下载拿到 response 后,可以用 js 新建一个隐藏的 a 标签(标签的 href 指向文件的链接),执行它的 click 事件,这样就触发了浏览器的内置下载事件,就可以下载文件了,不过需要注意的事,创建的 a 标签中要添加一个 download 属性。 。

这个 download 属性有啥用呢,对于浏览器能打开的文件,例如 html,xml 等,如果你不加 download,点击 a 标签就不是下载了,而是打开。(注意 download 属性目前只被火狐和谷歌兼容)

使用 ajax 来执行下载文件的代码示例如下:

const filename = response.headers['content-disposition'].match(
      /filename=(.*)/
)[1]
// 首先要创建一个 Blob 对象(表示不可变、原始数据的类文件对象)
const blob = new Blob([response.data], {type: 'application/zip'});
if (typeof window.navigator.msSaveBlob !== 'undefined') {
    // 兼容IE,window.navigator.msSaveBlob:以本地方式保存文件
    window.navigator.msSaveBlob(blob, decodeURI(filename))
} else {
    let elink = document.createElement("a"); // 创建一个<a>标签
    elink.style.display = "none"; // 隐藏标签
    elink.href = window.URL.createObjectURL(blob); // 配置href,指向本地文件的内存地址
    elink.download = filename;
    elink.click();
    URL.revokeObjectURL(elink.href); // 释放URL 对象
    document.body.removeChild(elink); // 移除<a>标签
}

现象二、在浏览器输入图片链接想预览,结果却变成了下载图片
这个问题其实经由上文分析,相信你不难猜出是咋回事,我们先抓包看一下:

图片

img
可以看到返回的 Content-Type 为 octet-stream,上文我们提到,它指任意类型的二进制流数据,一般下载文件返回的是这种类型,浏览器由于无法识别打开流数据,所以会下载,那为啥大多数图片在浏览器上是可以预览的呢,因为它返回的 Content-Type 是 image/png 或 image/jpeg 等浏览器可以直接识别打开的文件,这样就不会执行下载事件

总结
以上两个问题需要我们对浏览器的工作机制与 HTTP 协议有一定的了解,所以基础真的很重要啊,不然很可能你排查半天也无从下手,但如果你知道了这些原理,抓个包分析一下它们的 Content-Type,瞬间就豁然开朗了!另外对一些疑难杂症,了解 HTTP 协议与浏览器的工作机制也有助于帮助你快速定位解决问题。

比如上图的解决方案中我们通过 content-disposition 来获取文件的名称

const filename = response.headers['content-disposition'].match(
      /filename=(.*)/
)[1]

但在最开始发现这段代码有问题,打印日志发现 response.headers[‘content-disposition’] 居然为空,可是打开浏览器的 network 会发现, content-disposition 明明存在啊

图片

img
那为啥在 reponse 的 header 里拿不到 content-disposition 呢?

一查发现原来还是 HTTP 协议的问题

默认情况下,header 只有七种 simple response headers (简单响应首部)可以暴露给外部:

Cache-Control
Content-Language
Content-Length
Content-Type
Expires
Last-Modified
Pragma

这里的暴露给外部,意思是让客户端(比如 Chrome)可以访问得到,既可以在 Network 里看到,也可以在代码里获取到他们的值。

而 content-disposition 不在其中,所以即使服务器在协议回包里加了该字段,如下

response.setHeader("content-disposition", "attachment; filename=" + filename);

但因没“暴露”给外部,客户端就「看得到,吃不到」。

而响应首部 Access-Control-Expose-Headers 就是控制“暴露”的开关,它列出了哪些首部可以作为响应的一部分暴露给外部。

所以如果想要让客户端可以访问到其他的首部信息,服务器不仅要在 header 里加入该首部,还要将它们在 Access-Control-Expose-Headers 里面列出来,如下:

response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
response.setHeader("content-disposition", "attachment; filename=" + filename);

这样的话 JS 的 response header 里就有 content-disposition 的值啦。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么浏览器中有些文件点击后是预览,有些是下载 的相关文章

随机推荐

  • 成功GET一款高大上又不显俗的Linux时间锁屏软件-GLUQLO

    闲来无事 总觉得目前的Ubuntu桌面锁屏看烦了 依然记得之前手机端曾经流行过一款锁屏软件 想想这个应该会有Linux版了 度娘果然查到了 名字就叫GLUQLO 而且也有Linux版还是开源的 那就开始动起来 一 安装环境 机器环境 Lin
  • 程序员到了35岁就会失业吗?三位程序员UP主这样说……

    Q 你自己也是 up 然后会接触很多程序员群体 你觉得你在跟这些程序员沟通下来 他们会有一些关于年龄方面的焦虑嘛 A 很多人都有 而且很多人都是 还没入行就开始焦虑了 就开始问我 水哥水哥 咱这程序员是不是真的到了 35 岁 就 就完蛋了
  • 【Rust】003-基础语法:流程控制

    Rust 003 基础语法 流程控制 文章目录 Rust 003 基础语法 流程控制 一 概述 二 if 表达式 1 语法格式 2 多个 3 获取表达式的值 三 循环 1 loop 无限循环 可跳出 无限循环 跳出循环 返回值 2 whil
  • EL-FROM动态添加item并设置必填

  • 短视频批量剪辑--矩阵源码---无人直播如何搭建技术开发

    一 核心技术 1 AI自动直播 智能系统通过丰富可定制的文案库 拥有有料有趣的灵魂 不仅能自动语音讲解内容 还可以在直播中和用户灵活互动 直播中可将团购商品同话术自动上下架 2 AI剪辑 可一键智能批量成片 也可跟着模板剪同款视频 更可针对
  • 全速下载微云方法

    下载tim 测试版 版本TIM 2 5 8 apk https www lanzous com iaroy3i 登陆QQ小号 转储文件至微云 QQ小号登陆tim 文件 微云文件 找到文件 点进去 点右上角转发到qq大号上 全速下载
  • OpenGL编程指南-freeglut安装(Windows平台)

    OpenGL编程指南 freeglut安装 Windows平台 1 前言 学习OpenGL编程首先需要可以跟着书中的示例代码进行学习 书中使用GLUT作为示例代码的演示 GLUT于1998年作者不在维护并不开源 freeglut是一个完美的
  • Regex-后向引用

    使用小括号指定一个子表达式后 匹配这个子表达式的文本 也就是此分组捕获的内容 可以在表达式或其它程序中作进一步的处理 默认情况下 每个分组会自动拥有一个组号 规则是 从左向右 以分组的左括号为标志 第一个出现的分组的组号为1 第二个为2 以
  • 在同一个canvas中绘制多个不同形状,颜色的图形

    今日踩坑 在同一个canvas中绘制多个不同形状 颜色的图形时 后面的总是将前面的颜色覆盖 解决方法 beginPath 和 closePath 这两个函数可以起到类似 div 的作用 用它来把每个图形包围 就可以绘制不同颜色的图形了 例如
  • 已上架的App在AppStore上无法搜索到的问题

    前言 如果还没有苹果开发者账号 自行注册苹果开发者中心 opens new window 并缴费成为开发者 证书配置 证书教程 opens new window Win系统请使用 appuploader opens new window 进
  • Android Studio更新3.1版本之后编译出现Program type already present: android.support.design.widget.CoordinatorLa

    前言 今天上午打包项目的时候出现了编译异常 怎么会出现这个问题 昨天编译都OK 然后想了下之后原来今天上午刚更新了新版本studio3 12 这有点尴尬了 感觉每次studio更新版本 都会遇到坑 要么gradle出现问题 要么编译异常提示
  • linux中$?,$#等代表什么

    0 这个程式的执行名字 n 这个程式的第n个参数值 n 1 9 这个程式的所有参数 此选项参数可超过9个 这个程式的参数个数 这个程式的PID 脚本运行的当前进程ID号 执行上一个背景指令的PID 后台运行的最后一个进程的进程ID号 执行上
  • C++primer plus 第十一章编程练习

    银行账户类 头文件 ifndef HEAD H define HEAD H include
  • muduo的高性能异步日志

    1 一个日志库大体可分为前端 frontend 与后端 backend 前端是供应用程序使用的接口 API 并生成日志信息 后端则是负责将日志信息写到目的地 每个线程都有自己的前端 而整个程序共用一个后端 对于生产者 前端 而言 要尽量做到
  • sql-labs 41-65关

    Less 41 这关还是堆叠注入 而且还是数字型闭合 可以照搬39关代码 但是与39不同的是 这关没有报错的显示位 查数据 id 1 id 0 union select 1 select group concat username from
  • 计数排序--时间复杂度为线性的排序算法

    我们知道基于比较的排序算法的最好的情况的时间复杂度是O nlgn 然而存在一种神奇的排序算法 不是基于比较的 而是空间换时间 使得时间复杂度能够达到线性O n k 这种算法就是本文将要介绍的计数排序 一 适用情况 这个算法在n个输入元素中每
  • 卷积神经网络超详细介绍

    文章目录 1 卷积神经网络的概念 2 发展过程 3 如何利用CNN实现图像识别的任务 4 CNN的特征 5 CNN的求解 6 卷积神经网络注意事项 7 CNN发展综合介绍 8 LeNet 5结构分析 9 AlexNet 10 ZFNet 1
  • layui option 动态添加_layui select 动态加载案例

    用到知识点 表单监听 form on 局部表单渲染 form render 动态加载的select表单 必须有默认的option项 第一个option 要不然layui 不会渲染出 select 组件 代码如下 添加数据 返回列表 查找所有
  • Vue中子组件通过v-model动态修改父组件中的值

    父子通信中的子传父 使用v model实现双向数据绑定 注意 vue组件是此组件的根组件 是该组件中所有注册的组件的父组件 现有需求 通过子组件中的输入框来动态绑定父组件中data中的数据 代码实现 父组件使用porps来向子组件传值 子组
  • 为什么浏览器中有些文件点击后是预览,有些是下载

    今天给大家分享两个比较有用的浏览器行为与预期不一致的现象 这两个问题其实并不是什么难题 但在工作中发现不少人被难住了 在我的印象中至少有三位同事在群里问这样的问题 上周又有同事被此现象困住了 所以我觉得这应该是个共性问题 在这里分享给大家