在BlogJava上已经有一位作者阐述了文件上传的问题,地址是在Struts 2中实现文件上传,因此我就不再讨论那个话题了。我今天简单介绍一下Struts 2的文件下载问题。
我们的项目名为 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-Type和Content-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配置中,只有两个是和浏览器有关的:contentType和contentDisposition。关于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目录下的文件。
我们的项目名为 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-Type和Content-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配置中,只有两个是和浏览器有关的:contentType和contentDisposition。关于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.xml`配置文件中定义一个Action,这个Action将会处理文件下载的请求。例如,我们可以创建一个名为`...
在这个“Struts2实现文件下载功能”的示例中,我们将深入探讨如何利用Struts2框架来实现在web应用中让用户下载文件的功能。 首先,我们需要理解文件下载的基本原理。在Web应用中,当用户点击一个链接或提交一个表单...
以下将详细介绍如何在Struts 2中实现文件下载,特别是处理中文文件名的问题。 首先,你需要创建一个Action类,例如`FileDownloadAction`,这个类需要包含一个方法来提供文件的输入流。通常,你会有一个属性如`...
本篇文章将详细探讨如何在Struts2框架下实现文件的上传与下载。 首先,我们需要了解Struts2中的文件上传机制。Struts2提供了`FileUploadInterceptor`拦截器来处理文件上传请求。在处理文件上传时,开发者需要在...
网上的Struts2进行的文件下载一般都是单文件或者固定的文件,并没有(很少)实现随意文件的下载的例子 提供多文件上传,上传成功后,提供刚上传的文件下载功能(其他的都可以在其上面进行扩充) 多文件 上传 下载...
本篇文章将详细讲解如何利用Struts2.2和Hibernate3.6实现文件的上传与动态下载。 **一、文件上传** 1. **环境配置**:首先,你需要一个集成开发环境,例如MyEclipse8.6,并安装所需的Struts2.21、JUnit4.8.2以及...
在处理文件上传和下载时,Struts2可能会遇到一个常见的问题,即中文文件名的乱码问题。这是因为不同的系统和软件对字符编码的支持不同,尤其是涉及到网络传输时,编码的兼容性尤为重要。 在“struts2 中文文件名...
描述中的链接指向了CSDN博主johnjobs的一篇文章,这篇文章详细解释了如何在Struts2中实现文件上传。博主可能讨论了以下关键点: 1. **配置Struts2 Action**:在`struts.xml`配置文件中,你需要定义一个Action,该...
文件下载在Struts2中可以通过多种方式实现,其中一种是直接在Action中处理。Action可以读取文件内容并将其设置到模型对象中,然后在JSP页面上使用`<s:property>`标签输出,这样浏览器就会根据Content-Type启动下载。...
在Struts2中实现文件下载的功能,可以帮助用户从服务器获取并保存文件到本地计算机,例如这里的"1.xls"文件。下面我们将深入探讨如何在Struts2框架下实现文件下载的实例。 首先,我们需要在项目的Web应用目录结构中...
在本项目中,我们关注的是如何使用Struts2来实现实时的文件交互操作,即文件的上传与下载。 文件上传在Web应用中很常见,例如用户提交表单时可能需要附加文件,如简历、图片或文档等。Struts2提供了一套完整的机制...
在本文中,我们将深入探讨如何使用Struts2框架实现文件下载功能,尤其是处理包含中文名字的附件。Struts2是一个流行的Java Web开发框架,它提供了丰富的功能,包括处理HTTP请求、表单提交以及数据绑定等。对于文件...
在Struts中实现文件下载是一项常见的功能,尤其在处理用户请求获取服务器上的静态或动态资源时。这篇博文将深入探讨如何在Struts框架下实现文件下载的功能。 首先,我们需要了解文件下载的基本原理。当用户点击下载...
在这个特定的场景中,我们关注的是如何使用Struts来实现文件的上传和下载功能。这个功能对于任何Web应用来说都是非常重要的,因为它允许用户交互地处理数据和资源。 首先,我们需要理解文件上传的基本流程。在...
本案例"Struts2之struts012"将深入探讨如何在Struts2中实现文件下载的功能。 首先,我们需要创建一个Action类,它是Struts2的核心组件,用于处理用户的请求。在Action类中,我们将定义一个方法,该方法负责获取要...
在这个“Struts2实现文件上传”的主题中,我们将深入探讨如何利用Struts2框架来实现在Web应用中的文件上传功能。 首先,我们注意到一个细节描述:“private String uploadContextType;应更正为private String ...
Struts2 Uploadify是一个在Java Web开发中常用的插件,它结合了Struts2框架和Uploadify jQuery插件,能够实现文件的多选、上传进度显示以及后台处理等功能。这个项目示例提供了一个完整的解决方案,使得用户在上传多...
通过上述步骤,我们就可以在Struts2框架下实现文件流方式的下载功能,无论是Excel、TXT还是图片,都能有效地直接返回给浏览器,让客户端进行下载。这种方式既节省了服务器资源,也提高了用户体验。在实际项目中,...
在标题提到的场景中,开发者可能遇到了在使用Myeclipse 6.5集成开发环境,JDK1.6和Tomcat 6.0服务器环境下,通过Struts2进行文件下载时,中文文件名显示为乱码的问题。 中文乱码是由于字符编码不一致导致的,尤其是...
Struts2是一个强大的Java web框架,它为开发者提供了丰富的功能,包括文件上传和下载。...以上就是Struts2中文件上传和下载的基本实现步骤和相关知识点。在实际开发中,可能还需要根据具体需求进行相应的优化和扩展。