web在线下载
浏览器里点一下“下载”,线下载文件就进了硬盘。线下载这事看起来天经地义,线下载但真正跑通这条链路,线下载中间要过的线下载坎比大多数人想象的多。
拿最普通的线下载场景说:一个用户想下载网站上的PDF。他点击链接,线下载浏览器向服务器发请求。线下载服务器返回的线下载不仅仅是文件本身,还有一堆响应头——Content-Type告诉浏览器这是线下载什么文件,Content-Length说明大小,线下载Content-Disposition决定是线下载直接打开还是弹出保存对话框。任何一个头设置错了,线下载用户体验就会出问题。线下载比如Content-Type写成application/octet-stream,线下载浏览器不知道该用什么程序打开,只能强制下载;写成application/pdf,浏览器可能直接在标签页里渲染出来,用户反而找不到文件存哪了。
下载大文件是另一个故事。我见过一个后台管理系统,导出Excel报表时直接在后端生成完整文件再推给前端。数据量小的时候没问题,超过几万行,服务器内存直接飙红。正确的做法是流式传输——边读数据库边写响应流,内存占用恒定。Node.js里用Stream管道,Java里用OutputStream逐块flush,原理都一样:别把整个文件攒在内存里。
断点续传是下载功能里最容易被忽略的部分。用户下到80%断了网,重新点击时如果从头开始,体验极差。HTTP协议其实早就支持这个——请求头里的Range: bytes=800000-告诉服务器从第800000字节开始传,服务器返回206 Partial Content状态码和对应的片段。但很多开发者压根没处理这个头,服务器直接返回200和完整文件,浏览器只能重新下载。我查过一个开源项目的issue区,有人抱怨下载慢,维护者查了半天才发现是Nginx配置里没开Accept-Ranges。
前端这边也有坑。用<a>标签加download属性是最简单的方案,但它只对同源URL生效。跨域资源想触发下载,得用fetch拿到Blob,再用URL.createObjectURL生成临时链接。这个过程中如果文件太大,Blob会占满内存。有个做在线视频剪辑的团队跟我聊过,他们最初用Blob下载视频,用户剪了半小时的片子,一点导出浏览器就崩。后来改成后端生成下载链接、前端只负责跳转,问题才解决。
还有文件名的问题。中文文件名在Content-Disposition里要经过URL编码,否则某些浏览器会乱码。RFC 5987规定了filename*=UTF-8''的写法,但IE系列的兼容性又得单独处理。我自己的做法是同时给filename和filename*两个参数,让浏览器自己选。
下载速度的瓶颈往往不在带宽。有次我排查一个下载慢的问题,发现服务器端每次读取文件都重新打开一次文件句柄,没有用缓存。改成sendfile系统调用后,速度直接翻倍。Linux下Nginx的sendfile on配置就是干这个的——数据从磁盘到网卡不经过用户态,减少一次内存拷贝。
安全方面,下载功能经常被用来做路径遍历攻击。用户请求../../etc/passwd,如果后端直接拼接路径去读文件,服务器就裸奔了。防御方法不复杂:校验文件名、限制下载目录、用白名单过滤。但总有人偷懒,觉得“内部系统无所谓”。
说到底,web下载不是标签加个href就完事。从HTTP头到流式处理,从断点续传到内存管理,每个环节都有具体的工程决策要做。做得粗糙,用户就是多等几秒、多点几次;做得细致,下载这件事可以完全无感——点一下,文件就在那儿了。
(责任编辑:综合)














