文件链接是“在线预览”还是“直接下载”:取决于这几个 HTTP 响应头

6 阅读7分钟

最近在给自己的小项目整理一个文件下载功能:把演示视频、资料压缩包、测试安装包放到一个固定地址,发给对方点开就能拿到。做的时候被一个基础问题绊了一下——同样是一个文件链接,为什么视频、图片在浏览器里直接播放或显示,而压缩包、安装包点开就弹出下载?有的视频进度条还能拖,有的只能从头缓冲?

查了一圈发现,这些行为几乎都不是“文件本身”决定的,而是服务端返回的几个 HTTP 响应头在指挥浏览器。这篇把这几个响应头和自查方法整理清楚,下次再做文件链接就不用反复试了。

一、“在线预览”还是“直接下载”,先看两个响应头

浏览器请求一个文件,不会只凭扩展名就决定怎么做,真正拍板的是响应头,主要是两个:

  • Content-Type:告诉浏览器这是什么类型的资源;
  • Content-Disposition:inline 表示能显示就直接显示,attachment 表示不要显示、直接下载。

浏览器自己能渲染的类型(网页、图片、PDF、纯文本、视频、音频)默认是 inline,点开就在页面里打开或播放;它不打算渲染的类型(zip、各类安装包),默认就触发下载。

资源浏览器默认动作想反过来怎么办
图片 / PDF / 网页 / 文本页面里直接显示服务端加 attachment,或页面用 <a download>
视频 / 音频调起播放器在线播放加 attachment 即变成下载
zip / 7z / dmg / apk / exe直接弹出下载本就下载,无需处理

这一篇和我之前写的怎么让一个 .html 拖上去就能当网页打开正好是一体两面:那篇讲怎么让浏览器“渲染”,这篇讲怎么让浏览器“下载”,卡住时要看的其实是同一套响应头。

二、视频能在线拖进度,关键不是播放器,是 Range

视频、音频这类大文件,最容易忽略的坑是:链接能播放,但进度条拖不动,一点就回到开头重新缓冲,暂停后再继续也会断。

这跟网速没关系,是服务端支不支持 Range 请求(分段请求):

  • 支持的服务,响应头里会有 Accept-Ranges: bytes;拖到第 10 分钟时,播放器发出 Range: bytes=10485760-,服务端回 206 Partial Content 和 Content-Range,只发那一段,跳转几乎瞬间完成;
  • 不支持的服务,播放器只能从头缓冲到底,不能跳,浏览器和下载工具的断点续传也无从谈起。

所以判断一个文件链接“扛不扛得住大文件”,光看“能不能点开”不够,还得看它给不给 Range。这也是对象存储、CDN 这类静态文件服务和一些一次性文件接口之间很实际的差别。

三、压缩包和安装包:类型给对,才会下载而不是乱码

我曾把一个 .apk 放到没配好类型的静态目录上,手机点开不是下载,而是满屏乱码文本——原因是服务端把它当成了 text/plain,浏览器便老老实实把二进制当文本显示了。常见类型心里有个数:

文件建议的 Content-Type
.zipapplication/zip
.7z / .rarapplication/x-7z-compressed / application/vnd.rar,拿不准可用 application/octet-stream 兜底
.dmg(macOS)application/x-apple-diskimage
.apk(安卓)application/vnd.android.package-archive
.exe(Windows)application/vnd.microsoft.portable-executable
.mp4 / .mp3video/mp4 / audio/mpeg

application/octet-stream 是通用兜底:浏览器一看是“不认识的二进制”,基本都会触发下载。

还要提醒一点:响应头只能决定“下不下得来”,决定不了“系统信不信任”。macOS 安装包要过公证、安卓要正式签名、Windows 也有签名和 SmartScreen,这些没做,文件即使能下载,对方系统也可能拦截。所以测试包用链接临时分发很方便,正式版还是应走应用商店和正规签名渠道。

