这格式到底什么意思?比方说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宽度然后又拷回去,但这样不应该得出这样的结果啊.
完整的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据说要废弃,没替代方案岂不傻眼
现在我认为主要是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:心中虽明白,写出来却花了大半小时,郁闷。
唉正如你所说下载都是二进制,和什么编码没有关系的,BUFFER这个相对低效的元素最好不要出现在常用函数中 看了半天不知道你说的是什么乱码,是需要读取的文件名是中文还是什么 如果NODE不支持二进制字符串,自己也是可以实现的,不需要用BUFFER来转 / 不过还是看不明白LZ具体是什么问题
@fireemissa 哦,那直接UTF8即可,这个响应头 content-type:application/force-download; charset=UTF-8 有设置吗,注意后面的UTF-8