您的位置:首页 > 其它

文件上传原理实现

2014-08-16 23:14 190 查看

文件上传原理实现

客户端浏览器是怎样上传数据的呢?服务器端如何接收上传的文件数据?
级别: 初级

王延成 (王延成), 作者/编者,

2004 年 9 月 01 日

文章主要描述http表单上传二进制数据流规范的简单实现

(一)关于Form表单上传文件规范

总结个人在对新技术、新事物的学习和解决问题的过程,深刻体会到多理解掌握技术基础理论知识再加上相应的实践,的确能帮助我们在解决某些问题的时候起到事半功倍的效果。

以前上传文件类似的功能都是采用第三方组件来做的,真的是基于接口编程了。不出问题还好,真要是出现问题解决起来太不舒服了,往往属于那种拆了西墙补东墙的策略。最近,在做文件上传时学习了一些 关于html>form上传数据的格式规范,依据人家定义的规范做了一些简单的工作。。。算是实现了个小轮子吧。

(二)实现

1、规则1.1 上传数据块的分割规则
基于html form表单上传的数据都是以类似-----------------------------7da3c8e180752{0x130x10}这样的分割符来标记一块数据的起止,可不要忘记后面的两个换行符。关于换行符有三种,如下:

操作系统换行符描述原始标记ascii码十六进制
WindowWindow的换行符是两个//r//n13100x0d0x0a
UnixUnix的换行符是一个//n100x0a
Mac OSMac OS的换行符是一个//r130x0d
这块没有对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、操作顺序流程描述









回页首
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、类图



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);
}
}

(三)资源

示例代码下载下载示例代码资料搜集文件上传原理表单规范1、http://www.ietf.org:80/rfc/rfc1867.txt2、http://www.ietf.org:80/rfc/rfc2045.txt3、http://www.iana.org:80/assignments/character-sets4、http://www.htmlhelp.com/reference/html40/forms/form.html



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: