中文化和国际化问题权威解析之五:URL编码/Misc

通过前面中文化、国际化问题解决的系列1-4,相信大家对字符集、字符编码、字符解码、字符乱码、Java中文问题解决等都有了一个比较清晰的认识;但文中的信息并非包罗万象,结合到自己平时对于字符集、编码相关的一些疑惑,本篇对一些前文中并未提及的一些问题进行补充,以便让该系列更加完善和全面;本文主要解决以下两个问题:其一,解决UltraEdit菜单中的 文件 -> 转换 子菜单中涉及的一些名词疑惑,主要涉及EBCDIC 、OEM字符集、ANSI字符集、HZ编码等;其二,补充关于URL编码相关的一些知识点,涉及浏览器、Web服务器设置、Servlet规范等;

更多的与字符集相关的专业词汇简介

相信很多同学都有在用UltraEdit这个文本编辑器软件,从菜单 文件 --> 转换 进入即可看见很多的字符集间的转换子菜单,不同版本间可能会有些差异,可以参考下图:

 中文化和国际化问题权威解析之五:URL编码/Misc_第1张图片

从上图中我们可以看到很多前面已经提及的一些词汇,比如ASCII、Unicode [Big Endian/Little Endian]、UTF-8等;但还有一些未提及的,下面就逐个介绍下:

  • l DOS/UNIX/MAC间的转换
    这几项和字符集无关,主要是由于各个系统对行结束字符的要求不一样,体现在:Mac格式只要求回车、Unix只要求换行、DOS则要求回车、换行 二者组合;这几个命令要做的就是对这几个特殊字符进行相应替换;
  • l EBCDIC (Extended Binary Coded Decimal Interchange Code):
    他是与ASCII同时期的一种字符编码,主要用于IBM计算机中;它采用8位码, 有256个编码状态, 但只选用其中一部分。0-9十个数字符的高4位编码为1111, 低4位仍为0000-1001。大、小写英文字母的编码同样满足正常的排序要求, 而且有简单的对应关系, 即同一个字母的大小写的编码值仅最高的第二位的值不同, 易于识别与变换。
  • l OEM字符集
    由于ASCII码只利用了低7位,第8位没有被使用,而随着计算机的发展,很多原始设备制造商(当时最有名的应该就是IBM了)觉得ASCII码已经无法满足需求了,所以他们各自对第8位进行了自定义扩展,从而形成了五花八门的OEM字符集;由于这些扩展都是不通用的,只有各自能够解释,这些不兼容的扩展又叫做代码页(Code Page),在同一台机器中,通过不同的代码页就可以实现系统间信息的转换了;
  • l ANSI字符集
    正如前面提到的,众多的OEM厂商对第8位进行了自定义扩展;但更加夸张和恐怖的事情是,随着计算机进入亚洲,此时却发现整整用完8位也无法将所有的他们需要的语言字符;于是在ASCII的基础上他们再次进行了扩展,从而产生了双字节字符集DBCS(double byte character set),我们常见的国标系列GB2312/GBK/BIG5......都属于此类;这些字符集有一个共同的特点:完全兼容ASCII!这是ANSI系列和Unicode系列最大的不同点;这类型基于ASCII码进行扩展的多字节字符集MBCS统称为ANSI字符集;注意:ANSI字符集之间也是无法直接识别的,需要进行自定义转换;
  • l 提到国标系列,在此我还想提一个编码:HZ,HZ是汉字的简称?
    他是基于GB字符集的一种简体中文编码,在新加坡被广泛使用;编码规则为:他将每个字节中的第8位(在GB字符集中该位为1)屏蔽,只保留低7位,并将经过变换后的部分用~{、~}括起来;解码的时候只需要将括号对里面的部分高位还原成1即可;这样就可以在互联网上广泛的传输了,而不必受制于某些系统只能接收基于ASCII码的字符;

URL编码

