深入浅出Web编码

一 问题:

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

3 Servlet API

我们使用以下 servlet API 获得 URL 的值及参数。

 

Java代码   收藏代码
  1. request.getParameter("name");          
  2.   
  3. // 获得queryString的参数值(来自于get和post),其值经过Servlet服务器URL Decode过的  
  4. request.getPathInfo();                 
  5.   
  6. // 注意:pathinfo返回的字符串是经过Servlet服务器URL Decode过的。  
  7. requestURI = request.getRequestURI();  
  8.   
  9. // 内容为:contextPath/servletPath/pathinfo 浏览器提交过来的原始数据,未被Servlet服务器URL Decode过。   

3 开发人员必须清楚的servlet规范:

(1) HttpServletRequest.setCharacterEncoding() 方法仅仅只适用于设置 post 提交的requestbody 的编码而不是设置 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 还有一个问题,我发现很多程序员并不明白 URLEncode 是需要指定字符集的。不明白的人可以看看这篇文档: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") 中指定的编码进行对表单中的数据进行 URL Encode 编码,然后发给服务器。


在服务器端的程序中我们可以通过 Request.setCharacterEncoding() 设置编码,然后通过 request.getParameter 获得正确的数据。


解决方案:


1、从最简单,所需代价最小来看,我们对 URL 以及网页中的编码使用统一的编码对我们来说是比较合适的。


如果不使用统一编码的话,我们就需要在程序中做一些编码转换的事情。这也是我们为什么看到有网络上大量的资料介绍如何对乱码进行处理,其中很多解决方案都只是一时的权宜之计,没有从根本上解决问题。

(二) Servlet服务器

Servlet服务器实现的Servlet遇到URL和POST提交的数据中含有%的字符串,它会按照指定的字符集解码。
下面两个Servlet方法返回的结果都是经过解码的:

 

Java代码   收藏代码
  1. request.getParameter("name");  
  2. 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" />  

 

useBodyEncodingForURI告诉服务器解码URL时候需要采用request body指定的编码。

(2) weblogic服务器

对于weblogic服务器,该文件是weblogic.xml

 

Xml代码   收藏代码
  1. <input-charset>  
  2.   <java-charset-name>GBK</java-charset-name>  
  3. </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:

  form有两种方法把数据提交给服务器:get 和 post。

(一)get提交

1.客户端(浏览器)

form表单用 get 方法如何将数据编码后提交给服务器端。

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


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


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


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


这里想多说几句的是,对于 get 方法来说,没有请求实体,含有数据的 url 都在请求头里面,之所以用 URLencode,我个人觉的原因是:对于请求头来说最终都是要用 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 ofthis request. This method must be called prior to reading requestparameters or reading input using getReader().可以看出对于 get 方法他是无能为力的。那么到底用什么编码方式解码数据的呢,这是 tomcat 的事情了,默认缺省用的是 iso-8859-1,这样我们就能理解为什么 get 请求带中文参数会在服务器端得到乱码了,原因是在客户端一般都是用UTF-8 或 GBK 对数据 URL encode,这里用iso-8859-1 方式 URL decode 显然不行,在程序里我们可以直接

 

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编码方式")  

 

还原回字节码,然后用正确的方式解码数据,网上的文章通常是在 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"/>  
 


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

(二)post提交

1. 客户端(浏览器)

form 表单用 post 方法如何将数据编码后提交给服务器端。


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


  
在 form 所在的 html 文件里如果有段

 

 

Html代码   收藏代码
  1. <meta http-equiv="Content-Type" content="text/html; charset=字符集(GBK,utf-8等)"/>  
 

 

     那么 post 就会用此处指定的编码方式编码。一般大家都认为这段代码是为了让浏览器知道用什么字符集来对网页解释,所以网站都会把它放在 html 代码的最前端,尽量不出现乱码,其实它还有个作用就是指定 form 表单的 post 方法提交数据的URL encode 编码方式。从这里可以看出对于 get 方法来说,浏览器对数据的 URLencode 的编码方式是有浏览器设置来决定,(也可以用 js 做统一指定),而 post 方法,开发人员可以指定。

2. 服务器端(tomcat)

如何将数据获取到进行解码的。


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

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



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值