Terry.Li-彬

          虛其心,可解天下之問(wèn);專其心,可治天下之學(xué);靜其心,可悟天下之理;恒其心,可成天下之業(yè)。

            BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            143 隨筆 :: 344 文章 :: 130 評(píng)論 :: 0 Trackbacks
          <2025年7月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          常用鏈接

          留言簿(19)

          隨筆分類(107)

          隨筆檔案(141)

          文章分類(284)

          文章檔案(342)

          相冊(cè)

          收藏夾(58)

          家裝

          最新隨筆

          搜索

          積分與排名

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          Struts 2中實(shí)現(xiàn)文件下載(修正中文問(wèn)題)

          BlogJava上已經(jīng)有一位作者闡述了文件上傳的問(wèn)題,地址是Struts 2中實(shí)現(xiàn)文件上傳,因此我就不再討論那個(gè)話題了。我今天簡(jiǎn)單介紹一下Struts 2的文件下載問(wèn)題。

          我們的項(xiàng)目名為 struts2hello,所使用的開(kāi)發(fā)環(huán)境是MyEclipse 6,當(dāng)然其實(shí)用哪個(gè)IDE都是一樣的,只要把類庫(kù)放進(jìn)去就行了,文件下載不需要再加入任何額外的包。讀者可以參考文檔:http://beansoft.java-cn.org/myeclipse_doc_cn/struts2_demo.pdf,來(lái)了解怎么下載和配置基本的Struts 2開(kāi)發(fā)環(huán)境。

          為了便于大家對(duì)比,我把完整的struts.xml的配置信息列出來(lái):

          <?xmlversion="1.0"encoding="UTF-8" ?>

          <!DOCTYPEstrutsPUBLIC

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

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

          <struts>

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

                  <!-- 在這里添加Action定義 -->

                  <!-- 簡(jiǎn)單文件下載 -->

                  <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>

                          <!-- 使用經(jīng)過(guò)轉(zhuǎn)碼的文件名作為下載文件名,downloadFileName屬性

                          對(duì)應(yīng)action類中的方法 getDownloadFileName() -->

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

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

                      </result>

                  </action>

                  

                  <!-- 下載現(xiàn)有文件 -->

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

                      <paramname="inputPath">/download/系統(tǒng)說(shuō)明.doc</param>

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

                      <paramname="fileName">系統(tǒng)說(shuō)明.doc</param>

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

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

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

                          <!-- 使用經(jīng)過(guò)轉(zhuǎn)碼的文件名作為下載文件名,downloadFileName屬性

                          對(duì)應(yīng)action類中的方法 getDownloadFileName() -->

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

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

                      </result>

                  </action>

                 

              </package>

          </struts>

          Struts 2中對(duì)文件下載做了直接的支持,相比起自己辛辛苦苦的設(shè)置種種HTTP頭來(lái)說(shuō),現(xiàn)在實(shí)現(xiàn)文件下載無(wú)疑要簡(jiǎn)便的多。說(shuō)起文件下載,最直接的方式恐怕是直接寫一個(gè)超鏈接,讓地址等于被下載的文件,例如:<a href=”file1.zip”>下載file1.zip</a>,之后用戶在瀏覽器里面點(diǎn)擊這個(gè)鏈接,就可以進(jìn)行下載了。但是它有一些缺陷,例如如果地址是一個(gè)圖片,那么瀏覽器會(huì)直接打開(kāi)它,而不是顯示保存文件的對(duì)話框。再比如如果文件名是中文的,它會(huì)顯示一堆URL編碼過(guò)的文件名例如%3457...。而假設(shè)你企圖這樣下載文件:http://localhost:8080/struts2hello/download/系統(tǒng)說(shuō)明.docTomcat會(huì)告訴你一個(gè)文件找不到的404錯(cuò)誤:HTTP Status 404 - /struts2hello/download/ϵͳ˵Ã÷.doc。雖然目前還沒(méi)發(fā)現(xiàn)直接配置Struts 2來(lái)正確的下載中文名字的附件,不過(guò)好在作者對(duì)JSP中的文件下載比較了解,因此我們另有辦法解決這個(gè)問(wèn)題。另外一個(gè)最大的用途,就是動(dòng)態(tài)的生成并下載文件了,例如動(dòng)態(tài)的下載生成的EXCELPDF,驗(yàn)證碼圖片等等。本節(jié)內(nèi)容就依次討論簡(jiǎn)單的下載文件代碼,下載中文附件,最后介紹如何下載已經(jīng)存在的文件。

          先說(shuō)文件下載,編寫一個(gè)普通的Action就可以了,只需要提供一個(gè)返回InputStream流的方法,該輸入流代表了被下載文件的入口,這個(gè)方法用來(lái)給被下載的數(shù)據(jù)提供輸入流,意思是從這個(gè)流讀出來(lái),再寫到瀏覽器那邊供下載。這個(gè)方法需要由開(kāi)發(fā)人員自己來(lái)編寫,只需要返回值為InputStream即可。在我們的例子中方法的簽名是:public InputStream getInputStream() throws Exception,當(dāng)然它也可以是別的名字,例如getDownloadFile()。好了,現(xiàn)在我們所寫的這個(gè)進(jìn)行文件下載的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(),在這個(gè)方法里面我們使用了一個(gè)數(shù)組輸入流來(lái)從字符串轉(zhuǎn)換成的數(shù)組作為數(shù)據(jù)的來(lái)源進(jìn)行讀取。也許方法體中使用這樣的實(shí)現(xiàn)代碼:

          return new java.io.FileInputStream(“c:""test.txt”);//從系統(tǒng)磁盤文件讀取數(shù)據(jù)

          這樣會(huì)更直觀一些。

          文件下載的第二步,乃是在struts.xml中對(duì)action進(jìn)行配置,其代碼清單如下所示:

          <!-- 簡(jiǎn)單文件下載 -->

          <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>

          。這個(gè)action特殊的地方在于result的類型是一個(gè)流(stream),配置stream類型的結(jié)果時(shí),因?yàn)闊o(wú)需指定實(shí)際的顯示的物理資源,所以無(wú)需指定location屬性,只需要指定inputName屬性,該屬性指向被下載文件的來(lái)源,對(duì)應(yīng)著Action類中的某個(gè)屬性,類型為InputStream。下面則列出了和下載有關(guān)的一些參數(shù)列表:

          參數(shù)

          說(shuō)明

          contentType

          內(nèi)容類型,和互聯(lián)網(wǎng)MIME標(biāo)準(zhǔn)中的規(guī)定類型一致,例如text/plain代表純文本,text/xml表示XMLimage/gif代表GIF圖片,image/jpeg代表JPG圖片

          inputName

          下載文件的來(lái)源流,對(duì)應(yīng)著action類中某個(gè)類型為Inputstream的屬性名,例如取值為inputStream的屬性需要編寫getInputStream()方法

          contentDisposition

          文件下載的處理方式,包括內(nèi)聯(lián)(inline)和附件(attachment)兩種方式,而附件方式會(huì)彈出文件保存對(duì)話框,否則瀏覽器會(huì)嘗試直接顯示文件。取值為:

          attachment;filename="struts2.txt",表示文件下載的時(shí)候保存的名字應(yīng)為struts2.txt。如果直接寫filename="struts2.txt",那么默認(rèn)情況是代表inline,瀏覽器會(huì)嘗試自動(dòng)打開(kāi)它,等價(jià)于這樣的寫法:inline; filename="struts2.txt"

          bufferSize

          下載緩沖區(qū)的大小

          。在這里面,contentType屬性和contentDisposition分別對(duì)應(yīng)著HTTP響應(yīng)中的頭Content-TypeContent-disposition頭。好,我們先來(lái)看看這個(gè)例子,發(fā)布運(yùn)行項(xiàng)目后鍵入測(cè)試地址:http://localhost:8080/struts2hello/download.action,將會(huì)看到瀏覽器彈出一個(gè)文件保存對(duì)話框,如圖12.12所示。

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

          如果此時(shí)使用某些工具來(lái)探測(cè)瀏覽器返回的HTTP頭,將會(huì)看到下列內(nèi)容:

          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

          。所以用來(lái)下載的action配置中,只有兩個(gè)是和瀏覽器有關(guān)的:contentTypecontentDisposition。關(guān)于contentType的取值,如果是未知的文件類型,或者說(shuō)出現(xiàn)了瀏覽器不能打開(kāi)的文件,例如.bean文件,或者說(shuō)這個(gè)action是用來(lái)做動(dòng)態(tài)文件下載的,事先并不知道未來(lái)的文件類型是什么,那么我們可以把它的值設(shè)置成為:application/octet-stream;charset=ISO8859-1,注意一定要加入charset,否則某些時(shí)候會(huì)導(dǎo)致下載的文件出錯(cuò);有人說(shuō)這時(shí)也可以設(shè)置成為application/x-download,根據(jù)筆者的實(shí)踐,這個(gè)頭也能正常工作,然而個(gè)別時(shí)候會(huì)出現(xiàn)瀏覽器無(wú)法識(shí)別的問(wèn)題。而contentDisposition,如果其取值是filename="struts2.txt",或者是inline; filename="struts2.txt",運(yùn)行后你可以看到瀏覽器直接顯示了文件的內(nèi)容:

          Struts 2 下載示例,而不再?gòu)棾鰧?duì)話框提示用戶保存文件到硬盤上。所以讀者如果想確保文件是被下載而不是被打開(kāi),務(wù)必使用格式attachment;filename="struts2.txt",不要丟了attachment;這個(gè)類型信息。

          至此,關(guān)于文件下載的技術(shù)內(nèi)容,已經(jīng)告一段落。然而做中文系統(tǒng),不可避免的要解決中文附件的下載問(wèn)題。關(guān)于這個(gè)內(nèi)容,也無(wú)權(quán)威的資料可查,我們只能用實(shí)踐中得到的解決方案來(lái)處理。也許有讀者以為將filename屬性設(shè)置為filename=”struts2中文.txt”就能解決問(wèn)題了,好,就來(lái)試試,把contentDisposition修改成:

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

          。再次鍵入地址進(jìn)行測(cè)試,看看顯示的結(jié)果,如圖12.13所示。唉,真是完全不給面子!IE壓根就不能顯示出來(lái)文件名,草草敷衍了download_action了事。Firefox稍好點(diǎn),還出來(lái)了一個(gè)對(duì)話框,但是很顯然,那個(gè)顯示的struts2--txt絕對(duì)不是我們?nèi)账家瓜氲?/span>struts2中文.txt。怎么辦?解決方法是有,那就是用ISO8859-1編碼來(lái)顯示這個(gè)中文字符,可以閱讀12.8參考資料一節(jié)中的JSP 文件下載的相對(duì)完整代碼(解決中文問(wèn)題和Weblogic報(bào)錯(cuò))這篇文章,可以這樣認(rèn)為,所有的文件下載代碼都是基于同樣的純Servlet的方式來(lái)進(jìn)行的。如果是Java代碼,我們可以這樣做:

          12.13 IEFirefox下的中文文件下載對(duì)話框

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

          然后把生成的結(jié)果字符串放到XML文件中就行了,然而它的輸出類似于struts2??.txt,是無(wú)法直接寫道我們的XML配置文件中的。所以,我們想到的的辦法,就是在Action類中寫一個(gè)方法來(lái)做轉(zhuǎn)碼,使它成為某個(gè)屬性,所以要以get開(kāi)頭。然后,再用12.3.8Action注入?yún)?shù)(param)值一節(jié)的內(nèi)容,將文件名以正常的方式設(shè)置為action類的某個(gè)屬性,最后呢,再利用一個(gè)小小的param參數(shù)取值中的伎倆:${屬性名},它可以直接從action類中動(dòng)態(tài)獲取某個(gè)屬性值。好了,現(xiàn)在讓我們來(lái)看看第二個(gè)文件下載類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;// 初始的通過(guò)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;

          }

          /** 提供轉(zhuǎn)換編碼后的供下載用的文件名 */

          public String getDownloadFileName() {

          String downFileName = fileName;

          try {

          downFileName = new String(downFileName.getBytes(), "ISO8859-1");

          } catch (UnsupportedEncodingException e) {

          e.printStackTrace();

          }

          return downFileName;

          }

          }

          。這個(gè)類有兩個(gè)屬性,第一個(gè)是fileName,它是需要被指定的下載文件名;第二個(gè)則是動(dòng)態(tài)的僅僅由getDownloadFileName()這個(gè)方法定義的屬性downloadFileName,它的值隨著fileName而動(dòng)態(tài)變動(dòng),僅僅是把它轉(zhuǎn)換成了ISO8859方式的西歐字符集。

          接下來(lái)就是如何配置這個(gè)action了,這是關(guān)鍵的地方所在,現(xiàn)在配置一個(gè)新的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>

          <!-- 使用經(jīng)過(guò)轉(zhuǎn)碼的文件名作為下載文件名,downloadFileName屬性

          對(duì)應(yīng)action類中的方法 getDownloadFileName() -->

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

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

          </result>

          </action>

          。其中特殊的代碼就是${downloadFileName},它的效果相當(dāng)于運(yùn)行的時(shí)候?qū)?/span>action對(duì)象的屬性的取值動(dòng)態(tài)的填充在${}中間的部分,我們可以認(rèn)為它等價(jià)于action. getDownloadFileName()

          好了,現(xiàn)在讓我們重新發(fā)布然后運(yùn)行這個(gè)項(xiàng)目,鍵入地址:

          http://localhost:8080/struts2hello/download2.action 進(jìn)行訪問(wèn),可以看到運(yùn)行結(jié)果完全正確,如圖12.14所示。

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

          在本節(jié)的最后部分,我們來(lái)討論一下如何下載已經(jīng)存在于當(dāng)前Web應(yīng)用目錄下的已經(jīng)存在的文件。一般的網(wǎng)站可能會(huì)把要下載的文件放在某個(gè)固定的目錄下,例如WebRoot/download,在這個(gè)子目錄下,我們放了一個(gè)名為系統(tǒng)說(shuō)明.doc的文件,希望最后我們的action能夠正確的下載這個(gè)文件。要檢驗(yàn)下載是否成功非常簡(jiǎn)單,文件內(nèi)容僅僅是粗體的系統(tǒng)說(shuō)明書這五個(gè)字,而word文件壞一個(gè)字節(jié)的話都是打不開(kāi)的,所以下載后再用word打開(kāi)即可檢驗(yàn)是否成功。現(xiàn)在我們創(chuàng)建第三個(gè)文件下載的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;// 初始的通過(guò)param指定的文件名屬性

          private String inputPath;// 指定要被下載的文件路徑

          public InputStream getInputStream() throws Exception {

          // 通過(guò) ServletContext,也就是application 來(lái)讀取數(shù)據(jù)

          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;

          }

          /** 提供轉(zhuǎn)換編碼后的供下載用的文件名 */

          public String getDownloadFileName() {

          String downFileName = fileName;

          try {

          downFileName = new String(downFileName.getBytes(), "ISO8859-1");

          } catch (UnsupportedEncodingException e) {

          e.printStackTrace();

          }

          return downFileName;

          }

          }

          。代碼中被改動(dòng)的部分已經(jīng)用粗斜體的方式顯示出來(lái)了。首先是新加入了一個(gè)名為inputPath的屬性,用來(lái)制定被下載文件的路徑。接著就是ServletActionContext.getServletContext()這段代碼,它的意義我們將在12.6節(jié)詳細(xì)討論,在這里讀者只需要知道它獲取了當(dāng)前Servlet容器的ServletContext,也就是大家常說(shuō)的jsp中的application對(duì)象,然后用它來(lái)打開(kāi)文件的輸入流。

          接著要做的就是配置action,它和剛剛配置過(guò)的download2的內(nèi)容差不多,只是多了一個(gè)被下載的資源的路徑屬性。現(xiàn)在我們?cè)?/span>struts.xml中加入這個(gè)新的action定義:

          <!-- 下載現(xiàn)有文件 -->

          <action name="download3" class="example.FileDownloadAction3">

          <param name="inputPath">/download/系統(tǒng)說(shuō)明.doc</param>

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

          <param name="fileName">系統(tǒng)說(shuō)明.doc</param>

          <result name="success" type="stream">

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

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

          <!-- 使用經(jīng)過(guò)轉(zhuǎn)碼的文件名作為下載文件名,downloadFileName屬性

          對(duì)應(yīng)action類中的方法 getDownloadFileName() -->

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

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

          </result>

          </action>

          。查看粗斜體的部分,首先就是自定了被下載文件的路徑,inputPath,接著就是修改了contentType為二進(jìn)制方式。最后重新發(fā)布項(xiàng)目并運(yùn)行,鍵入地址進(jìn)行訪問(wèn):http://localhost:8080/struts2hello/download3.action 。很好,可以看到文件下載對(duì)話框,保存系統(tǒng)說(shuō)明.doc后再用word打開(kāi)它,內(nèi)容正確。

          注意:而這種文件下載方式卻是存在安全隱患的,因?yàn)樵L問(wèn)者如果精通Struts 2的話,它可能使用這樣的帶有表單參數(shù)的地址來(lái)訪問(wèn):http://localhost:8080/struts2hello/download3.action?inputPath=/WEB-INF/web.xml,這樣的結(jié)果就是下載后的文件內(nèi)容是您系統(tǒng)里面的web.xml的文件的源代碼,甚至還可以用這種方式來(lái)下載任何其它JSP文件的源碼。這對(duì)系統(tǒng)安全是個(gè)很大的威脅。作為一種變通的方法,讀者最好是從數(shù)據(jù)庫(kù)中進(jìn)行路徑配置,然后把Action類中的設(shè)置inputPath的方法統(tǒng)統(tǒng)去掉,簡(jiǎn)言之就是刪除這個(gè)方法定義:

          public void setInputPath(String value) {

          inputPath = value;

          }

          。而實(shí)際情況則應(yīng)該成為 download3.action?fileid=1類似于這樣的形式來(lái)進(jìn)行。或者呢,讀者可以在execute()方法中進(jìn)行路徑檢查,如果發(fā)現(xiàn)有訪問(wèn)不屬于download下面文件的代碼,就一律拒絕,不給他們返回文件內(nèi)容。例如,我們可以把剛才類中的execute()方法加以改進(jìn),成為這樣:

          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();// 真實(shí)文件路徑,去掉里面的..等信息

          // 發(fā)現(xiàn)企圖下載不在 /download 下的文件, 就顯示空內(nèi)容

          if(!downloadFile.startsWith(downloadDir)) {

          return null;

          }

          return SUCCESS;

          }

          。這時(shí)候如果訪問(wèn)者再企圖下載web.xml的內(nèi)容,它只能得到一個(gè)空白頁(yè),現(xiàn)在訪問(wèn)者只能下載位于/download目錄下的文件。

          posted on 2008-12-18 08:43 禮物 閱讀(2081) 評(píng)論(0)  編輯  收藏 所屬分類: struts2.0
          主站蜘蛛池模板: 盘锦市| 长乐市| 潼关县| 浪卡子县| 常宁市| 怀集县| 凤山县| 伊宁县| 琼中| 大兴区| 历史| 宣城市| 林周县| 泸西县| 大冶市| 南开区| 彰化市| 法库县| 阜新市| 武乡县| 宁陕县| 古蔺县| 都匀市| 茂名市| 荆门市| 宁安市| 奉贤区| 银川市| 博爱县| 闽清县| 洪雅县| 尚义县| 灵宝市| 巧家县| 和顺县| 承德县| 五大连池市| 沈阳市| 嘉荫县| 射阳县| 太仓市|