扒一扒Nodejs formidable的onPart

话说使用Nodejs实现一个文件上传,还是蛮简单的,基于Express4.x一般也就formidable用的多些吧;基本的不多说了,github一下都会的;接着《也说文件上传之兼容IE789的进度条---丢掉flash》,新版的大文件上传,最后就差断点续传了,业余跟进中...;对于IE789,在文件上传这块,算是与HTML5无缘了,当然我也选择丢掉了flash,就用最原始的input[type="file"]+hideIframe+轮询;OK,IE789可以凉快去了,BSIE!

那么,现代浏览器上就不一样了;大家都知道用HTML5上传大文件必然会选择分段,files API的file.slice(start,end)+formData;简单的将就看吧:

 1 var uploader=function(){
 2
 3   //....
 4
 5   function Files(obj){
 6     this.files=obj.files;
 7     this.__token__=utils.getRandomStr();
 8     this.url=obj.url||location.href;
 9     this.chunkSize=obj.chunkSize||200*1024;
10     this.chunks=Math.ceil(this.files.size/this.chunkSize);
11     this.index=0;
12     this.onprogress=obj.onprogress||function(p){console.log(p);};
13   }
14   Files.prototype={
15     postFiles:function(){
16       var $self=this;
17       //大于50M 断点续传
18       if (this.files.size>50*1024*1024) {
19         var fileReader = new FileReader(),spark = new SparkMD5.ArrayBuffer();
20         fileReader.onload = function (e) {
21               spark.append(e.target.result);
22               $self.hash=spark.end();
23               window.__hash__=$self.hash;
24               var stored=localStorage.getItem(‘fileUploadInfos‘);
25               //断点信息
26               $self.postSlice();
27           };
28         fileReader.readAsArrayBuffer(this.files.slice(0, 10240));
29       }else{
30         this.postSlice();
31       };
32     },
33     postSlice:function(){
34       var $self=this;
35       if (this.index>=this.chunks) {
36         return false;
37       };
38       this.start=this.index*this.chunkSize;
39       this.end=Math.min(this.files.size,this.start+this.chunkSize);
40
41       var self=this;
42       var fd = new FormData();
43       fd.append("sliceData", this.files.slice(this.start,this.end));
44       this.url=//url datas
45       var xhr = new XMLHttpRequest();
46       xhr.upload.addEventListener("progress", function(evt){
47         if (evt.lengthComputable) {
48           var led=self.index*self.chunkSize*1+evt.loaded*1;
49           var p=parseFloat((led)/self.files.size*100).toFixed(2);
50           self.onprogress&&self.onprogress(p);
51         }else {
52           console.log(‘unable to compute‘);
53         }
54       }, false);
55       xhr.addEventListener("load", function(){
56         self.index++;
57         self.postSlice();
58         eval(xhr.responseText);
59       }, false);
60       xhr.open("POST", this.url);
61       // xhr.addEventListener("error", uploadFailed, false);
62       xhr.addEventListener("abort", function () {
63         //记录断点信息
64       }, false);
65       xhr.send(fd);
66     }
67   }
68
69   return {
70     Files:Files
71     //.....
72   }
73 }();
74
75 if (this.files) {
76   var Files=new uploader.Files({
77     files:this.files[0],
78     chunkSize:10*1024*1024,
79     onprogress:function(p){
80       callbk(p);
81     }
82   });
83   Files.postFiles();
84 }

好吧,其实大家都懂,我就不多BB了;还是说formidable吧,既然用到分段上传,formidable的一般做法肯定是行不通的;不过github上人家也说了,onPart或许可以。。。。。。原谅我英语有点low,一知半解;原文这样的:

You may overwrite this method if you are interested in directly accessing the multipart stream. Doing so will disable any‘field‘ / ‘file‘ events processing which would occur otherwise, making you fully responsible for handling the processing.

