zoukankan      html  css  js  c++  java
  • 小心buffer的拼接问题 --转

    最近遇见一个从前没有遇见的陷阱,就是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正常转码。
    那么正确的拼接该是怎样呢,在大神兼好基友发烧友 的帮助指点下,以下代码才是正确的:

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

  • 相关阅读:
    app卡顿问题检测--KMCGeigerCounter
    报错---[UIApplication _runWithMainScene:transitionContext:completion:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3505.16/UIApplication.m:3294**
    键盘工具栏的快速集成--IQKeyboardManager
    iOS 对网络视频采集视频截图
    iOS-label出现未知边框线的bug
    iOS开发中图片方向的获取与更改
    通过代码设置button中文字的对齐方式
    util.date
    统计字符串每个字母的个数
    异常处理之多重catch
  • 原文地址:https://www.cnblogs.com/Joans/p/4089544.html
Copyright © 2011-2022 走看看