文件上传原理实现
客户端浏览器是怎样上传数据的呢?服务器端如何接收上传的文件数据?
级别: 初级
2004 年 9 月 01 日
文章主要描述http表单上传二进制数据流规范的简单实现
(一)关于Form表单上传文件规范
总结个人在对新技术、新事物的学习和解决问题的过程,深刻体会到多理解掌握技术基础理论知识再加上相应的实践,的确能帮助我们在解决某些问题的时候起到事半功倍的效果。
以前上传文件类似的功能都是采用第三方组件来做的,真的是基于接口编程了。不出问题还好,真要是出现问题解决起来太不舒服了,往往属于那种拆了西墙补东墙的策略。最近,在做文件上传时学习了一些 关于html>form上传数据的格式规范,依据人家定义的规范做了一些简单的工作。。。算是实现了个小轮子吧。
(二)实现
-
1、规则
-
1.1 上传数据块的分割规则
基于html form表单上传的数据都是以类似-----------------------------7da3c8e180752{0x130x10}这样的分割符来标记一块数据的起止,可不要忘记后面的两个换行符。关于换行符有三种,如下:
操作系统 换行符描述 原始标记 ascii码 十六进制 Window Window的换行符是两个 //r//n 1310 0x0d0x0a Unix Unix的换行符是一个 //n 10 0x0a Mac OS Mac OS的换行符是一个 //r 13 0x0d 这块没有对Unix、MacOS上做测试,只在Window上测试了换行是两个(0x0d0x0a) - 1.2 注意在后台从request中取得分割串少两个 --,在看下面的原始数据你会发现流的最后是以 --结束的。
-
1.3 上传的原始数据串,本来中文字符是乱码的。为了清晰一些使用字符集UTF-8转了下码。
- -----------------------------7da3c8e180752
- Content-Disposition: form-data; name="fileData1"; filename="C:/abcdef.log"
- Content-Type: application/octet-stream
- HelloWorld
- HelloWorld
- -----------------------------7da3c8e180752
- Content-Disposition: form-data; name="fileData2"; filename="C:/deleteThumb.bat"
- Content-Type: application/octet-stream
- FOR %%a IN ( C: D: E: F: ) DO DEL /f/s/q/a %%a/Thumbs.db
- -----------------------------7da3c8e180752
- Content-Disposition: form-data; name="textAreaContent"
- HelloWorld
- -----------------------------7da3c8e180752
- Content-Disposition: form-data; name="id"
- 文件编码
- -----------------------------7da3c8e180752
- Content-Disposition: form-data; name="name"
- 文件名称
- -----------------------------7da3c8e180752--
-
1.4 小结
基于1.3小节可以非常容易总结归纳出html-->form元素内容。有两个文件类型元素,三个text元素(其中一个元素是textarea)
2、操作顺序流程描述
![](http://hi.csdn.net/attachment/201009/7/0_1283854876twnJ.gif)
![]() ![]() |
![]()
|
3、实现代码
需要明确注意的一个问题是关于request.getInputStream();获取请求体数据流不可用的问题,见示例代码:- /*我是这么认为的在获取流之前,已经通过第三方应用服务器相关对ServletRequest接口实现解析过流了,所以我们再次取请求流内容时会有问题。。在参与.NET设备与JavaWeb交互时也遇到过此类型的问题。*/
- /*这就要求我们如果是自己解析request流内容数据,在解析之前不可以调用getParameter(String paramName)相关的需要用到解析流的行为方法。*/
- /*以上仅个人理解、个人意见,如有不对请多多指正。*/
- request.getParameter("USER_CODE");
- InputStream ins = request.getInputStream();
- byte[] buff = new byte[1024];
- int count = -1;
- while ((count = ins.read(buff))!=-1) {
- System.out.println(new String(buff,0, count, "UTF-8"));
- }
3.1、类图
![](http://hi.csdn.net/attachment/201009/7/0_1283854881MPGp.gif)
3.2、代码内容
-
入口Servlet
-
- package org.ybygjy.web;
- import java.io.IOException;
- 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.ybygjy.web.comp.FileItem;
- import org.ybygjy.web.comp.FileMgrComp;
- import org.ybygjy.web.utils.WrapperRequest;
- /**
- * 处理基于WebHttp的请求/响应
- * @author WangYanCheng
- * @version 2010-1-10
- */
- public class Servlet extends HttpServlet {
- /** default serial */
- private static final long serialVersionUID = 1L;
- /**
- * {@inheritDoc}
- */
- public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException,
- IOException {
- request.setCharacterEncoding("UTF-8");
- List<FileItem> fileItemArr = null;
- String contentType = request.getContentType();
- if (null != contentType && contentType.startsWith(WrapperRequest.ContentType.FORM_DATA.getType())) {
- fileItemArr = new FileMgrComp().doAnalyse(WrapperRequest.getInstance(request), response);
- for (Iterator<FileItem> iterator = fileItemArr.iterator(); iterator.hasNext();) {
- System.out.println(iterator.next());
- }
- }
- }
- }
工具类
-
- package org.ybygjy.web.utils;
- import javax.servlet.http.HttpServletRequest;
- /**
- * RequestUtils
- * @author WangYanCheng
- * @version 2010-1-10
- */
- public class WrapperRequest {
- /**ContentType*/
- public enum ContentType {
- /**MIME类型-二进制数据标记*/
- FORM_DATA("multipart/form-data"),
- /**MIME类型-标准编码格式标记*/
- FORM_URLENCODE("application/x-www-form-urlencoded"),
- /**MIME类型-文本格式标记*/
- FORM_TEXT("text/plain");
- /**inner type*/
- private final String type;
- /**
- * Constructor
- * @param str type
- */
- private ContentType(String str) {
- this.type = str;
- }
- /**
- * getter Type
- * @return type
- */
- public String getType() {
- return this.type;
- }
- }
- /**ContentType*/
- private String contentType;
- /**request*/
- private HttpServletRequest request = null;
- /**
- * Constructor
- * @param request request
- */
- private WrapperRequest(HttpServletRequest request) {
- this.request = request;
- }
- /**
- * getInstance
- * @param request request
- * @return wrapperRequest
- */
- public static final WrapperRequest getInstance(HttpServletRequest request) {
- return new WrapperRequest(request);
- }
- /**
- * get no wrapper Request
- * @return request request/null
- */
- public HttpServletRequest getRequest() {
- return this.request;
- }
- /**
- * getContentType
- * @return contentTypeStr/null
- */
- public String getContentType() {
- if (null == this.contentType) {
- this.contentType = null == this.request ? null : this.request.getContentType();
- }
- return this.contentType;
- }
- /**
- * 是否二制数据格式
- * @return true/false
- */
- public boolean isBinaryData() {
- boolean rtnBool = false;
- String tmpStr = getContentType();
- if (tmpStr.contains(ContentType.FORM_DATA.getType())) {
- rtnBool = true;
- }
- return rtnBool;
- }
- /**
- * 取得内容界定符
- * @return rtnStr/null
- */
- public String getBoundary() {
- String rtnStr = null;
- String tmpType = getContentType();
- if (null != tmpType) {
- rtnStr = tmpType.matches("^[//s//S]*boundary=[//s//S]*$") ? tmpType.split("boundary=")[1] : null;
- }
- return "--".concat(rtnStr);
- }
- /**
- * 测试入口
- * @param args 参数列表
- */
- public static void main(String[] args) {
- // WrapperRequest.ContentType[] cts = WrapperRequest.ContentType.values();
- // for (WrapperRequest.ContentType ct : cts) {
- // System.out.println(ct.getType());
- // }
- // System.out.println(WrapperRequest.getInstance(null).getBoundary());
- /*Matcher matcher = Pattern.compile("(//s)+").matcher("分/n/r割符");
- while (matcher.find()) {
- int count = matcher.groupCount();
- for (int i = 0; i < count; i++) {
- byte[] tmpByte = matcher.group(i).getBytes();
- for (int tmpI : tmpByte) {
- System.out.print(tmpI);
- }
- }
- }*/
- WrapperRequest wpInst = WrapperRequest.getInstance(null);
- wpInst.getBoundary();
- }
- }
基础实现
-
- package org.ybygjy.web.comp;
- import java.io.BufferedOutputStream;
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import org.ybygjy.web.utils.FileUtils;
- /**
- * 负责文件的存储解析
- * @author WangYanCheng
- * @version 2010-08-31
- */
- public class FileItem {
- /** 源文件路径 */
- private String srcFilePath;
- /** 源文件全名称 */
- private String srcFileFullName;
- /** 源文件名称 */
- private String srcFileName;
- /** 文件转储路径 */
- private String filePath;
- /** 文件名称 */
- private String fileName;
- /** 文件全名称 */
- private String fileFullName;
- /** 上传文件参数名称 */
- private String paramName;
- /** MIME Type */
- private String mimeType;
- /** 分割串 */
- private String boundaryStr;
- /**
- * Constructor
- * @param paramStr 参数名称
- * @param fileStr 源文件地址串
- * @param mimeType MIMEType
- * @param boundaryStr 分割约束
- */
- public FileItem(String paramStr, String fileStr, String mimeType, String boundaryStr) {
- String[] tmpStrArr = paramStr.split("=");
- this.setParamName(tmpStrArr[1].substring(1, tmpStrArr[1].length() - 1));
- tmpStrArr = fileStr.split("=");
- this.setSrcFilePath(tmpStrArr[1].substring(1, tmpStrArr[1].length() - 1));
- this.setMIME(mimeType);
- this.setBoundaryStr(boundaryStr);
- }
- /**
- * setFilePath
- * @param filePath filePath
- */
- public void setSrcFilePath(String filePath) {
- this.srcFilePath = filePath;
- if (this.srcFilePath != null && filePath.length() > 0) {
- this.srcFileFullName = new File(this.srcFilePath).getName();
- this.srcFileName = this.srcFileFullName.substring(0, this.srcFileFullName.indexOf('.'));
- }
- }
- /**
- * setMIME
- * @param mimeType mimeType
- */
- public void setMIME(String mimeType) {
- this.mimeType = mimeType;
- }
- /**
- * getMIME
- * @return mimeType mimeType
- */
- public String getMIME() {
- return this.mimeType;
- }
- /**
- * setBoundary
- * @param boundaryStr the boundaryStr to set
- */
- public void setBoundaryStr(String boundaryStr) {
- this.boundaryStr = boundaryStr;
- }
- /**
- * setParamName
- * @param paramName paramName
- */
- public void setParamName(String paramName) {
- this.paramName = paramName;
- }
- /**
- * getParamName
- * @return paramName
- */
- public String getParamName() {
- return this.paramName;
- }
- /**
- * 源上传文件全名称
- * @return the srcFileName
- */
- public String getSrcFileFullName() {
- return srcFileFullName == null ? null : srcFileFullName.length() == 0 ? null : this.srcFileFullName;
- }
- /**
- * 源上传文件名称
- * @return the srcFileName
- */
- public String getSrcFileName() {
- return srcFileName;
- }
- /**
- * 文件存储路径
- * @return the filePath
- */
- public String getFilePath() {
- return filePath;
- }
- /**
- * 取文件名称
- * @return the fileName
- */
- public String getFileName() {
- return fileName;
- }
- /**
- * 取文件全名称
- * @return the fileFullName
- */
- public String getFileFullName() {
- return fileFullName;
- }
- /**
- * 转储文件
- * @param ins ins
- * @throws IOException IOException
- */
- public void doStoreFile(InputStream ins) throws IOException {
- String id = String.valueOf(System.currentTimeMillis());
- File tmpFile = new File(FileUtils.getTmpFilePath(), id);
- this.filePath = tmpFile.getPath();
- this.fileFullName = tmpFile.getName();
- this.fileName = id;
- BufferedOutputStream bos = null;
- try {
- bos = new BufferedOutputStream(new FileOutputStream(tmpFile));
- this.doStoreFile(ins, bos);
- } catch (IOException ioe) {
- throw ioe;
- } finally {
- try {
- bos.close();
- } catch (IOException ioe) {
- ioe.printStackTrace();
- }
- }
- }
- /**
- * 存储文件
- * @param ins ins
- * @param bos bos
- * @throws IOException IOException
- */
- private void doStoreFile(InputStream ins, OutputStream bos) throws IOException {
- byte[] byteArr = new byte[this.boundaryStr.getBytes().length];
- try {
- int tmpI = -1;
- int tmpL = -1;
- ins.skip(2);
- while (((tmpI = ins.read()) != -1)) {
- if (13 == tmpI) {
- tmpL = ins.read();
- if (10 == tmpL && isBoundary(ins, byteArr)) {
- break;
- } else {
- bos.write(tmpI);
- bos.write(tmpL);
- if (10 == tmpL) {
- bos.write(byteArr);
- }
- continue;
- }
- }
- bos.write(tmpI);
- }
- bos.flush();
- } catch (IOException ioe) {
- throw ioe;
- }
- }
- /**
- * 检验是否边界
- * @param ins ins
- * @param byteArr byteArr
- * @return true/false
- * @throws IOException IOException
- */
- private boolean isBoundary(InputStream ins, byte[] byteArr) throws IOException {
- if (null == this.boundaryStr) {
- return false;
- }
- boolean rtnFlag = false;
- int count = ins.read(byteArr);
- if (count != -1) {
- String str = new String(byteArr, 0, count);
- if (this.boundaryStr.equals(str)) {
- rtnFlag = true;
- }
- byteArr = str.getBytes();
- }
- return rtnFlag;
- }
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("FileItem [boundaryStr=");
- builder.append(boundaryStr);
- builder.append(", fileFullName=");
- builder.append(fileFullName);
- builder.append(", fileName=");
- builder.append(fileName);
- builder.append(", filePath=");
- builder.append(filePath);
- builder.append(", mimeType=");
- builder.append(mimeType);
- builder.append(", paramName=");
- builder.append(paramName);
- builder.append(", srcFileName=");
- builder.append(srcFileFullName);
- builder.append(", srcFilePath=");
- builder.append(srcFilePath);
- builder.append("]");
- return builder.toString();
- }
- }
回页首 - package org.ybygjy.web.comp;
- import java.io.BufferedInputStream;
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.UnsupportedEncodingException;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import org.ybygjy.web.utils.WrapperRequest;
- /**
- * 负责对文件实体进行的操作
- * @author WangYanCheng
- * @version 2010-8-27
- */
- public class FileMgrComp {
- /** serialNumber */
- private static final long serialVersionUID = 5563862601527965192L;
- /** boundaryStr */
- private String boundaryStr;
- /** ifcArray */
- private List<FileItem> ifcArray;
- /**
- * Constructor
- */
- public FileMgrComp() {
- ifcArray = new ArrayList<FileItem>();
- }
- /**
- * doService
- * @param wrRequest wrRequest
- * @param response response
- * @return rtnList rtnList/null
- * @throws IOException IOException
- */
- public List<FileItem> doAnalyse(WrapperRequest wrRequest, HttpServletResponse response) throws IOException {
- doInnerTest(wrRequest.getRequest());
- List<FileItem> fileArr = null;
- if (wrRequest.isBinaryData()) {
- this.boundaryStr = wrRequest.getBoundary();
- if (null != boundaryStr) {
- fileArr = doAnalyseBinaryData(wrRequest);
- }
- }
- return fileArr;
- }
- /**
- * 分析存储二进制数据
- * @param wrRequest wrRequest
- * @return fileItemArr fileItemArr
- */
- private List<FileItem> doAnalyseBinaryData(WrapperRequest wrRequest) {
- BufferedInputStream bins = null;
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- try {
- bins = new BufferedInputStream(wrRequest.getRequest().getInputStream());
- int tmpI = -1;
- int tmpL = -1;
- while ((tmpI = bins.read()) != -1) {
- if (tmpI == 13) {
- tmpL = (bins.read());
- if (tmpL == 10) {
- if (baos.size() == 0) {
- continue;
- }
- FileItem fi = analyseFileInput(baos, bins);
- if (fi != null) {
- ifcArray.add(fi);
- }
- baos.reset();
- continue;
- }
- baos.write(tmpI);
- baos.write(tmpL);
- }
- baos.write(tmpI);
- }
- } catch (IOException ioe) {
- ioe.printStackTrace();
- } finally {
- if (null != bins) {
- try {
- baos.close();
- bins.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- return this.getIfcArray();
- }
- /**
- * 解析验证上传内容是否文件类型
- * @param os outStream
- * @param ins insStream
- * @return ifcInst ifcInst/null
- */
- private FileItem analyseFileInput(ByteArrayOutputStream os, InputStream ins) {
- String tmpStr = null;
- try {
- tmpStr = os.toString("UTF-8");
- } catch (UnsupportedEncodingException e1) {
- e1.printStackTrace();
- }
- FileItem ifcIns = null;
- if (tmpStr.indexOf("filename") != -1) {
- String[] tmpStrArr = tmpStr.split(";");
- if (tmpStrArr.length > 2) {
- // 取MIME文件类型
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- try {
- doRead(ins, baos);
- tmpStr = baos.toString();
- if (tmpStr.startsWith("Content-Type:")) {
- ifcIns = new FileItem(tmpStrArr[1].trim(), tmpStrArr[2].trim(),
- tmpStr.split(":")[1].trim(), this.boundaryStr);
- if (ifcIns.getSrcFileFullName() != null) {
- ifcIns.doStoreFile(ins);
- }
- }
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- try {
- baos.close();
- } catch (IOException ioe) {
- ioe.printStackTrace();
- }
- }
- }
- }
- return ifcIns;
- }
- /**
- * doRead
- * @param ins ins
- * @param baos baos
- * @throws IOException IOException
- */
- private void doRead(InputStream ins, ByteArrayOutputStream baos) throws IOException {
- int tmpI = -1;
- while ((tmpI = ins.read()) != -1) {
- if (tmpI == 13) {
- tmpI = ins.read();
- if (tmpI == 10) {
- break;
- } else {
- baos.write(13);
- baos.write(tmpI);
- continue;
- }
- }
- baos.write(tmpI);
- }
- }
- /**
- * getIfcArray
- * @return the ifcArray
- */
- public List<FileItem> getIfcArray() {
- return ifcArray;
- }
- /**
- * innerTest
- * @param request request
- */
- private void doInnerTest(HttpServletRequest request) {
- Map<String, Object> testInfo = new HashMap<String, Object>();
- testInfo.put("AuthType", request.getAuthType());
- testInfo.put("CharacterEncoding", request.getCharacterEncoding());
- testInfo.put("ContentLength", request.getContentLength());
- testInfo.put("ContentType", request.getContentType());
- testInfo.put("ContextPath", request.getContextPath());
- testInfo.put("HeaderNames", request.getHeaderNames());
- testInfo.put("LocalAddr", request.getLocalAddr());
- testInfo.put("LocalName", request.getLocalName());
- testInfo.put("PathInfo", request.getPathInfo());
- testInfo.put("RequestedSessionId", request.getRequestedSessionId());
- testInfo.put("UserPrincipal", request.getUserPrincipal());
- org.ybygjy.test.TestUtils.doPrint(testInfo);
- }
- }
![]() ![]() |
![]()
|
(三)资源