form.onPart = function(part) { part.addListener(‘data‘, function() { // ... }); }

If you want to use formidable to only handle certain parts for you, you can do so:

form.onPart = function(part) { if (!part.filename) { // let formidable handle all non-file parts form.handlePart(part); } }

也就是我们需要使用onPart来分段接收前端发过来的数据,然后合成一个文件,生成到指定目录;

当使用formData上传时,在request headers里我们会看到有项request payload,也就是我们发送过去的数据,这是未解析的原始数据;那么,难道我们还要自己解析吗?不会玩了。。。

扒一扒formidable的源代码,会发现有好几个_parser结尾的js文件;再看incoming_form.js里有这么一段:

 1 IncomingForm.prototype._parseContentType = function() {
 2   if (this.bytesExpected === 0) {
 3     this._parser = dummyParser(this);
 4     return;
 5   }
 6
 7   if (!this.headers[‘content-type‘]) {
 8     this._error(new Error(‘bad content-type header, no content-type‘));
 9     return;
10   }
11
12   if (this.headers[‘content-type‘].match(/octet-stream/i)) {
13     this._initOctetStream();
14     return;
15   }
16
17   if (this.headers[‘content-type‘].match(/urlencoded/i)) {
18     this._initUrlencoded();
19     return;
20   }
21
22   if (this.headers[‘content-type‘].match(/multipart/i)) {
23     var m = this.headers[‘content-type‘].match(/boundary=(?:"([^"]+)"|([^;]+))/i);
24     if (m) {
25       this._initMultipart(m[1] || m[2]);
26     } else {
27       this._error(new Error(‘bad content-type header, no multipart boundary‘));
28     }
29     return;
30   }
31
32   if (this.headers[‘content-type‘].match(/json/i)) {
33     this._initJSONencoded();
34     return;
35   }
36
37   this._error(new Error(‘bad content-type header, unknown content-type: ‘+this.headers[‘content-type‘]));
38 };

这几条if很是让人欣喜啊,有木有?特别是看到这句:

this.headers[‘content-type‘].match(/boundary=(?:"([^"]+)"|([^;]+))/i);

这不是在解决咱在request headers里看到的request payload吗?终于在心中大喜,咱不用自己解析那堆数据了;接着往下看:

 1 IncomingForm.prototype.onPart = function(part) {
 2   // this method can be overwritten by the user
 3   this.handlePart(part);
 4 };
 5
 6 IncomingForm.prototype.handlePart = function(part) {
 7   var self = this;
 8
 9   if (part.filename === undefined) {
10     var value = ‘‘
11       , decoder = new StringDecoder(this.encoding);
12
13     part.on(‘data‘, function(buffer) {
14       self._fieldsSize += buffer.length;
15       if (self._fieldsSize > self.maxFieldsSize) {
16         self._error(new Error(‘maxFieldsSize exceeded, received ‘+self._fieldsSize+‘ bytes of field data‘));
17         return;
18       }
19       value += decoder.write(buffer);
20     });
21
22     part.on(‘end‘, function() {
23       self.emit(‘field‘, part.name, value);
24     });
25     return;
26   }
27
28   this._flushing++;
29
30   var file = new File({
31     path: this._uploadPath(part.filename),
32     name: part.filename,
33     type: part.mime,
34     hash: self.hash
35   });
36
37   this.emit(‘fileBegin‘, part.name, file);
38
39   file.open();
40   this.openedFiles.push(file);
41
42   part.on(‘data‘, function(buffer) {
43     if (buffer.length == 0) {
44       return;
45     }
46     self.pause();
47     file.write(buffer, function() {
48       self.resume();
49     });
50   });
51
52   part.on(‘end‘, function() {
53     file.end(function() {
54       self._flushing--;
55       self.emit(‘file‘, part.name, file);
56       self._maybeEnd();
57     });
58   });
59 };

至此,终于明白作者的话了;自己处理上传的数据,是在handlePart中通过part.on(‘data‘)和part.on(‘end‘)来收集分段数据,然后生成文件的;那么使用分段上传的话,我们就需要在Nodejs里重写form.handlePart了;

 1 form.handlePart=function(part) {
 2   var dd=[],ll=0;
 3   part.on(‘data‘, function(data) {
 4     if (data.length == 0) {
 5       return;
 6     }
 7     dd.push(data);
 8     ll+=data.length;
 9   });
10
11   part.on(‘end‘, function() {
12       var p=‘./public/imgs/‘+uploadToken+‘_‘+req.query.name;
13       fs.open(p, ‘a‘, function (err, fd) {
14         if (err) {
15           throw err;
16         }
17         fs.write(fd, Buffer.concat(dd,ll),0, ll,0,function(){
18             if (req.query.chunks==req.query.index*1+1) {
19               res.write(bk);
20             }
21             fs.close(fd,function(){});
22             res.end();
23           });
24       });
25     }
26   });
27 }

拿到data后生成文件并不难,fs.writeFile、stream都可以的;原谅我初入Nodejs,怎么感觉最后一步的写入文件,这两种方式都特慢呢?不能忍啊,再探!

试来试去,最后还是选择在接收到第一段数据时就生成文件,之后接收到的数据直接push进去;即上面的fs.write(fd,buffer,offset,length,position,cb);话说明显快了不少呢!而且,意外的收获是:想一想接下来还要实现断点续传呢!想一想,貌似这样做,基本等于Nodejs端的断点续传已经实现了呢;前端记录断点的位置,下次上传时从断点位置开始,然后直接push到这个没上传完的文件里;

到这里,Nodejs端的分段接收文件就可以的了,而且还为之后的断点续传做了个很好的铺垫呢;

好了,对于大文件上传,formidable能做的差不多就这么多了,onPart是必须的;如果大家伙有什么更好的方法,欢迎与我分享!简单的记录,与君共勉,谢谢你能看到这儿!

原文来自:花满楼(http://www.famanoder.com/bokes/57586f3c09c0517810c81633

时间: 2024-10-23 18:53:39

扒一扒Nodejs formidable的onPart的相关文章

扒一扒智能手机里的隐晦财富

自2007年iPhone横空出世之后,全世界都为之倾倒,并迅速投入感情.从美国总统到地铁青年,从第一夫人到东莞站街妹,无时无刻不与智能手机产生暧昧,我们恨不得天天挂到移动互联网上欲仙欲死:每天早上醒来,首先说一句"小屏,你好":而后一天中的任何阶段:吃饭.工作.社交.上厕所全要带着手机,如果有20分钟没打开手机,90%的人就会焦虑,觉得好像跟世界失去联系一样:每天入睡前,枕头边总会有星星点点的手机灯光,饥渴的双眼正做最后的挣扎,以其获取梦中的快感-."低头族"更是作

扒一扒ReentrantLock以及AQS实现原理

提到JAVA加锁,我们通常会想到synchronized关键字或者是Java Concurrent Util(后面简称JCU)包下面的Lock,今天就来扒一扒Lock是如何实现的,比如我们可以先提出一些问题:当我们通实例化一个ReentrantLock并且调用它的lock或unlock的时候,这其中发生了什么?如果多个线程同时对同一个锁实例进行lock或unlcok操作,这其中又发生了什么? AQS ReentrantLock 羊群效应 摘要 提到JAVA加锁,我们通常会想到synchroniz

“MF+”十一月汇:扒一扒APP的四大灵魂

这个月的MF+(妹夫家)活动又开始啦!每个月总有那么一天可以见到各个领域的大拿,小编激动得又不想减肥了. 11月28日,在这个万众瞩目的日子,所有MF+的家庭成员再次来到"言几又"咖啡店参加家族聚会啦.又是熟悉的咖啡店,又是亲切的家族成员,所有的一切都在海风凛冽的冬日给每位家族成员带来了丝丝暖意.今天来参加家族聚会的有我们的大表姐新浪网副总编闻进.二表姐viva副总裁王琳.三表姐网易新闻客户端媒拓主编王雯.大表哥豆果美食副总裁张猛.二表哥百度移动云计算测试部负责人潘钧儒.三表哥夸氪金融

View绘制详解(三),扒一扒View的测量过程

所有东西都是难者不会,会者不难,Android开发中有很多小伙伴觉得自定义View和事件分发或者Binder机制等是难点,其实不然,如果静下心来花点时间把这几个技术点都研究一遍,你会发现其实这些东西都很简单.OK,废话不多说,今天我们就来看看View的测量.View的测量纷繁复杂,不过如果能够做到提纲挈领,其实也不难.那么今天,我们就来扒一扒View的测量.本文主要涉及如下知识点: 1.View的测量 2.在父容器中对View进行测量 3.LinearLayout测量举例 4.最根上容器测量 如

8.8全民健身日,扒一扒音视频互动与健身的那些事儿

8.8全民健身日,扒一扒音视频互动与健身的那些事儿 偶然间,翻开日历,今天是8月8日——全名健身日,作为一名体育运动爱好者.IT工作者,今天就来扒一扒音视频互动与健康的哪些事儿... 北京体博会现场照片,用户正在使用AnyChat与上海世博会现场语音视频连线,并接受中央电视台等媒体采访. (北京市副市长刘敬民在爱动健身营开幕式上致辞) 集成“AnyChat在线音视频互动平台”的“爱动在线运动游戏平台”是2010北京奥运城市体育文化节的一个亮点,集中体现了现代体育的大众性.互动性和趣味性,既满足了

扒一扒Cookie和Session的那些事

首先,众所周知:cookie是客户端技术——cookie是把用户的数据写给用户的浏览器:session是服务端技术——session是把用户的数据写到用户独占的session中. 下面具体来扒一扒他们之间数据保存的区别: cookie保存用户数据的原理: Cookie是客户端技术,程序把每个用户的数据以cookie的形式写给用户各自的浏览器. 当用户使用浏览器再去访问服务器中的web资源时,就会带着各自的数据去.这样,web资源处理的就是用户各自的数据了. session保存用户数据的原理: S

扒一扒系列之开发中常用的Java集合类(ArrayList篇 jdk 1.7)

关于这个系列,因为开发主要用的是java语言,一直想写写java开发中常用的一些类(虽然这才是开始的第三篇>_<),所有就起了“扒一扒”系列.这个系列会有框架,或者其他学到的东西.文章尽量做到简洁,用少的篇幅理顺相关的知识点和使用方法.废话少说,接下来扒一扒ArrayList这个集合类. 一.数据结构 让我们先看下ArrayList所在包的位置,在java.util.ArrayList中(如图1-1),它在jdk中的util中,说明它在开发中比较常用.从它的名字能看出数据结构为一个数组,看一下

扒一扒最近爆火的SDT-LUCK CLUB究竟是什么鬼?

Super Single Dog(超级单身狗)游戏从昨晚上线到现在十分火爆,大批量的玩家进场!今天我们来扒一扒这款区块链去中心化的游戏究竟是什么鬼? Super Single Dog(超级单身狗)是全球知名游戏开发公司LUCK CLUB(幸运俱乐部)旗下首款区块链去中心化游戏.SDT是区块链发展以来最具综合应用的应用产品. 大家都知道,区块链发展以来,经历了可编程货币.可编程金融与可编程智能社会的三个阶段,准确地说就是在应用上引用了挖矿(工作量证明机制).智能合约系统及数据防伪等技术.那么,SD

nodejs --- formidable模块 , post 上传.

1. 只有一个文件域: 1 var formidable = require('formidable'), 2 http = require('http'), 3 util = require('util'); 4 5 http.createServer(function(req, res) { 6 if (req.url == '/upload' && req.method.toLowerCase() == 'post') { 7 // parse a file upload 8 var