nodejs开发的一个关于Buffer截断导致�字符出现的bug - pod4g/tool GitHub Wiki

一、问题的发现与排查

最近遇到一个很棘手的问题,作者在使用新CMS编辑文章时,点击保存,有很大的概率文章内的某些文字会被替代。

  1. 首先怀疑是输入法的问题,输入了某些不被识别的字符,但是我这边用跟编辑们不一样的输入法测试了一下,也是会出现这个问题,所以不大可能是输入法的问题,故排除;

  2. 因为CMS文章做了LocalStrorage本地缓存,所以怀疑是不是chrome的bug,经过查找资料,发现没有人遇到过这个问题,所以也不大可能是chrome的问题,故排除;

  3. 会不会是数据库的问题呢?经过排查,数据库编码格式完全正确,是没有问题的,打印sql语句发现,在入库之前已经有某些字符被替代了。故排除数据库的问题;

  4. 所以问题肯定出现在nodejs这一层,经过排查,发现是自己写的bodyParser中间件有问题

  bodyParse (req, res, next) {
    let data = ''
    req.setEncoding('utf8')
    // 取出请求数据
    req.on("data", chunk => data += chunk); // eslint-disable-line
    req.on('end', () => {
      req.body = data
      next()
    })
  }

这个问题在于如果数据量足够大,网络比较差,则data事件会执行多次,如果截断的文本截断的位置正好不能被识别(即不能被utf8解码),则会出现不识别的字符被代替的问题。原来在我本机上进行测试从来没有发现过这个问题的原因就在于,虽然数据足够大,但是网络很好且是本机,所以data事件只会执行一次,就没有截断问题。

二、解决办法

// 如果不获取charset也是可以的,基本上没问题,默认都是utf8。
// 但是怕万一上来的数据不是utf8编码的,那就麻烦了,所以干脆处理一下
const getCharset = req => {
  let charset = ''
  if (!req) return charset
  let contentType = null
  if (typeof req === 'string') {
    contentType = req
  } else {
    contentType = req.headers['content-type']
  }
  if (!contentType) return charset
  const charsetReg = /; *charset=(.+)/i
  const match = contentType.match(charsetReg)
  if (!match) return charset
  charset = match[1]
  return charset.trim().toLowerCase()
}

bodyParse (req, res, next) {
    const charset = getCharset(req) || 'utf8'
    // 提前设置charset会报错
    // req.setEncoding(charset)
    const chunks = []
    let totalLength = 0
    req.on('data', chunk => {
      chunks.push(chunk)
      totalLength += chunk.length
    })
    req.on('end', () => {
      // 根据Content-Type的charset,来转换。Buffer的toString默认参数就是utf8
      const data = Buffer.concat(chunks, totalLength).toString(charset)
      req.body = data
      next()
    })
  }

三、一些笔记

  1. data事件的chunk是一个Buffer实例;
  2. += 触发了Buffer实例的toString方法;
  3. Buffer的toString方法参数默认是utf8

四、参考资料

  1. http://apps.timwhitlock.info/unicode/inspect?s=%EF%BF%BD
  2. https://stackoverflow.com/questions/26580265/nodejs-dealing-with-characters-encoding
  3. https://www.npmjs.com/package/iconv-lite

记录于:2017-07-06