Springboot实现Streaming Upload基本篇

近期忙于各种各样的的项目开发,难得有时间来更新文章,话说最近其实因为项目的原因做过和实践过各种各样的技术,这个新主机和域名算起来也一年多了,忙于各种事务一直没有好好的更新文章,从今日开始应该告诉自己需要勤奋一点,尽量多更新这里的文章。
今天先总结下近期因为项目需求在项目内使用的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博客
请先登录后发表评论
  • 最新评论
  • 总共0条评论
  • 本博客使用免费开源的 laravel-bjyblog v5.5.1.1 搭建 © 2014-2018 lokie.wang 版权所有 ICP证:沪ICP备18016993号
  • 联系邮箱:kitche1985@hotmail.com