`
baobeituping
  • 浏览: 1040825 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
社区版块
存档分类
最新评论

struts2 配置下载文件解决中文文件名的问题

 
阅读更多

我们的项目名为 struts2hello ,所使用的开发环境是MyEclipse 6,当然其实用哪个IDE都是一样的,只要把类库放进去就行了,文件下载不需要再加入任何额外的包。读者可以参考文档:http://beansoft.java-cn.org/myeclipse_doc_cn/struts2_demo.pdf ,来了解怎么下载和配置基本的Struts 2开发环境。

 

为了便于大家对比,我把完整的struts.xml的配置信息列出来:

<?
xml
 version
="1.0"
 encoding
="UTF-8"
 ?>

<!
DOCTYPE
 struts
 PUBLIC

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

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


<
struts
>

    <
package
 name
="default"
 extends
="struts-default"
  >

        <!-- 在这里添加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
 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
>

        
        <!-- 下载现有文件 -->

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

        
    </
package
>


</
struts
>

 

Struts 2中对文件下载做了直接的支持,相比起自己辛辛苦苦的设置种种HTTP头来说,现在实现文件下载无疑要简便的多。说起文件下载,最直接的方式恐怕是直接写一个超链接,让地址等于被下载的文件,例如:<a href=”file1.zip”> 下载file1.zip</a> ,之后用户在浏览器里面点击这个链接,就可以进行下载了。但是它有一些缺陷,例如如果地址是一个图片,那么浏览器会直接打开它,而不是显示保存文件的对话框。再比如如果文件名是中文的,它会显示一堆URL编码过的文件名例如%3457...。而假设你企图这样下载文件:http://localhost:8080/struts2hello/download/系统说明.doc ,Tomcat会告诉你一个文件找不到的404错误:HTTP Status 404 - /struts2hello/download/ϵͳ˵Ã÷.doc 。 虽然目前还没发现直接配置Struts 2来正确的下载中文名字的附件,不过好在作者对JSP中的文件下载比较了解,因此我们另有办法解决这个问题。另外一个最大的用途,就是动态的生成并下载文 件了,例如动态的下载生成的EXCEL,PDF,验证码图片等等。本节内容就依次讨论简单的下载文件代码,下载中文附件,最后介绍如何下载已经存在的文 件。

先说文件下载,编写一个普通的Action就可以了,只需要提供一个返回InputStream流的方法,该输入流代表了被下载文件的入口,这个方 法用来给被下载的数据提供输入流,意思是从这个流读出来,再写到浏览器那边供下载。这个方法需要由开发人员自己来编写,只需要返回值为 InputStream即可。在我们的例子中方法的签名是:public InputStream getInputStream() throws Exception ,当然它也可以是别的名字,例如getDownloadFile() 。好了,现在我们所写的这个进行文件下载的Action类example.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表示XML,image/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所示。

clip_image002

clip_image004

图12.12 文件下载对话框(IE 7和Firefox 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代码,我们可以这样做:

clip_image006

clip_image008

图12.13 IE和Firefox下的中文文件下载对话框

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

然后把生成的结果字符串放到XML文件中就行了,然而它的输出类似于struts2??.txt ,是无法直接写道我们的XML配置文件中的。所以,我们想到的的办法,就是在Action类中写一个方法来做转码,使它成为某个属性,所以要以get开头。然后,再用12.3.8 给Action 注入参数(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所示。

clip_image010

clip_image012

图 12.14 正确显示了文件下载名的对话框(IE和Firefox)

在本节的最后部分,我们来讨论一下如何下载已经存在于当前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 目录下的文件。

分享到:
评论

相关推荐

    Struts2属性文件详解

    该属性指定Struts 2框架默认加载的配置文件,如果需要指定默认加载多个配置文件,则多个配置文件的文件名之间以英文逗号(,)隔开.该属性的默认值为struts- default.xml,struts-plugin.xml,struts.xml,看到该属性值,所以...

    ajax_struts2_上传与下载_无刷新

    4. 批量导入数据采用的是导入test文件夹下的测试压缩包upload.rar上传到服务器的临时目录,然后利用WinRar.exe对其进行解压,解压后对指定文件名的Excel文件进行解析,然后对每条记录所配置的文件路径进行比对然后...

    02_张孝祥09年Struts视频_文件上传单元

    04.用struts获取上传文件名和解决中文乱码 05.保存用struts获取的上传文件内容 06.分析文件上传时的一个安全问题 07.分析高质量项目该如何处理文件上传 08.处理不上传文件时的情况 09.在配置文件中配置上传文件的...

    11_张孝祥09年Struts视频_文件上传单元

    04.用struts获取上传文件名和解决中文乱码 05.保存用struts获取的上传文件内容 06.分析文件上传时的一个安全问题 07.分析高质量项目该如何处理文件上传 08.处理不上传文件时的情况 09.在配置文件中配置上传文件的...

    struts1的文件上传

    文件上传、多文件上传,文件名 乱码,上传文件名的重名都在此工程中有具体的解决 此工程中为了上传容易,删除了jar包,要使的工程运行,只要加入struts-1.2.9的jar包就可以了

    Struts2\constant应用

    该属性指定Struts 2框架默认加载的配置文件,如果需要指定默认加载多个配置文件,则多个配置文件的文件名之间以英文逗号(,)隔开。 该属性的默认值为struts-default.xml,struts-plugin.xml,struts.xml,看到该属性...

    struts 文件的上传下载 简要实现 下载过程传递中文实现

    这是个Strtus对上传下载功能的简要实现 参考了Strtus2GA包附带源码关于上传下载的程序 在Strtus.xml中配置路径和文件名,页面直接获取 下载过程对中文的处理方式

    SpringStrutsHibernate的配置

    Servlet配置文件(一个配置文件) Servlet必须在:项目名\WebRoot\WEB-INF这个目录下面的web.xml文件进行配置。改变web.xml的默认文件名:(一般默认引入的文件名,我们不改) ...web.xml文件配置: ...

    struts-上传文件

    //封装文件标题请求参数的属性 private String title; //封装上传文件域的属性 private File upload; //封装上传文件类型的属性 private String uploadContentType;... //直接在struts.xml文件中配置的属性

    ssh2(struts2+spring2.5+hibernate3.3)自动生成模版

    4个xml文件(applicationContext-dao.xml(dao注入配置),applicationContext-service.xml(service注入配置),action-servlet.xml(action注入配置),struts-{自定义的存放包名}.xml(struts2的action配置文件)) ...

    JSP图书管理系统myeclipse开发sql数据库struts2框架java编程mvc结构

    JSP struts2 图书管理系统 是一套完善的web设计系统,对理解JSP java编程开发语言有帮助(MVC模式 struts+dao+bean),系统具有完整的源代码和数据库,系统主要采用B/S模式开发。 二、功能介绍 (1)权限管理:对权限...

    JSP struts2工资管理系统myeclipse开发oracle数据库bs框架java编程网结构

    JSP 工资管理系统是一套完善的struts2设计系统MVC结构,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,开发环境为TOMCAT7.0,Myeclipse8.5开发,数据 库为Oracle10g,使用java语言开发,系统...

    JSp城市生活信息收集发布网myeclipse开发sqlserver2008数据库BS模式java编程网页结构struts2

    JSP城市生活信息收集发布网是一套完善的web设计系统,对理解JSP java编程开发语言有帮助 struts2 dao+bean mvc模式,系统具有完整的源代码和数据库,开发环境为TOMCAT7.0,Myeclipse8.5开发,数据库 为sqlserver...

    poi+struts的excel导入action

    poi+struts的excel导入action,加入包,配置文件就可以用

    ssh(structs,spring,hibernate)框架中的上传下载

    WEB-INF下的applicationContext.xml为Spring的配置文件,struts-config.xml为Struts的配置文件,file-upload.jsp为文件上传页面,file-list.jsp为文件列表页面。  本文后面的章节将从数据持久层->业务层->Web层的...

    JSP贸易管理系统myeclipse开发mysql数据库struts编程java语言

    JSP贸易管理系统 是一套完善的web设计系统,系统采用struts2框架进行开发一套源码,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库 系统主要采用B/S模式开发。 二、功能介绍 1.管理员管理 (1)...

    JSP仓库进销存系统ssh框架mysql数据库myeclipse开发mvc结构java

    JSP 仓库进销存系统(struts2+hibernate) 是一套完善的web设计系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。 二、功能介绍 (1)权限管理:对权限信息进行...

    好用的代码生成源码

    配置简单,只有一个配置文件generator.properties 生成器的运行 在eclipse中运行 配置classpath,将generator/lib中的rapid-generator.jar及其它数据库驱动加入classpath 修改generator.xml的数据库连接属性及其它属性...

    JAVA上百实例源码以及开源项目

    2个目标文件,FTP的目标是:(1)提高文件的共享性(计算机程序和/或数据),(2)鼓励间接地(通过程序)使用远程计算机,(3)保护用户因主机之间的文件存储系统导致的变化,(4)为了可靠和高效地传输,虽然用户...

    haivDb4J2ee1.3

     1、能通过表名、列名、列类型、表和列的注释等信息,自动生成PO类、Hibernate、VO类、struts+spring+hibernate的配置文件、Jsp文件、使用说明等文档,以实现对单表的增、删、改、查的操作。生成的代码非常规范,...

Global site tag (gtag.js) - Google Analytics