`
cheng888qi
  • 浏览: 282502 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

WEB文件上传之apache common upload使用(一)

阅读更多

  文件上传一个经常用到的功能,它有许多中实现的方案。

页面表单 + RFC1897规范 + http协议上传

页面控件(flash/html5/activeX/applet) + RFC1897规范 + http协议上传

页面控件(flash/html5/activeX/applet) + 自定义数据规范 + http协议上传

页面控件(flash/html5/activeX/applet) + FTP协议上传

页面控件(flash/html5/activeX/applet) + 自定义协议

 

  用apache common upload组件实际就是采用的“页面表单 + RFC1897规范 + http协议上传”实现方式,需要实现的技术点:

1. 多文件数据的提交

2. 文件数据包接收存储功能

3. 文件数据上传进度

4. WEB页面无刷新异步提交

 

 时序图:

  • 文件上传时序图


  • 文件上传进度获取时序图
实现思路:

1. 多文件数据的提交

在WEB页面采用多个<input type="file">利用form表单进行文件提交

 

2. 文件数据包接收存储功能

服务端采用servlet,利用apache common upload组件接收解析数据包,接收解析的过程中保存进度到session, 文件接收完毕后保存到指定目录

 

3. 文件数据上传进度

在WEB页面在界面写一个定时器,定时访问服务器提供上传进度获取功能的servlet,获取文件上传进度信息

 

4. WEB页面无刷新异步提交

利用iframe来实现WEB页面无刷新异步上传

 

关键代码:

UploadFileServlet.java

package com.test.servlet;

import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.util.Iterator;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.FileCleanerCleanup;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileCleaningTracker;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 文件上传数据接收类
 * 
 * @author chengqi
 *
 */
public class UploadFileServlet extends HttpServlet {

	/** 日志对象*/
	private Log logger = LogFactory.getLog(this.getClass());

	private static final long serialVersionUID = 1L;

	/** 上传目录名*/
	private static final String uploadFolderName = "uploadFiles";

	/** 上传临时文件存储目录*/
	private static final String tempFolderName = "tempFiles";

	/** 上传文件最大为30M*/ 
	private static final Long fileMaxSize = 30000000L; 

	/** 允许上传的扩展名*/
	private static final String [] extensionPermit = {"txt", "xls", "zip"};

	/** 统一的编码格式*/
	private static final String encode = "UTF-8";

	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		logger.info("UploadFileServlet#doPost() start");
		try {
			String curProjectPath = this.getServletContext().getRealPath("/");
			String saveDirectoryPath = curProjectPath + "/" + uploadFolderName;
			String tempDirectoryPath = curProjectPath + "/" + tempFolderName;
			File saveDirectory = new File(saveDirectoryPath);
			File tempDirectory = new File(tempDirectoryPath);
			logger.debug("Project real path [" + saveDirectory.getAbsolutePath() + "]");
			//上传时产生的临时文件的默认保存目录
			logger.debug("Temp files default save path [" + System.getProperty("java.io.tmpdir") + "]");
			DiskFileItemFactory factory = new DiskFileItemFactory();
			//DiskFileItemFactory中DEFAULT_SIZE_THRESHOLD=10240表示如果上传文件大于10K则会产生上传临时文件
			//上传临时文件的默认目录为java.io.tmpdir中保存的路径,根据操作系统的不同会有区别
			
			if(!tempDirectory.exists()) {
				tempDirectory.mkdir();
			}
			//重新设置临时文件保存目录
			factory.setRepository(tempDirectory);

			//设置文件清除追踪器,文件上传过程中产生的临时文件会在
			FileCleaningTracker fileCleaningTracker = FileCleanerCleanup.getFileCleaningTracker(this.getServletContext());
			factory.setFileCleaningTracker(fileCleaningTracker);

			ServletFileUpload upload = new ServletFileUpload(factory);

			//设置文件上传进度监听器
			FileProcessListener processListener = new FileProcessListener(request.getSession());
			upload.setProgressListener(processListener);

			// 设置文件上传的大小限制
			upload.setFileSizeMax(fileMaxSize);

			// 设置文件上传的头编码,如果需要正确接收中文文件路径或者文件名
			// 这里需要设置对应的字符编码,为了通用这里设置为UTF-8
			upload.setHeaderEncoding(encode);

			//解析请求数据包
			List<FileItem> fileItems = upload.parseRequest(request);
			//遍历解析完成后的Form数据和上传文件数据
			for (Iterator<FileItem> iterator = fileItems.iterator(); iterator.hasNext();) {
				FileItem fileItem = iterator.next();
				String fieldName = fileItem.getFieldName();
				String name = fileItem.getName();
				//如果为上传文件数据
				if(!fileItem.isFormField()) {
					logger.debug("fieldName[" + fieldName + "] fileName[" + name + "] ");
					if(fileItem.getSize() > 0) {
						String fileExtension = FilenameUtils.getExtension(name);
						if(!ArrayUtils.contains(extensionPermit, fileExtension)) {
							throw new NoSupportExtensionException("No Support extension.");
						}
						String fileName = FilenameUtils.getName(name);
						FileUtils.copyInputStreamToFile(fileItem.getInputStream(), 
								new File(saveDirectory, fileName));
					}
				} else { //Form表单数据
					String value = fileItem.getString(encode);
					logger.debug("fieldName[" + value + "] fieldValue[" + fieldName + "]");
				}
			}
			responseMessage(response, State.OK);
		} catch(FileSizeLimitExceededException e) { 
			logger.error(e.getMessage(), e);
			responseMessage(response, State.OVER_FILE_LIMIT);
		} catch(NoSupportExtensionException e) { 
			logger.error(e.getMessage(), e);
			responseMessage(response, State.NO_SUPPORT_EXTENSION);
		} catch(Exception e) {
			logger.error(e.getMessage(), e);
			responseMessage(response, State.ERROR);
		} finally {
			//清除上传进度信息
			request.getSession().removeAttribute("fileUploadProcess");
		}
		logger.info("UploadFileServlet#doPost() end"); 
	}

	public enum State {
		OK(200, "上传成功"),
		ERROR(500, "上传失败"),
		OVER_FILE_LIMIT(501, "超过上传大小限制"),
		NO_SUPPORT_EXTENSION(502, "不支持的扩展名");

		private int code;
		private String message;
		private State(int code, String message) {
			this.code = code;
			this.message = message;
		}

		public int getCode() {
			return code;
		}
		public String getMessage() {
			return message;
		}

	}

	/**
	 * 返回结果函数
	 * @param response
	 * @param state
	 */
	private void responseMessage(HttpServletResponse response, State state) {
		response.setCharacterEncoding(encode);
		response.setContentType("text/html; charset=" + encode);
		Writer writer = null;
		try {
			writer = response.getWriter();
			writer.write("<script>");
			writer.write("window.parent.fileUploadCallBack({\"code\":" + state.getCode() +",\"message\":\"" + state.getMessage()+ "\"});");
			writer.write("</script>");
			writer.flush();
			writer.close();
		} catch(Exception e) {
			logger.error(e.getMessage(), e);
		} finally {
			IOUtils.closeQuietly(writer);
		}
	}


}

  

 

GetFileProcessServlet.java

package com.test.servlet;

import java.io.IOException;
import java.io.Writer;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 文件上传进度获取Servlet
 * 
 * @author chengqi
 *
 */
public class GetFileProcessServlet extends HttpServlet {

	/** 日志对象*/
	private Log logger = LogFactory.getLog(this.getClass());

	private static final long serialVersionUID = 1L;

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		logger.info("GetFileProcessServlet#doGet start");
		String fileUploadPercent = (String)request.getSession().getAttribute("fileUploadProcess");
		Writer writer = null;
		try {
			writer = response.getWriter();
			logger.info("percent:" + fileUploadPercent);
			IOUtils.write(fileUploadPercent == null ? "0%" : fileUploadPercent, writer);
			writer.flush();
			writer.close();
		} catch(Exception e) {
			logger.error(e.getMessage(), e);
		} finally {
			IOUtils.closeQuietly(writer);
		}
		logger.info("GetFileProcessServlet#doGet end");
	}

}

 

FileProcessListener.java

package com.test.servlet;

import java.text.NumberFormat;

import javax.servlet.http.HttpSession;

import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 文件进度监听器
 * 
 * @author chengqi
 *
 */
public class FileProcessListener implements ProgressListener{

	/** 日志对象*/
	private Log logger = LogFactory.getLog(this.getClass());

	private HttpSession session;

	public FileProcessListener(HttpSession session) {
		this.session = session;  
	}
	

	public void update(long pBytesRead, long pContentLength, int pItems) {
		double readByte = pBytesRead;
		double totalSize = pContentLength;
		if(pContentLength == -1) {
			logger.debug("item index[" + pItems + "] " + pBytesRead + " bytes have been read.");
		} else {
			logger.debug("item index[" + pItems + "] " + pBytesRead + " of " + pContentLength + " bytes have been read.");
			String p = NumberFormat.getPercentInstance().format(readByte / totalSize);
			session.setAttribute("fileUploadProcess", p);
		}
	}

}
 

 

apacheUploadDemo.html

 

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
	<title>Apache common实现基本文件上传</title>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	<script type="text/javascript" src="js/jquery/jquery-1.9.1.js"></script>
	<script type="text/javascript" src="js/jquery/jquery.form.js"></script>
	<script type="text/javascript">

	//定时器对象
	var uploadProcessTimer = null;

	$(function (){
		//绑定定时器开始操作到提交按钮
		$('input[type=submit]').click(function () {
			//启动上传进度查询定时器
			uploadProcessTimer = window.setInterval(getFileUploadProcess, 20);
		})
	});

	//获取文件上传进度
	function getFileUploadProcess() {
		$.get('/upload/getFileProcessServlet', function(data) {
			$('#fileUploadProcess').html(data);
		});
	}

	//上传完成后,由iframe返回脚本自动调用
	function fileUploadCallBack(res) {
		//清除定时器
		if(uploadProcessTimer) {
			window.clearInterval(uploadProcessTimer);
		}
		var message = res['message'];
		var code = res['code'];
		if(code != 200) {
			$('#fileUploadProcess').html('0%');
		}
		alert(message);
	}

	</script>
</head>
<body>
<h2>上传文件1</h2>

用户信息:  <br/>
<form id="testForm" action="/upload/uploadServlet" method="post" enctype="multipart/form-data" target="iframeUpload">
	姓名:<input name="name" type="text"> <br/>
	附件1:<input name="file1" type="file" > <br/>
	附件2:<input name="file2" type="file" > <br/>
	<br><br>
	<input type="submit" value="提交" ><br/>
</form>
上传进度:<label id="fileUploadProcess"></label>
<iframe name="iframeUpload" src="" width="350" height="35" frameborder=0  SCROLLING="no" style="display:NONE"></iframe>   
</body>
</html>
 
总结:
虽然使用apache common upload组件实现了文件上传,但是从上传的效果来看,并不是一个很完美的解决方案。
有如下缺点:
1. 当有多个文件上传时,无法知道单个文件的上传进度,因为文件上传消息中根本就没有关于单个文件大小的信息
文件上传消息
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 22 Apr 2014 07:45:45 GMT

POST /upload/uploadServlet HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:28.0) Gecko/20100101 Firefox/28.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://localhost:8080/upload/apacheUploadDemo.html
Cookie: JSESSIONID=33498CE814284D67F957CA53D45F0174
Connection: keep-alive

Content-Length 2363
Content-Type multipart/form-data; boundary=---------------------------189163093917262

-----------------------------189163093917262
Content-Disposition: form-data; name="name"

-----------------------------189163093917262
Content-Disposition: form-data; name="file1"; filename="New Text Document.txt" Content-Type: text/plain
文件数据

-----------------------------189163093917262
Content-Disposition: form-data; name="file2"; filename="New Text Document (2).txt" Content-Type: text/plain
文件数据

-----------------------------189163093917262--

  

 2. 浏览器必须将所有文件读取完毕才开始上传,并且是一次性提交所有的数据文件,在互联网环境下,会http连接超时,大文件无法上传成功。

 

3. 服务端判断是否超过大小限制,是通过计算接收数据的累积字节数和限制大小比较,这种情况下,如果限制大小是30M,那么在服务端已经读取了30M完成后才会抛出异常,多余的消耗的服务器的内存和硬盘空间

 

所以基于这些原因,页面表单 + RFC1897规范 + http协议上传 + 后台apache common upload组件接收的这种解决方案,不适合解决WEB页面一次多文件上传,大文件上传情况,比较适合一次单个小文件附件的情况,如:博客附件,登记照片上传,预览等情况。

 

Demo源码见附件

 

 

 

 

  • 大小: 11.7 KB
  • 大小: 5.9 KB
分享到:
评论
2 楼 HGRX 2017-02-14  
非常好的演示demo,代码也写的很棒,学习了!
1 楼 yizhichao116 2016-09-01  
  测试过了,非常好

相关推荐

    j2EE文件上传集合

    上传后文件都保存在upload目录下面(与WEB-INF同级的upload) 新添加单个文件上传用的是apache的commonfileupload有上传进度条 本工程内所有上传复制过去直接可用. 上传后的文件都保存在根目录下面的upload目录文件夹...

    Jsp文件上传下载(工具类源码)

    // 实例化一个硬盘文件工厂,用来配置上传组件ServletFileUpload DiskFileItemFactory dfif = new DiskFileItemFactory(); dfif.setSizeThreshold(4096);// 设置上传文件时用于临时存放文件的内存大小,这里是4K....

    BBS-CS_3_Tomcat4.rar

    |---upload(上传图片文件夹) | |---WEB-INF | |---classes | | | | | |---com(以下有所有类文件) | | | web.xml | *.jsp(主程序文件) *.sql(数据库建立文件) *.htm(Html文件) *.js(javascript文件)

    commons-fileupload-1.2.jar和commons-io-1.3.2.jar

    common-fileupload组件是apache的一个开源项目之一,可以从http://jakarta.apache.org/commons/fileupload/下载。该组件简单易用,可实现一次上传一个或多个文件,并可限制文件大小。 -下载后解压zip包,将commons-...

    嘟嘟跑腿外卖系统源码.zip

    Tpl 前端模板目录 Upload 文件上传目录 .htaccess 目录级别的修改配置的方式 admin.php 总管理后端入口 index.php 站点首页 shop.php 商家入口 开发语言:基于Thinkphp框架+bootstrap+layer 开发工具...

    .htaccess

    个人档案 查看文章 .htaccess怎么用2007-05-16 14:04(文章来源)http://www.dnpark.com.cn/news/mm/www/1179329504375ZKlMSgYr.html&lt;br&gt;&lt;br&gt;Apache服务器的.htaccess是一个非常强大的分布式配置文件,学会使用....

    PHP基础教程 是一个比较有价值的PHP新手教程!

    只需要很少的编程知识你就能使用PHP建立一个真正交互的WEB站点。本教程并不想让你完全了解这种语言,只是能使你尽快加入开发动态web站点的行列。我假定你有一些HTML(或者HTML编辑器)的基本知识和一些编程思想。 1...

    Securing PHP Web Applications.pdf

    Common Input Validation Patterns 65 Wrapping It Up 67 Chapter 6 Filesystem Access: Accessing the Filesystem for Fun and Profit 69 Opening Files 69 Local Filesystem Access 69 Remote Filesystem Access ...

    SentCMS网站管理系统-PHP

    SentCMS网站管理系统是南昌腾速科技有限公司倾力打造的一款简单易用的网站管理系统,SentCMS网站管理系统(下文简称SentCMS)继承了thinkphp的优秀品质,秉承“大道至简”的设计理念。SnetCMS为网站建设而生,为网站...

    计算机网络第六版答案

    FTTH: 2-10Mbps upload; 10-20 Mbps download; bandwidth is not shared. 10. There are two popular wireless Internet access technologies today: Wifi (802.11) In a wireless LAN, wireless users transmit...

    php.ini-development

    interesting side-effects depending on your application and web server. ; You may be able to send headers and cookies after you've already sent output ; through print or echo. You also may see ...

Global site tag (gtag.js) - Google Analytics