三、 向服务器发送中文
虽然大家一般都不用JSP甚至Servlet来处理处理客户提交的数据或访问请求参数,但JSP的使用或更新总是比Servlet或 JavaBean来得方便(至少在Tomcat 4.0.4中是如此,因为我们常常不得不为修改了Servlet或JavaBean而重启服务器),所以在这里我们还是要用JSP来访问请求参数了。
不管是在JSP还是在Servlet中,我们都是用ServletRequest(或其子类)的方法getParameter(String name)来访问请求参数的,这个方法返回的是String,也就是说我们能得到的是已经对Internet传来的字节流解码所得的字符串。如果服务器能对这些字节流进行正确的解码,那将是件完美的事。其实说来也很简单,要做到这一点只需要服务器知道这些字节流在客户端是用什么编码进行编码也就行了。如图 2-17我们希望decoding==encoding。
图 3-1 数据从浏览器到服务器
可理想与现实往往是没有交集的,我们怎么也不能让服务器知道这些字节到底是用什么编码被编码的,即使在万维网相关技术的主要设计组织W3C(World Wide Web Consortium)的《HTML 4.01 Specification》和Internet工程任务组(Internet Engineering Task Force, IETF)定义HTTP1.1的rfc2616(《Hypertext Transfer Protocol -- HTTP/1.1》)中也没有发现有什么相关的推荐办法,能在现有的浏览器和HTML Web页下让服务器知道这个编码是什么(如果你知道该怎么做,一定请记得告诉我),所以在缺省情况下,Tomcat 4.0.4又一厢情愿的用起了ISO8859-1来对客户端提交的数据进行解码。如图 2-17,decoding=ISO8859-1,可如果encoding!=ISO8859-1呢,很明显这就是一个数据错误传输了。注意,我们这里所讲的数据,仅仅是客户端给服务器发送的数据中的实体(Entity Body)中的数据。
1. 谁决定了Encoding
谁决定了浏览器的当前Web页通过Form向Internet(最终的对象当然是服务器)发送数据字节流的编码呢?当然是浏览器了。那浏览器又是靠什么决定这个编码的呢?那是继承的浏览器解码当前页(它当然也要对当前Web页解码了,别忘了任何文件或Internet上的元数据都是字节)所使用的decoding了,其实说是继承也不全对,后面你就会发现的。
2. 靠什么决定了Encoding
大约是这六个方面来的信息使浏览器决定用什么encoding:
1) XSL所决定的
2) 实体(Entity Body)中的特殊标记
3) 用户手动对该Web页设置的decoding
4) 响应头(Response Header Field)中的Content-Type
5) HTML元素META中的charset
6) 浏览器以前所用的decoding
它们的优先级可能会因为浏览器的不同而不同,但在IE6.0中是递减的,微软这种做法确实暧昧、耐人寻味,难怪它会在浏览器大战中取得胜利。下面仅对这六点一一解释。
XSL(eXtensible Stylesheet Language, 可扩展样式单语言)可以方便地将XML(eXtensible Markup Language, 可扩展标记语言)转换为其他很多种内容,我们这里只关心它把XML转换为HTML,而对浏览器编码设置的影响。如果该XSL遵循 W3C在1998年发布的有关XSL的第一个工作草案标准(设置XSL文件中的xmlns:xsl="http://www.w3.org/TR/WD-xsl"),则可以通过在XSL中添加元素meta,并作相类似的设置达到设置浏览器编码的目的:
<meta http-equiv="Content-Type" content="text/html; charset=GB2312"/>
如果该XSL是事实上的XSLT(eXtensible Stylesheet Language Transformation, 可扩展样式单转换语言),即xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”,则浏览器编码无条件使用UTF=16,即使在XSL文件中添加meta元素来设置编码都将被忽略。
实体是指服务器给浏览器返回的数据中的实体,也可以简单理解为返回数据中除附加头和空白行之外的所有数据。在前面说过,Windows 2000 Server(我实验所用的操作系统)会在以UTF-8编码的文件前加三个流氓字节(0xEF0xBB0xBF),如果当前Web页是静态资源,则服务器会不加任何处理直接把这个Web 页返回到客户端,如果这个静态Web页也是在Windows 2000 Server中生成的话,那么实体的最初三个字节将是0xEF0xBB0xBF,浏览器很快检测到这三个字节,于是就用UTF-8对正确解码,后面那四点都会被忽略。
手动设置decoding就是在浏览器窗口中对编码进行设置,这是明确的告诉浏览器该Web页应该使用的编码,用户永远是对的。
Tomcat 4.0.4是不会在返回静态Web页设置响应头中的Content-Type,我们在JSP中所使用的:
<%@ page contentType="text/html;charset=gb2312"%>
或在Servlet中使用的:
response.setContentType(“text/html;charset=gb21312”);
就是对响应头(Response Header)中的Content-Type的设置,它的值遵循MIME(Multipurpose Internet Mail Extension protocol, 多用途的网际邮件扩充协议)规范,如图 3-2,请求JSP页面http://localhost/scqdac/t.jsp在客户端收到的所有数据,0x7d是实体中有效数据的长度。
图3-2服务器通过HTTP协议返回给浏览器的所有数据
由图中可以看出,响应头中的Content-Type与HTML中的meta的Content-Type完全无关,这是可以理解的。t.jsp的所有源代码如下:
<%@ page contentType="text/html;charset=gb2312"%><html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
</head>
<body>
</body>
</html>
JSP文件中的
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">。
已经没有什么意义了,没有谁会去理会它。关于响应头中的Content-Type,RFC2616中有详细的定义和说明,请参阅:http://www.ietf.org/rfc/rfc2616.txt。
我们也可以通过设置Web页中的HTML元素meta来达到设置Web页编码的目的:
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
关于HTML文档的字符集(Document Character Set)请参阅《HTML 4.01 Specification》,http://www.w3c.org/TR/html401/html401.html(你同时还可以了解到HTML元素form的属性enctype的设置以及它的作用)。
如果实在找不到charset及相关的信息,浏览器就使用最近一次使用过的编码。
在前面提到了浏览器的编码并不完全继承于decoding,当解码Web页面所使用的decoding是UTF-16时,向服务器发送数据的实体还是使用UTF-8,至少在IE6.0的默认设置下是如此的。还有一个有趣的就是当encoding=ISO8859-1时,请求头(Request Header Field)
Content-Type:application/x-www-form-urlencoded
时,通过Form向服务器发送数据“我是中国人”(name=”text”)时,被编码成:
%26%2325105%3B%26%2326159%3B%26%2320013%3B%26%2322269%3B%
26%2320154%3B
这的确是一种有趣的编码方式,我们略加分析就可以发现这个转义字符串就是
我是中国人
那么在服务器端我们通过
request.getParameter(“text”);
所得到的字符串也将是”我是中国人”,这明显是SGML(Standard Generalized Markup Language, 标准通用标记语言)所采用的实体字符嘛,HTML当然也就能很好地处理它了,所以如果form属性action所对应的JSP中有:
<%=request.getParameter(“text”)%>
那么浏览器将重现“我是中国人”。所以我们可以由得它,当然也可以很容易地处理它,毕竟它是标准的东西。
3. 把字节串还给我们
服务器在没有得到我们的通知的情况下,自我主张地执行了类似
String str = new String(bytes,”ISO8859-1”);
的操作,而且还不让我们能够直接获得客户传输的字节串(Servlet API中没有这个方法)。但我们还是可以让ServletRequest把字节串还给我们,那就是执行它的逆运算,用ISO8859-1编码:
String str = request.getParameter(“text”);
byte bs[] = str.getBytes(“ISO8859-1”);
这时所得的bs,我们有足够的理由相信它完全就是客户端发给服务器的,因为用ISO8859-1对字节流解码,是不会失真的,它得到的字符串,所有的字符高位字节都等于0,也就是说用ISO8859-1对它编码,也不会丢失数据,我们将得到本来的字节串。
4. 重新解码
只要我们对这些字节串用正确的编码重新解码,我们将得到客户提交的真实字符,也许它们和客户端的字符的内码不同,但绝对是相同的字符。
String str = request.getParameter(“text”);
byte bs[] = str.getBytes(“ISO8859-1”);
String text = new String(bs, ”GBK”);
当然最简单和有效的莫过于:
request.setCharacterEncoding(“GBK”);
String text = request.getParameter(“text”);
有时我们在学习中没有使用String的第二个参数,直接使用
String text = new String(bs);
其实是我们系统的缺省编码是GBK,而String正是引用了这个缺省编码。
也许在这个时候,我们才真正地感觉到,如果能够知道客户端浏览器使用了什么编码,那将是多么愉快的事,可是我们不能。不要指望ServletRequest.getCharacterEncoding()能给你带来什么,如果没有在服务器端明确使用ServletRequest对象的方法:setCharacterEncoding(String encoding)设置该对象所描述的请求中的数据的编码,那么该对象的getCharacterEncoding()将返回null,而我们认为的完美组合:
request.setCharacterEncoding(request. getCharacterEncoding());
不管getCharacterEncoding()有没有帮助,都是没有意义的——你本就是从我这里知道的,我还用得着你告诉我么。
既然决定浏览器编码的六种方法中,有三种都可以被服务器所使用(但我们的确不屑于用第一种方法,尽管在IE面前它是最有效的)。如果向服务器提交数据的表单是包含在一个静态Web页面中的,那么我们就设置HTML元素meta的属性,如果该表单是包含在JSP的,我们就设置page指令中的contentType。那么在处理该表单所提交的数据时,我们可以用相应的编码对字节串重解码。但不要大意,这种方法并不是可完全信任的,因为我们的用户可能使用了六种方法中的第二种方法重置了浏览器的编码,幸运的是如果Web页中的不是所有的信息都是英文字符的话,用户还是不会无聊地执行这种非法操作,除非他真的想得到乱码。
本文地址:http://com.8s8s.com/it/it17110.htm