注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

phperwuhan的博客

记载一个phper的历程!phperwuhan.blog.163.com

 
 
 

日志

 
 

深入浅出 web 编码  

2009-09-02 17:02:01|  分类: ajax |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

来源:http://yiminghe.javaeye.com/blog/243812

一、问题:


        编码问题是 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
说明:

 

Xml代码 复制代码
  1. <!--   
  2. 1、ContextPath是在Servlet服务器的配置文件中指定的。   
  3. 对于weblogic:   
  4. contextPath是在应用的weblogic.xml中配置。   
  5. -->  
  6. <context-root>/</context-root>  
  7. <!--   
  8. 对于tomcat:   
  9. contextPath是在server.xml中配置。   
  10. -->  
  11.   
  12. <Context path="/" docBase="D:/server/blog.war" debug="5" reloadable="true"    
  13. crossContext="true"/>  
  14.   
  15. <!--   
  16. 对于jboos:   
  17. contextPath是在应用的jboss-web.xml中配置。   
  18. -->  
  19.   
  20. <jboss-web>  
  21.     <context-root>/</context-root>  
  22. </jboss-web>  
  23.   
  24. <!--  
  25. 2、ServletPath是在应用的web.xml中配置。  
  26. -->  
  27. <servlet-mapping>  
  28.     <servlet-name>Example</servlet-name>  
  29.     <url-pattern>/example/*</url-pattern>  
  30. </servlet-mapping>  
<!--
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


(2) 对于中文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采用相同的编码,这样对服务器端处理的时候会更加简单。


3、还有一个问题,我发现很多程序员并不明白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

Xml代码 复制代码
  1. <Connector port="8080" protocol="HTTP/1.1"  
  2.                maxThreads="150" connectionTimeout="20000"  
  3.                redirectPort="8443" URIEncoding="GBK"/>  
  4. URIEncoding告诉服务器servlet解码URL时采用的编码。   
  5. <Connector port="8080" ... useBodyEncodingForURI="true" />  
<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

Xml代码 复制代码
  1. <input-charset>  
  2.   <java-charset-name>GBK</java-charset-name>  
  3. </input-charset>  
<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的字符集采用相同的字符集,这样程序的实现就很简单,不用做复杂的编码转换。




ps :


做java的web开发有段日子了,有个问题老是困扰着我,就是乱码问题,基本上是网上查找解决方案(网上资料真的很多),都是一大堆的介绍如何解决此类的乱码问题,但是没几个把问题的来龙去脉说清楚的,有时候看了些文章后,以为自己懂了,但是在开发中乱码问题又像鬼魂一样出来吓人,真是头大了!这篇文章是我长时间和乱码做斗争的一些理解的积累,还希望有更多的朋友给出指点和补充。


  form有2中方法把数据提交给服务器,get和post,分别说下吧。

(一)get提交
  1.首先说下客户端(浏览器)的form表单用get方法是如何将数据编码后提交给服务器端的吧。

 
    对于get方法来说,都是把数据串联在请求的url后面作为参数,如:http://localhost:8080/servlet?msg=abc
(很常见的一个乱码问题就要出现了,如果url中出现中文或其它特殊字符的话,如:http://localhost:8080 /servlet?msg=杭州,服务器端容易得到乱码),url拼接完成后,浏览器会对url进行URL encode,然后发送给服务器,URL encode的过程就是把部分url做为字符,按照某种编码方式(如:utf-8,gbk等)编码成二进制的字节码,然后每个字节用一个包含3个字符的字符串 "%xy" 表示,其中xy为该字节的两位十六进制表示形式。


了解了URL encode的过程,我们能看到2个很重要的问题,


第一:需要URL encode的字符一般都是非ASCII的字符(笼统的讲),再通俗的讲就是除了英文字母以外的文字(如:中文,日文等)都要进行URL encode,所以对于我们来说,都是英文字母的url不会出现服务器得到乱码问题,出现乱码都是url里面带了中文或特殊字符造成的;


第二:URL encode到底按照那种编码方式对字符编码?这里就是浏览器的事情了,而且不同的浏览器有不同的做法,中文版的浏览器一般会默认的使用GBK,通过设置浏览器也可以使用UTF-8,可能不同的用户就有不同的浏览器设置,也就造成不同的编码方式,所以很多网站的做法都是先把url里面的中文或特殊字符用 javascript做URL encode然后再拼接url提交数据也就是替浏览器做了URL encode好处就是网站可以统一get方法提交数据的编码方式 。 完成了URL encode,那么现在的url就成了ASCII范围内的字符了,然后以iso-8859-1的编码方式转换成二进制 随着请求头一起发送出去。


这里想多说几句的是,对于get方法来说,没有请求实体,含有数据的url都在请求头里面,之所以用URL encode,我个人觉的原因是:对于请求头来说最终都是要用iso-8859-1编码方式编码成二进制的101010.....的纯数据在互联网上传送,如果直接将含有中文等特殊字符做iso-8859-1编码会丢失信息,所以先做URL encode是有必要的。

 


   2。服务器端(tomcat)是如何将数据获取到进行解码的。


   第一步是先把数据用iso-8859-1进行解码 ,对于get方法来说,tomcat获取数据的是ASCII范围内的请求头字符,其中的请求url里面带有参数数据,如果参数中有中文等特殊字符,那么目前还是 URL encode后的%XY状态, 先停下,我们先说下开发人员一般获取数据的过程。通常大家都是 request.getParameter("name")获取参数数据,我们在request对象或得的数据都是经过解码过的,而解码过程中程序里是无法指定,这里要说下,有很多新手说用 request.setCharacterEncoding("字符集")可以指定解码方式,其实是不可以的,看servlet的官方API说明有对此方法的解释:Overrides the name of the character encoding used in the body of this request. This method must be called prior to reading request parameters or reading input using getReader().可以看出 对于get方法他是无能为力的 。那么到底用什么编码方式解码数据的呢,这是 tomcat的事情了,默认缺省用的是 iso-8859-1 ,这样我们就能找到为什么get请求带中文参数为什么在服务器端得到乱码了,原因是在客户端一般都是用UTF-8或GBK对数据 URL encode,这里用iso-8859-1方式URL decoder显然不行,在程序里我们可以直接

 

 

Java代码 复制代码
  1. new String(request.getParameter("name").getBytes("iso-8859-1"),   
  2. "客户端指定的URL encode编码方式")    
  3.   
  4. new String(request.getParameter("name").getBytes("iso-8859-1"),   
  5. "客户端指定的URL encode编码方式")  
new String(request.getParameter("name").getBytes("iso-8859-1"),
"客户端指定的URL encode编码方式") 

new String(request.getParameter("name").getBytes("iso-8859-1"),
"客户端指定的URL encode编码方式")

 


还原回字节码,然后用正确的方式解码数据,网上的文章通常是在tomcat里面做个配置


Xml代码 复制代码
  1. <Connector port="8080" protocol="HTTP/1.1" maxThreads="150" connectionTimeout="20000"  
  2.  redirectPort="8443" URIEncoding="GBK"/>    
  3.   
  4. <Connector port="8080" protocol="HTTP/1.1" maxThreads="150" connectionTimeout="20000"    
  5. redirectPort="8443" URIEncoding="GBK"/>  
<Connector port="8080" protocol="HTTP/1.1" maxThreads="150" connectionTimeout="20000"
 redirectPort="8443" URIEncoding="GBK"/> 

<Connector port="8080" protocol="HTTP/1.1" maxThreads="150" connectionTimeout="20000" 
redirectPort="8443" URIEncoding="GBK"/>

 



这样是让tomcat在获取数据后用指定的方式URL decoder


(二)post提交


1.客户端(浏览器)的form表单用post方法是如何将数据编码后提交给服务器端的。


  在post方法里所要传送的数据也要URL encode,那么他是用什么编码方式的呢?


  
在form所在的html文件里如果有段<meta http-equiv="Content-Type" content="text/html; charset=字符集(GBK,utf-8等)"/>,那么post就会用此处指定的编码方式编码。一般大家都认为这段代码是为了让浏览器知道用什么字符集来对网页解释,所以网站都会把它放在html代码的最前端,尽量不出现乱码,其实它还有个作用就是指定form表单的post方法提交数据的 URL encode编码方式。从这里可以看出对于get方法来数,浏览器对数据的URL encode的编码方式是有浏览器设置来决定,(可以用js做统一指定),而post方法,开发人员可以指定。


2。服务器端(tomcat)是如何将数据获取到进行解码的。


如果用tomcat默认缺省设置,也没做过滤器等编码设置,那么他也是用 iso-8859-1 解码的,但是request.setCharacterEncoding("字符集")可以派上用场。

上面说的tomcat所做的事情前提都是在请求头里没有指定编码方式,如果请求头里指定了编码方式将按照这种方式编码。 (如 ajax 里设了 charset=utf-8 ,就默认用 utf-8)

  评论这张
 
阅读(506)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017