研究一下下面的代碼,就會(huì)對(duì)JSP下載文件有個(gè)完整的了解,順帶了解JSP輸出流的問(wèn)題
<%--
有些朋友詢問(wèn)使用 JSP Smart 下載文件的時(shí)候報(bào)錯(cuò), 這里給出一個(gè)測(cè)試過(guò)的不
需要使用 JSP Smart 的 JSP 頁(yè)面中進(jìn)行文件下載的代碼(改 Servlet 或者
JavaBean 的話自己改吧), 支持中文附件名(做了轉(zhuǎn)內(nèi)碼處理). 事實(shí)上只要向
out 輸出字節(jié)就被認(rèn)為是附件內(nèi)容, 不一定非要從文件讀取原始數(shù)據(jù), 從數(shù)據(jù)
庫(kù)中讀取也可以的.
測(cè)試結(jié)果: Tomcat, Resin 工作正常, Weblogic 尚未測(cè)試
--%>
<%@ 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 下的相對(duì)路徑文件, 請(qǐng)使用下列代碼:
String filePath = application.getRealPath("測(cè)試文檔.htm");
boolean isInline = false;// 是否允許直接在瀏覽器內(nèi)打開(kāi)(如果瀏覽器能夠預(yù)覽此文件內(nèi)容,
// 那么文件將被打開(kāi), 否則會(huì)提示下載)
// 清空緩沖區(qū), 防止頁(yè)面中的空行, 空格添加到要下載的文件內(nèi)容中去
// 如果不清空的話在調(diào)用 response.reset() 的時(shí)候 Tomcat 會(huì)報(bào)錯(cuò)
// java.lang.IllegalStateException: getOutputStream() has already been called for
// this response,
out.clear();
// {{{ BEA Weblogic 必讀
// 修正 Bea Weblogic 出現(xiàn) "getOutputStream() has already been called for this response"錯(cuò)誤的問(wèn)題
// 關(guān)于文件下載時(shí)采用文件流輸出的方式處理:
// 加上response.reset(),并且所有的%>后面不要換行,包括最后一個(gè);
// 因?yàn)锳pplication Server在處理編譯jsp時(shí)對(duì)于%>和<%之間的內(nèi)容一般是原樣輸出,而且默認(rèn)是PrintWriter,
// 而你卻要進(jìn)行流輸出:ServletOutputStream,這樣做相當(dāng)于試圖在Servlet中使用兩種輸出機(jī)制,
// 就會(huì)發(fā)生:getOutputStream() has already been called for this response的錯(cuò)誤
// 詳細(xì)請(qǐng)見(jiàn)《More Java Pitfill》一書(shū)的第二部分 Web層Item 33:試圖在Servlet中使用兩種輸出機(jī)制 270
// 而且如果有換行,對(duì)于文本文件沒(méi)有什么問(wèn)題,但是對(duì)于其它格式,比如AutoCAD、Word、Excel等文件
//下載下來(lái)的文件中就會(huì)多出一些換行符0x0d和0x0a,這樣可能導(dǎo)致某些格式的文件無(wú)法打開(kāi),有些也可以正常打開(kāi)。
// 同時(shí)這種方式也能清空緩沖區(qū), 防止頁(yè)面中的空行等輸出到下載內(nèi)容里去
response.reset();
// }}}
try {
java.io.File f = new java.io.File(filePath);
if (f.exists() && f.canRead()) {
// 我們要檢查客戶端的緩存中是否已經(jīng)有了此文件的最新版本, 這時(shí)候就告訴
// 客戶端無(wú)需重新下載了, 當(dāng)然如果不想檢查也沒(méi)有關(guān)系
if( checkFor304( request, f ) )
{
// 客戶端已經(jīng)有了最新版本, 返回 304
response.sendError( HttpServletResponse.SC_NOT_MODIFIED );
return;
}
// 從服務(wù)器的配置來(lái)讀取文件的 contentType 并設(shè)置此contentType, 不推薦設(shè)置為
// application/x-download, 因?yàn)橛袝r(shí)候我們的客戶可能會(huì)希望在瀏覽器里直接打開(kāi),
// 如 Excel 報(bào)表, 而且 application/x-download 也不是一個(gè)標(biāo)準(zhǔn)的 mime type,
// 似乎 FireFox 就不認(rèn)識(shí)這種格式的 mime type
String mimetype = null;
mimetype = application.getMimeType( filePath );
if( mimetype == null )
{
mimetype = "application/octet-stream;charset=ISO8859-1";
}
response.setContentType( mimetype );
// IE 的話就只能用 IE 才認(rèn)識(shí)的頭才能下載 HTML 文件, 否則 IE 必定要打開(kāi)此文件!
String ua = request.getHeader("User-Agent");// 獲取終端類(lèi)型
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";
}
// 下面我們將設(shè)法讓客戶端保存文件的時(shí)候顯示正確的文件名, 具體就是將文件名
// 轉(zhuǎn)換為 ISO8859-1 編碼
String downFileName = new String(f.getName().getBytes(), "ISO8859-1");
String inlineType = isInline ? "inline" : "attachment";// 是否內(nèi)聯(lián)附件
// 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());// 設(shè)置下載內(nèi)容大小
byte[] buffer = new byte[4096];// 緩沖區(qū)
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);
%>
<%--
有些朋友詢問(wèn)使用 JSP Smart 下載文件的時(shí)候報(bào)錯(cuò), 這里給出一個(gè)測(cè)試過(guò)的不
需要使用 JSP Smart 的 JSP 頁(yè)面中進(jìn)行文件下載的代碼(改 Servlet 或者
JavaBean 的話自己改吧), 支持中文附件名(做了轉(zhuǎn)內(nèi)碼處理). 事實(shí)上只要向
out 輸出字節(jié)就被認(rèn)為是附件內(nèi)容, 不一定非要從文件讀取原始數(shù)據(jù), 從數(shù)據(jù)
庫(kù)中讀取也可以的.
測(cè)試結(jié)果: Tomcat, Resin 工作正常, Weblogic 尚未測(cè)試
--%>
<%@ 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 下的相對(duì)路徑文件, 請(qǐng)使用下列代碼:
String filePath = application.getRealPath("測(cè)試文檔.htm");
boolean isInline = false;// 是否允許直接在瀏覽器內(nèi)打開(kāi)(如果瀏覽器能夠預(yù)覽此文件內(nèi)容,
// 那么文件將被打開(kāi), 否則會(huì)提示下載)
// 清空緩沖區(qū), 防止頁(yè)面中的空行, 空格添加到要下載的文件內(nèi)容中去
// 如果不清空的話在調(diào)用 response.reset() 的時(shí)候 Tomcat 會(huì)報(bào)錯(cuò)
// java.lang.IllegalStateException: getOutputStream() has already been called for
// this response,
out.clear();
// {{{ BEA Weblogic 必讀
// 修正 Bea Weblogic 出現(xiàn) "getOutputStream() has already been called for this response"錯(cuò)誤的問(wèn)題
// 關(guān)于文件下載時(shí)采用文件流輸出的方式處理:
// 加上response.reset(),并且所有的%>后面不要換行,包括最后一個(gè);
// 因?yàn)锳pplication Server在處理編譯jsp時(shí)對(duì)于%>和<%之間的內(nèi)容一般是原樣輸出,而且默認(rèn)是PrintWriter,
// 而你卻要進(jìn)行流輸出:ServletOutputStream,這樣做相當(dāng)于試圖在Servlet中使用兩種輸出機(jī)制,
// 就會(huì)發(fā)生:getOutputStream() has already been called for this response的錯(cuò)誤
// 詳細(xì)請(qǐng)見(jiàn)《More Java Pitfill》一書(shū)的第二部分 Web層Item 33:試圖在Servlet中使用兩種輸出機(jī)制 270
// 而且如果有換行,對(duì)于文本文件沒(méi)有什么問(wèn)題,但是對(duì)于其它格式,比如AutoCAD、Word、Excel等文件
//下載下來(lái)的文件中就會(huì)多出一些換行符0x0d和0x0a,這樣可能導(dǎo)致某些格式的文件無(wú)法打開(kāi),有些也可以正常打開(kāi)。
// 同時(shí)這種方式也能清空緩沖區(qū), 防止頁(yè)面中的空行等輸出到下載內(nèi)容里去
response.reset();
// }}}
try {
java.io.File f = new java.io.File(filePath);
if (f.exists() && f.canRead()) {
// 我們要檢查客戶端的緩存中是否已經(jīng)有了此文件的最新版本, 這時(shí)候就告訴
// 客戶端無(wú)需重新下載了, 當(dāng)然如果不想檢查也沒(méi)有關(guān)系
if( checkFor304( request, f ) )
{
// 客戶端已經(jīng)有了最新版本, 返回 304
response.sendError( HttpServletResponse.SC_NOT_MODIFIED );
return;
}
// 從服務(wù)器的配置來(lái)讀取文件的 contentType 并設(shè)置此contentType, 不推薦設(shè)置為
// application/x-download, 因?yàn)橛袝r(shí)候我們的客戶可能會(huì)希望在瀏覽器里直接打開(kāi),
// 如 Excel 報(bào)表, 而且 application/x-download 也不是一個(gè)標(biāo)準(zhǔn)的 mime type,
// 似乎 FireFox 就不認(rèn)識(shí)這種格式的 mime type
String mimetype = null;
mimetype = application.getMimeType( filePath );
if( mimetype == null )
{
mimetype = "application/octet-stream;charset=ISO8859-1";
}
response.setContentType( mimetype );
// IE 的話就只能用 IE 才認(rèn)識(shí)的頭才能下載 HTML 文件, 否則 IE 必定要打開(kāi)此文件!
String ua = request.getHeader("User-Agent");// 獲取終端類(lèi)型
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";
}
// 下面我們將設(shè)法讓客戶端保存文件的時(shí)候顯示正確的文件名, 具體就是將文件名
// 轉(zhuǎn)換為 ISO8859-1 編碼
String downFileName = new String(f.getName().getBytes(), "ISO8859-1");
String inlineType = isInline ? "inline" : "attachment";// 是否內(nèi)聯(lián)附件
// 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());// 設(shè)置下載內(nèi)容大小
byte[] buffer = new byte[4096];// 緩沖區(qū)
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);
%>