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>
总结:
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源码见附件
上一篇: MSI NF主板跑D4维修一例