关于使用 Connect-Busboy 实现文件上传 优化说明

这篇博文完全上关于上一篇的优化 先看上一篇 node.js 在 Express4.0 框架使用 Connect-Busboy 实现文件上传 因为从上次博客改用 connect-busboy 来上传文件后,发现了明显的一个bug

bug 说明

文件显示上传 100% ,然后预览的时候,偶尔会发现图片只能显示一部分 这种情况在 png 格式 图片尤其严重 昨天重新 review 代码,发现一个bug ,当然和 connect-busboy 一点关系都没有,而是涉及到流的处理过程.


这里把上一篇 blog 里贴出的上传代码在放上来分析一下

function upload(req, res, next) {
   req.busboy.on(‘file‘, function (fieldname, file, filename, encoding, mimetype) {
       var tmp_path=path.join(os.tmpDir(), path.basename(filename));
       file.pipe(fs.createWriteStream(tmp_path));
       file.on(‘end‘,function(){
           var uuid = tool.generateUUID();
           commfile.savePathFile(uuid, mimetype, tmp_path, true, function(err, fileBase64) {
               if (err) {
                   res.json({
                       success:false,
                       url:‘‘
                   });
               }else{
                   res.json({
                       success: true,
                       url: ‘/file/‘ + uuid
                   });
               }
           });
       });
   });

   req.pipe(req.busboy);
   }

针对代码,我们细致理一下 req.busboy.on(‘file‘ ,function(......).....)

1 .req.busboy.on 方法用来监听 form 提交的参数的事件,其中 on(‘file’ ) 是监听 form 提交二进制文件流事件. 上面的监听事件的回调函数传递过来几个参数,我们重点看下 file 参数 file 是二进制文件流句柄,它是一个标准的 stream 2 .接着我取到服务器临时文件目录 ,和 文件名,组合成一个文件路径
类似这样的路径: /Users/zhangzhi/data/temp/aaa.png
var tmp_path=path.join(os.tmpDir(), path.basename(filename));
3 .接着我把 file 流写入上面定义的文件路径
file.pipe(fs.createWriteStream(tmp_path));

  • 这里出现了一个流程冗余问题

上面用到了 pipe 希望读一下我的另外一篇文章了解 pipe 原理 node.js 在io实现异步非阻塞

  1. 然后我读取刚才的临时文件到字节数组,转化为 base64 字符串,存储在了 LevelDB数据库file.on(‘end‘ ....)
  • bug 就出现在这里

我们回头分析一下上面出现的流程冗余问题及bug

流程冗余

假如我的图片是直接保存在服务器的某个文件夹下面,那么上面的 1,2,3 流程是没有问题,(当然不需要临时文件目录,直接写入到服务器指定的保存图片目录即可)
但是我目前的博客图片全部在 levelDB 数据库里,所以没有必要先在临时目录生成图片,然后读取图片字节数组,再转化为base64串写入数据库
中间的弯路就是保存临时文件再读取

我们已经拿到了 file 上传文件流句柄,所以完全可以直接塞到二进制数组中,临时保存,毕竟不是上面巨大文件,对内存容忍度为0 ,如下代码

var dataArr = [];
file.on("data", function (chunk){
      dataArr.push(chunk);
});

上面我们监听 file 的 data 事件,通过源源不断的字节流传输到服务器,我们就不管三七二十一先塞进 dataArr 二进制数组中 那么接着我们要做什么,我们直接可以把上面的 dataArr 数据转化成一个 base64 字符串存入数据库,具体的代码实现在下面,我们该分析下上面的bug了

bug出现

我们上面说到了第 4 步有一个隐形的bug ,在上传文件的时候偶尔出现,尤其是 png 图片时,100% 暴露,叔能忍,婶婶也忍不了啊… 为什么第 4 步这里有bug ? 还是脱离不了整个流程,简单回顾下.



