如何强制浏览器下载文件?

56

一切都运行良好,但只有文件较小(大约1MB)时才有效。当我尝试使用更大的文件,例如20MB时,浏览器显示该文件而不是强制下载。 我已经尝试了许多标头,现在我的代码如下:

PrintWriter out = response.getWriter();
String fileName = request.getParameter("filename");

File f= new File(fileName);

InputStream in = new FileInputStream(f);
BufferedInputStream bin = new BufferedInputStream(in);
DataInputStream din = new DataInputStream(bin);

while(din.available() > 0){
    out.print(din.readLine());
    out.print("\n");
}

response.setContentType("application/force-download");
response.setContentLength((int)f.length());
response.setHeader("Content-Transfer-Encoding", "binary");
response.setHeader("Content-Disposition","attachment; filename=\"" + "xxx\"");//fileName);


in.close();
bin.close();
din.close();

设置内容类型应该可以解决问题。使用的是哪个浏览器? - Mathias Schwarz
哪个浏览器?八位字节流应该是正确的答案,但旧版本的IE过去常常尝试“嗅探”内容类型,而不管Web服务器正在告诉它什么。 - Paolo
Chrome,FF 3.6和其他可能的浏览器... - dieeying
3
标题先,数据后,不要颠倒顺序! - Vladimir Dyuzhev
Content-Disposition attachment就可以了。请注意,application/force-download是一种特定的黑客方式,不是标准,请阅读此文:http://www.media-division.com/the-right-way-to-handle-file-downloads-in-php/。 - Christophe Roussy
3个回答

66
您是在将文件内容写入输出流之后设置响应标头的。这在响应生命周期中相当晚,设置标头的正确顺序应该是先设置标头,然后将文件内容写入servlet的outputstream。
因此,您的方法应按以下方式编写(这不会编译,因为它只是一种表示):
response.setContentType("application/force-download");
response.setContentLength((int)f.length());
        //response.setContentLength(-1);
response.setHeader("Content-Transfer-Encoding", "binary");
response.setHeader("Content-Disposition","attachment; filename=\"" + "xxx\"");//fileName);
...
...
File f= new File(fileName);

InputStream in = new FileInputStream(f);
BufferedInputStream bin = new BufferedInputStream(in);
DataInputStream din = new DataInputStream(bin);

while(din.available() > 0){
    out.print(din.readLine());
    out.print("\n");
}

失败的原因是,servlet 实际发送的标头可能与您打算发送的标头不同。毕竟,如果 servlet 容器不知道在 HTTP 响应中正文之前出现的标头,则它可能设置适当的标头以确保响应有效;在写入文件后设置标头因此是徒劳和多余的,因为容器可能已经设置了标头。您可以通过使用 Wireshark 或类似 Fiddler 或 WebScarab 的 HTTP 调试代理来查看网络流量来确认这一点。
您还可以参考 Java EE API 文档中的 ServletResponse.setContentType 了解此行为:
设置发送到客户端的响应的内容类型,如果响应尚未提交。给定的内容类型可以包括字符编码规范,例如 text/html;charset=UTF-8。只有在调用 getWriter 之前调用此方法时,才会从给定的内容类型设置响应的字符编码。
可以重复调用此方法以更改内容类型和字符编码。 如果在响应提交后调用此方法,则此方法无效。

application/force-download是一种黑客技巧,而不是标准:http://www.media-division.com/the-right-way-to-handle-file-downloads-in-php/ - Christophe Roussy
@ChristopheRoussy,是的,没有人声称过这样的事情。请停止在这里刷屏所有的答案。 - Vineet Reynolds
5
我希望你能翻译如下内容:我想告知其他SO用户这是一个黑客攻击,并解释其工作原理,因为这一点在任何地方都没有提到。以下是在SO上的解释链接:https://dev59.com/NWkv5IYBdhLWcg3waAJT - Christophe Roussy

6

在输出文件之前,设置内容类型和其他头部信息。对于小文件,内容会被缓存,浏览器首先获取头部信息。对于大文件,数据会先到达。


4

这段来自一个用PHP编写的脚本,我已经测试过多个浏览器(包括Firefox 3.5及以上、IE8+和Chrome),它可以完美解决问题。

header("Content-Disposition: attachment; filename=\"".$fname_local."\"");
header("Content-Type: application/force-download");
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($fname));

据我所见,您的操作都是正确的。您是否检查过浏览器设置?

5
我猜是“你做得非常正确”这句话让你感到困惑。而被接受的答案已经明确指出了哪里不正确。 - Vladimir Dyuzhev
1
application/force-download 是一种黑客技巧,而不是标准。 - Christophe Roussy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接