基于Java开发的Web应用URL组成如下:
http://domain:port/contextPath/servletPath/pathInfo?queryString
其中各个部分含义如下:
Domain、Port:分别是域名和端口;
contextPath:应用上下文路径,默认为应用名称,比如我们的apps;但可以通过应用服务器的相关配置进行修改,一般线上环境会修改成/,此时相当于contextPath为空;
servletPath:Servlet路径,一般在应用的web.xml文件中配置servlet-mapping;但由于现在的web应用一般都会用一些框架,比如Struts、Webwork等,此时各框架都会对此进行封装,会在另外的配置文件中进行设置;但原理都是一样的;
pathInfo:可以理解为最终接收用户请求的具体执行类,比如我们常说的Action;
queryString:get方式传入的请求参数;
以上各个部分中可能存在中文问题的是pathInfo、queryString两个部分;

首先,我们来看下Servlet中和URL相关的一些api及其注意事项:
HttpServletRequest.setCharacterEncoding(); //仅仅只适用于设置post提交的request body的编码而不是设置get方法提交的queryString的编码。该方法还告诉应用服务器应该采用什么编码解析post传过来的内容;注意:若没有设定characterEncoding,则使用ISO-8859-1来解码用户输入的表单,而不是使用系统默认的编码
HttpServletResponse.setContentType();      //告诉浏览器网页中数据是什么编码;表单提交时,根据ContentType指定的charset对表单中的数据编码,然后发送给服务器。
HttpServletRequest.getParameter("name");   //返回的字符串为:queryString(包括get和post),其值经过Servlet服务器URL Decode过的,默认编码来源于应用服务器中的配置,比如tomcat中server.xml的URIEncoding。
HttpServletRequest.getPathInfo();          //返回的字符串为:pathinfo;由Servlet服务器解码(decode)过的。默认编码同上,tomcat中可设置useBodyEncodingForURI。
HttpServletRequest.getRequestURI();        //返回的字符串为:contextPath/servletPath/pathinfo;注意是浏览器提交过来的原始数据,未被Servlet服务器URL Decode过。

对URL编码【URL Encoding/Percent Encoding】时,使用以下规则:
字母数字字符 "a" 到 "z"、"A" 到 "Z" 和 "0" 到 "9" 保持不变。
特殊字符 "."、"-"、"*" 和 "_" 保持不变。
空格字符 " " 转换为一个加号 "+"。
所有其他字符都是不安全的,因此首先使用一些编码机制将它们转换为一个或多个字节。然后每个字节用一个包含 3 个字符的字符串 "%xy" 表示,其中 xy 为该字节的两位十六进制表示形式。推荐的编码机制是 UTF-8。但是,出于兼容性考虑,如果未指定一种编码,则使用相应平台的默认编码。

假定我们待请求URL为:http://localhost:8080/example/中国?name=中国;
Html内content-type或meta中的charset=GBK;文件格式为ANSI/ASCII;
URL中的两个汉字"中国"的各字符集下的编码为:

汉字 编码 二进制表示
中国 UTF-8 0xe4 0xb8 0xad 0xe5 0x9b 0xbd[-28, -72, -83, -27, -101, -67]
中国 GBK 0xd6 0xd0 0xb9 0xfa[-42, -48, -71, -6]
中国 ISO8859-1 0x3f 0x3f[63, 63][??]

对于Get方式的URL请求有两种情况,其一:用户直接在浏览器地址栏中输入URL,此时浏览器没有编码可参考,直接用浏览器的默认编码进行解析并提交到服务端;其二:在form表单内提交,只是form属性method为GET,此时浏览器会参考目前html中对编码的相关设置进行解析,比如content-type或meta中的charset。

以下就重点讲讲第二种方式的提交:
GET方式form submit:浏览器会对URL进行URL encoding,然后发送给服务器。

  • 对于中文IE,如果在高级选项中选中总以UTF-8发送(默认方式),则PathInfo在URL Encoding时按照UTF-8编码;QueryString按照GBK编码。
    此时提交是:GET /example/%E4%B8%AD%E5%9B%BD?name=%D6%D0%B9%FA
  • 对于中文IE,如果在高级选项中取消总以UTF-8发送,则PathInfo和QueryString在URL encoding时按照GBK编码。
    此时提交是:GET /example/%D6%D0%B9%FA?name=%D6%D0%B9%FA
  • 对于中文firefox、Chrome,因为没有类似IE中的这种设置选项,所以这两种浏览器中对pathInfo的编码规则没有做特殊处理,MS是和queryString采取同样的编码规则;在本例中的假设情况下:pathInfo和queryString在URL encoding时都按照GBK编码。
    此时提交是:GET /example/%D6%D0%B9%FA?name=%D6%D0%B9%FA