四、几条命令,先在命令行把链接验清楚

发任何文件链接前,我都会先用 curl 看一眼,不拿对方当测试:

# 1) 看类型、是否强制下载、文件大小、支不支持 Range
curl -sIL https://example.com/files/demo.mp4 \
  | grep -iE 'content-type|content-disposition|content-length|accept-ranges'

# 2) 模拟 Range 请求,能看到 206 Partial Content 才支持拖进度/断点续传
curl -s -r 0-1023 -D - -o /dev/null https://example.com/files/demo.mp4 \
  | grep -iE 'HTTP/|content-range|accept-ranges'

根据结果对症处理:

  • 想让文件被下载而不是播放:服务端给 Content-Disposition: attachment; filename="demo.zip";也可以在网页里用 <a href="..." download="演示视频.mp4">下载</a>,但 download 属性对跨域资源经常被浏览器忽略,跨域时最终还得靠服务端的 attachment。
  • 中文文件名下载后乱码:响应头里同时给 filename 和按 RFC 5987 编码的 filename*,形如 attachment; filename="demo.zip"; filename*=UTF-8''%E6%BC%94%E7%A4%BA.zip。

五、我固定的自查流程

踩过几次坑后,我把这类文件链接的自查固定成五步:

  1. 命名:文件名先用英文和数字(如 app-v1.2.apk、demo-2026.mp4),中文更适合放在网页按钮或说明文字里;压缩包先确认能正常解开,安装包确认是最终签名的那版。
  2. 得到链接:确认拿到的是文件本身的地址,而不是需要二次操作才能取到文件的中间页。
  3. curl 三连验:Content-Type 对不对、要不要 attachment、大文件支不支持 Range(206)。
  4. 按页面需要选择预览或下载:希望直接看的用 inline,希望保存的用 attachment 或 <a download>;需要手机扫码取文件的,再把链接转成二维码。
  5. 多端复测:手机直接点一次,Safari、Chrome 各点一次,弱网下拖一下视频进度、暂停后继续,确认断点续传正常。

另外注意时效:临时生成的文件链接不要写进需要长期有效的文档。对于会反复更新的文件(比如每周一个测试版),我更推荐用固定文件名做同名覆盖,链接保持不变、文件随时是最新的,不用每发一版就换一批链接,这个我在之前讲文档改版的一篇里写过。

六、预览还是下载,按使用场景选

这两种行为没有谁更好,关键看对方拿到文件后要做什么:

  • 适合在线预览:图片、PDF、短视频、说明文档这类“看一眼就行”的内容,少一次下载、不占本地空间;
  • 适合直接下载:压缩包、安装包、原始素材、需要离线归档或二次编辑的文件,以及要被其他程序读取的内容;
  • 大文件、长期分发:要考虑流量和分发成本,正式 App 优先走应用商店;临时测试、技术支持、网页下载按钮这类场景,用一个可直接访问的文件链接最省事。

七、这套做法其实不绑定某一家

回头看,真正起作用的就那几条服务端能力:按扩展名给对 Content-Type、需要时给 attachment、大文件支持 Range/206、给一个稳定的 https 链接。这些自己用对象存储加 CDN 能做,任何满足条件的静态托管、图床或文件托管也都能做。我个人图省事,平时传图传文件顺手放在图床小镇,并没有为它单独开一套部署;但这只是个人选择,换成同样具备这几条能力的服务,结论完全一样,按自己的需求挑就好。

小结

一个文件链接到底是“在线预览”还是“直接下载”,关键不在上传动作,而在响应头(Content-Type、Content-Disposition),大文件能不能拖进度、断点续传,则看 Accept-Ranges 和 206;把这几点理顺,文件分享就不用反复试。

这类能力现在不少静态托管、图床和对象存储都提供,挑一个顺手、能长期用的就行,不必拘泥于具体哪一家。我自己用的那家官网在 imgbed.cn ,仅供参考,同类选择还有很多。