关于Buffer的binary格式问题.
发布于 12 年前 作者 fireemissa 14465 次浏览 最后一次编辑是 8 年前

这格式到底什么意思?比方说console.log(new Buffer(‘牛’)); 输出<Buffer,e7,89,9b> e7,89,9b是utf-8没错. 然后 var buf0=new Buffer(‘牛’); console.log(new Buffer(buf0.toString(‘binary’))); 输出<Buffer,c3,a7,c2,89,c2,9b> 这转换算法怎么来的?我追踪了下V8代码,无果,某地转换成binary似乎是把字符全部转到int16_t宽度然后又拷回去,但这样不应该得出这样的结果啊.

10 回复

完整的Buffer构造函数是new Buffer(str, [encoding]), encoding的默认值是utf-8。 console.log(new Buffer(buf0.toString('binary'))); 等于 console.log(new Buffer(buf0.toString('binary'),'utf-8')); 这里第二个初始化参数encoding不能用默认的utf-8,而应该用 ?(你猜)。

我把工程告诉你们好了:enter link description here

file.js里面 让人下载文件的代码:

 	var filename = allpath.replace(/^.*[\\\/]/, '');	     		 
     		 	var buf0=new Buffer(filename);
     			
     		 	 res.download(allpath,buf0.toString('binary'));
     		 	  return;

这里filename 本身就是utf-8的,但我非得用binary参数否则中文的文件名乱码.我想知道原因,因为 binary据说要废弃,没替代方案岂不傻眼

你这种下载是错误且低效的

自己看API实现吧,不要迷信这些仅仅实现了功能的框架

现在我认为主要是Buffer问题,用api实现也要附加上文件名啊. 和express的实现里set(‘Content-Disposition’, ‘attachment; filename="’+filename+’"'会有什么不同?不就是文件名数据不同嘛.

如此,实际你想问的其实与本帖的主题关系不大,虽然你认为是Buffer出问题了,不过呢这只是表象,导致中文乱码的元凶是http模块发送header的方式。 先从结论而言:nodejs仅支持单字节字符(准确的说是ascii)的header,所有的多字节字符都会被去掉高位字节,只保留最低位字符。

filename='中文名.txt';// \u4e2d\u6587\u540d.txt;js内部字符编码是unicode
res.download(allpath,filename);

客户端得到的文件名就变成:\x2d\x87\x0d.txt,高位被去掉了。 testcase: https://gist.github.com/shiedman/5472925

进一步挖掘源码看看究竟怎么回事,从response.write这个方法开始追踪,最终定位到OutgoingMessage.prototype._send(https://github.com/joyent/node/blob/v0.10.5-release/lib/http.js#L488)

  this.output.unshift(this._header);
  this.outputEncodings.unshift('ascii');

实际的写入操作发生在OutgoingMessage.prototype._writeRaw(https://github.com/joyent/node/blob/v0.10.5-release/lib/http.js#L505)

  var c = this.output.shift();
  var e = this.outputEncodings.shift();
  this.connection.write(c, e);

一入队一出队,相互对应,header的发送相当于固定为connection.write(header,'ascii'),当header包含中文字符,高字节被削,然后乱码;当header包含binary编码的字符,与ascii同属单字节,字节流得以完整保留,文件名显示正常。(PS:response.end的实现与response.write稍有出入,在特定的情况下可指定header的编码,有兴趣的同学可翻下源码)

至于如何输出正确的中文文件名,提供2个思路: 1.拦截_writeRaw的调用,将this._header的对应outputEncoding设为utf-8后,恢复执行流程

var _fn=res._writeRaw;
res._writeRaw=function(){
    res.output.forEach(function(e,i){
        if(e==res._headers){res.outputEncodings[i]=='utf-8';}
    })
    _fn.apply(res,arguments);
}

(大概如此,未实际验证)

2.遵循RFC规范 详细的讨论见http://stackoverflow.com/questions/93551/how-to-encode-the-filename-parameter-of-content-disposition-header-in-http

 var userAgent=(request.headers['user-agent']||'').toLowerCase();
    if(userAgent.indexOf('msie')>=0 || userAgent.indexOf('chrome')>=0){
        headers['Content-Disposition']='attachment; filename='+encodeURIComponent(filename);
    }else if(userAgent.indexOf('firefox')>=0){
        headers['Content-Disposition']='attachment; filename*="utf8\'\''+encodeURIComponent(filename)+'"';
    } else{
        /** safari等其他非主流浏览器只能自求多福了 **/
        headers['Content-Disposition']='attachment; filename='+new Buffer(filename).toString('binary');
    }

除开某个畸形的IE版本和特殊的浏览器,大部分情况下都能正确显示中文名。 PS:心中虽明白,写出来却花了大半小时,郁闷。

谢了,不过这些解决方案一点不优雅,还是继续binary好了…这应该算nodejs的bug,也许后面会矫正.

唉正如你所说下载都是二进制,和什么编码没有关系的,BUFFER这个相对低效的元素最好不要出现在常用函数中 看了半天不知道你说的是什么乱码,是需要读取的文件名是中文还是什么 如果NODE不支持二进制字符串,自己也是可以实现的,不需要用BUFFER来转 / 不过还是看不明白LZ具体是什么问题

因为BINARY STRING就是“乱码”,对UNICODE友好的JS不合适处理这种东西,NODE才要把它去除

@seasonx4 就是指浏览器下载文件时弹出的文件名里中文字是乱码,不管传的是utf8格式的String还是Buffer

@fireemissa 哦,那直接UTF8即可,这个响应头 content-type:application/force-download; charset=UTF-8 有设置吗,注意后面的UTF-8

回到顶部