Struts 2中實現文件下載(修正中文問題)

BlogJava上已經有一位做者闡述了文件上傳的問題,地址是Struts 2中實現文件上傳,所以我就再也不討論那個話題了。我今天簡單介紹一下Struts 2的文件下載問題。html

咱們的項目名爲 struts2hello,所使用的開發環境是MyEclipse 6,固然其實用哪一個IDE都是同樣的,只要把類庫放進去就好了,文件下載不須要再加入任何額外的包。讀者能夠參考文檔:http://beansoft.java-cn.org/myeclipse_doc_cn/struts2_demo.pdf,來了解怎麼下載和配置基本的Struts 2開發環境。java


爲了便於你們對比,我把完整的struts.xml的配置信息列出來:web

<?xmlversion="1.0"encoding="UTF-8" ?>數據庫

<!DOCTYPEstrutsPUBLICapache

    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"數組

    "http://struts.apache.org/dtds/struts-2.0.dtd">瀏覽器

<struts>安全

    <packagename="default"extends="struts-default" >app

        <!-- 在這裏添加Action定義 -->eclipse

        <!-- 簡單文件下載 -->

        <actionname="download"class="example.FileDownloadAction">

            <resultname="success"type="stream">

                <paramname="contentType">text/plain</param>

                <paramname="inputName">inputStream</param>

                <paramname="contentDisposition">attachment;filename="struts2中文.txt"</param>

                <paramname="bufferSize">4096</param>

            </result>

        </action>

       

        <!-- 文件下載,支持中文附件名 -->

        <actionname="download2"class="example.FileDownloadAction2">

            <!-- 初始文件名 -->

            <paramname="fileName">Struts中文附件.txt</param>

            <resultname="success"type="stream">

                <paramname="contentType">text/plain</param>

                <paramname="inputName">inputStream</param>

                <!-- 使用通過轉碼的文件名做爲下載文件名,downloadFileName屬性

                對應action類中的方法 getDownloadFileName() -->

                <paramname="contentDisposition">attachment;filename="${downloadFileName}"</param>

                <paramname="bufferSize">4096</param>

            </result>

        </action>

        

        <!-- 下載現有文件 -->

        <actionname="download3"class="example.FileDownloadAction3">

            <paramname="inputPath">/download/系統說明.doc</param>

            <!-- 初始文件名 -->

            <paramname="fileName">系統說明.doc</param>

            <resultname="success"type="stream">

                <paramname="contentType">application/octet-stream;charset=ISO8859-1</param>

                <paramname="inputName">inputStream</param>

                <!-- 使用通過轉碼的文件名做爲下載文件名,downloadFileName屬性

                對應action類中的方法 getDownloadFileName() -->

                <paramname="contentDisposition">attachment;filename="${downloadFileName}"</param>

                <paramname="bufferSize">4096</param>

            </result>

        </action>

       

    </package>

</struts>

Struts 2中對文件下載作了直接的支持,相比起本身辛辛苦苦的設置種種HTTP頭來講,如今實現文件下載無疑要簡便的多。提及文件下載,最直接的方式恐怕是直接寫一個超連接,讓地址等於被下載的文件,例如:<a href=」file1.zip」>下載file1.zip</a>,以後用戶在瀏覽器裏面點擊這個連接,就能夠進行下載了。可是它有一些缺陷,例如若是地址是一個圖片,那麼瀏覽器會直接打開它,而不是顯示保存文件的對話框。再好比若是文件名是中文的,它會顯示一堆URL編碼過的文件名例如%3457...。而假設你企圖這樣下載文件:http://localhost:8080/struts2hello/download/系統說明.docTomcat會告訴你一個文件找不到的404錯誤:HTTP Status 404 - /struts2hello/download/ϵͳ˵Ã÷.doc。雖然目前還沒發現直接配置Struts 2來正確的下載中文名字的附件,不過好在做者對JSP中的文件下載比較瞭解,所以咱們另有辦法解決這個問題。另一個最大的用途,就是動態的生成並下載文件了,例如動態的下載生成的EXCELPDF,驗證碼圖片等等。本節內容就依次討論簡單的下載文件代碼,下載中文附件,最後介紹如何下載已經存在的文件。

