注:本文部分内容摘自网络,摘抄内容版权归原作者所有。
1. 背景知识
1.1. Http协议
1.1.1. URL和URI
1.1.2. 媒体类型定义
HTTP 在 Content-Type(14.17 节)和 Accept(14.1 节)头部域中使用因特网媒体类型 [17],为了提供打开和可扩展的数据类型和类型协议。
media-type = type "/" subtype *( ";" parameter )
type = token
subtype = token
parameter【可以】 接在 type/subtype 后面, 按 attribute/value 对的形式(如 3.6 节中所定义)。
type、subtype 和 parameter 属性名是大小写非敏感的。 parameter 值可以是或不是大小写敏感的, 取决于 parameter 名称的语义。【禁止】在 type 和 subtype 间使用线性空白符(LWS),属性与其值间也禁止。 存在或缺少 parameter 可以对 media-type 处理有意义, 取决于媒体类型注册表中的定义。
要注意, 一些老的 HTTP 应用程序不认识媒体类型参数。 当发送数据给老 HTTP 应用程序时, 实现【应该】只在该 type/subtype 定义需要时使用媒体类型的参数。
media-type 的值由因特网分配数字权威(IANA [19])注册。 媒体类型注册过程在 RFC
1.1.3. 和字符集相关的指令
在jsp、servlet和html,指定IE按照那一种字符集,解析字节流。即:IE中html页面的默认页面编码。
JSP文件中ContentType,指定传输内容的编码格式。如下:
<%@ pagecontentType="text/html;charset=GBK"%>。
Java Servlet中,在response对象中设置。如下:
response.setContentType("text/html;charset=GBK");
HTML中,通过<meta http-equiv>元素设置。如下:
<META HTTP-EQUIV="Content-Type" CONTENT="text/html;CHARSET=GBK">
2. “火”的编码
“火” 字,
UTF-8 编码:0xE781AB
GBK 编码:0xBBF0
GB2312编码:0xBBF0
3. 在地址栏中输入地址,提交
3.1. 涉及参数
IE选项à高级:
3.2. “以UTF-8发送”选中,不带中文参数
IE选项à高级:
地址中出现中文,例如:
浏览器把中文“火”字,先按照UTF-8编码转译,如下:
把这个地址转ISO-8859-1编码;下图,浏览器提交字节流,二进制编码;如下:
服务器端响应的二进制流编码:
3.3. “以UTF-8发送”选中,带中文参数
IE选项à高级:
地址,方式:js调用、地址栏输入。
浏览器把url地址中“火”字,先按照UTF-8编码转译,?后参数的火字,按照页面编码转码(页面编码GBK),:
过程为:url地址处理,将中文字符转为%的形式,将转译后的字符串作为新的地址字符串;将地址和参数编译为字节流,地址按照ISO-8859-1编码,参数按照gbk编码(页面编码)。
当页面字符集时UTF-8时,提交字节流,如下:
参数编码为UTF-8编码。
3.4. “以UTF-8发送”不选中
IE选项à高级:
地址中出现中文,例如:
提交字符串,直接包括“火”字。
浏览器提交字节流,二进制编码:
“火”字的编码bbf0是GBK字符集格式的编码。
服务器端响应的二进制流编码:
此时的过程为:
浏览器将“火”字按照GBK编码 bbf0 的字节流发送到服务器端,服务器端默认按照ISO-8859-1解析该字节流,服务器端进行处理(解析的目的是转换为UTF-16的内部编码,注意:这里是以字符为基准的转换,因为二进制字节流是没有意义的,只有转换为字符后,才能找到utf-16中对应的编码,或者说,是把字符iso-8859-1对应的码值转换为utf-16中对应的码值),处理完毕后按照UTF-8字符集转换字节流编码(c2bbc3b0),输出到客户端。注意utf-16转换是无损的。当然如果出现,gbk转换为iso8859-1出现信息损伤是不可恢复的。
结论:
不选中时,URL地址中的汉字,按照页面字符集进行编码。
3.5. 结论
地址栏中输入的URL地址字符串,在提交给服务器端时,对URL分成两块单独编码;“?”前的地址为一部分;“?”后的参数为一部分。
地址部分,在“以UTF-8”选中时,地址部分出现的多字节字符,按照UTF-8转码;如果未选中,按照操作系统默认字符集进行编码。
参数部分,和“以UTF-8”参数无关,按照操作系统默认字符集进行编码。
4. 通过js方式提交地址
4.1. 对URL地址不转码提交
不转码和get方式相同。例如:
提交URL
原始URL:
页面编码为UTF-8
提交到Web服务器端,
URL编码
查看userid后的编码
0xe781,编码格式是UTF-8,和页面字符集一致。
结论
和在地址栏直接输入URL一致。
4.2. 对URL地址转码提交
对URL地址转码,通过调用encodeURI或encodeURIComponent函数,对整体的URL地址(包括参数)中非ANSI字符(见encodeURI方法的描述)进行编码,编码字符集为UTF-8(指定其他字符集也无效),在URL中显示的格式为,%+十六进制的字符序列。
原始URL:
转码后的URL:
提交到Web服务器中的URL(拦截得到):
4.3. 转码提交后解析过程
在上述两种情况中,应用服务器默认编码为ISO-8859-1,服务器不能正确解析该URI。结果如下:
未转码:
转码:
解析过程
应用服务器(Tomcat)对地址中的%E7%81%AB进行解析过程为,去掉%号,将%后的字符作为HEX数,根据Tomcat设置的字符集翻译为字符。Tomcat默认字符集为ISO-8859-1,使用ISO-8859-1对E7,81翻译为字符,发现E7,81超出ISO-8859-1的代码空间,不识别该字符,翻译为“?”(即0x3f)。
解决方法,(一)将应用服务器字符集设置UTF-8;(二)将对字节序列的转码放到程序中实现。
方法(一)在server.xml中的<connect/>元素中,添加URIEncoding=”UTF-8”属性。
方法(二)在客户端二次转码encodeURI(encodeURI(url)),此时url字符串为:
详见下面样例。注意,此方法,只能对URL字符串中参数部分解析,对URI中地址部分不能解析。
4.4. 转码提交解析样例
4.4.1. 环境设置
环境设置
目标文件所在目录:urlencode\火\urlencode-2.jsp
IE页面字符集:UTF-8。
IE选项:选中“以UTF-8发送”。
request编码设置:
4.4.2. 应用服务器设置UTF-8字符集
设置URIEncoding为UTF-8
Tomcat配置文件设置URI解析字符集
过程
通过js请求的URL:
提交到服务器端的地址串(截获):
结果:成功。
成功页面显示的地址:
结论:
Tomcat应用服务器设置的字符集对URI进行解码。
4.4.3. 二次转码和二次解码
URL地址中不包含中文
请求的URL
IE编码后提交服务器端的字符串
直接从request中获取的值
通过URLDecode类解码后的值
URL地址中包含中文
请求的URL
IE编码后提交服务器端的字符串
失败页面
4.5. 结论
js提交的URL地址字符串,在提交给服务器端时,对URL分成两块单独编码;“?”前的地址为一部分;“?”后的参数为一部分。
地址部分,在“以UTF-8”选中时,地址部分出现的多字节字符,按照UTF-8转码;如果未选中,按照页面默认字符集进行编码。
参数部分,和“以UTF-8”参数无关,按照页面默认字符集进行编码。
5. 服务端解析
对URI的解析
对URI的解析包括两部分,地址部分和参数部分。应用服务器根据指定的字符集解析URI中的地址部分和参数部分,即以GET方式提交的数据。
注意二次编码和二次解析,只能解析参数,不能解析URI。
对提交数据内容的解析
以GET方式提交的数据,根据应用服务器的字符集解析。在应用中设置request.setCharacterEncoding()不起作用。
以Post方式提交的数据,根据request.setCharacterEncoding()确定的字符集解析。默认字符集为Java默认的字符集,java默认的字符集为操着系统默认的字符集。
6. URI允许的字符
ASCII字母、数字、保留字符、标记字符
7. 结论
7.1. 在地址栏输入URL,编码和解码
URL地址部分(“?之前”):
编码
选中“以UTF-8发送”,非多字节序列的字符,被转译为UTF-8字节序列。例如,汉字被转译为%A1%B0格式,其中A1是汉字对应的UTF-8编码,让后整个URL按照ISO-8859-1编码发送到服务器端。服务器端以ISO-8859-1解码。
未选中“以UTF-8发送”,URL地址的编码,按照操作系统默认字符集进行编码。
解码
服务器端默认按照iso-8859-1解码,可以手工修改应用服务器的URI解析字符集
URL地址后的参数(?后的部分)
编码:
按照操作系统默认字符集进行编码。
解码:
在Tomcat中,服务器端默认按照iso-8859-1解码,不会使用request.setCharacterEncode()方法设置的字符集解码。
7.2. js方式提交URL,编码和解码
URL地址部分(“?之前”)
编码
选中“以UTF-8发送”,和在地址栏输入URL相同。如果js做了转码,URL字符串则是符合URL规范的字符串。
未选中“以UTF-8发送”,URL地址的编码,按照IE页面默认字符集进行编码。
解码
服务器端默认按照iso-8859-1解码,可以手工修改应用服务器的URI解析字符集。将%的字节序列,转换为字符。
URL地址后的参数(?后的部分)
编码:
按照IE页面默认字符集进行编码。如果js做了转码,URL字符串则是符合URL规范的字符串。
解码:
在Tomcat中,服务器端默认按照iso-8859-1解码,不会使用request.setCharacterEncode()方法设置的字符集解码。将%的字节序列,转换为字符。
7.3. GET内容编码和和解码
指:URL地址中“?”之后的部分。
以页面编码字符集,对提交内容进行编码;见“以’UTF-8发送’选中,带参数”部分。
转码:如果调用encodeURI进行转译,在服务器端接收时,需要做相应的解码(详见“对URL地址转码”小节),有两种方式,一 在tomcat端设置URI的字符集为UTF-8字符集;二、tomcat默认字符集(ISO-8859-1);客户端进行二次转码;tomcat应用服务器接收时一次解码,在代码中使用URLDecode类进行二次解码。
编码:如果不对地址转码,直接提交,参数一页面字符集进行编码。
解码:则以应用服务器(Tomcat)设置的默认字符集进行解析。 equest.setCharacterEncoding()命令无效。
7.4. Post内容编码和解码
编码:以页面编码字符集,对提交内容进行编码。
解码:服务器端根据
request.setCharacterEncoding("GBK");
命令进行解码。
7.5. IE“以UTF-8发送”参数
该选项的转换的范围包括:1)在地址栏直接输入的URL地址字符串;2)js中提交的URL地址字符串。不包括“?”之后的部分。
将URL地址部分(“?”之前的部分)按照UTF-8编码,效果和encodeURI一致。“?”后面的参数部分不做转码。
7.6. 设置Tomcat URIEncoding参数
Tomcat配置文件设置URI解析字符集
对提交的整个URI进行解析(包括参数部分,不包括IP地址和端口)。
8. URI转码函数
8.1. encodeURI / decodeURI函数
“转义”定义
所谓转义,程序用指定的字符构成,当程序处理组成本程序的字符时,或者该字符不可识别时,此时程序会默认将该字符作为程序本身的字符进行处理,而不是作为被处理对象的内容进行处理。对于此种情况,则需要转义,即转义字符后的字符,表示的不是本意,例如我们常见的换行“/n”,“/”表示转义字符,即后面的字符“n”表示的含义已经不是原来26个字母中n的语义,而是表示换行。
同理在URL中%表示后面的为字符编码。
encodeURI()
转译URI中的字符。
摘要:encodeURI(uri)
参数:
uri 一个字符串,含有uri或者需要编码的文本。
返回值:
uri的副本,其中某些字符被十六进制的转译序列替代了。
抛出:
说明uri中含有格式化错误的Unicode替代对,不能编码。
描述:
encodeURI()是全局函数,返回uri的编码副本。ASCII的字母和数字不编码,此外以下的ASCII标点符号(ASCII Mark)也不编码。
因为endcodeURI()目的是给URI进行完整的编码,所以URI中有特殊含义的保留字符,也不转义。
除了上述四种字符(ASCII字母、数字、保留字符、标记字符),uri中的其他字符都将转换成它的UTF-8的编码字符,
decodeURI()
8.2. encodeURIComponent / decodeURIComponent
8.3. 两者区别
<以下摘自EMCA-262>
【大意:】对于保留字符“:”,“/”,“;”和“?”encodeURI是不编码的的,encodeURICompoent方法是编码的。
9. 样例
环境
js将url进行编码(调用encodeURI方法)提交。
IE页面设置的字符集为:GBK。
url地址提交的数据,key:userid,“火”。
form表单提交的数据:key:userid1,“火”。
即:
url地址按照UTF-8编码,提交。
get参数按照UTF-8编码,提交。
post参数按照GBK编码,提交。
1)GET提交数据:
截获的提交字符串。
得出,get提交的数据,被转义为UTF-8的十六进制序列。
2)提交post数据:
编码
根据编码可以得出,POST提交数据是GBK编码。
Tomcat 按照默认编码(ISO-8859-1)
找不到。
结果如下:
Tomcat 按照UTF-8编码
Tomcat按照UTF-8解析URI字节流。
当request设置GBK字符集,post数据可识别,设置代码,如下:
结果如下:
如果不显示的设置request编码,出现乱码,如下:
userid的解码字符集,为UTF-8,即tomcat中URIEncoding中设置的字符集。
10. 处理流程
准备:需要提交的URL:http://localhost/urlencode/火/urlencode-2.jsp?userid='火'
第一步:输入URL地址,获得URL字符串
包括两种:
在地址栏直接输入URL字符串。
调用js方法输入URL字符串。在此方式中,可以对URL字符串进行转码操作(调用encodeURI /encodeURIComponent 方法,将URL规则以外的字符,转换为十六进制的字符序列)得到转换后的字符串。最为最终的URL字符串。如下:
第二步:IE对提交URL字符串进行编码
首先查看“以UTF-8发送”是否选中,如果选中,对URL字符串中的地址部分(不包括“?”后的参数),进行转码。未选中,保留字符串不变。
按照页面编码字符集对URL字符串(整体)进行编码。
第三步:通过HTT协议提交到服务器端
第四步:服务器对解码。
服务器端解码,包括两部分,如下:
一:应用服务器解析URL。
应用服务器接受到请求的字节序列,按照指定的字符集(Tomcat默认是ISO-8859-1)对字节流解码。注意:遇到“%”,将“%”后的字符按照十六进制字节序列处理,按照指定的字符集转换为相应的字符串。
注意:如果客户端调用encodeURI方法,对URL字符串进行转换,应用服务器端字符集应设置为UTF-8,和客户端保持一致。如果依旧为默认的ISO-8859-1,则需要应用程序对提交UTF-8字符进行解码,此时需要IE客户端对URL进行二次重复转换( encodeURI(encodeURI(url)) )。
二:应用服务器解析提交的内容。
应用服务器读取request.setCharacterEncoding()方法设置的字符集,对提交的内容进行解码,提交的内容包括:post内容,和get方式的内容。
11. HTML
<META http-equiv="Content-Type"content="text/html; charset=GBK" />
指定了页面内容的字符集,被绑定到http响应头信息中,如果jsp中也设置了,jsp优先。
12. jsp字符集
<%@page contentType="text/html;charset=GBK" %>
指令,表示jsp文件以gbk格式的字节流,应答给客户端。
当jsp源文件以gbk保存,而在page中,标记为utf-8。
例子:
汉字:火。
12.1. 完整的JSP文件
<%@ page pageEncoding="GBK" %>
<%@ page contentType="text/html;charset=GBK" %>
<HTML>
<HEAD>
<TITLE> New Document</TITLE>
</HEAD>
<BODY>火</BODY>
</HTML>
12.2. GBK保存,contentType:GBK,pageCoding:无
<%@ page contentType="text/html;charset=GBK" %>
文件保存ansi/GBK格式。
IE访问正常。
找到该jsp对应的java文件,“火”字显示正常,如下:
以二进制打开,该字符二进制显示,如下:
(“火”字的UTF-8编码值)
根据字符映射,java文件的保存编码为UTF-8格式。
12.3. GBK保存,contentType:UTF-8,pageCoding:无
在jsp文件中,如下:
<%@page contentType="text/html;charset=UTF-8" %>
将jsp文件保存GBK格式。部署到Tomcat,通过IE访问。页面如下:
显示乱码。
找到该jsp对应的java文件,“火”字显示乱码,以二进制打开,该字符二进制显示,如下:
该结果,以UTF-8读取GBK字节流结果一致。即以GBK格式转换“火”的字节流保存在磁盘上,应用服务器以utf-8格式解析该字节流。
12.4. GBK保存,contentType:UTF-8,pageCoding GBK
添加头
<%@page pageEncoding="GBK" %>
<%@page contentType="text/html;charset=UTF-8" %>
将jsp文件保存GBK格式。部署到Tomcat,通过IE访问,显示正常,IE显示的编码格式为UTF-8,。
http响应头信息:
找到该jsp对应的java文件,“火”字显示正常,如下:
以二进制打开,该字符二进制显示,如下:
根据字符映射,java文件的保存编码为UTF-8格式。
另:将头改为
<%@page pageEncoding="GBK" %>
<%@page contentType="text/html;charset=GBK"%>后,显示正常。
HTTP头显示为:
找到该jsp对应的java文件,“火”字显示正常,如下:
12.5. 步骤
JSP文件载入
从硬盘中读取JSP文件,得到该文件的二进制流。
应用服务器根据指定的字符集解码JSP文件。
生成Servlet
调用JSP引擎,生成Servlet文件,根据指定字符集编码,保存到硬盘上。
编译Servlet,生成class文件。
生成应答的输出流
根据指定的字符集,加载该Servlet的class文件。
根据Servlet指定的字符集,生成输出流。
IE接收并到输出流
IE客户端接收字节流
根据指定字符集生成HTML页面
12.6. 结论
jsp文件被应用服务器编译为java文件后,java文件以utf-8格式保存。
应用服务器读取jsp文件,读取的字符集,按照jsp的pageEncoding指令决定,如下:
<%@ page pageEncoding="GBK" %>
如果没有设置该指令,以默认取contentType属性值。
在contentType属性中,mimeType指示浏览器显示内容的格式,即用什么应用程序或者字符集显示内容;text/html,设置response的输出字节流,返回客户端的字节流编码格式。
<%@ page contentType=" text/html;charset=UTF-8" %>
应用服务器将该指令的内容,绑定到HTTP响应头中,浏览器根据响应头信息解码。
13. java平台字符集
char类型的编码为utf-16的编码格式。由char组成String,String为平台提供操作字符的工具。String是基础,char则是构建基础的元素。
java源文件,由本地操作系统决定。javac在读取源文件时,默认读取本地操作系统的字符集,读取java源文件。
javac编译class文件为unicode字符集,编码为utf-8格式。
jvm运行,字符集为unicode字符集,编码格式utf-16格式。
14. Tomcat处理内容的默认编码
、Tomcat 6.0设置字符集, URIEncoding="UTF-8"
15. 字符集编码-问题研究(摘自网络)
15.1. 概述
本文主要包括以下几个方面:编码基本知识,java,系统软件,url,工具软件等。
在下面的描述中,将以"中文"两个字为例,经查表可以知道其GB2312编码是"d6d0 cec4",Unicode编码为"4e2d 6587",UTF编码就是"e4b8ad e69687"。注意,这两个字没有iso8859-1编码,但可以用iso8859-1编码来"表示"。
15.2. 编码基本知识
最早的编码是iso8859-1,和ascii编码相似。但为了方便表示各种各样的语言,逐渐出现了很多标准编码,重要的有如下几个。
15.2.1. iso8859-1
属于单字节编码,最多能表示的字符范围是0-255,应用于英文系列。比如,字母'a'的编码为0x61=97。
很明显,iso8859-1编码表示的字符范围很窄,无法表示中文字符。但是,由于是单字节编码,和计算机最基础的表示单位一致,所以很多时候,仍旧使用iso8859-1编码来表示。而且在很多协议上,默认使用该编码。比如,虽然"中文"两个字不存在iso8859-1编码,以gb2312编码为例,应该是"d6d0 cec4"两个字符,使用iso8859-1编码的时候则将它拆开为4个字节来表示:"d6 d0 ce c4"(事实上,在进行存储的时候,也是以字节为单位处理的)。而如果是UTF编码,则是6个字节"e4 b8 ad e6 96 87"。很明显,这种表示方法还需要以另一种编码为基础。
15.2.2. GB2312/GBK
这就是汉子的国标码,专门用来表示汉字,是双字节编码,而英文字母和iso8859-1一致(兼容iso8859-1编码)。其中gbk编码能够用来同时表示繁体字和简体字,而gb2312只能表示简体字,gbk是兼容gb2312编码的。
15.2.3. unicode
这是最统一的编码,可以用来表示所有语言的字符,而且是定长双字节(也有四字节的)编码,包括英文字母在内。所以可以说它是不兼容iso8859-1编码的,也不兼容任何编码。不过,相对于iso8859-1编码来说,uniocode编码只是在前面增加了一个0字节,比如字母'a'为"00 61"。
需要说明的是,定长编码便于计算机处理(注意GB2312/GBK不是定长编码),而unicode又可以用来表示所有字符,所以在很多软件内部是使用unicode编码来处理的,比如java。
15.2.4. UTF
考虑到unicode编码不兼容iso8859-1编码,而且容易占用更多的空间:因为对于英文字母,unicode也需要两个字节来表示。所以unicode不便于传输和存储。因此而产生了utf编码,utf编码兼容iso8859-1编码,同时也可以用来表示所有语言的字符,不过,utf编码是不定长编码,每一个字符的长度从1-6个字节不等。另外,utf编码自带简单的校验功能。一般来讲,英文字母都是用一个字节表示,而汉字使用三个字节。
注意,虽然说utf是为了使用更少的空间而使用的,但那只是相对于unicode编码来说,如果已经知道是汉字,则使用GB2312/GBK无疑是最节省的。不过另一方面,值得说明的是,虽然utf编码对汉字使用3个字节,但即使对于汉字网页,utf编码也会比unicode编码节省,因为网页中包含了很多的英文字符。
15.3. java对字符的处理
在java应用软件中,会有多处涉及到字符集编码,有些地方需要进行正确的设置,有些地方需要进行一定程度的处理。
15.3.1. getBytes(charset)
这是java字符串处理的一个标准函数,其作用是将字符串所表示的字符按照charset编码,并以字节方式表示。注意字符串在java内存中总是按unicode编码存储的。比如"中文",正常情况下(即没有错误的时候)存储为"4e2d6587",如果charset为"gbk",则被编码为"d6d0 cec4",然后返回字节"d6 d0 ce c4"。如果charset为"utf8"则最后是"e4 b8 ad e6 96 87"。如果是"iso8859-1",则由于无法编码,最后返回 "3f 3f"(两个问号)。
15.3.2. newString(charset)
这是java字符串处理的另一个标准函数,和上一个函数的作用相反,将字节数组按照charset编码进行组合识别,最后转换为unicode存储。参考上述getBytes的例子,"gbk" 和"utf8"都可以得出正确的结果"4e2d 6587",但iso8859-1最后变成了"003f 003f"(两个问号)。
因为utf8可以用来表示/编码所有字符,所以new String( str.getBytes("utf8" ), "utf8" ) === str,即完全可逆。
15.3.3. setCharacterEncoding()
该函数用来设置http请求或者相应的编码。
对于request,是指提交内容的编码,指定后可以通过getParameter()则直接获得正确的字符串,如果不指定,则默认使用iso8859-1编码,需要进一步处理。参见下述"表单输入"。值得注意的是在执行setCharacterEncoding()之前,不能执行任何getParameter()。java doc上说明:This method must be called priorto reading request parameters or reading input using getReader()。而且,该指定只对POST方法有效,对GET方法无效。分析原因,应该是在执行第一个getParameter()的时候,java将会按照编码分析所有的提交内容,而后续的getParameter()不再进行分析,所以setCharacterEncoding()无效。而对于GET方法提交表单是,提交的内容在URL中,一开始就已经按照编码分析所有的提交内容,setCharacterEncoding()自然就无效。
对于response,则是指定输出内容的编码,同时,该设置会传递给浏览器,告诉浏览器输出内容所采用的编码。
15.3.4. 处理过程
下面分析两个有代表性的例子,说明java对编码有关问题的处理方法。
15.3.4.1. 表单输入
Userinput *(gbk:d6d0 cec4) browser *(gbk:d6d0cec4) web server iso8859-1(00d6 00d 000ce 00c4) class,需要在class中进行处理:getbytes("iso8859-1")为d6 d0 ce c4,new String("gbk")为d6d0 cec4,内存中以unicode编码则为4e2d 6587。
l 用户输入的编码方式和页面指定的编码有关,也和用户的操作系统有关,所以是不确定的,上例以gbk为例。
l 从browser到web server,可以在表单中指定提交内容时使用的字符集,否则会使用页面指定的编码。而如果在url中直接用?的方式输入参数,则其编码往往是操作系统本身的编码,因为这时和页面无关。上述仍旧以gbk编码为例。
l Web server接收到的是字节流,默认时(getParameter)会以iso8859-1编码处理之,结果是不正确的,所以需要进行处理。但如果预先设置了编码(通过request. setCharacterEncoding ()),则能够直接获取到正确的结果。
l 在页面中指定编码是个好习惯,否则可能失去控制,无法指定正确的编码。
15.3.4.2. 文件编译
假设文件是gbk编码保存的,而编译有两种编码选择:gbk或者iso8859-1,前者是中文windows的默认编码,后者是linux的默认编码,当然也可以在编译时指定编码。
Jsp *(gbk:d6d0 cec4) java file *(gbk:d6d0 cec4) compilerread uincode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4) compilerwrite utf(gbk: e4b8ad e69687; iso8859-1: *) compiled file unicode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4) class。所以用gbk编码保存,而用iso8859-1编译的结果是不正确的。
class unicode(4e2d 6587) system.out / jsp.out gbk(d6d0 cec4) os console / browser。
l 文件可以以多种编码方式保存,中文windows下,默认为ansi/gbk。
l 编译器读取文件时,需要得到文件的编码,如果未指定,则使用系统默认编码。一般class文件,是以系统默认编码保存的,所以编译不会出问题,但对于jsp文件,如果在中文windows下编辑保存,而部署在英文linux下运行/编译,则会出现问题。所以需要在jsp文件中用pageEncoding指定编码。
l Java编译的时候会转换成统一的unicode编码处理,最后保存的时候再转换为utf编码。
l 当系统输出字符的时候,会按指定编码输出,对于中文windows下,System.out将使用gbk编码,而对于response(浏览器),则使用jsp文件头指定的contentType,或者可以直接为response指定编码。同时,会告诉browser网页的编码。如果未指定,则会使用iso8859-1编码。对于中文,应该为browser指定输出字符串的编码。
l browser显示网页的时候,首先使用response中指定的编码(jsp文件头指定的contentType最终也反映在response上),如果未指定,则会使用网页中meta项指定中的contentType。
15.3.5. 几处设置
对于web应用程序,和编码有关的设置或者函数如下。
15.3.5.1. jsp编译
指定文件的存储编码,很明显,该设置应该置于文件的开头。例如:<%@page pageEncoding="GBK"%>。另外,对于一般class文件,可以在编译的时候指定编码。
15.3.5.2. jsp输出
指定文件输出到browser是使用的编码,该设置也应该置于文件的开头。例如:<%@ page contentType="text/html; charset= GBK" %>。该设置和response.setCharacterEncoding("GBK")等效。
15.3.5.3. meta设置
指定网页使用的编码,该设置对静态网页尤其有作用。因为静态网页无法采用jsp的设置,而且也无法执行response.setCharacterEncoding()。例如:<META http-equiv="Content-Type"content="text/html; charset=GBK" />
如果同时采用了jsp输出和meta设置两种编码指定方式,则jsp指定的优先。因为jsp指定的直接体现在response中。
需要注意的是,apache有一个设置可以给无编码指定的网页指定编码,该指定等同于jsp的编码指定方式,所以会覆盖静态网页中的meta指定。所以有人建议关闭该设置。
15.3.5.4. form设置
当浏览器提交表单的时候,可以指定相应的编码。例如:<form accept-charset= "gb2312">。一般不必不使用该设置,浏览器会直接使用网页的编码。
15.4. 系统软件
下面讨论几个相关的系统软件。
15.4.1. mysql数据库
很明显,要支持多语言,应该将数据库的编码设置成utf或者unicode,而utf更适合与存储。但是,如果中文数据中包含的英文字母很少,其实unicode更为适合。
数据库的编码可以通过mysql的配置文件设置,例如default-character-set=utf8。还可以在数据库链接URL中设置,例如: useUnicode=true&characterEncoding=UTF-8。注意这两者应该保持一致,在新的sql版本里,在数据库链接URL里可以不进行设置,但也不能是错误的设置。
15.4.2. apache
appache和编码有关的配置在httpd.conf中,例如AddDefaultCharset UTF-8。如前所述,该功能会将所有静态页面的编码设置为UTF-8,最好关闭该功能。
另外,apache还有单独的模块来处理网页响应头,其中也可能对编码进行设置。
15.4.3. linux默认编码
这里所说的linux默认编码,是指运行时的环境变量。两个重要的环境变量是LC_ALL和LANG,默认编码会影响到java URLEncode的行为,下面有描述。
建议都设置为"zh_CN.UTF-8"。
15.4.4. 其它
为了支持中文文件名,linux在加载磁盘时应该指定字符集,例如:mount /dev/hda5 /mnt/hda5/ -t ntfs -o iocharset=gb2312。
另外,如前所述,使用GET方法提交的信息不支持request.setCharacterEncoding(),但可以通过tomcat的配置文件指定字符集,在tomcat的server.xml文件中,形如:<Connector ...URIEncoding="GBK"/>。这种方法将统一设置所有请求,而不能针对具体页面进行设置,也不一定和browser使用的编码相同,所以有时候并不是所期望的。
15.5. URL地址
URL地址中含有中文字符是很麻烦的,前面描述过使用GET方法提交表单的情况,使用GET方法时,参数就是包含在URL中。
15.5.1. URL编码
对于URL中的一些特殊字符,浏览器会自动进行编码。这些字符除了"/?&"等外,还包括unicode字符,比如汉子。这时的编码比较特殊。
IE有一个选项"总是使用UTF-8发送URL",当该选项有效时,IE将会对特殊字符进行UTF-8编码,同时进行URL编码。如果改选项无效,则使用默认编码"GBK",并且不进行URL编码。但是,对于URL后面的参数,则总是不进行编码,相当于UTF-8选项无效。比如"中文.html?a=中文",当UTF-8选项有效时,将发送链接"%e4%b8%ad%e6%96%87.html?a=\x4e\x2d\x65\x87";而UTF-8选项无效时,将发送链接"\x4e\x2d\x65\x87.html?a=\x4e\x2d\x65\x87"。注意后者前面的"中文"两个字只有4个字节,而前者却有18个字节,这主要时URL编码的原因。
当web server(tomcat)接收到该链接时,将会进行URL解码,即去掉"%",同时按照ISO8859-1编码(上面已经描述,可以使用URLEncoding来设置成其它编码)识别。上述例子的结果分别是"\ue4\ub8\uad\ue6\u96\u87.html?a=\u4e\u2d\u65\u87"和"\u4e\u2d\u65\u87.html?a=\u4e\u2d\u65\u87",注意前者前面的"中文"两个字恢复成了6个字符。这里用"\u",表示是unicode。
所以,由于客户端设置的不同,相同的链接,在服务器上得到了不同结果。这个问题不少人都遇到,却没有很好的解决办法。所以有的网站会建议用户尝试关闭UTF-8选项。不过,下面会描述一个更好的处理办法。
15.5.2. rewrite
熟悉的人都知道,apache有一个功能强大的rewrite模块,这里不描述其功能。需要说明的是该模块会自动将URL解码(去除%),即完成上述web server(tomcat)的部分功能。有相关文档介绍说可以使用[NE]参数来关闭该功能,但我试验并未成功,可能是因为版本(我使用的是apache 2.0.54)问题。另外,当参数中含有"?& "等符号的时候,该功能将导致系统得不到正常结果。
rewrite本身似乎完全是采用字节处理的方式,而不考虑字符串的编码,所以不会带来编码问题。
15.5.3. URLEncode.encode()
这是Java本身提供对的URL编码函数,完成的工作和上述UTF-8选项有效时浏览器所做的工作相似。值得说明的是,java已经不赞成不指定编码来使用该方法(deprecated)。应该在使用的时候增加编码指定。
当不指定编码的时候,该方法使用系统默认编码,这会导致软件运行结果得不确定。比如对于"中文",当系统默认编码为"gb2312"时,结果是"%4e%2d%65%87",而默认编码为"UTF-8",结果却是"%e4%b8%ad%e6%96%87",后续程序将难以处理。另外,这儿说的系统默认编码是由运行tomcat时的环境变量LC_ALL和LANG等决定的,曾经出现过tomcat重启后就出现乱码的问题,最后才郁闷的发现是因为修改修改了这两个环境变量。
建议统一指定为"UTF-8"编码,可能需要修改相应的程序。
15.5.4. 一个解决方案
上面说起过,因为浏览器设置的不同,对于同一个链接,web server收到的是不同内容,而软件系统有无法知道这中间的区别,所以这一协议目前还存在缺陷。
针对具体问题,不应该侥幸认为所有客户的IE设置都是UTF-8有效的,也不应该粗暴的建议用户修改IE设置,要知道,用户不可能去记住每一个web server的设置。所以,接下来的解决办法就只能是让自己的程序多一点智能:根据内容来分析编码是否UTF-8。
比较幸运的是UTF-8编码相当有规律,所以可以通过分析传输过来的链接内容,来判断是否是正确的UTF-8字符,如果是,则以UTF-8处理之,如果不是,则使用客户默认编码(比如"GBK"),下面是一个判断是否UTF-8的例子,如果你了解相应规律,就容易理解。
publicstatic boolean isValidUtf8(byte[] b,int aMaxCount){
int lLen=b.length,lCharCount=0;
for(int i=0;i<lLen && lCharCount<aMaxCount;++lCharCount){
byte lByte=b[i++];//to fast operation, ++ now, ready for the following for(;;)
if(lByte>=0) continue;//>=0 is normal ascii
if(lByte<(byte)0xc0 || lByte>(byte)0xfd) return false;
int lCount=lByte>(byte)0xfc?5:lByte>(byte)0xf8?4
:lByte>(byte)0xf0?3:lByte>(byte)0xe0?2:1;
if(i+lCount>lLen) return false;
for(int j=0;j<lCount;++j,++i) if(b[i]>=(byte)0xc0) return false;
}
return true;
}
相应地,一个使用上述方法的例子如下:
publicstatic String getUrlParam(String aStr,String aDefaultCharset)
throwsUnsupportedEncodingException{
if(aStr==null) return null;
byte[] lBytes=aStr.getBytes("ISO-8859-1");
return new String(lBytes,StringUtil.isValidUtf8(lBytes)?"utf8":aDefaultCharset);
}
不过,该方法也存在缺陷,如下两方面:
l 没有包括对用户默认编码的识别,这可以根据请求信息的语言来判断,但不一定正确,因为我们有时候也会输入一些韩文,或者其他文字。
l 可能会错误判断UTF-8字符,一个例子是"学习"两个字,其GBK编码是" \xd1\xa7\xcf\xb0",如果使用上述isValidUtf8方法判断,将返回true。可以考虑使用更严格的判断方法,不过估计效果不大。
有一个例子可以证明google也遇到了上述问题,而且也采用了和上述相似的处理方法,比如,如果在地址栏中输入"http://www.google.com/search?hl=zh-CN&newwindow=1&q=学习",google将无法正确识别,而其他汉字一般能够正常识别。
最后,应该补充说明一下,如果不使用rewrite规则,或者通过表单提交数据,其实并不一定会遇到上述问题,因为这时可以在提交数据时指定希望的编码。另外,中文文件名确实会带来问题,应该谨慎使用。
15.6. 其它
下面描述一些和编码有关的其他问题。
15.6.1. SecureCRT
除了浏览器和控制台与编码有关外,一些客户端也很有关系。比如在使用SecureCRT连接linux时,应该让SecureCRT的显示编码(不同的session,可以有不同的编码设置)和linux的编码环境变量保持一致。否则看到的一些帮助信息,就可能是乱码。
另外,mysql有自己的编码设置,也应该保持和SecureCRT的显示编码一致。否则通过SecureCRT执行sql语句的时候,可能无法处理中文字符,查询结果也会出现乱码。
对于Utf-8文件,很多编辑器(比如记事本)会在文件开头增加三个不可见的标志字节,如果作为mysql的输入文件,则必须要去掉这三个字符。(用linux的vi保存可以去掉这三个字符)。一个有趣的现象是,在中文windows下,创建一个新txt文件,用记事本打开,输入"连通"两个字,保存,再打开,你会发现两个字没了,只留下一个小黑点。
15.6.2. 过滤器
如果需要统一设置编码,则通过filter进行设置是个不错的选择。在filterclass中,可以统一为需要的请求或者回应设置编码。参加上述setCharacterEncoding()。这个类apache已经给出了可以直接使用的例子SetCharacterEncodingFilter。
15.6.3. POST和GET
很明显,以POST提交信息时,URL有更好的可读性,而且可以方便的使用setCharacterEncoding()来处理字符集问题。但GET方法形成的URL能够更容易表达网页的实际内容,也能够用于收藏。
从统一的角度考虑问题,建议采用GET方法,这要求在程序中获得参数是进行特殊处理,而无法使用setCharacterEncoding()的便利,如果不考虑rewrite,就不存在IE的UTF-8问题,可以考虑通过设置URIEncoding来方便获取URL中的参数。
15.6.4. 简繁体编码转换
GBK同时包含简体和繁体编码,也就是说同一个字,由于编码不同,在GBK编码下属于两个字。有时候,为了正确取得完整的结果,应该将繁体和简体进行统一。可以考虑将UTF、GBK中的所有繁体字,转换为相应的简体字,BIG5编码的数据,也应该转化成相应的简体字。当然,仍旧以UTF编码存储。
例如,对于"语言 語言",用UTF表示为"\xE8\xAF\xAD\xE8\xA8\x80\xE8\xAA\x9E\xE8\xA8\x80",进行简繁体编码转换后应该是两个相同的 "\xE8\xAF\xAD\xE8\xA8\x80>"。
Manufacturer.com刘科垠
2006-3-8