首页 > 代码库 > [故障引起的故事]URL中带加号的处理

[故障引起的故事]URL中带加号的处理

问题起因: 
    客户订购了一关键字为"e+h变送器" , 在首页推荐广告中,会根据用户在search搜索过的关键字进行一个匹配投放。技术实现是UED通过JS获取cookie中的h_keys内容,拼装到 http://xxxxx/advert/ctp_advert.htm?num=4&keyword={keyword}。这里取出来对应的cookie信息为中文,最后通过一个ajax发起一个GET请求。

    所以针对最后的请求是:http://xxxxxx/advert/ctp_advert.htm?num=4&keyword=e+h变送器。 而在服务端接受到对应的请求参数时,发现参数为:e h变送器, +号没了。 初步怀疑跟URL规范相关,需要进行url encode。


问题分析:

    查了下JS encode的相关内容, 总于发现+号的秘密。
   html中因为一些非标准的做法,将+等同于空格进行处理(当Html的表单被提交时, 每个表单域都会被Url编码之后才在被发送。由于历史的原因,表单使用的Url编码实现并不符合最新的标准。例如对于空格使用的编码并不是%20,而是+号,如果表单使用的是Post方法提交的,我们可以在HTTP头中看到有一个Content-Type的header,值为 application/x-www-form-urlencoded,大部分应用程序均能处理这种非标准实现的Url编码)。
    在搜索引擎中做了下尝试: 
    keyword =  e h变送器  , url =http://www.google.cn/search?hl=zh-CN&newwindow=1&q=e+h变送器   (空格被转化为+号)
    keyword = e+ h变送器 , url = http://www.google.cn/search?hl=zh-CN&newwindow=1&q=e%2Bh变送器  (+号被进行了转义为%2B,程序才能正常处理)
   


问题解决:

思路1:
    1.  要想正常传输+号而不被转义为空格,需要进行进行编码为%2B。查了下几个编码函数,发现只有encodeURIComponent 才会对+号进行编码处理。
    2. encodeURIComponent默认为采用UTF-8字符集,理论上只需要在原先的请求中添加_input_charset=utf-8(由 pipeline中的SetLocaleValve进行解析),就可以得到正确的 e+h变送器。
   
    在实施过程中,发现结果并不是预期的那样。 客户端通过js encode后,在服务端解析后一直是乱码。查了下byte,发现服务端一直是用GBK在进行解析, 针对变送器的UTF-8编码的byte为{-27,-113,-104,-23,-128,-127},客户端用GBK解析后变为{-27.-113.- 104.-23,-63,-63},针对最后两byte因为字符不可见,导致全部被替换为-63。网上查了下,针对 utf-8 -> gbk -> utf-8 在一定情况下就会出现该问题(http://lingqi1818.iteye.com/blog/348953)。


思路2:
    继续追查对应的_input_charset=utf-8未生效的原因,DEBUG看到在SetLocaleValve中的确设置了 request.setCharsetEncoding为utf-8。初步怀疑是否跟jboss server的配置有关,查了下跟URIEncoding 和useBodyEncodingForURI 设置有关。 目前公司所使用的jboss为4.05,对应俄tomact配置中只指定了对应的URIEncoding=GBK。正因为这样,导致设置的_input_charset针对GBK的提交没有效果,还是按照GBK进行解析。

    1.  考虑将请求由GET换成POST , 这样就可以使用_input_charset

    但在实施过程中,和UED沟通过程,针对POST的会引起一个跨域请求的问题。此方案又只能做罢


思路3 (实践成功):
   
    1.  UED进行伪url encode的实现 , 将+号进行%2B的编码。因为目前JS中没有现成的函数,这里只是通过replace(/\+/g,‘%2B‘) 进行了转化。


总结

针对+号的处理,针对不同的业务场景需要不同的处理方案,描述下几种场景:
1. 非Ajax 请求
    可以直接使用Form表单的 GET ,POST的urlencode协议,自动实现+ => %2B 的转化
2.  Ajax 请求
    * GET请求 : 很无奈,只能使用方案3,人为进行+号转化。
    * POST请求(同一应用,非跨域请 求) :  使用encodeURIComponent + _input_charset=utf-8 指定编码进行处理。

ps: 前面提的这几种方案,都是基于+号是正常的业务场景进行考虑。同时我们也可以从业务层面进行一个梳理,+号处理是否有其必要性,能从业务数据入口直接规避 那就最好了。

 


背景知识:


URIEncoding和useBodyEncodingForURI

    对于URL提交的数据和表单中GET方式提交的数据,在接收数据的JSP中设置request.setCharacterEncoding参数是不行的, 因为在Tomcat5.0中,默认情况下使用ISO- 8859-1对URL提交的数据和表单中GET方式提交的数据进行重新编码(解码),而不使用该参数对URL提交的数据和表单中GET方式提交的数据进行 重新编码(解码)。要解决该问题,应该在Tomcat的配置文件的Connector标签中设置useBodyEncodingForURI或者 URIEncoding属性,其中useBodyEncodingForURI参数表示是否用request.setCharacterEncoding 参数对URL提交的数据和表单中GET方式提交的数据进行重新编码,在默认情 况下,该参数为false(Tomcat4.0中该参数默认为true); URIEncoding参数指定对所有GET方式请求(包括URL提交的数据和表单中GET方式提交的数据)进行统一的重新编码(解码)的编码。URIEncoding和useBodyEncodingForURI区别是,URIEncoding是对所有GET方式的请求的数据进行统一的重新编码 (解码),而useBodyEncodingForURI则是根据响应该请求的页面的request.setCharacterEncoding参数对数据进行的重新编码(解码),不同的页面可以有不同的重新编码(解码)的编码。所以对于URL提交的数据和表单中GET方式提交的数据,可以修改 URIEncoding参数为浏览器编码或者修改useBodyEncodingForURI为true,并且在获得数据的JSP页面中 request.setCharacterEncoding参数设置成浏览器编码。


为什么需要Url编码
1.  Url中有些字符会引起歧义 , =,&号等
2.  Url的编码格式采用的是ASCII码,而不是Unicode,这也就是说你不能在Url中包含任何非ASCII字符,例如中文


哪些字符需要编码
RFC3986文档规定,Url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符。
Url可以划分成若干个组件,协议、主机、路径等。RFC3986中指定了以下字符为保留字符: ! * ‘ ( ) ; : @ & = + $ , / ? # [ ]


如何对Url中的非法字符进行编码
Url编码通常也被称为百分号编码(Url Encoding,alsoknown as percent-encoding),是因为它的编码方式非常简单,使用%百分号加上两位的字符——0123456789ABCDEF——代表一个字节的 十六进制形式。Url编码默认使用的字符集是US-ASCII。例如a在US-ASCII码中对应的字节是0x61,那么Url编码之后得到的就是%61,我们在地址栏上输入http://g.cn/search?q=%61%62%63,实际上就等同于在google上搜索abc了。又如@符号在ASCII字符集中对应的字节为0x40,经过Url编码之后得到的是%40。

[故障引起的故事]URL中带加号的处理