【转】HTTP Response Header 的 Content-Disposition

来源:互联网 发布:java onvif协议 编辑:程序博客网 时间:2024/04/26 05:23

因为听到有同事讨论JSP输出Excel文件的,就是在页面上有一个【导出】按钮,能够将查询结果导出到Excel文件让用户下载。有人说要用POI在后台生成临时的Excel文件,然后通过读取FileStream写入到OutputStream来解决。其实这个功能不需要这么重型的武器的,虽然很多人讨厌MS,但是不得不承认MS绝对不是乱盖的,IE和Office产品的几近完美的结合就是一个列子。页面里面的Table很容易就可以导出到Excel文件,而且格式都能够完好的保存,所以如果要将查询结果导出到Excel,只需将页面的Context-Type修改一下就可以了:

<%@ page language="java" contentType="application/vnd.ms-excel"%>

然后请求这个页面的时候,就会出现这样的IE窗口:

image

看到上面的菜单了么?除了IE的,还有Excel的。而且在点击“文件”->“另存为”的时候,如果选择“Excel工作簿”,那么保存的文件就是真正的转换到Excel文件的存储格式了。

不过,有一个问题是,如果我不希望直接在IE里面打开Excel文件,我希望能够提供那个打开/保存的对话框,应该如何做?

模模糊糊记得很久很久以前用过一个参数,于是乎,google一下吧,找到了,就是这个Content-Disposition参数,HTTP Response Header的一个参数。但是这个不是标准参数,查看一下HTTP/1.1的规范文档,对于这个参数的解释大意如下:

Content-Disposition参数本来是为了在客户端另存文件时提供一个建议的文件名,但是考虑到安全的原因,就从规范中去掉了这个参数。但是由于很多浏览器已经能够支持这个参数,所以只是在规范文档中列出,但是要注意这个不是HTTP/1.1的标准参数。

于是,在页面加入一行代码:

<%
response.addHeader("Content-Disposition", "attachment;filename=test.xls");
%>

能够看到“文件下载”的对话框了:

image

测试了一下,其实IE是根据Content-Disposition中filename这个段中文件名的后缀来识别这个文件类型的,所以,如果有很多种文件类型的时候,可以将Content-Type设置为二进制模式的:

<%@ page language="java" contentType="application/octet-stream"%>

附Content-Disposition的解释:

19.5.1 Content-Disposition The Content-Disposition response-header field has been proposed as a means for the origin server to suggest a default filename if the user requests that the content is saved to a file. This usage is derived from the definition of Content-Disposition in RFC 1806 [35].

content-disposition = "Content-Disposition" ":" disposition-type *( ";" disposition-parm ) 
disposition-type = "attachment" | disp-extension-token 
disposition-parm = filename-parm | disp-extension-parm 
filename-parm = "filename" "=" quoted-string 
disp-extension-token = token 
disp-extension-parm = token "=" ( token | quoted-string )

An example is 
    Content-Disposition: attachment; filename="fname.ext"

The receiving user agent SHOULD NOT respect any directory path information present in the filename-parm parameter, which is the only parameter believed to apply to HTTP implementations at this time. The filename SHOULD be treated as a terminal component only.

