<%--
有些朋友询问使用 JSP Smart 下载文件的时候报错, 这里给出一个测试过的不
需要使用 JSP Smart 的 JSP 页面中进行文件下载的代码(改 Servlet 或者
JavaBean 的话自己改吧), 支持中文附件名(做了转内码处理). 事实上只要向
out 输出字节就被认为是附件内容, 不一定非要从文件读取原始数据, 从数据
库中读取也可以的.
测试结果: Tomcat 5.0, 5.5, Resin 3.0.18 , Weblogic 8.1, 9.2 测试通过, 无异常产生
--%>
<%
@ page contentType
=
"
text/html; charset=GBK
"
pageEncoding
=
"
GBK
"
%>
<%
@ page
import
=
"
java.io.*, java.util.*, java.text.*
"
%>
<%!
/**
* If returns true, then should return a 304 (HTTP_NOT_MODIFIED)
*/
public
static
boolean
checkFor304( HttpServletRequest req,
File file )
{
//
//
We'll do some handling for CONDITIONAL GET (and return a 304)
//
If the client has set the following headers, do not try for a 304.
//
//
pragma: no-cache
//
cache-control: no-cache
//
if
(
"
no-cache
"
.equalsIgnoreCase(req.getHeader(
"
Pragma
"
))
||
"
no-cache
"
.equalsIgnoreCase(req.getHeader(
"
cache-control
"
)))
{
//
Wants specifically a fresh copy
}
else
{
//
//
HTTP 1.1 ETags go first
//
String thisTag
=
Long.toString(file.lastModified());
String eTag
=
req.getHeader(
"
If-None-Match
"
);
if
( eTag
!=
null
)
{
if
( eTag.equals(thisTag) )
{
return
true
;
}
}
//
//
Next, try if-modified-since
//
DateFormat rfcDateFormat
=
new
SimpleDateFormat(
"
EEE, dd MMM yyyy HH:mm:ss z
"
);
Date lastModified
=
new
Date(file.lastModified());
try
{
long
ifModifiedSince
=
req.getDateHeader(
"
If-Modified-Since
"
);
//
log.info("ifModifiedSince:"+ifModifiedSince);
if
( ifModifiedSince
!=
-
1
)
{
long
lastModifiedTime
=
lastModified.getTime();
//
log.info("lastModifiedTime:" + lastModifiedTime);
if
( lastModifiedTime
<=
ifModifiedSince )
{
return
true
;
}
}
else
{
try
{
String s
=
req.getHeader(
"
If-Modified-Since
"
);
if
( s
!=
null
)
{
Date ifModifiedSinceDate
=
rfcDateFormat.parse(s);
//
log.info("ifModifiedSinceDate:" + ifModifiedSinceDate);
if
( lastModified.before(ifModifiedSinceDate) )
{
return
true
;
}
}
}
catch
(ParseException e)
{
//
log.warn(e.getLocalizedMessage(), e);
}
}
}
catch
( IllegalArgumentException e )
{
//
Illegal date/time header format.
//
We fail quietly, and return false.
//
FIXME: Should really move to ETags.
}
}
return
false
;
}
%>
<%
//
String filePath = "c:/文档.doc";
//
如果是 WEB APP 下的相对路径文件, 请使用下列代码:
String filePath
=
application.getRealPath(
"
测试文档.htm
"
);
boolean
isInline
=
false
;
//
是否允许直接在浏览器内打开(如果浏览器能够预览此文件内容,
//
那么文件将被打开, 否则会提示下载)
//
清空缓冲区, 防止页面中的空行, 空格添加到要下载的文件内容中去
//
如果不清空的话在调用 response.reset() 的时候 Tomcat 会报错
//
java.lang.IllegalStateException: getOutputStream() has already been called for
//
this response,
out.clear();
//
{{{ BEA Weblogic 必读
//
修正 Bea Weblogic 出现 "getOutputStream() has already been called for this response"错误的问题
//
关于文件下载时采用文件流输出的方式处理:
//
加上response.reset(),并且所有的%>后面不要换行,包括最后一个;
//
因为Application Server在处理编译jsp时对于%>和<%之间的内容一般是原样输出,而且默认是PrintWriter,
//
而你却要进行流输出:ServletOutputStream,这样做相当于试图在Servlet中使用两种输出机制,
//
就会发生:getOutputStream() has already been called for this response的错误
//
详细请见《More Java Pitfill》一书的第二部分 Web层Item 33:试图在Servlet中使用两种输出机制 270
//
而且如果有换行,对于文本文件没有什么问题,但是对于其它格式,比如AutoCAD、Word、Excel等文件
//
下载下来的文件中就会多出一些换行符0x0d和0x0a,这样可能导致某些格式的文件无法打开,有些也可以正常打开。
//
同时这种方式也能清空缓冲区, 防止页面中的空行等输出到下载内容里去
response.reset();
//
}}}
try
{
java.io.File f
=
new
java.io.File(filePath);
if
(f.exists()
&&
f.canRead()) {
//
我们要检查客户端的缓存中是否已经有了此文件的最新版本, 这时候就告诉
//
客户端无需重新下载了, 当然如果不想检查也没有关系
if
( checkFor304( request, f ) )
{
//
客户端已经有了最新版本, 返回 304
response.sendError( HttpServletResponse.SC_NOT_MODIFIED );
return
;
}
//
从服务器的配置来读取文件的 contentType 并设置此contentType, 不推荐设置为
//
application/x-download, 因为有时候我们的客户可能会希望在浏览器里直接打开,
//
如 Excel 报表, 而且 application/x-download 也不是一个标准的 mime type,
//
似乎 FireFox 就不认识这种格式的 mime type
String mimetype
=
null
;
mimetype
=
application.getMimeType( filePath );
if
( mimetype
==
null
)
{
mimetype
=
"
application/octet-stream;charset=ISO8859-1
"
;
}
response.setContentType( mimetype );
//
IE 的话就只能用 IE 才认识的头才能下载 HTML 文件, 否则 IE 必定要打开此文件!
String ua
=
request.getHeader(
"
User-Agent
"
);
//
获取终端类型
if
(ua
==
null
) ua
=
"
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0;)
"
;
boolean
isIE
=
ua.toLowerCase().indexOf(
"
msie
"
)
!=
-
1
;
//
是否为 IE
if
(isIE
&&
!
isInline) {
mimetype
=
"
application/x-msdownload
"
;
}
//
下面我们将设法让客户端保存文件的时候显示正确的文件名, 具体就是将文件名
//
转换为 ISO8859-1 编码
String downFileName
=
new
String(f.getName().getBytes(),
"
ISO8859-1
"
);
String inlineType
=
isInline
?
"
inline
"
:
"
attachment
"
;
//
是否内联附件
//
or using this, but this header might not supported by FireFox
//
response.setContentType("application/x-download");
response.setHeader (
"
Content-Disposition
"
, inlineType
+
"
;filename=/
""
+
downFileName
+
"
/
""
);
response.setContentLength((
int
) f.length());
//
设置下载内容大小
byte
[] buffer
=
new
byte
[
4096
];
//
缓冲区
BufferedOutputStream output
=
null
;
BufferedInputStream input
=
null
;
//
try
{
output
=
new
BufferedOutputStream(response.getOutputStream());
input
=
new
BufferedInputStream(
new
FileInputStream(f));
int
n
=
(
-
1
);
while
((n
=
input.read(buffer,
0
,
4096
))
>
-
1
) {
output.write(buffer,
0
, n);
}
response.flushBuffer();
}
catch
(Exception e) {
}
//
用户可能取消了下载
finally
{
if
(input
!=
null
) input.close();
if
(output
!=
null
) output.close();
}
}
return
;
}
catch
(Exception ex) {
//
ex.printStackTrace();
}
//
如果下载失败了就告诉用户此文件不存在
response.sendError(
404
);
%>