上传的文件流 file 写入到临时文件目录中 ,假设这个过程叫 A 我们读取 临时文件 文件转化为 base64 字符串, 这个过程叫 B 我们应该什么时候去读取 临时文件,应该在 A 过程全部结束, 而 A 过程结束是指 file 上传文件流全部传完 并且 文件流全部写入到了临时文件 但是我们上面 监听的 file.on(‘end’,function(){…} 只是监听上传文件流结束的事件,具体这个文件流是否完全写入到临时文件,根本不知道,但是这时我们已经做了一个错误的决定,就是开始读取 临时文件 转化为base64 字符串 所以: 当文件流仅仅是通过 http 传完时,但是并没有写完到临时文件,这是我们立即读取图片,一定是不完整的图片

如果这里一定要这样去做,改如果修改一下代码呢:

function upload(req, res, next) {
    req.busboy.on(‘file‘, function (fieldname, file, filename, encoding, mimetype) {
        var tmp_path=path.join(os.tmpDir(), path.basename(filename));
        var passedLength = 0;
        var writeStream = fs.createWriteStream(tmp_path);
        file.on(‘data‘,function(trunk){
                passedLength += chunk.length;
                 if (writeStream.write(chunk) === false) {
                        file.pause();
                 }
        });
        file.on(‘end‘,function(){
            //当没有数据读取,关闭写入数据流
            writeStream.end();
        });
        writeStream.on(‘drain‘, function() {
        //速度写入完毕后
        //这里我们才去执行把图片文件转换成 base64 字符串写入数据库操作
        var uuid = tool.generateUUID();
            commfile.savePathFile(uuid, mimetype, tmp_path, true, function(err, fileBase64) {
                if (err) {
                    res.json({
                        success:false,
                        url:‘‘
                    });
                }else{
                    res.json({
                        success: true,
                        url: ‘/file/‘ + uuid
                    });
                }
           });
        });

    req.pipe(req.busboy);
    }

上面的代码是没有经过测试的,直接敲上去,只是为了说明按照原来流程的正确处理方式


流程问题理清了,bug 也找到了.下面做如下修复

先上代码

req.busboy.on(‘file‘, function (fieldname, file, filename, encoding, mimetype) {
        var dataArr = [], len = 0,strBase64;
        file.on("data", function (chunk){
            dataArr.push(chunk);
            len += chunk.length;
        });
        file.on("end", function () {
            strBase64 = Buffer.concat( dataArr, len ).toString(‘base64‘);
            var uuid=tool.generateUUID();
            commfile.saveFile(uuid,mimetype,strBase64,function(err){
                if (err) {
                    res.json({
                        success:false,
                        url:‘‘
                    });
                }else{
                    res.json({
                        success: true,
                        url: ‘/file/‘ + uuid
                    });
                }
            })
        });

    });

    req.pipe(req.busboy);

简单说明一下上面的代码 file.on(“data” 事件实时把字节流写入数组中 file.on(“end” 当上传文件字节流结束后, 把字节数组转化为 base64 字符串 strBase64 = Buffer.concat( dataArr, len ).toString(‘base64’); 最后我们把 base64 字符串写入数据库 这样上传图片再也不会出现写入数据库的图片不完整的bug 了.

出自:关于使用 Connect-Busboy 实现文件上传 优化说明

你可能感兴趣

时间: 2024-08-24 10:44:35

关于使用 Connect-Busboy 实现文件上传 优化说明的相关文章

springMVC3学习(十二)--文件上传优化CommonsMultipartResolver

基于上一篇文件上传发现效率很慢,我们应该对它进行优化  使用springMVC对文件上传的解析器 来处理文件上传的时候需要在spring的applicationContext里面加上springMVC提供的MultipartResolver的申明 这样客户端请求的时候 springMVC会检查request里面是否包含多媒体信息 如果包含了就会使用MultipartResolver进行解析, springMVC会使用一个支持文件  处理的MultipartHttpServletRequest来包

springMVC文件上传优化

1. 新建web project 2. 填 jar,注意新加入两个上传文件的jar, commons-io, commons-fileupload 3. 改写web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="

【springMVC】之文件上传

通过前两篇博客的学习,想必大家对springMVC已经有了一个基本的认识.今天我们主要来学习一下springMVC两种文件上传的方式. 首先介绍第一种,通过字节流的方式实现文件上传.首先创建一个upload.jsp页面 <body> <h>添加用户</h> <!-- entype要声音和支撑这种类型的,保证文件上传不会被解码--> <!-- form表单,action是说讲这个表单提交到什么路径: method="post"是提交方

TCP实现文件上传

文件上传分析 一.基本实现 1.服务端 public class FileUpload_Server { public static void main(String[] args) throws IOException { System.out.println("服务器 启动..... "); // 1. 创建服务端ServerSocket ServerSocket serverSocket = new ServerSocket(8888); // 2. 建立连接 Socket ac

3Python全栈之路系列之基于socket实现文件上传

Python全栈之路系列之基于socket实现文件上传 发布时间:2017年3月16日 00:04 浏览(106) 评论(0) 分类:Python 前言 此处没有前言 粘包 在实现发送文件功能之前我们先来理解下粘包的问题,下面有两张图,我觉得很清晰的就可以理解到了. 正常情况下发送文件 第一步: 客户端把获取到的文件总大小(size=65426)先放到缓冲区,然后发送给服务端 第二步: 此时客户端接收到的文件总大小就是65426 粘包的问题下发送文件 第一步: 客户端把获取到的文件总大小(siz

使用Paramiko实现SSH登陆,文件上传下载

1,SSh登陆命令行实现: #!/usr/bin/env python # encoding: utf-8 import paramiko private_key_path = '/Users/aolens/.ssh/id_rsa' key = paramiko.RSAKey.from_private_key_file(private_key_path) ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.Aut

windows 下文件上传到fastdfs

php.ini 配置 [fastdfs]; the base pathfastdfs_client.base_path = D:/tmp; connect timeout in seconds; default value is 30sfastdfs_client.connect_timeout = 2; network timeout in seconds; default value is 30sfastdfs_client.network_timeout = 60 ; standard l

JavaWeb之文件上传与下载&amp;邮件技术(十八)

文件上传 简介 最近发现这几篇很少写简介,说自己没时间写,可能是说服自己吧.总之能多写就多写.还有个一直存在的问题,每天写的博客质量不是很好.再次说服自己,现在处于学习阶段,写博客为了巩固知识点,以便后期作复习使用.其实我每次写博客之前笔记老早都在nopad++上写好了,所以大部分在wlw上直接粘贴的,请见谅 1. 文件上传必要前提 1. form表单:属性enctype必须取值为multipart/form-data enctype的默认值是:application/x-www-form-ur

JAVA中使用FTPClient实现文件上传下载

在JAVA程序中,经常需要和FTP打交道,比如向FTP服务器上传文件.下载文件,本文简单介绍如何利用jakarta commons中的FTPClient(在commons-net包中)实现上传下载文件. 一.上传文件 原理就不介绍了,大家直接看代码吧 /** * Description: 向FTP服务器上传文件 * @Version1.0 Jul 27, 2008 4:31:09 PM by 崔红保([email protected])创建 * @param url FTP服务器hostname