Node.js 不深也不浅得了解下编码

659 查看

背景:目前,在开发基于微信的Web App应用,也就是借助微信所有资源,如公众号,账号系统和扫描JS-JDK等。后端是用node做中间件,依赖API服务(坑爹的是,API服务是用base64保存图片...)。现需实现一功能:用户选择图片,然后调用微信JS-JDK API,再上传至微信服务器(不能直接从微信里发送选择的图片),最后重微信服务器下载下来保存至我们自己服务器里。(下载下来后,需要转成data URIs)

关键代码:

javascriptcontroller.action('image', function * (next) {
  ......

  token = yield base.getAccessToken(); // 获取access_token
  url = resource.genFetchImage(token, mediaId); // 组合请求图片的链接

  response = yield request.get(url); // 通过co-request向微信服务器发出请求

  // 处理响应,编码成base64
  type = response.headers["content-type"];
  prefix = "data:" + type + ";base64,";
  base64 = new Buffer(response.body, 'binary').toString('base64');

  this.body = prefix + base64;
  yield next;
});

最后的响应结果也是有模有样的,看起来也很像data URIs,坑爹的这货是假的,假的。。。Google,百度,啥啥的都找了一遍,还是没结果(我寻找问题答案时,时常定位不准。。。),有以下答案的:

方案一:

javascript// 别说了,网上的十有八九就是我上面那种方式!

方案二:

javascript...
base64 = new Buffer(response.body).toString('base64');

方案三:

javascript...
base64 = response.body.toString('base64');

方案四:

javascript...
base64 = new Buffer(response.body, 'utf8').toString('base64');

一一试了个遍,结果千奇百怪,千万个草泥马奔腾啊!!!都不行,后来还是解决了,才有这文章。其实原理很简单,只是对编码的理解不够而已。(小白我前端一枚,目前在较为深入学习node,想走全栈,反正路还很长呢!!)

正确方案:

javascript...
// 通过co-request向微信服务器发出请求
response = yield request.get({
  url: url,
  encoding: null // 指定编码
});

response = response.body.toString('base64');

重点在request.get的参数上,{ encoding: null },我会慢慢讲解下去(大神勿喷!!)

--------------------------分割线--------------------------

在这里会涉及几个关键知识

  • Buffer对象
  • 字符编码,uf8,binary和base64等
  • co-request和request NPM包

Buffer对象

Buffer是一个像Array的对象,但它主要用于操作字节,也就是二进制数据,它的元素为16进制的两位数,即0到255的数值。(欲想较为深入了解,看《深入浅出nodejs》)

关键API,具体参考new Bufferbuf.toString

javascriptnew Buffer(str[, encoding]) 

javascriptbuf.toString([encoding][, start][, end])

字符编码,ascii, utf8,binary和base64等

字符编码,简单讲就是将我们显示器看到的字符编码成计算机识别的位(bit),比如:

  • 小写字母 a 通过ascii编码成 0110 0001,十六进制表示成 0x61,占8位,1字节
  • 中文 通过utf8 编码成 1110 0110 1000 1000 1001 0001,十六进制表示成 0xE68891,占24位,3字节

这里有几个关键点:

  • utf8编码是常用的字符编码,它向下兼容ascii编码。并不是所有的 byte串 都能成功解码成人们能识别的 chat串,它是有解码算法(参考wiki),所以我们像���\u001d�)u�m\u001f�\u001a�͸��E这样常见的乱码是由于解码出错造成的。
  • 对于不能识别的byte串会解码成重点这货竟然有相应的utf8编码,编码为0xFFFD。这里有个关键点,很多byte串是无法正确解码的,但他们都会用表示,而字符又只有一种编码,所以对二进制数据如:图片,视频等,通过utf8编码并保存到变量后,是无法通过utf8原样解码成原来二进制的。
  • binary编码,也就是二进制编码,通常通过consle打印,为了“好看”会打印成16进制。
  • Base64是一种基于64个可打印字符来表示二进制数据的表示方法。对于我通常会用于将图片转换成data URLs,为了减少请求,或充分利用localStorage等。

co-request和request NPM包

request是非常非常强大的模拟浏览器发送HTTP请求的模块,非常非常强大!!而co-request,是通过TJ大神写的co模块简单对request包装了下,实现 yield + promise 优雅实现异步控制流,摆脱倒金字塔的利器!!

--------------------------分割线--------------------------

好,基础知识就差不多了,回到我之前遇到的问题上,并对其讲解下:

javascript  response = yield request.get(url); // 通过co-request向微信服务器发出请求

  // 处理响应,编码成base64
  type = response.headers["content-type"];
  prefix = "data:" + type + ";base64,";
  base64 = new Buffer(response.body, 'binary').toString('base64');

为什么这段代码有问题??对于request方法它有一个关键的参数encoding,默认值是utf8,所以response.body的值已经乱码的字符,再所以new Buffer(response.body, 'binary')将其转换成二进制时,已经不是原来的二进制了,这解释了为什么“最后的响应结果也是有模有样的,看起来也很像data URIs,坑爹的这货是假的,假的。。。”。而通过将encoding设为null,也就不对原始数据编码,保持原始的二进制图片数据。

网上的大多数的方案一,其实是没有错的!!!错在我遇到的错误和解决方案不拉边,它适用于通过fs.createReadStream(path[, options]) 读取本地图片,并转换成base64编码。

而方案四我觉得特别有趣

javascript...
base64 = new Buffer(response.body, 'utf8').toString('base64');

我想,response.body竟然是utf8编码的,那我可以通过new Buffer(response.body, 'utf8'),将其反编码成二进制数据(也就是binary),然后再转换成base64,结果试了不行!!!最后痛苦的想了一遍,才发现自己脑短路了,也许这问题对很对大神来说很明显错误,可我还是觉得有趣,不懂的可以仔细想想。

到了最后了,我想答案很明显了,原理很简单,无非原始的二进制数据才是转换base64的正确数据。。。其它的错误都是“白忙活”惹的祸!!

小白之手,大神勿喷,欢迎意见,及时添正!