先說文件下載,編寫一個普通的Action就能夠了,只須要提供一個返回InputStream流的方法,該輸入流表明了被下載文件的入口,這個方法用來給被下載的數據提供輸入流,意思是從這個流讀出來,再寫到瀏覽器那邊供下載。這個方法須要由開發人員本身來編寫,只須要返回值爲InputStream便可。在咱們的例子中方法的簽名是:public InputStream getInputStream() throws Exception,固然它也能夠是別的名字,例如getDownloadFile()。好了,如今咱們所寫的這個進行文件下載的Actionexample.FileDownloadAction的源代碼清單以下:

package example;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import com.opensymphony.xwork2.Action;
public class FileDownloadAction implements Action {
public InputStream getInputStream() throws Exception {
return new ByteArrayInputStream("Struts 2 下載示例".getBytes());
}
public String execute() throws Exception {
return SUCCESS;
}
}


 注意這裏惟一特殊的方法就是getInputStream(),在這個方法裏面咱們使用了一個數組輸入流來從字符串轉換成的數組做爲數據的來源進行讀取。也許方法體中使用這樣的實現代碼:

return new java.io.FileInputStream(「c:""test.txt」);//從系統磁盤文件讀取數據

這樣會更直觀一些。

文件下載的第二步,乃是在struts.xml中對action進行配置,其代碼清單以下所示:

<!-- 簡單文件下載 -->

<action name="download" class="example.FileDownloadAction">
<result name="success" type="stream">
<param name="contentType">text/plain</param>
<param name="inputName">inputStream</param>
<param name="contentDisposition">attachment;filename="struts2.txt"</param>
<param name="bufferSize">4096</param>
</result>
</action>


 這個action特殊的地方在於result的類型是一個流(stream),配置stream類型的結果時,由於無需指定實際的顯示的物理資源,因此無需指定location屬性,只須要指定inputName屬性,該屬性指向被下載文件的來源,對應着Action類中的某個屬性,類型爲InputStream。下面則列出了和下載有關的一些參數列表:

參數

說明

contentType

內容類型,和互聯網MIME標準中的規定類型一致,例如text/plain表明純文本,text/xml表示XMLimage/gif表明GIF圖片,image/jpeg表明JPG圖片

inputName

下載文件的來源流,對應着action類中某個類型爲Inputstream的屬性名,例如取值爲inputStream的屬性須要編寫getInputStream()方法

contentDisposition

文件下載的處理方式,包括內聯(inline)和附件(attachment)兩種方式,而附件方式會彈出文件保存對話框,不然瀏覽器會嘗試直接顯示文件。取值爲:

attachment;filename="struts2.txt",表示文件下載的時候保存的名字應爲struts2.txt。若是直接寫filename="struts2.txt",那麼默認狀況是表明inline,瀏覽器會嘗試自動打開它,等價於這樣的寫法:inline; filename="struts2.txt"

bufferSize

下載緩衝區的大小

。在這裏面,contentType屬性和contentDisposition分別對應着HTTP響應中的頭Content-TypeContent-disposition頭。好,咱們先來看看這個例子,發佈運行項目後鍵入測試地址:http://localhost:8080/struts2hello/download.action,將會看到瀏覽器彈出一個文件保存對話框,如圖12.12所示。

