转载

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

背景:目前,在开发基于微信的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 Buffer 和 buf.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的正确数据。。。其它的错误都是“白忙活”惹的祸!!

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

正文到此结束
Loading...