近期忙于各种各样的的项目开发,难得有时间来更新文章,话说最近其实因为项目的原因做过和实践过各种各样的技术,这个新主机和域名算起来也一年多了,忙于各种事务一直没有好好的更新文章,从今日开始应该告诉自己需要勤奋一点,尽量多更新这里的文章。
今天先总结下近期因为项目需求在项目内使用的streaming file upload技术。
首先说下什么是streaming upload。Http streaming是streaming upload背后的技术。要说Http stream的原理就要从http头和http的传输方式说起,http协议用头部中content-length字段用来表示请求内容或者响应内容的字节长度。如:
Content-Length: 49
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
对于Stream upload来因为要实现通过流来传递数据,所以实现是不知道content-length的,因此content-length被忽略掉。这种情况下Http 服务器会自动的添加上头:Transfer-Encoding: chunked表明需要分包传输,content-length和ransfer-Encoding: chunked是不可能同时在http头不出现的。同时对于流式http,connetion也必须是keep-alive的,因为http协议是基于tcp协议的,在三次握手后才能完成通讯,服务器和客户端可以选择断开连接。
对于chunk data,如下标示:
4\r\n
Wiki\r\n
5\r\n
pedia\r\n
e\r\n
in\r\n\r\nchunks.\r\n
0\r\n
\r\n
要实现http stream最需要解决的问题是关于缓冲区的问题。
stream upload load的关键就是结合流和缓冲区去做,传统的文件上传会将这个文件上传收到服务器一个临时位置后,在转存到应用文件夹中,这时候就会有一个服务器设定的大小限制。对应chunked的,只需要从流中一点点读入文件流就可以了做到了。这涉及到http流的操作。
这里先暂时不去深究原理留在后续文章去探索。直接来看下springboot如何实现。
首先文件上传还是基于multipart-form这个经典的http encoding方式,要实spring-boot实现首先需要引用apache common uload包。看下pom文件
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
主要的包:package org.apache.tomcat.util.http.fileupload.servlet
上传的主要代码如下:
public JsonResult<Object> fileUpload(HttpServletRequest request, Object object /*, Iverifycation iverifycation*/){
int len = -1;
byte[] bytes = new byte[1024];
String filePaths = "";
JsonResult<Object> jsonResult = new JsonResult<>();
ArrayList<StreamingUploadEntity> list = new ArrayList<>();
//判断是否是上传文件的格式
if(ServletFileUpload.isMultipartContent(request)){
//如果是创建上传实例对象ServletFileUpload 此对象可设置文件大小等参数
ServletFileUpload servletFileUpload = new ServletFileUpload();
try {
//通过request 获得FileItemIterator 文件对象
FileItemIterator fileItemIterator = servletFileUpload.getItemIterator(request);
HashMap<String,String> uploadParams = new HashMap<>();
List<String> tmpFiles = new ArrayList<>();
while (fileItemIterator.hasNext()){
//获取文件流
FileItemStream itemStream = fileItemIterator.next();
//通过文件流获取 返回与此文件项对应的multipart表单中的字段名称。
if(!itemStream.isFormField()) {
//文件名称
String fileName = new SimpleDateFormat("yyyyMMddhhmmssSSSS").format(new Date());
String folderFullPath = fileUpload+File.separator+"temp"+File.separator;
String extName = itemStream.getName().substring(itemStream.getName().lastIndexOf("."));
String fileFullPath = folderFullPath + fileName + extName;
filePaths = fileFullPath+"," + filePaths;
//创建文件
File folder = new File(folderFullPath);
//判断此文件夹是否存在 否则创建
if(!folder.exists()){
folder.mkdirs();
}
/*System.out.println("form field :"+name+"\n value:"+ Streams.asString(inputStream)+"detected.");*/
File file = new File(fileFullPath);
//放入写入流
FileOutputStream out = new FileOutputStream(file);
//将写入流放入缓存里
BufferedOutputStream bufferOut = new BufferedOutputStream(out);
InputStream fileInputStream = itemStream.openStream();
//读取当前文件内容
BufferedInputStream bufferinput = new BufferedInputStream(fileInputStream);
while (-1 != (len = bufferinput.read(bytes))){
bufferOut.write(bytes,0,len);
}
bufferinput.close();
bufferOut.flush();
bufferOut.close();
fileInputStream.close();
//存储上传文件路径
tmpFiles.add(fileFullPath);
} else {
String fieldName = itemStream.getFieldName();
InputStreamReader inputStreamReader = new InputStreamReader(itemStream.openStream(),"utf8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
uploadParams.put(fieldName,bufferedReader.readLine());
inputStreamReader.close();
bufferedReader.close();
}
}
需要注意的是,流的方式忽略了原有HttpServletRequest 中的内容,不可以通过HttpServletRequest 拿到其他字段,对于文件和非文件可以通过fileItemIterator遍历后,通过流的isFiled字段去判断。
值得注意的是必须通过一定的配置来关闭spring原有的multi-part的处理,否则fileItemIterator是不能取得数据的。
spring:
servlet:
multipart:
enabled: false
本文为Lokie.Wang原创文章,转载无需和我联系,但请注明来自lokie博客http://lokie.wang