小心data事件里的chunk拼接 - CNode技术社区

小心data事件里的chunk拼接
发布于 14 年前 作者 JacksonTian 24181 次浏览 最后一次编辑是 9 年前

这篇文章由于从wordpress升级过来,格式完全不能看了,请访问 <a href="http://cnodejs.org/topic/4faf65852e8fb5bc65113403">这里</a>查看吧,内容一样的。 <br /><br /><br /><br /><br /><br /><br /><br />

最近遇见一个从前没有遇见的陷阱,就是data里的chunk拼接。
由于本人身为前端工程师,对buffer的概念实在是认识不足。这次的场景是我要通过http.get去抓取远端的网页文件,很不小心的是对方的文件编码是gbk(估计是老年代Java环境下的解决方案),而我本地的代码是utf8的编码,最终我需要将两部分代码合并之后输出到客户端,所以我需要将接受到的部分进行转码,转码则需要通过iconv实现。
在这之前我需要将接受到的chunk进行组装。下面是我最原始的组装方式,因为在我的概念中都把他们当做string给组装了。

var data = ""; 
res.on('data', function (chunk) { 
 data += chunk; 
}) 
.on("end", function () { 
 //对data转码 
}); 

很遗憾,我调用:

var iconv = new Iconv('GBK', 'UTF-8'); 
iconv.convert(data).toString(); 

EILSEQ异常被抛出。
其原因是两个chunk(Buffer对象)的拼接并不正常,相当于进行了buffer.toString() + buffer.toString()。如果buffer不是完整的,则toString出来后的string是存在问题的(比如一个中文字被截断)。这样出来的string就无法被iconv正常转码。
那么正确的拼接该是怎样呢,在大神兼好基友@Python发烧友 的帮助指点下,以下代码才是正确的:

var chunks = []; 
var size = 0; 
res.on('data', function (chunk) { 
 chunks.push(chunk); 
 size += chunk.length; 
}); 
res.on('end', function () { 
 var data = null; 
 switch(chunks.length) { 
 case 0: data = new Buffer(0); 
 break; 
 case 1: data = chunks[0]; 
 break; 
 default: 
 data = new Buffer(size); 
 for (var i = 0, pos = 0, l = chunks.length; i < l; i++) { 
 var chunk = chunks[i]; 
 chunk.copy(data, pos); 
 pos += chunk.length; 
 } 
 break; 
 } 
}); 

这时候的data才是一个正确的buffer对象。

但是,对于接收数据而言,这样的场景应当是一个十分常见的场景才对,每次都要写这样一大堆的代码,实在是很费事的。那么我们封装重构吧:

var BufferHelper = function () {
 this.buffers = [];
 this.size = 0;
 this._status = "changed";
};
BufferHelper.prototype.concat = function (buffer) {
 for (var i = 0, l = arguments.length; i < l; i++) {
 this._concat(arguments[i]);
 }
 return this;
};
BufferHelper.prototype._concat = function (buffer) {
 this.buffers.push(buffer);
 this.size = this.size + buffer.length;
 this._status = "changed";
 return this;
};
BufferHelper.prototype._toBuffer = function () {
 var data = null;
 var buffers = this.buffers;
 switch(buffers.length) {
 case 0:
 data = new Buffer(0);
 break;
 case 1:
 data = buffers[0];
 break;
 default:
 data = new Buffer(this.size);
 for (var i = 0, pos = 0, l = buffers.length; i < l; i++) {
 var buffer = buffers[i];
 buffer.copy(data, pos);
 pos += buffer.length;
 }
 break;
 }
 // Cache the computed result
 this._status = "computed";
 this.buffer = data;
 return data;
};
BufferHelper.prototype.toBuffer = function () {
 return this._status === "computed" ? this.buffer : this._toBuffer();
};
BufferHelper.prototype.toString = function () {
 return Buffer.prototype.toString.apply(this.toBuffer(), arguments);
};
module.exports = BufferHelper;

这里有两个私有方法,_concat和_toBuffer。其目的是保证每个方法的职责单一,还在toBuffer里做了一下状态设置,使得不浪费CPU。接下来的调用就非常之简单了。

var http = require('http');
var BufferHelper = require('bufferhelper');
http.createServer(function (request, response) {
 var bufferHelper = new BufferHelper();
 request.on("data", function (chunk) {
 bufferHelper.concat(chunk);
 });
 request.on('end', function () {
 var html = bufferHelper.toBuffer().toString();
 response.writeHead(200);
 response.end(html);
 });
}).listen(8001);

可以看到代码量少了很多,跟第一种方法的使用相差无几。嗯,不虚心的自夸一下,很干净利落:)。
另外可以看到上面的代码是直接require的:

var BufferHelper = require('bufferhelper');

其原因是我已经将其发布到NPM中,可以通过npm install bufferhelper直接搞定。项目地址在github上: https://github.com/JacksonTian/bufferhelper

最后这个lib还没写单元测试,和做压测,之后会添加上。
最后谢谢基友@Python发烧友。
最后,其实node-iconv的作者还提供了一个工具集(https://github.com/bnoordhuis/node-buffertools),是有部分通过c/c++完成的,不过我的需求没那么复杂,只要一个最简单的concat就可以满足了。相信这个bufferhelper对于中文环境下的同学是非常有用的~

13 回复

不错!可以有。

中文貌似很坑爹么~

相当坑爹。

学习了。。。前段时间在做php的时候,也是,默认对方传过来的时候是gbk编码,然后数据怎么转,就是不对。。。原来是在过filter的时候被截断了。。。 <br/>中文问题真麻烦。。。

data += chunk 的作用相当于进行了buffer.toString() + buffer.toString(),这点隐藏得够深,够坑爹。

哈哈,怎么都没头像。。。

正是我需要的资料,谢谢

奇怪, 看编辑记录 5.9 的... 怎么全乱了... @Jackson

楼主把格式整理一下吧。看不下去了。

使得,node-iconv 作为编码转换工具对于只支持 utf-8 的 NodeJS 来说,确实非常必要,否则我们根本就没有机会处理我们喜爱的 GBK 了。

直接在data事件里res.write(data); 在end事件里res.end();

不管什么编码都解决了。自动的。

如果不需要转码或者做任何中间操作的话,这样当然没问题了。

回到顶部

AltStyle によって変換されたページ (->オリジナル) /