织梦网站后台一键更新没反应,自己免费建站平台推荐,四川建设招投标网站,wordpress 提示插件安装插件我今天简单介绍一下Struts 2的文件下载问题。
我们的项目名为 struts2hello#xff0c;所使用的开发环境是MyEclipse 6#xff0c;当然其实用哪个IDE都是一样的#xff0c;
只要把类库放进去就行了#xff0c;文件下载不需要再加入任何额外的包。读者可以参考文档#x…
我今天简单介绍一下Struts 2的文件下载问题。
我们的项目名为 struts2hello所使用的开发环境是MyEclipse 6当然其实用哪个IDE都是一样的
只要把类库放进去就行了文件下载不需要再加入任何额外的包。读者可以参考文档
[url]http://beansoft.java-cn.org/myeclipse_doc_cn/struts2_demo.pdf[/url]来了解怎么下载
和配置基本的Struts 2开发环境。 为了便于大家对比我把完整的struts.xml的配置信息列出来 ?xml version1.0 encodingUTF-8 ?
!DOCTYPE struts PUBLIC -//Apache Software Foundation//DTD Struts Configuration 2.0//EN http://struts.apache.org/dtds/struts-2.0.dtd
struts package namedefault extendsstruts-default !-- 在这里添加Action定义 -- !-- 简单文件下载 -- action namedownload classexample.FileDownloadAction result namesuccess typestream param namecontentTypetext/plain/param param nameinputNameinputStream/param param namecontentDispositionattachment;filenamestruts2中文.txt/param param namebufferSize4096/param /result /action !-- 文件下载支持中文附件名 -- action namedownload2 classexample.FileDownloadAction2 !-- 初始文件名 由此可以知道在struts2请求的时候也可以通过配置文件传入参数值-- param namefileNameStruts中文附件.txt/param result namesuccess typestream param namecontentTypetext/plain/param param nameinputNameinputStream/param !-- 使用经过转码的文件名作为下载文件名downloadFileName属性 对应action类中的方法 getDownloadFileName() -- param namecontentDispositionattachment;filename${downloadFileName}/param param namebufferSize4096/param /result /action !-- 下载现有文件 -- action namedownload3 classexample.FileDownloadAction3 !-- 初始文件名 由此可以知道在struts2请求的时候也可以通过配置文件传入参数值-- param nameinputPath/download/系统说明.doc/param !-- 初始文件名 -- param namefileName系统说明.doc/param result namesuccess typestream param namecontentTypeapplication/octet-stream;charsetISO8859-1/param param nameinputNameinputStream/param !-- 使用经过转码的文件名作为下载文件名downloadFileName属性 对应action类中的方法 getDownloadFileName() -- param namecontentDispositionattachment;filename${downloadFileName}/param param namebufferSize4096/param /result /action /package
/struts Struts 2中对文件下载做了直接的支持相比起自己辛辛苦苦的设置种种HTTP头来说现在实现文件下载无疑要简便的多。
说起文件下载最直接的方式恐怕是直接写一个超链接让地址等于被下载的文件例如a href”file1.zip”下载
file1.zip/a之后用户在浏览器里面点击这个链接就可以进行下载了。但是它有一些缺陷例如如果地址是一个图片
那么浏览器会直接打开它而不是显示保存文件的对话框。再比如如果文件名是中文的它会显示一堆URL编码过的文件名
例如%3457...。而假设你企图这样下载文件[url]http://localhost:8080/struts2hello/download/[/url]系统说明.doc
Tomcat会告诉你一个文件找不到的404错误HTTP Status 404 - /struts2hello/download/?μí3?μ?÷.doc。虽然目前还没发
现直接配置Struts 2来正确的下载中文名字的附件不过好在作者对JSP中的文件下载比较了解因此我们另有办法解决这个
问题。另外一个最大的用途就是动态的生成并下载文件了例如动态的下载生成的EXCELPDF验证码图片等等。本节内
容就依次讨论简单的下载文件代码下载中文附件最后介绍如何下载已经存在的文件。 先说文件下载编写一个普通的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 namedownload classexample.FileDownloadAction result namesuccess typestream param namecontentTypetext/plain/param param nameinputNameinputStream/param param namecontentDispositionattachment;filenamestruts2.txt/param param namebufferSize4096/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;filenamestruts2.txt表示文件下载的时候保存的名字应为struts2.txt。如果直接写filename
struts2.txt那么默认情况是代表inline浏览器会尝试自动打开它等价于这样的写法inline; filenamestruts2.txt 。 bufferSize
下载缓冲区的大小。在这里面contentType属性和contentDisposition分别对应着HTTP响应中的头Content-Type和Content-disposition头。
好我们先来看看这个例子发布运行项目后键入测试地址[url]http://localhost:8080/struts2hello/download.action[/url]将会看
到浏览器弹出一个文件保存对话框。 如果此时使用某些工具来探测浏览器返回的HTTP头将会看到下列内容 HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-disposition: attachment;filenamestruts2.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;charsetISO8859-1 注意一定要加入charset否则某些时候会导致下载的文件
出错有人说这时也可以设置成为application/x-download根据笔者的实践这个头也能正常工作然而个别时候会出现浏览器无法识别的问题。
而contentDisposition如果其取值是filenamestruts2.txt或者是inline; filenamestruts2.txt运行后你可以看到浏览器直接显示了
文件的内容Struts 2 下载示例而不再弹出对话框提示用户保存文件到硬盘上。所以读者如果想确保文件是被下载而不是被打开务必使用格
式attachment;filenamestruts2.txt不要丢了attachment;这个类型信息。 至此关于文件下载的技术内容已经告一段落。然而做中文系统不可避免的要解决中文附件的下载问题。关于这个内容也无权威的资料可查
我们只能用实践中得到的解决方案来处理。也许有读者以为将filename属性设置为filename”struts2中文.txt”就能解决问题了好就来试试
把contentDisposition修改成 param namecontentDispositionattachment;filenamestruts2中文.txt/param 再次键入地址进行测试看看显示的结果如图12.13所示。唉真是完全不给面子IE压根就不能显示出来文件名草草敷衍了download_action
了事。Firefox稍好点还出来了一个对话框但是很显然那个显示的struts2--txt绝对不是我们日思夜想的struts2中文.txt。怎么办解决方
法是有那就是用ISO8859-1编码来显示这个中文字符可以阅读12.8参考资料一节中的JSP 文件下载的相对完整代码(解决中文问题和Weblogic报错)
这篇文章可以这样认为所有的文件下载代码都是基于同样的纯Servlet的方式来进行的。如果是Java代码我们可以这样做 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 namedownload2 classexample.FileDownloadAction2 !-- 初始文件名 -- param namefileNameStruts中文附件.txt/param result namesuccess typestream param namecontentTypetext/plain/param param nameinputNameinputStream/param !-- 使用经过转码的文件名作为下载文件名downloadFileName属性 对应action类中的方法 getDownloadFileName() -- param namecontentDispositionattachment;filename${downloadFileName}/param param namebufferSize4096/param /result
/action 。其中特殊的代码就是${downloadFileName}它的效果相当于运行的时候将action对象的属性的取值动态的填充在${}中间的部分我们可以认为它
等价于action. getDownloadFileName()。 好了现在让我们重新发布然后运行这个项目键入地址
[url]http://localhost:8080/struts2hello/download2.action[/url] 进行访问可以看到运行结果完全正确。在本节的最后部分我们来讨论一下如何下载已经存在于当前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 namedownload3 classexample.FileDownloadAction3 param nameinputPath/download/系统说明.doc/param !-- 初始文件名 -- param namefileName系统说明.doc/param result namesuccess typestream param namecontentTypeapplication/octet-stream;charsetISO8859-1/param param nameinputNameinputStream/param !-- 使用经过转码的文件名作为下载文件名downloadFileName属性 对应action类中的方法 getDownloadFileName() -- param namecontentDispositionattachment;filename${downloadFileName}/param param namebufferSize4096/param /result
/action
。查看粗斜体的部分首先就是自定了被下载文件的路径inputPath接着就是修改了contentType为二进制方式。最后重新发布项目并运行键入地址进行访问
[url]http://localhost:8080/struts2hello/download3.action[/url] 。很好可以看到文件下载对话框保存系统说明.doc后再用word打开它内容正确。
注意而这种文件下载方式却是存在安全隐患的因为访问者如果精通Struts 2的话它可能使用这样的带有表单参数的地址来访问
[url]http://localhost:8080/struts2hello/download3.action?inputPath/WEB-INF/web.xml[/url]这样的结果就是下载后的文件内容是您系统里面的web.xml
的文件的源代码甚至还可以用这种方式来下载任何其它JSP文件的源码。这对系统安全是个很大的威胁。作为一种变通的方法读者最好是从数据库中进行路径配置
然后把Action类中的设置inputPath的方法统统去掉简言之就是删除这个方法定义 public void setInputPath(String value) { inputPath value;
} 而实际情况则应该成为 download3.action?fileid1 类似于这样的形式来进行。或者呢读者可以在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目录下的文件。