12.12 文件下載對話框(IE 7Firefox 3

若是此時使用某些工具來探測瀏覽器返回的HTTP頭,將會看到下列內容:

HTTP/1.1 200 OK

Server: Apache-Coyote/1.1

Content-disposition: attachment;filename="struts2.txt"

Content-Type: text/plain

Transfer-Encoding: chunked

Date: Sun, 02 Mar 2008 02:58:25 GMT

。因此用來下載的action配置中,只有兩個是和瀏覽器有關的:contentTypecontentDisposition。關於contentType的取值,若是是未知的文件類型,或者說出現了瀏覽器不能打開的文件,例如.bean文件,或者說這個action是用來作動態文件下載的,事先並不知道將來的文件類型是什麼,那麼咱們能夠把它的值設置成爲:application/octet-stream;charset=ISO8859-1,注意必定要加入charset,不然某些時候會致使下載的文件出錯;有人說這時也能夠設置成爲application/x-download,根據筆者的實踐,這個頭也能正常工做,然而個別時候會出現瀏覽器沒法識別的問題。而contentDisposition,若是其取值是filename="struts2.txt",或者是inline; filename="struts2.txt",運行後你能夠看到瀏覽器直接顯示了文件的內容:

Struts 2 下載示例,而再也不彈出對話框提示用戶保存文件到硬盤上。因此讀者若是想確保文件是被下載而不是被打開,務必使用格式attachment;filename="struts2.txt",不要丟了attachment;這個類型信息。

至此,關於文件下載的技術內容,已經告一段落。然而作中文系統,不可避免的要解決中文附件的下載問題。關於這個內容,也無權威的資料可查,咱們只能用實踐中獲得的解決方案來處理。也許有讀者覺得將filename屬性設置爲filename=」struts2中文.txt」就能解決問題了,好,就來試試,把contentDisposition修改爲:

<param name="contentDisposition">attachment;filename="struts2中文.txt"</param>

。再次鍵入地址進行測試,看看顯示的結果,如圖12.13所示。唉,真是徹底不給面子!IE壓根就不能顯示出來文件名,草草敷衍了download_action了事。Firefox稍好點,還出來了一個對話框,可是很顯然,那個顯示的struts2--txt絕對不是咱們日思夜想的struts2中文.txt。怎麼辦?解決方法是有,那就是用ISO8859-1編碼來顯示這個中文字符,能夠閱讀12.8參考資料一節中的JSP 文件下載的相對完整代碼(解決中文問題和Weblogic報錯)這篇文章,能夠這樣認爲,全部的文件下載代碼都是基於一樣的純Servlet的方式來進行的。若是是Java代碼,咱們能夠這樣作:

12.13 IEFirefox下的中文文件下載對話框

String downFileName = new String(「struts2中文.txt」.getBytes(), "ISO8859-1");

而後把生成的結果字符串放到XML文件中就好了,然而它的輸出相似於struts2??.txt,是沒法直接寫道咱們的XML配置文件中的。因此,咱們想到的的辦法,就是在Action類中寫一個方法來作轉碼,使它成爲某個屬性,因此要以get開頭。而後,再用12.3.8Action注入參數(param)值一節的內容,將文件名以正常的方式設置爲action類的某個屬性,最後呢,再利用一個小小的param參數取值中的伎倆:${屬性名},它能夠直接從action類中動態獲取某個屬性值。好了,如今讓咱們來看看第二個文件下載類FileDownloadAction2的代碼:

package example;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import com.opensymphony.xwork2.Action;
public class FileDownloadAction2 implements Action {
private String fileName;// 初始的經過param指定的文件名屬性
public InputStream getInputStream() throws Exception {
return new ByteArrayInputStream("Struts 2 下載示例".getBytes());
}
public String execute() throws Exception {
return SUCCESS;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
/** 提供轉換編碼後的供下載用的文件名 */
public String getDownloadFileName() {
String downFileName = fileName;
try {
downFileName = new String(downFileName.getBytes(), "ISO8859-1");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return downFileName;
}
}


 這個類有兩個屬性,第一個是fileName,它是須要被指定的下載文件名;第二個則是動態的僅僅由getDownloadFileName()這個方法定義的屬性downloadFileName,它的值隨着fileName而動態變更,僅僅是把它轉換成了ISO8859方式的西歐字符集。

接下來就是如何配置這個action了,這是關鍵的地方所在,如今配置一個新的action,名爲download2,其源代碼以下:

<!-- 文件下載,支持中文附件名 -->
<action name="download2" class="example.FileDownloadAction2">
<!-- 初始文件名 -->
<param name="fileName">Struts中文附件.txt</param>
<result name="success" type="stream">
<param name="contentType">text/plain</param>
<param name="inputName">inputStream</param>
<!-- 使用通過轉碼的文件名做爲下載文件名,downloadFileName屬性
對應action類中的方法 getDownloadFileName() -->
<param name="contentDisposition">attachment;filename="${downloadFileName}"</param>
<param name="bufferSize">4096</param>
</result>
</action>


 其中特殊的代碼就是${downloadFileName}它的效果至關於運行的時候將action對象的屬性的取值動態的填充在${}中間的部分,咱們能夠認爲它等價於action. getDownloadFileName()

好了,如今讓咱們從新發布而後運行這個項目,鍵入地址:

http://localhost:8080/struts2hello/download2.action 進行訪問,能夠看到運行結果徹底正確,如圖12.14所示。

 12.14 正確顯示了文件下載名的對話框(IEFirefox

在本節的最後部分,咱們來討論一下如何下載已經存在於當前Web應用目錄下的已經存在的文件。通常的網站可能會把要下載的文件放在某個固定的目錄下,例如WebRoot/download,在這個子目錄下,咱們放了一個名爲系統說明.doc的文件,但願最後咱們的action可以正確的下載這個文件。要檢驗下載是否成功很是簡單,文件內容僅僅是粗體的系統說明書這五個字,而word文件壞一個字節的話都是打不開的,因此下載後再用word打開便可檢驗是否成功。如今咱們建立第三個文件下載的Action類,名爲example. FileDownloadAction3,其源代碼清單以下所示:

package example;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.Action;
public class FileDownloadAction3 implements Action {
private String fileName;// 初始的經過param指定的文件名屬性
private String inputPath;// 指定要被下載的文件路徑
public InputStream getInputStream() throws Exception {
// 經過 ServletContext,也就是application 來讀取數據
return ServletActionContext.getServletContext().getResourceAsStream(inputPath);
}
public String execute() throws Exception {
return SUCCESS;
}
public void setInputPath(String value) {
inputPath = value;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
/** 提供轉換編碼後的供下載用的文件名 */
public String getDownloadFileName() {
String downFileName = fileName;
try {
downFileName = new String(downFileName.getBytes(), "ISO8859-1");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return downFileName;
}
}


 代碼中被改動的部分已經用粗斜體的方式顯示出來了。首先是新加入了一個名爲inputPath的屬性,用來制定被下載文件的路徑。接着就是ServletActionContext.getServletContext()這段代碼,它的意義咱們將在12.6節詳細討論,在這裏讀者只須要知道它獲取了當前Servlet容器的ServletContext,也就是你們常說的jsp中的application對象,而後用它來打開文件的輸入流。

接着要作的就是配置action,它和剛剛配置過的download2的內容差很少,只是多了一個被下載的資源的路徑屬性。如今咱們在struts.xml中加入這個新的action定義:

<!-- 下載現有文件 -->
<action name="download3" class="example.FileDownloadAction3">
<param name="inputPath">/download/系統說明.doc</param>
<!-- 初始文件名 -->
<param name="fileName">系統說明.doc</param>
<result name="success" type="stream">
<param name="contentType">application/octet-stream;charset=ISO8859-1</param>
<param name="inputName">inputStream</param>
<!-- 使用通過轉碼的文件名做爲下載文件名,downloadFileName屬性
對應action類中的方法 getDownloadFileName() -->
<param name="contentDisposition">attachment;filename="${downloadFileName}"</param>
<param name="bufferSize">4096</param>
</result>
</action>


 查看粗斜體的部分,首先就是自定了被下載文件的路徑,inputPath,接着就是修改了contentType爲二進制方式。最後從新發布項目並運行,鍵入地址進行訪問:http://localhost:8080/struts2hello/download3.action 。很好,能夠看到文件下載對話框,保存系統說明.doc後再用word打開它,內容正確。

注意:而這種文件下載方式倒是存在安全隱患的,由於訪問者若是精通Struts 2的話,它可能使用這樣的帶有表單參數的地址來訪問:http://localhost:8080/struts2hello/download3.action?inputPath=/WEB-INF/web.xml,這樣的結果就是下載後的文件內容是您系統裏面的web.xml的文件的源代碼,甚至還能夠用這種方式來下載任何其它JSP文件的源碼。這對系統安全是個很大的威脅。做爲一種變通的方法,讀者最好是從數據庫中進行路徑配置,而後把Action類中的設置inputPath的方法通通去掉,簡言之就是刪除這個方法定義:

public void setInputPath(String value) {
inputPath = value;
}

 而實際狀況則應該成爲 download3.action?fileid=1相似於這樣的形式來進行。或者呢,讀者能夠在execute()方法中進行路徑檢查,若是發現有訪問不屬於download下面文件的代碼,就一概拒絕,不給他們返回文件內容。例如,咱們能夠把剛纔類中的execute()方法加以改進,成爲這樣:

public String execute() throws Exception {
// 文件下載目錄路徑
String downloadDir = ServletActionContext.getServletContext().getRealPath("/download");
// 文件下載路徑
String downloadFile = ServletActionContext.getServletContext().getRealPath(inputPath);
java.io.File file = new java.io.File(downloadFile);
downloadFile = file.getCanonicalPath();// 真實文件路徑,去掉裏面的..等信息
// 發現企圖下載不在 /download 下的文件, 就顯示空內容
if(!downloadFile.startsWith(downloadDir)) {
return null;
}
return SUCCESS;
}

這時候若是訪問者再企圖下載web.xml的內容,它只能獲得一個空白頁,如今訪問者只能下載位於/download目錄下的文件。

相關文章
相關標籤/搜索