背景: 在一个项目中,队友反应浏览器点击下载一个近100M的文件和 10+KB的文件,
下载保存提示框要等很长时间, 而且等待弹出的时间与文件大小成正比关系.
接着 ,找了一个有空的时间, 写了一个极简单的文件下载,使用servlet repsonse输出.
我的环境是一个500M的文件,好家伙 , IE8 CHROME 都要等待很久.
郁闷了..
好吧, 请同事也写一个DEMO吧. 咦..他也是200M的文件,访问他的WEB ,一两秒的时间就出来了.
问题
1.真的是WEB SERVER要读完文件才向broswer输出吗?
2.如果问题1是正确的,那我同事的WEB SERVER,我去访问就一两秒. 我访问自己的DEMO却要等20多秒才弹??
随后比较我的DEMO和我同事的DEMO代码.
相同的文件流读取.
不过发现代码不同了.
我用的 Content-type: application/octet-stream
他用的 Content-type: application/x-download
然后是相同的马上用servlet HttpServletResponse flushBuffer(),向浏览器输出头.
查了一遍mime. 我用的是标准的MIME, application/x-download却不存在.
而真实的项目 文件下载代码Content-type却没有写.
查了'[b]Beginning Java Networking[/b]'的书([b]很旧[/b]的书).servlet HttpServletResponse flushBuffer() 明确的说明头信息是会马上从server输出.
和同事讨论, 他猜测是不同的浏览器对接受流下载的时候会分析http的header和body.
他的猜测 更坚定了上述中我对书中的认识.
那继续查资料. 终于在 "Servlet Best Practices, Part 3"找到我要的答案.
我最后使用 :
1. Content-type: xxxyyy 非标准的MIME.
2. Content-Disposition: attachment; filename=...
3. servlet HttpServletResponse flushBuffer() 马上送出一个只含头信息的HTTP包. 免的文件要是2GB+的文件,用户就要抓狂了....
[b]译文:[/b]
[b]servlet 最佳实践 [/b] -- 实践:没有形成理论之前的某些经验.
servlet中其它的编程提示.
给发送的文件使用上内容描述.
当我们正在使用像魔法一样的神奇的头部信息的题材,servlet开发人员经常会挣扎于寻找正确的头部信息的组合内容来给浏览器发送一个文件,同时这个文件打算是让浏览器触发一个文件保存的对话框的,而不是直接的打开查看或是浏览. 针对这一个目标, 我有一些好消息同时也有一些坏消息.
坏消息是:尽管HTTP的规范(请查阅HTTP/1.1,19.5.1章节)为文件下载提供了机制,然而许多浏览器会"自作多情"地去猜测服务端发送来的指令,并且比起服务端告诉的事,浏览器做一些认为自己应该的做的最好的行动.这些浏览器(包括MS IE 和Opera)会查看文件的扩展名和从得到的输入流的内容中去嗅出(文件类型).假如他们知道的是HTML或IMAGE内容,他们就会直接在自己那显示文件内容而不是响应一个文件下载对话框. 事实证明不是有100%可信的方式通过浏览器去下载一个文件(可以让用户保存起来).也许,通过努力的编程人员比起魔法师更像是炼金的术士,他们努力尝试地将粪土变成黄金.
好消息是如果正确的组合头部信息去做文件下载那么就是一个足够用的实践做法.带上这些特殊头部信息集,一个"兼容"的浏览器就会正常响应一个文件下载对话框,而不会像不"兼容"的浏览器,他们只为不是HTML和IMAGE文件打开下载对话框.
在这些文件类型(指HTML和IMAGE)中,当用户去使用右键菜单保存内容文件时,不兼容浏览器会直接显示文件.
例子 3-9 为发送文件展示最好的做法
// Set the headers. 发送头
res.setContentType("application/x-download");
res.setHeader("Content-Disposition", "attachment; filename=" + filename);
// Send the file. 发送文件体.
OutputStream out = res.getOutputStream( );
returnFile(filename, out); // Shown earlier in the chapter
译注: 此示例代码没有提示马上发送HTTP头信息,如果将HTTP头信息作一个小包马上发送,那么加上示例代码就可以让"兼容"浏览器迟早响应一个文件下载框. 假设目前的网络,去下载一个2~8G的文件.
首先:给Content-Type header设定一个非标准的值例如application/x-download.
如果这个头部信息是浏览器不认识那是非常重要的,因为当浏览器识别了内容的类型,那么浏览器经常会做一些特殊的事情.
然后为头部信息设定内容描述,值为attachment; filename=foo,这里的foo是文件下载框对话框中默认的文件名.
最后,作为二进制发送文件内容.二进制的数组内容可以来自文件系统也可以是动态生成的.
使用这些头部信息,那么输出的文件内容就会让大多数的浏览器响应文件下载框去让用户保存,
最糟糕的情况文件内容会显示在浏览器而不是保存.在response响应中没有一个标准的文件下载所有类型的文件.
最后的(一点提示),包括给servlet下载文件名作为额外的路径信息,也是有用的.
那个Servlet通过知道哪个文件要给用户下载就可以使用文件名,或是可以忽略额外的路径信息.无论哪个方式,它(下载文件名)总是也是有用的:因为文件名会出现在浏览器上作为将被保存资源文件的文件名. 同时浏览器经常会在文件下载提示框上使用文件名.
例如:使用/servlet/FileDownload/inventory.pdf?fileid=5. 而不是使用 /servlet/FileDownload?fileid=5
注:下面一小段不知道在书中哪个部分,就不翻译了. 我看的是网上的节选.
3. Microsoft documents Internet Explorer's deviant behavior, although I've found that reality doesn't exactly match the documentation.
4. The HTTP specification recommends setting the Content-Type to application/octet-stream. Unfortunately, this causes problems with Opera 6 on Windows (which will display the raw bytes for any file whose extension it doesn't recognize) and on Internet Explorer 5.1 on the Mac (which will display inline content that would be downloaded if sent with an unrecognized type).
原文:
[b]Other Servlet Tips[/b]
[b]Use Content-Disposition to Send a File[/b]
While we're on the subject of magic header incantations, servlet developers often struggle with finding the right header combination to send a browser a file that's intended for saving rather than viewing and thus triggers a "Save As" dialog. For the solution to this problem, I have some good news and some bad news.
The bad news is that although the HTTP specification provides a mechanism for file downloads (see HTTP/1.1, Section 19.5.1), many browsers second-guess the server's directives and do what they think is best rather than what they're told. These browsers--including Microsoft Internet Explorer and Opera--look at the file extension and "sniff" the incoming content. If they see HTML or image content, they inline-display the file contents instead of offering a Save As dialog.[3] Turns out there's no 100% reliable way to download a file across all browsers. Perhaps, with this effort, programmers are more like alchemists than magicians, trying in vain to turn lead into gold.
The good news is that the right combination of headers will download files well enough to be practical. With these special headers set, a compliant browser will open a Save As dialog, while a noncompliant browser will open the dialog for all content except HTML or image files. For these types it will display the content inline, where a user can use the menu to save the content. Example 3-9 shows the best technique for sending files.
Example 3-9: Sending a file for download
// Set the headers.
res.setContentType("application/x-download");
res.setHeader("Content-Disposition", "attachment; filename=" + filename);
// Send the file.
OutputStream out = res.getOutputStream( );
returnFile(filename, out); // Shown earlier in the chapter
First, set the Content-Type header to a nonstandard value such as application/x-download. It's very important that this header is something unrecognized by browsers because browsers often try to do something special when they recognize the content type.[4] Then set the Content-Disposition header to the value attachment; filename=foo, in which foo is substituted with the filename to be used by default in the Save As dialog. Finally, send the file content as bytes. The bytes can come from the filesystem or be dynamically generated.
Using these headers, the file content in the response will be saved by most browsers or, in worst cases, displayed inline where the user can save the file. There's no standard way to download multiple files in one response.
Finally, it can be useful to include the download file's name as extra path information to the servlet. The servlet can use the filename to learn which file to download, or it can ignore the extra path info. Either way, it's useful because the name appears to the browser as the name of the resource being retrieved, and browsers often use that name in the Save As dialog prompt. For example, instead of serving content from /servlet/FileDownload?fileid=5, serve it from /servlet/FileDownload/inventory.pdf?fileid=5.