版权声明:如有转载请求,请注明出处:http://blog.csdn.net/yzhz 杨争
一、问题:
编码问题是JAVA初学者在web开发过程中经常会遇到问题,网上也有大量相关的文章介绍,但其中很多文章并没有对URL中使用了中文等非ASCII的字符造成服务器后台程序解析出现乱码的问题作出准确的解释和说明。本文将详细介绍由于在URL中使用了中文等非ASCII的字符造成乱码的问题。
1、在URL中中文字符通常出现在以下两个地方:
(1)、Query String中的参数值,比如http://search.china.alibaba.com/search/offer_search.htm?keywords=中国
(2)、servlet path,比如:http://search.china.alibaba.com/selloffer/中国.html
2、出现乱码问题的原因主要是以下几方面:
(1)、浏览器:我们的客户端(浏览器)本身并没有遵循URI编码的规范(http://www.w3.org/International/O-URL-code.html)。
(2)、Servlet服务器:Servlet服务器的没有正确配置。
(3)、开发人员并不了解Servlet的规范和API的含义。
二、基础知识:
1、一个http请求经过的几个环节:
浏览器(ie firefox)【get/post】------------>Servlet服务器------------------------------->浏览器显示
编码 解码成unicode,然后将显示的内容编码 解码
(1) 浏览器把URL(以及post提交的内容)经过编码后发送给服务器。
(2) 这里的Servlet服务器实际上指的是由Servlet服务器提供的servlet实现ServletRequestWrapper,不同应用服务器的servlet实现不同,这些servlet的实现把这些内容解码转换为unicode,处理完毕后,然后再把结果(即网页)编码返回给浏览器。
(3) 浏览器按照指定的编码显示该网页。
当对字符串进行编码和解码的时候都涉及到字符集,通常使用的字符集为ISO8859-1、GBK、UTF-8、UNICODE。
2、URL的组成:
域名:端口/contextPath/servletPath/pathInfo?queryString
说明:
1、ContextPath是在Servlet服务器的配置文件中指定的。
对于weblogic:
contextPath是在应用的weblogic.xml中配置。
<context-root>/</context-root>
对于tomcat:
contextPath是在server.xml中配置。
<Context path="/" docBase="D:/server/blog.war" debug="5" reloadable="true" crossContext="true"/>
对于jboos:
contextPath是在应用的jboss-web.xml中配置。
<jboss-web>
<context-root>/</context-root>
</jboss-web>
2、ServletPath是在应用的web.xml中配置。
<servlet-mapping>
<servlet-name>Example</servlet-name>
<url-pattern>/example/*</url-pattern>
</servlet-mapping>
2、Servlet API
我们使用以下servlet API获得URL的值及参数。
request.getParameter("name"); // 获得queryString的参数值(来自于get和post),其值经过Servlet服务器URL Decode过的
request.getPathInfo(); // 注意:pathinfo返回的字符串是经过Servlet服务器URL Decode过的。
requestURI = request.getRequestURI(); // 内容为:contextPath/servletPath/pathinfo 浏览器提交过来的原始数据,未被Servlet服务器URL Decode过。
3、开发人员必须清楚的servlet规范:
(1) HttpServletRequest.setCharacterEncoding()方法 仅仅只适用于设置post提交的request body的编码而不是设置get方法提交的queryString的编码。该方法告诉应用服务器应该采用什么编码解析post传过来的内容。很多文章并没有说明这一点。
(2) HttpServletRequest.getPathInfo()返回的结果是由Servlet服务器解码(decode)过的。
(3) HttpServletRequest.getRequestURI()返回的字符串没有被Servlet服务器decoded过。
(4) POST提交的数据是作为request body的一部分。
(5) 网页的Http头中ContentType("text/html; charset=GBK")的作用:
(a) 告诉浏览器网页中数据是什么编码;
(b) 表单提交时,通常浏览器会根据ContentType指定的charset对表单中的数据编码,然后发送给服务器的。
这里需要注意的是:这里所说的ContentType是指http头的ContentType,而不是在网页中meta中的ContentType。
三、下面我们分别从浏览器和应用服务器来举例说明:
URL:http://localhost:8080/example/中国?name=中国
汉字 编码 二进制表示
中国 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]信息失去
(一)、浏览器
1、GET方式提交,浏览器会对URL进行URL encode,然后发送给服务器。
(1) 对于中文IE,如果在高级选项中选中总以UTF-8发送(默认方式),则PathInfo是URL Encode是按照UTF-8编码,QueryString是按照GBK编码。
http://localhost:8080/example/中国?name=中国
实际上提交是:
GET /example/%E4%B8%AD%E5%9B%BD?name=%D6%D0%B9%FA
(1) 对于中文IE,如果在高级选项中取消总以UTF-8发送,则PathInfo和QueryString是URL encode按照GBK编码。
实际上提交是:
GET /example/%D6%D0%B9%FA?name=%D6%D0%B9%FA
(3) 对于中文firefox,则pathInfo和queryString都是URL encode按照GBK编码。
实际上提交是:
GET /example/%D6%D0%B9%FA?name=%D6%D0%B9%FA
很显然,不同的浏览器以及同一浏览器的不同设置,会影响最终URL中PathInfo的编码。对于中文的IE和FIREFOX都是采用GBK编码QueryString。
小结:解决方案:
1、URL中如果含有中文等非ASCII字符,则浏览器会对它们进行URLEncode。为了避免浏览器采用了我们不希望的编码,所以最好不要在URL中直接使用非ASCII字符,而采用URL Encode编码过的字符串%.
比如:
URL:http://localhost:8080/example/中国?name=中国
建议:
URL:http://localhost:8080/example/%D6%D0%B9%FA?name=%D6%D0%B9%FA
2、我们建议URL中PathInfo和QueryString采用相同的编码,这样对服务器端处理的时候会更加简单。
2、还有一个问题,我发现很多程序员并不明白URL Encode是需要指定字符集的。不明白的人可以看看这篇文档:http://gceclub.sun.com.cn/Java_Docs/html/zh_CN/api/java/net/URLEncoder.html
2、 POST提交
对于POST方式,表单中的参数值对是通过request body发送给服务器,此时浏览器会根据网页的ContentType("text/html; charset=GBK")中指定的编码进行对表单中的数据进行编码,然后发给服务器。
在服务器端的程序中我们可以通过Request.setCharacterEncoding() 设置编码,然后通过request.getParameter获得正确的数据。
解决方案:
1、从最简单,所需代价最小来看,我们对URL以及网页中的编码使用统一的编码对我们来说是比较合适的。
如果不使用统一编码的话,我们就需要在程序中做一些编码转换的事情。这也是我们为什么看到有网络上大量的资料介绍如何对乱码进行处理,其中很多解决方案都只是一时的权宜之计,没有从根本上解决问题。
(二)、Servlet服务器
Servlet服务器实现的Servlet遇到URL和POST提交的数据中含有%的字符串,它会按照指定的字符集解码。下面两个Servlet方法返回的结果都是经过解码的:
request.getParameter("name");
request.getPathInfo();
这里所说的"指定的字符集"是在应用服务器的配置文件中配置。
(1) tomcat服务器
对于tomcat服务器,该文件是server.xml
<Connector port="8080" protocol="HTTP/1.1"
maxThreads="150" connectionTimeout="20000"
redirectPort="8443" URIEncoding="GBK"/>
URIEncoding告诉服务器servlet解码URL时采用的编码。
<Connector port="8080" ... useBodyEncodingForURI="true" />
useBodyEncodingForURI告诉服务器解码URL时候需要采用request body指定的编码。
(2) weblogic服务器
对于weblogic服务器,该文件是weblogic.xml
<input-charset>
<java-charset-name>GBK</java-charset-name>
</input-charset>
(三)浏览器显示
浏览器根据http头中的ContentType("text/html; charset=GBK"),指定的字符集来解码服务器发送过来的字节流。我们可以调用HttpServletResponse.setContentType()设置http头的ContentType。
总结:
1、URL中的PathInfo和QueryString字符串的编码和解码是由浏览器和应用服务器的配置决定的,我们的程序不能设置,不要期望用request.setCharacterEncoding()方法能设置URL中参数值解码时的字符集。
所以我们建议URL中不要使用中文等非ASCII字符,如果含有非ASCII字符的话要使用URLEncode编码一下,比如:
http://localhost:8080/example1/example/中国
正确的写法:
http://localhost:8080/example1/example/%E4%B8%AD%E5%9B%BD
并且我们建议URL中不要在PathInfo和QueryString同时使用非ASCII字符,比如
http://localhost:8080/example1/example/中国?name=中国
原因很简单:不同浏览器对URL中PathInfo和QueryString编码时采用的字符集不同,但应用服务器对URL通常会采用相同的字符集来解码。
2、我们建议URL中的URL Encode编码的字符集和网页的contentType的字符集采用相同的字符集,这样程序的实现就很简单,不用做复杂的编码转换。
发表于 @ 2007年07月03日 15:14:00 | | 编辑| 举报| 收藏
有个疑问?
原文1:
(b) 表单提交时,通常浏览器会根据ContentType指定的charset对表单中的数据编码,然后发送给服务器的。
原文2:
很显然,不同的浏览器以及同一浏览器的不同设置,会影响最终URL中PathInfo的编码。对于中文的IE和FIREFOX都是采用GBK编码QueryString。
那么浏览器提交的时候到底按哪个标准编码?
难道原文1处指的是post?原文2处指的是get?
yzhz 发表于2007年7月5日 15:50:47 IP:举报
是的,原文1是指post的方式,因为get方式的时候用户只输入url,浏览器没有什么编码可参考,所以get方式的时候浏览器采用自己的规定去编码url。
对于post方式,由于服务器先前已返回了网页给浏览器,所以post的时候浏览器会根据网页的contenttype来编码表单的数据。
如果提交的数据中不含%,服务器又该如何解码
yzhz 发表于2007年7月5日 16:31:40 IP:举报
呵呵,你可以看看java.net.URLDecoder的decode方法是怎么做的?它按照字符集,该怎么做就怎么做。
package java.net;
public class URLDecoder {
public static String decode(String s, String enc)
throws UnsupportedEncodingException{
boolean needToChange = false;
int numChars = s.length();
StringBuffer sb = new StringBuffer(numChars > 500 ? numChars / 2 : numChars);
int i = 0;
if (enc.length() == 0) {
throw new UnsupportedEncodingException ("URLDecoder: empty string enc parameter");
}
char c;
byte[] bytes = null;
while (i < numChars) {
c = s.charAt(i);
switch (c) {
case '+':
sb.append(' ');
i++;
needToChange = true;
break;
case '%':
/*
* Starting with this instance of %, process all
* consecutive substrings of the form %xy. Each
* substring %xy will yield a byte. Convert all
* consecutive bytes obtained this way to whatever
* character(s) they represent in the provided
* encoding.
*/
try {
// (numChars-i)/3 is an upper bound for the number
// of remaining bytes
if (bytes == null)
bytes = new byte[(numChars-i)/3];
int pos = 0;
while ( ((i+2) < numChars) &&
(c=='%')) {
bytes[pos++] =
(byte)Integer.parseInt(s.substring(i+1,i+3),16);<
你给我的这段代码实际上已经假定传入了编码即enc,否则会抛意外
我想问的其实是如果不通过浏览器请求,通过应用程序或者脚本请求某个url,而且这个url并未显式的编码,那最终发送的服务器的url的编码情况是怎样的
yzhz 发表于2007年7月5日 17:28:39 IP:举报
那服务器会通过默认的编码解析URL。
当存在这种情况的话,我们通常的做法是双方约定好url采用的编码,这样服务器端的servlet根据约定好的编码去解析url。
比如我在程序中发送请求http://xxx.com?y=中国,到服务器的时候
"中国"两个字编码情况是怎样(此时服务器还没开始解码)
yzhz 发表于2007年7月5日 17:56:26 IP:举报
举个例子:
如果你采用httpClient来发送这个url的话,httpclient会把这个url encode,然后发送给服务器。这时候encode采用的字符集为httpclient默认的字符集。
所以我建议如果你的url带有中文等非ascii字符的话,你最好自己先encode,这样httpclient encode的时候就不会对这些字符进行改变,这样你就控制url的编码,比如我采用gbk,那么我发送的url就是:
String url = http://xxx.com?y=%D6%D0%B9%FA
HttpClient client = new HttpClient();
GetMethod method = new GetMethod(url);
yzhz 发表于2007年7月5日 17:58:58 IP:举报
接上面:
那么服务器端按照gbk解码就可以了。
所以一句话,采用get方式最重要的是发送方和接收方都约定好编码。
谢谢你的回答
另外一个问题:
我一个应用全部使用utf-8 编码,页面meta,urlencode,数据库,服务器tomcat使用默认编码iso- 8859-1
使用post提交中文的时候,无须转码OK
但采用get+urlencode的时候必须后台转码iso-8859-1---〉utf-8
这个原因是什么?按照你上面说的,我页面的meta是utf-8,那么form提交应该是utf-8编码,那么应该和get+urlencode效果一样,但到服务器端的时候,前者需要转码后者不需要,难道服务器只对get解码而不理会post
yzhz 发表于2007年7月6日 11:13:31 IP:举报
没看明白,你现在是get方式提交是好的,还是post方式提交是好的。
有几个问题,你要告诉我。
1、在tomcat的配置中URIEncoding和useBodyEncodingForURI的值是多少,你的tomcat版本是多少?
<Connector port="8080" protocol="HTTP/1.1"
maxThreads="150" connectionTimeout="20000"
redirectPort="8443" URIEncoding="GBK" useBodyEncodingForURI="true"/>
2、页面meta写明是编码有时候是没有用的,关键是你的http头中的编码是什么,就是HttpServletResponse.setContentType()设置http头的ContentType的编码是什么?
我现在post提交没问题
get+urlencode也没问题,但是需要在后台转一下码(iso-8859-1---〉utf-8)
我没弄明白为什么后者需要转码,前者不需要
你有msn吗? 我的pwlazy@sina.com
yzhz 发表于2007年7月6日 15:50:22 IP:举报
yangzheng027@hotmail.com
如果我在IE7中直接输入"http://localhost:8080/example1/example/中国?name=中国",是怎样处理的?
谢谢,
我遇到了类似的问题,当post的时候,中文不会乱码;而使用GET的时候,中文就会乱码
我使用的是UTF-8的编码,使用get方式,在接收端发现中文被转换成gb2312,
读完此文,大概知道了原因,也就不在尝试其他解决办法了,而是直接使用post方式,就不会转码了。
好文,不过文档里面有些测试结果应该是有前置条件的,比如queryString为什么用GBK编码、可能会用其他字符集来编码吗?
我在本文的基础上我进一步整理了下,具体见《中文化和国际化问题权威解析之五:URL编码/Misc》;
http://blog.csdn.net/sfdev/archive/2009/01/20/3841719.aspx
好文,不过文档里面有些测试结果应该是有前置条件的,比如queryString为什么用GBK编码、可能会用其他字符集来编码吗?
我在本文的基础上我进一步整理了下,具体见《中文化和国际化问题权威解析之五:URL编码/Misc》;
http://blog.csdn.net/sfdev/archive/2009/01/20/3841719.aspx
我在jsp(charset=gbk)里面用ajax 的时候querystring中用js的函数URIencode() 服务器用URIdecode可以,但是用window.location.href=...../?area=德清用上面同样的方法怎么不行啊?