最近在面试IBM时,面试官突然问到:如果让你自己实现一个文件上传,你的代码要如何写,不借助其他开源的文件上传jar包?
这一问,我楞了。现在开源MVC框架满天飞,大部分的框架像Struts2都会自动的把你上传的文件封装成Action的属性。几乎没有人自己去分析请求流数据,然后获取文件数据的。
总结下文件上传的几种实现:
一、像Struts2这样的框架自动将你上传的文件封装到Action的属性中,在配置文件里面你只需要指定文件上传的临时目录即可。
二、借助Apache的common-fileupload组件,实现文件上传。让开源组件帮你自动分析请求流数据,核心代码如下,详细的请参考 http://morfil.iteye.com/blog/66945:
三、Servlet 3.0通过@MultipartConfig(location = "/home/yongboy/tmp/", maxFileSize = 1024 * 1024 * 10)和@WebServlet("/upload")注解也可以实现文件上传,核心代码如下,详情请见 http://www.blogjava.net/yongboy/archive/2011/01/15/346202.html:
获取上传文件名:
四、就是自己去解析请求流数据,当然现在没有人会傻到还去自己做这种事情。这里我们只是了解下Apache的那帮牛人是怎么做到文件上传的。
首先我们先分析文件上传HTTP请求头:
一、文件上传Http请求Request截图:
贴个文字版的,里面的数据我使用谷歌浏览器截获的,方便大家拷贝
Request URL:http://127.0.0.1/dbm2-web/flowmgmt/FlowMgmtAction/uploadFile.go
Request Method:POST
Status Code:200 OK
Request Headers view source
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Charset:GBK,utf-8;q=0.7,*;q=0.3
Accept-Encoding:gzip,deflate,sdch
Accept-Language:zh-CN,zh;q=0.8
Cache-Control:max-age=0
Connection:keep-alive
Content-Length:54738
Content-Type:multipart/form-data; boundary=---- WebKitFormBoundaryhQslmBE7nbTLTJzD
Cookie:JSESSIONID=1ge6jxgy4s166bzms8og4j7us
Host:127.0.0.1
Origin:http://127.0.0.1
Referer:http://127.0.0.1/dbm2-web/page/modules/flowmgmt/main.jsp
User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1163.0 Safari/537.1
Request Payload
------WebKitFormBoundaryhQslmBE7nbTLTJzD
Content-Disposition: form-data; name="field1"; filename="exportFlow-4113.fd"
Content-Type: application/octet-stream
------WebKitFormBoundaryhQslmBE7nbTLTJzD--
Response Headers view source
Expires:Thu, 01-Jan-1970 00:00:00 GMT
Server:Jetty(7.3.1.v20110307)
Set-Cookie:JSESSIONID=oyklz60lg9aq1dn127p62ki0t;Path=/dbm2-web
Transfer-Encoding:chunked
从上述结构上我们可以知道,文件上传的Content-Type为multipart/form-data; boundary=----WebKitFormBoundaryhQslmBE7nbTLTJzD,而普通的form表单的Content-Type为application/x-www-form-urlencoded。因此,我们可以利用HttpServletRequest的request.getHeaderNames()方法和request.getHeaders(headName)方法得到请求头Headers中的Content-Type数据,然后根据Content-Type数据中是否包含multipart/form-data来区分请求是否为文件上传请求。其中boundary为文件数据的分隔符,用于区分上传多个文件。详细介绍请见http://yefeng.iteye.com/blog/315847
了解请求头的结构后,我们看看如何用程序去得到普通请求和文件上传请求的数据
http://blog.csdn.net/liuyar/article/details/6202331
二、文件上传Http请求Response截图:
三、文件上传Http请求Cookies截图:
这一问,我楞了。现在开源MVC框架满天飞,大部分的框架像Struts2都会自动的把你上传的文件封装成Action的属性。几乎没有人自己去分析请求流数据,然后获取文件数据的。
总结下文件上传的几种实现:
一、像Struts2这样的框架自动将你上传的文件封装到Action的属性中,在配置文件里面你只需要指定文件上传的临时目录即可。
二、借助Apache的common-fileupload组件,实现文件上传。让开源组件帮你自动分析请求流数据,核心代码如下,详细的请参考 http://morfil.iteye.com/blog/66945:
public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException { try { DiskFileUpload fu = new DiskFileUpload(); // 设置最大文件尺寸,这里是4MB fu.setSizeMax(4194304); // 设置缓冲区大小,这里是4kb fu.setSizeThreshold(4096); // 设置临时目录: fu.setRepositoryPath(tempPath); // 得到所有的文件: List fileItems = fu.parseRequest(request); Iterator i = fileItems.iterator(); // 依次处理每一个文件: while(i.hasNext()) { FileItem fi = (FileItem)i.next(); // 获得文件名,这个文件名包括路径: String fileName = fi.getName(); // 在这里可以记录用户和文件信息 // ... // 写入文件,暂定文件名为a.txt,可以从fileName中提取文件名: fi.write(new File(uploadPath + "a.txt")); } } catch(Exception e) { // 可以跳转出错页面 } }
三、Servlet 3.0通过@MultipartConfig(location = "/home/yongboy/tmp/", maxFileSize = 1024 * 1024 * 10)和@WebServlet("/upload")注解也可以实现文件上传,核心代码如下,详情请见 http://www.blogjava.net/yongboy/archive/2011/01/15/346202.html:
/** * 上传文件测试 location为临时文件保存路径 * * @author yongboy * @date 2011-1-13 * @version 1.0 */ @MultipartConfig(location = "/home/yongboy/tmp/", maxFileSize = 1024 * 1024 * 10) @WebServlet("/upload") public class UploadFileAction extends HttpServlet { private static final long serialVersionUID = 92166165626L; private static final Log log = LogFactory.getLog(UploadFileAction.class); // 得到注解信息 private static final MultipartConfig config; static { config = UploadFileAction.class.getAnnotation(MultipartConfig.class); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.getRequestDispatcher("/upload.jsp").forward(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 为避免获取文件名称时出现乱码 request.setCharacterEncoding("UTF-8"); Part part = null; try { // <input name="file" size="50" type="file" /> part = request.getPart("file"); } catch (IllegalStateException ise) { // 上传文件超过注解所标注的maxRequestSize或maxFileSize值 if (config.maxRequestSize() == -1L) { log.info("the Part in the request is larger than maxFileSize"); } else if (config.maxFileSize() == -1L) { log.info("the request body is larger than maxRequestSize"); } else { log.info("the request body is larger than maxRequestSize, or any Part in the request is larger than maxFileSize"); } forwardErrorPage(request, response, "上传文件过大,请检查输入是否有误!"); return; } catch (IOException ieo) { // 在接收数据时出现问题 log.error("I/O error occurred during the retrieval of the requested Part"); } catch (Exception e) { log.error(e.toString()); e.printStackTrace(); } if (part == null) { forwardErrorPage(request, response, "上传文件出现异常,请检查输入是否有误!"); return; } // 得到文件的原始名称,eg :测试文档.pdf String fileName = UploadUtils.getFileName(part); log.info("contentType : " + part.getContentType()); log.info("fileName : " + fileName); log.info("fileSize : " + part.getSize()); log.info("header names : "); for (String headerName : part.getHeaderNames()) { log.info(headerName + " : " + part.getHeader(headerName)); } String saveName = System.currentTimeMillis() + "." + FilenameUtils.getExtension(fileName); log.info("save the file with new name : " + saveName); // 因在注解中指定了路径,这里可以指定要写入的文件名 // 在未执行write方法之前,将会在注解指定location路径下生成一临时文件 part.write(saveName); request.setAttribute("fileName", fileName); request.getRequestDispatcher("/uploadResult.jsp").forward(request, response); } private void forwardErrorPage(HttpServletRequest request, HttpServletResponse response, String errMsg) throws ServletException, IOException { request.setAttribute("errMsg", errMsg); request.getRequestDispatcher("/upload.jsp").forward(request, response); } }
获取上传文件名:
/** * 如何得到上传的文件名, API没有提供直接的方法,只能从content-disposition属性中获取 * * @param part * @return */ protected static String getFileName(Part part) { if (part == null) return null; String fileName = part.getHeader("content-disposition"); if (StringUtils.isBlank(fileName)) { return null; } return StringUtils.substringBetween(fileName, "filename="", """); }
四、就是自己去解析请求流数据,当然现在没有人会傻到还去自己做这种事情。这里我们只是了解下Apache的那帮牛人是怎么做到文件上传的。
首先我们先分析文件上传HTTP请求头:
一、文件上传Http请求Request截图:
贴个文字版的,里面的数据我使用谷歌浏览器截获的,方便大家拷贝
Request URL:http://127.0.0.1/dbm2-web/flowmgmt/FlowMgmtAction/uploadFile.go
Request Method:POST
Status Code:200 OK
Request Headers view source
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Charset:GBK,utf-8;q=0.7,*;q=0.3
Accept-Encoding:gzip,deflate,sdch
Accept-Language:zh-CN,zh;q=0.8
Cache-Control:max-age=0
Connection:keep-alive
Content-Length:54738
Content-Type:multipart/form-data; boundary=---- WebKitFormBoundaryhQslmBE7nbTLTJzD
Cookie:JSESSIONID=1ge6jxgy4s166bzms8og4j7us
Host:127.0.0.1
Origin:http://127.0.0.1
Referer:http://127.0.0.1/dbm2-web/page/modules/flowmgmt/main.jsp
User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1163.0 Safari/537.1
Request Payload
------WebKitFormBoundaryhQslmBE7nbTLTJzD
Content-Disposition: form-data; name="field1"; filename="exportFlow-4113.fd"
Content-Type: application/octet-stream
------WebKitFormBoundaryhQslmBE7nbTLTJzD--
Response Headers view source
Expires:Thu, 01-Jan-1970 00:00:00 GMT
Server:Jetty(7.3.1.v20110307)
Set-Cookie:JSESSIONID=oyklz60lg9aq1dn127p62ki0t;Path=/dbm2-web
Transfer-Encoding:chunked
从上述结构上我们可以知道,文件上传的Content-Type为multipart/form-data; boundary=----WebKitFormBoundaryhQslmBE7nbTLTJzD,而普通的form表单的Content-Type为application/x-www-form-urlencoded。因此,我们可以利用HttpServletRequest的request.getHeaderNames()方法和request.getHeaders(headName)方法得到请求头Headers中的Content-Type数据,然后根据Content-Type数据中是否包含multipart/form-data来区分请求是否为文件上传请求。其中boundary为文件数据的分隔符,用于区分上传多个文件。详细介绍请见http://yefeng.iteye.com/blog/315847
了解请求头的结构后,我们看看如何用程序去得到普通请求和文件上传请求的数据
http://blog.csdn.net/liuyar/article/details/6202331
二、文件上传Http请求Response截图:
三、文件上传Http请求Cookies截图: