日常工作中,中文乱码算是比较常见的问题了,大家或多或少都遇到过。这里简单分析下我遇到过的乱码场景,错误和遗漏之处,欢迎大家补充、纠正和交流。
一、常见乱码问题
1.1 Ajax请求中文参数乱码
发送Ajax请求时,如果参数中有中文,服务端获取到参数后,有可能乱码。
1.2 外联js代码中文字符乱码
外联方式引入js文件,如果js的代码中有中文,有可能乱码。
下面就来依次分析这两个场景。
二、Ajax请求中文参数乱码
Ajax请求分为GET和POST两种方式,对于这2种方式,Webx框架的处理不尽相同。此外,在客户端浏览器的处理上,也有可能出现一些中文编码字符集的不确定性。
2.1 知识点
首先介绍下encodeURIComponent('xxx'),这个js原生函数采用UTF-8编码,js代码中对中文字符进行显式编码时,基本上用的都是该函数。
接下来介绍下编码的基本知识,在此copy一下Webx文档的一段话,如下。
2.1.1 GET请求
对于GET方式的Ajax请求,如果未显式执行encodeURIComponent对中文参数进行编码,那么浏览器会根据“输出字符集”对中文字符进行编码,Webx默认配置的是GBK输出字符集,也就是说浏览器极有可能采用GBK字符集进行中文字符编码。当前,也不排除个别浏览器在实现上不是这个套路。如果使用了某些JS库的Ajax组件,JS库也有可能额外搞一些动作。而对于英文操作系统,由于我没有测试过,也不确定是否会影响到浏览器的编码字符集。因此存在着一定的编码字符集不确定性。
在服务端对GET请求的处理过程中,Webx框架没有采用Servlet引擎的解码方式,而是另起炉灶,特殊进行了参数解析和字符解码。需要特别说明的是,Webx在此之前,统一设置了CharacterEncoding,Webx默认配置的是GBK输入字符集。GET请求参数解析和字符解码的关键代码如下图所示。
key = decode(key);
value = decode(value);
详细代码可参见Webx3的com.alibaba.citrus.service.requestcontext.parser.impl.ParameterParserImpl类,以构造函数为入口进行阅读。
2.1.2 POST请求
对于POST方式的Ajax请求,字符编码一律采用UTF-8,这应该是Ajax的一个规范。在KISSY的代码和Webx的文档中都有阐述,Ajax规范还有待进一步研究确认。
对于非Ajax的正常表单提交,字符编码会采用web页面的字符集,对于淘宝的页面来讲,通常就是GBK。
在服务端对POST请求的处理过程中,Webx框架采用了Servlet引擎提供的解码方式。仍然需要特别说明的是,Webx在Servlet引擎解码之前,也统一设置了HttpServletRequest的CharacterEncoding,即“输入字符集”,Webx默认配置的是GBK,与GET请求设置CharacterEncoding是在同一个地方。如下所示。
// 试图从queryString中取得inputCharset
String queryString = getRequest().getQueryString();
String inputCharset = locale.getCharset().name();
if (queryString != null) {
Matcher matcher = inputCharsetPattern.matcher(queryString);
if (matcher.find()) {
String charset = matcher.group(1);
if (LocaleUtil.isCharsetSupported(charset)) {
inputCharset = charset;
}
}
}
getRequest().setCharacterEncoding(inputCharset);
详细代码可参见Webx3的com.alibaba.citrus.service.requestcontext.locale.impl.SetLocaleRequestContextImpl类,以prepare方法为入口进行阅读。
2.2 乱码原因
由以上知识点可知,我们通常遇到的乱码一般是由于客户端对中文采用了UTF-8编码,而服务端采用GBK解码导致。
2.3 解决方法
2.3.1 对于GET请求产生的乱码,通常需要做两件事情。第一,使用encodeURIComponent对中文字符进行编码,消除编码字符集的不确定性。第二,需要在url中额外增加 _input_charset 参数,值为UTF-8,这个参数是Webx预留的参数,可以优先设置本次请求的解码字符集,核心代码同2.1.2的代码贴图,这里补充一下webx.xml的配置和匹配_input_charset 参数的正则Pattern,如下所示。
String INPUT_CHARSET_PARAM_DEFAULT = "_input_charset";
inputCharsetParam = defaultIfNull(inputCharsetParam, INPUT_CHARSET_PARAM_DEFAULT);
inputCharsetPattern = Pattern.compile(inputCharsetParam + "=([w-]+)");
这里还有另外一种处理方式,即寻找第三方js库,提供GBK编码的encodeUri函数,可以免去url中的_input_charset 参数。
2.3.2 对于POST请求产生的乱码,通常只需要在url(请注意是url,不是表单参数)中额外增加 _input_charset 参数,值为UTF-8即可。
如果这样处理仍然有问题,并且使用的是KISSY库的Ajax组件,那么可以先使用encodeURIComponent对中文字符进行编码,然后再发送Ajax请求,这时候url中是否有_input_charset 参数已经无关紧要。之后,服务端业务代码要显式执行URLDecoder.decode("xxx","UTF-8"),即可获取到正确的中文字符。这里面发生了一些有趣的事情,简单YY下。在显式执行encodeURIComponent之后,发送Ajax请求之前,KISSY的Ajax组件又额外进行了一次encodeURIComponent。以“测试”举例,经过一次encodeURIComponent处理后的值是“测试”,再经过一次encodeURIComponent,值变成了“%25E6%25B5%258B%25E8%25AF%2595”,这样无论Webx框架采用何种输入字符集,Servlet引擎解码后,值都会恢复成“测试”,之后业务代码再显式执行URLDecoder # decode,就拿到中文字符了。
KISSY额外的一次encodeURIComponent,发生在对传入的表单数组进行S.param的时候,可以参看源码。
三、外联js代码中文字符乱码
3.1 知识点
web页面引用外联JS,若未加特殊处理,一般会按照web页面的字符集对外联JS进行解码。
浏览器解码的字符集,一般按照以下优先级判断。http header(”content-type:text/html; charset=xxx”)优先级最高,如http-header未指定则依据html ,如果http-header和html-meta都没有指定,那么一般就是依据文件的BOM编码格式。
3.2 乱码原因
我遇到的乱码都是发生在daily环境。
淘宝的web页面基本都是GBK字符集。
淘宝外联的JS,daily环境放在assets测试服务器上,线上环境放在tbcdn上,编码字符集由JS文件的BOM决定。
前端同学编写JS代码,在保存时一般都采用UTF-8编码。
因此,用GBK解析UTF-8编码的中文字符,就出现乱码了。
3.3 解决方法
3.3.1 对script单独设置解码字符集,,这样处理后,该script的解码就按照指定的charset进行。
3.3.2 前端采用GBK字符集保存代码并提交。这个应该不太可行,前端采用UTF-8编码应该有一些原因,至于是对压缩有影响,还是其它的原因,有待深入探究。
3.3.3 采用ucool等工具作代理,并将代理文件以GBK字符集保存。
3.3.4 直接引用压缩之后的-min.js,这样会给debug带来困难,但肯定不会有乱码问题。因为淘宝使用的压缩工具,会将所有的中文字符替换成Unicode编码。因为线上引用的都是-min.js,所以就没有乱码问题。由此可见,压缩JS不单是缩短客户端下载时间,也能够避免乱码问题。