最近遇见一个从前没有遇见的陷阱,就是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对于中文环境下的同学是非常有用的~
学习之
oppa高产style
提示buffer.copy(data, pos);
copy typeError呢??
求case
在一次数据比较大的过程中报错。也是提示 object {…(一段数据)has no method copy
学习了
在学习中。感谢。
这个作者已经写入《深入浅出NodeJS》了
我特么就吃惊。。 是这么两句,你这性能差爆了。
var bufferList=[];
bufferList.push(trunk);
onend!
var data = Buffer.concat(bufferList);
可惜 4 的 buffer 已经不是 buffer 了
好像是我理解错了。
@MiguelValentine 来写段更好的,提交进node内核你吧。
@xadillax 没搜到相关信息…可以解释下嘛?
解决了一个困扰很久的问题,谢谢楼主挽救了我所剩无几的头发
好像还不错,之前遇到个GB2312编码的,也是乱码,试试这个方法
bucuo
这坟贴。。。
楼主搞复杂了。直接把chunk push进临时数组。 dataArray.push(chunk) 再调用buffer的concat方便 拼接就可以了。。 Buffer.concat(dataArray).toString(‘base64’)
五年前的坟贴都被翻出来了 😂,Buffer.concat 是 v0.7.11 加入的,好奇去看了一下 v0.7.11 是 2012/06/15 发布的,按时间算这帖子写的时候应该还没有 Buffer.concat 呢。但是本帖最后一次编辑是 6 月前,不知道为什么没有顺便提一句 Buffer.concat。
:翻坟贴系列///Python发烧友难道就是《深入浅出node.js》的那个吗?你说的这个方法跟朴大大的那个差不多的。
赞那两位使用concat的,就这两句就比楼主强。
朴灵的黑历史吗?
@xadillax 我下意识翻了下发帖日期
想当年朴老师还是 由于本人身为前端工程师
@atian25 现在是 你才是前端,你全小区都是前端
。
学习了
对我学习node有点点帮助!
哇,这坟贴,糟心
博主,你代码中的copy方法是哪里来的啊?