很显然,不同的浏览器以及同一浏览器的不同设置,会影响最终URL中PathInfo的编码,该编码可能不会由我们应用来控制;对于queryString,则是可以由我们的应用来完全控制的,对于上面的事例:中文的IE和FIREFOX都是采用GBK编码queryString。

若调整下上例中的假设条件,设置Html内content-type或meta中的charset=UTF-8
此时在IE中queryString会按照UTF-8进行编码,即name=%E4%B8%AD%E5%9B%BD;
但是在非IE(Firefox、Chrome)中,此时提交时URL中会以中文直接提交,即name=中文;此时服务端的web服务器上肯定要进行相应的编码配置,否则肯定会出现乱码;

若设置Html内content-type或meta中的charset=ISO-5899-1
此时在IE、Firefox、Chrome中queryString都被用ISO-5899-1编码了,即name= %26%2320013%3B%26%2322269%3B;
对于编码串中的%26、%3B应该是百分号编码【Percent Encoding】中的保留字符,分别对应&、;,两者之间是经过编码的十进制码;对于这点偶也不是十分肯定?要是有同学比较清楚,请告诉偶下,thx。

POST方式提交:表单中的参数值对是通过request body发送给服务器,此时浏览器会根据网页的ContentType("text/html; charset=GBK")中指定的编码进行对表单中的数据进行编码,然后发给服务器。
在服务器端的程序中我们可以通过Request.setCharacterEncoding() 设置编码,然后通过request.getParameter获得正确的数据。

小结:

  • 1. URL中的PathInfo字符串的编码和解码是由浏览器和应用服务器的配置决定的,我们的程序不能设置,不要期望用request.setCharacterEncoding()方法能设置URL中pathInfo解码时的字符集,这在servlet规范里面有说明。
  • 2. URL中如果含有中文等非ASCII字符,则浏览器会对它们进行URLEncode。为了避免浏览器采用了我们不希望的编码,所以最好不要在URL中直接使用非ASCII字符,而采用URL Encode编码过的字符串%xy;
    对此建议URL:http://localhost:8080/example/%D6%D0%B9%FA?name=%D6%D0%B9%FA
  • 3. URL中PathInfo和QueryString应该采用相同的编码,pathInfo最好不要用中文;因为不同浏览器对URL中PathInfo和QueryString编码时采用的字符集不同,但应用服务器对URL通常会采用相同的字符集来解码。
  • 4. URL中的queryString的编码方式是依赖于网页的contentType、meta中的charset字符集设置的,记住这点对于form get方式提交乱码问题排查是非常有好处的。
  • 5. 页面文件的编码最好和页面内设置的charset一致,因为我们可能在页面内部有很多中文,若编码不一致,则可能出现在编辑器内看着是正常的,但到了浏览器上则变成了乱码(虽然页面显示为乱码,但这并不意味着在form submit时数据会有问题,很可能出现负负为正的情况);这点在前面宝宝的文档中也有说明;
    另外,文件的编码很可能会影响问题的排查;比如上例中我们就已说明:文件格式为ANSI/ASCII,若文件格式为Unicode/UTF-*系列,那测试结果会不会不一样呢?大家可以自行测试下;
  • 6. 服务端URL Encode时最好指定字符集,否则encoding的字符集依赖于本地系统的默认编码。可以参考javadoc:
    http://gceclub.sun.com.cn/Java_Docs/html/zh_CN/api/java/net/URLEncoder.html

相关文档参考:
字符,字节和编码:http://www.regexlab.com/zh/encoding.htm
各种字符集和编码详解:http://blog.csdn.net/ancky/archive/2008/01/11/2034809.aspx
深入浅出URL编码:http://blog.csdn.net/yzhz/archive/2007/07/03/1676796.aspx
javascript html 相关编码问题研究:http://stauren.net/log/fpev3c89q.html
J2EE Web组件中中文及相关的问题(系列):http://blog.csdn.net/whodsow/archive/2003/10/27/19465.aspx

 

你可能感兴趣的:(WEB,JAVA)