If this header is used in a response with the application/octet- stream content-type, the implied suggestion is that the user agent should not display the response, but directly enter a `save response as...' dialog.

Content-Disposition中文乱码

Response.setHeader(”Content-Disposition”, “attachment; filename=” + fileName+”.xls”);
如果file.Name为中文则乱码。解决办法是
方法1:
response.setHeader(”Content-Disposition”, “attachment; filename=” + java.net.URLEncoder.encode(fileName, “UTF-8″));
下载的程序里有了上面一句,一般在IE6的下载提示框上将正确显示文件的名字,无论是简体中文,还是日文。但是文字只要超过17个字,就不能下载了。
一. 通过原来的方式,也就是先用URLEncoder编码,当中文文字超过17个时,IE6 无法下载文件。这是IE的bug,参见微软的知识库文章 KB816868 。原因可能是IE在处理 Response Header 的时候,对header的长度限制在150字节左右。而一个汉字编码成UTF-8是9个字节,那么17个字便是153个字节,所以会报错。而且不跟后缀也不对.

方法2:
response.setHeader( “Content-Disposition”, “attachment;filename=” + new String( fileName.getBytes(”gb2312″), “ISO8859-1″ ) );
在确保附件文件名都是简体中文字的情况下,那么这个办法确实是最有效的,不用让客户逐个的升级IE。如果台湾同胞用,把gb2312改成big5就行。但现在的系统通常都加入了国际化的支持,普遍使用UTF-8。如果文件名中又有简体中文字,又有繁体中文,还有日文。那么乱码便产生了。另外,在上Firefox (v1.0-en)下载也是乱码。

本文我们来说一下MIME 协议的一个扩展Content-disposition。

    我们在开发web系统时有时会有以下需求:

  • 希望某类或者某已知MIME 类型的文件(比如:*.gif;*.txt;*.htm)能够在访问时弹出“文件下载”对话框
  • 希望以原始文件名(上传时的文件名,例如:山东省政府1024号文件.doc)提供下载,但服务器上保存的地址却是其他文件名(如:12519810948091234_asdf.doc)
  • 希望某文件直接在浏览器上显示而不是弹出文件下载对话框
  • ……………………

    要解决上述需求就可以使用Content-disposition来解决。第一个需求的解决办法是

Response.AddHeader "content-disposition","attachment; filename=fname.ext" 将上述需求进行归我给出如下例子代码:public static void ToDownload(string serverfilpath,string filename)
{
FileStream fileStream = new FileStream(serverfilpath, FileMode.Open);
long fileSize = fileStream.Length;
HttpContext.Current.Response.ContentType = "application/octet-stream";
HttpContext.Current.Response.AddHeader("Content-Disposition", "attachment; filename=\"" + UTF_FileName(filename) + "\";");
////attachment --- 作为附件下载
////inline --- 在线打开
HttpContext.Current.Response.AddHeader("Content-Length", fileSize.ToString());
byte[] fileBuffer = new byte[fileSize];
fileStream.Read(fileBuffer, 0, (int)fileSize);
HttpContext.Current.Response.BinaryWrite(fileBuffer);
fileStream.Close();
HttpContext.Current.Response.End();
}

public static void ToOpen(string serverfilpath, string filename)
{
FileStream fileStream = new FileStream(serverfilpath, FileMode.Open);
long fileSize = fileStream.Length;
HttpContext.Current.Response.ContentType = "application/octet-stream";
HttpContext.Current.Response.AddHeader("Content-Disposition", "inline; filename=\"" + UTF_FileName(filename) + "\";");
HttpContext.Current.Response.AddHeader("Content-Length", fileSize.ToString());
byte[] fileBuffer = new byte[fileSize];
fileStream.Read(fileBuffer, 0, (int)fileSize);
HttpContext.Current.Response.BinaryWrite(fileBuffer);
fileStream.Close();
HttpContext.Current.Response.End();
}

private static string UTF_FileName(string filename)
{
return HttpUtility.UrlEncode(filename, System.Text.Encoding.UTF8);
}

简单的对上述代码做一下解析,ToDownload方法为将一个服务器上的文件(serverfilpath为服务器上的物理地址),以某文件名 (filename)在浏览器上弹出“文件下载”对话框,而ToOpen是将服务器上的某文件以某文件名在浏览器中显示/打开的。注意其中我使用了 UTF_FileName方法,该方法很简单,主要为了解决包含非英文/数字名称的问题,比如说文件名为“衣明志.doc”,使用该方法客户端就不会出现 乱码了。

需要注意以下几个问题:

  1. Content-disposition是MIME协议的扩展,由于多方面的安全性考虑没有被标准化,所以可能某些浏览器不支持,比如说IE4.01
  2. 我们可以使用程序来使用它,也可以在web服务器(比如IIS)上使用它,只需要在http header上做相应的设置即可

1 0