文件的上传和下载 |
发布: 2010-03-20
返回
|
1,文件上传和下载的简单知识说明
文件上传和下载是WEB应用经常需要面对的问题,在大部分的时候用户请求的参数是在表单域输入的字符串,但如果为表单元素设置enctype="multipart/form-data"属性,则提交表单时不再以字符串方式提交请求参数,而是以二进制编码的方式提交请求,此时可以直接通过HttpServletRequest的getParameter方法无法正常获取请求参数,我们可以通过二进制流来获取请求内容--通过这种方式,就可以取得希望上传文件的内容,从而实现文件的上传。 上面的这种方式编码很麻烦,JAVA领域有两个常用的文件上传项目:Common-FileUpload和COS,我接下来会分别介绍如何使用它们,Struts2在原有的文件上传项目上进行了进一步的封装,实现更为简单。同时Struts2还提供了对文件下载支持的stream的结果类型,通过struts2的支持,可以实现非西欧字符文件名的文件下载。并可以在下载前检查用户的权限。从而通过授权控制来控制文件的下载。 2,文件上传的原理
表单的enctype属性指定的是表单数据的编码方式,该属性有如下3个值:
1,application/x-www-form-urlencoded:这是默认的编码方式,它只处理表单域里的value值,采用这种编码方式的表单会将表单域的值处理成URL编码方式。
2,multipart/form-data:这种编码方式会以二进制流的方式来处理表单数据,它把文件域指定文件的内容也封装到请求参数里。一旦设置了这种方式,就无法通过HttpServletRequest.getParameter()访求获取请求参数。
3,text/plain:这种编码方式当表单的action属性为mailto:URL的形式时比较方便,这种方式主要适用于直接通过表单发送邮件的方式。
3,手动文件上传
1,HTML页面代码如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <!-- 设置表单的enctype属性为multipart/form-data --> <form action="pro.jsp" id="form1" name="form1" enctype="multipart/form-data" method="post"> 上传文件:<input type="file" name="file" /><br> 请求参数:<input type="text" name="wawa" /><br> <input type="submit" name="dd" value="提交" />
</form> P> </body> </html>
2,JAVA处理,我这里直接用JSP页面进行处理。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ page import="java.io.*" %> <% //取得HttpServletRequest的InputStream输入流 InputStream is = request.getInputStream(); //以InputStream输入流为基础,建立一个BufferedReader对象 BufferedReader br = new BufferedReader(new InputStreamReader(is)); String buffer = null; //循环读取请求内容的每一行内容 while((buffer = br.readLine()) != null){ //如果读到的内容以-------------开始,且以--结束,表明忆到请求内容的结尾 if(buffer.endsWith("--")&&buffer.startsWith("---------")){ //跳出循环 break; } //如果读到的内容以-------开始,表明开始了一个表单域 if(buffer.startsWith("-------")){ //如查下一行内容中有filename字符串,表明这是一个文件域 if(br.readLine().indexOf("filename") > 1){ //跳过两行,开始处理上传的文件内容 br.readLine(); br.readLine(); //以系统时间为文件名,创建一个新文件 File file = new File(request.getRealPath("/") + System.currentTimeMillis()); //创建文件输出流 PrintStream ps = new PrintStream(new FileOutputStream(file)); //接着开始读取文件内容 String content = null; while((content = br.readLine()) != null){ //如果读取的内容以-----开始,表明开始了下一个表单域内容 if(content.startsWith("------")){ //跳出处理 break; } //否则,将读到的内容输出到文件中 ps.println(content); } //关闭输出流 ps.flush(); ps.close(); } } } //关闭输入流 br.close();
%>
这里只是一个最简单的处理,我是根据HTML页面上传后的request读出的上传输入流的内容,来设计以上代码的,上传后的文件存放在WEB应用的根目录下,并且没有设置文件的后缀名。而且因为用的是Reader字符流处理上传,所以只能处理文本文件的上传。
4,使用框架来完成上传
对于JAVA应用而言,最常用的上传框架有两个:Common-FileUpload和COS,不管使用哪个上传框架,它都负责解析出HttpServletRequest请求中的所有域--不管是文件域还是普通表单域。
一旦通过上传框架获得了文件域对应的文件内容,我们就可以通过IO流将文件内容写入服务器的任意位置,从而完成文件上传。
1,Common-FileUpload框架的上传
Common-FileUpload框架是apache的一个项目。
A,准备工作:首先在http://commons.apache.org/fileupload/上下载common fileupload框架,目前最新的版本是1.2.1,下面就是用的这个版本。将commons-fileupload-1.2.1\lib\commons-fileupload-1.2.1.jar文件复制到我们项目的WEB-INF\lib目录下,因为它还需要common io项目的支持,因此我们还要去http://commons.apache.org/io/这里下载common io,我用的是最新的1.4版本。将commons-io-1.4-bin\commons-io-1.4\commons-io-1.4.jar文件复制到我们项目的WEB-INF\lib目录下。
B,编写代码:
HTML页面代码不变,JSP页面的代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ page import="java.io.*" %> <%@ page import="java.util.*" %> <%@ page import="org.apache.commons.fileupload.disk.*,org.apache.commons.fileupload.*" %> <%@ page import="org.apache.commons.fileupload.servlet.*" %> <% request.setCharacterEncoding("UTF-8"); DiskFileItemFactory factory = new DiskFileItemFactory(); //设置上传工厂的限制 factory.setSizeThreshold(1024 * 1024 * 20); factory.setRepository(new File(request.getRealPath("/"))); //创建一个上传文件的ServletFileUpload对象 ServletFileUpload upload = new ServletFileUpload(factory); //设置最大的上传限制 upload.setSizeMax(1024 * 1024 * 20); //处理HTTP请求,items是所有的表单项 List items = upload.parseRequest(request); //遍历所有的表单项 for(Iterator it = items.iterator();it.hasNext();){ FileItem item = (FileItem) it.next(); if(item.isFormField()){ String name = item.getFieldName(); String value = item.getString("GBK"); out.println("表单域的name=value对为:" + name + "=" + value); }else{ //取得文件域的表单域名 String fieldName = item.getFieldName(); //取得文件名 String fileName = item.getName(); //取得文件类型 String contentType = item.getContentType(); out.println("表单域的name=value对为:" + fieldName + "=" + fileName); //以原文件名作为新的文件名 FileOutputStream fos = new FileOutputStream(request.getRealPath("/") + fileName); //如果上传文件域对应文件的内容已经在内存中 if(item.isInMemory()){ fos.write(item.get()); }else{ //获取上传文件内容的输入流 InputStream is = item.getInputStream(); byte[] buffer = new byte[1024]; int len; //读取上传文件的内容,并将其写入服务器的文件中 while((len = is.read(buffer)) > 0){ fos.write(buffer,0,len); } //关闭输入流和输出流 is.close(); fos.close(); } } } %>
这里将上传文件写到WEB应用程序的根目录下,文件名跟上传文件的文件名一致。
从以上代码可以看到,借助common file upload框架,我们可以很容易地获取请求中的各个表单域,不管是文件域还是普通表单域。
2,COS框架的上传
COS框架是oreilly下的一个小项目,该项目同样将multipart/form-data类型请求中的各种表单域解析出来,相对而言,它比Common-FiloUpload更加方便。它的核心类是MultipartParser,该类用于解析HttpServletRequest请求,取出请求中全部表单域,COS用Part实例代表了所有的表单域,不管是普通表单域还是文件域都是Part实例,Part有两个子类:ParamPart和FilePart,分别代表普通表单域和文件域。
A,准备工作:首先在http://www.servlets/cos/上下载COS框架,目前最新的版本是05NOV2002,下面就是用的这个版本。将cos-05Nov2002\lib\cos.jar文件复制到我们项目的WEB-INF\lib目录下。
B,编写代码:
HTML页面代码不变,JSP页面的代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ page import="java.io.*,java.util.*" %> <%@ page import="com.oreilly.servlet.multipart.*,com.oreilly.servlet.*" %>
<% request.setCharacterEncoding("UTF-8"); //设置POST请求的内容最大字节为10M,该类用于解析HTTP请求 MultipartParser mp = new MultipartParser(request,10 * 1024 * 1024); //所有表单域都是Part实例 Part part; //遍历请求中的所有表单域 while((part = mp.readNextPart()) != null ){ //取得表单域的name属性值 String name = part.getName(); //对于普通表单域 if(part.isParam()){ //取得普通表单域的值 ParamPart paramPart = (ParamPart) part; String value = paramPart.getStringValue("GBK"); out.println("普通表单域部分:<br>name=" + name + ";value=" + value + "<br>"); } //对于文件域 else if(part.isFile()){ //获取文件上传域 FilePart filePart = (FilePart) part; String fileName = filePart.getFileName(); if(fileName != null){ //输出文件内容 long size = filePart.writeTo(new File(request.getRealPath("/"))); out.println("上传文件:<br>文件域的名=" + name + ";文件名=" + fileName + "<br>"); }else{ //文件名为空 out.println("file,name=" + name + " is empty!"); } out.flush(); } } } %> 同时COS的Part还可以获取文件的文件名,文件类型,文件大小等详细信息,具体请参考COS的参考文档。
5,Struts2的上传
Struts2没有提供自己的请求解析器,它在原有的上传解析器基础上做了进一步的封装,更时一步简化了文件上传。
可以在struts.properties配置文件中配置struts2所使用的解析器,配置文件中的内容如下:
#指定使用COS的文件上传解析器 #struts.multipart.parser=cos #指定使用Pell的文件上传解析器 #struts.multipart.parser=pell #Struts2默认使用Jakarta的Common-FileUpload的文件上传解析器 struts.multipart.parser=jakarta
Struts2默认使用的是jakarta的Common-FileUpload框架,Struts2对于以上三个框架都做了支持,因此如果想使用其它的框架只需要修改以上参数,并把相应的JAR包放到复制到我们项目的WEB-INF\lib目录下就可以了。
1,HTML页面只需要将Form的action改成upload.action就可以了,如下
<form action="upload.action" id="form1" name="form1" enctype="multipart/form-data" method="post">
请求参数:<input type="text" name="title" /><br> 上传文件:<input type="file" name="upload" /><br> <input type="submit" name="dd" value="提交" />
</form>
这个action的名字就是我接下来要开发的action。
2,编写Action,源代码如下: package com.annlee.upload; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import org.apache.struts2.ServletActionContext; import com.opensymphony.xwork2.ActionSupport; public class UploadAction extends ActionSupport { //封装文件标题请求参数的属性 private String title; //封装上传文件域的属性 private File upload; //封装上传文件类型的属性 private String uploadContentType; //封装上传文件名的属性 private String uploadFileName; //接受依赖注入的属性 private String savePath; //接受依赖注入的方法 public void setSavePath(String savePath) { this.savePath = savePath; } //返回上传文件的保存位置 public String getSavePath() { return ServletActionContext.getRequest().getRealPath(savePath); } //文件标题的set和get方法 public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } //上传文件对应文件内容的set和get方法 public File getUpload() { return upload; } public void setUpload(File upload) { this.upload = upload; } //上传文件的文件类型的set和get方法 public String getUploadContentType() { return uploadContentType; } public void setUploadContentType(String uploadContentType) { this.uploadContentType = uploadContentType; } //上传文件的文件名的set和get方法 public String getUploadFileName() { return uploadFileName; } public void setUploadFileName(String uploadFileName) { this.uploadFileName = uploadFileName; }
public String execute() throws Exception{ //以服务器的文件保存地址和原文件名建立上传文件输出流 FileOutputStream fos = new FileOutputStream(getSavePath() + "\\" + getUploadFileName()); //以上传文件建立一个文件上传输入流 FileInputStream fis = new FileInputStream(getUpload()); //将上传的内容写入服务器 byte[] buffer = new byte[1024]; int len = 0; while((len = fis.read(buffer)) > 0){ fos.write(buffer , 0 , len); } return this.SUCCESS;
} }
说明:这个Action还包含了另外两个属性xxxFileName和xxxContentType,这个是Struts2自动装载的,此外还有一个属性savePath,这个是我通过配置文件注入的。
3,struts.xml配置中加上以下部分:
<ac tion name="upload" class="com.annlee.upload.UploadAction" > <param name="savePath">/</param> <result>/common/succ.jsp</result> </action>
savePath是我设置的参数,它的好处是可以动态存放上传的文件的位置,要注意Struts2不会自动创建目录,因此文件存放的目录必须事先已经存在。
4,运行: 运行后可以在后台看到文件已经上传了。
特别说明:如果你上传成功后,想转到静态的HTML页面或者其它的资源,则应在web.xml文件中加上以下拦截器:
<!-- 配置struts2的CleanUp的Filter --> <filter> <filter-name>struts2-cleanup</filter-name> <filter-class>org.apache.struts2.dispatcher.ActionContextCleanUp</filter-class> </filter> <!-- 定义Struts2的CleanUp Filter的拦截器 --> <filter-mapping> <filter-name>struts2-cleanup</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
这个拦截器的作用是Struts2与Sitemesh的整合。 |