get post 乱码深入分析

浏览器 IE/FireFox ----------->Servlet容器------------------------>显示页面

编码        使用容器的URIEncoding解码/request解码,再编码发出响应             解码

一、请求
我把用户发送请求方式不同引起的中文问题划分了四种类型:

1、表单的 get 提交

2、表单的 post 提交

3、页面链接传递中文参数(参考get提交)

4、地址栏中参数直接输入中文提交(不讨论,违背寻常规则,而且这种方式很难控制)

1.get提交
提交过程:3 步
第一步:浏览器会根据自己的【页面的编码格式作为起始编码格式】(右击菜单编码有显示的),把字符使用浏览器的编码格式编码成byte字节进行传输。
第二步:请求发送到 tomcat 之后,tomcat会使用URIEncoding进行重新编码(解码),如果tomcat没有配置的话就会使用iso-8859-1对byte进行重新编码(解码)成字符。
第三步:doGet 方法从 request.getParameter("xxx") 中取值。

从上面的提交过程来看,get 请求在前两步的编码是一定发生了的,所有的乱码问题都是从前面两步中产生出来的,因此要解决问题,就只要分析前两步里面的问题就可以了。

第一步分析:页面的编码格式指的是当前页面本身的编码格式(右击菜单编码有显示的),比如正常有 ISO-8859-1,但是这是对于英文的网页,而对于中文的网页来说这种编码格式是不合适的,中文通常使用的是 GBK 和 UTF-8,这里我们以 UTF-8 为例, 如果这个地方是 UTF-8 的话,那么当经过 UTF-8 就相当于是第一步提交的内容是经过 UTF-8 编码过的。

第二步分析:对于到达 tomcat 的所有 get 请求,tomcat会使用URIEncoding进行重新编码(解码),如果tomcat没有配置的话就会使用iso-8859-1对byte进行重新编码(解码)成字符。至于 tomcat 的 URIEncoding 在 tomcat conf 目录下的 server.xml 文件中的  <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />,默认情况下,URIEncoding 是 ISO-8859-1 格式的,并且是没有显示的指出来的,如果要自己显示的定义成其他的编码格式就需要自己手动加上URIEncoding="xxx",例如下面的这种设置为 UTF-8 的方式:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" />。

第三步分析:对于正常情况下,因为 tomcat 的 URIEncoding 是 ISO-8859-1,所以相当于字符串被 UTF-8 编码了一次,然后到了 tomcat 之后又被使用 ISO-8859-1 编码了一次,这样当我们要使用里面存放的值得实时候,我们只能够通过转码的格式来获取真正的值了,过程就是 xxx -> utf-8 编码一次 -> iso-8859-1 编码一次,所以我们如果要使用它的话就需要按照编码的顺序逆序的解析,也就是使用:xxx = new String(request.getParameter("name").getBytes("iso-8859-1"),"utf-8")。

于是从上面的分析我们知道,要解决编码问题其实就是要解决这个过程中编码的不一致性,因此有两种方法:
第一:让 tomcat 的 URIEnconding 的解析方式跟页面的编码格式一致,那就是页面的编码格式为 utf-8,tomcat 的 URIEncoding 也要设置为utf-8。
第二:在获取值得的时候直接手动的进行转换 xxx = new String(request.getParameter("name").getBytes("iso-8859-1"),"utf-8"),但是这样也有一个问题,就是你不得不在你的代码中写很多重复的无用的这些转换代码出来。

2.post提交
第一步:浏览器根据自己页面的编码格式作为起始编码格式,把字符进行编码成byte进行传输。
第二步:tomcat不进行干涉其中的重新编码(解码)格式,这个时候 request.setCharacterEncoding 对于页面的编码有影响,当没有对 request.setCharacterEncoding设置的时候值为null,则默认采用iso-8859-1来进行重新编码(解码),如果设置了就按照指定的格式去解析。
第三步:在 doPost 方法中使用 request 获取值。

因此从上面的分析来看,对 post 的编码有影响的也是两个地方,一个是页面本身的编码,一个是 request 的编码,为了解决这个问题,我们有两种选择:
第一:在获取值时使用xxx = new String(request.getParameter("name").getBytes("iso-8859-1"),"utf-8")。
第二:使用 Filter 对编码进行统一的处理如下:
public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        // 只对 post 参数有效
        request.setCharacterEncoding("utf-8");
        chain.doFilter(request, response);
    }

注意:在 request.setCharacterEncoding("utf-8"); 语句之前,不可以执行任何 getParameter(),分析原因可能是因为在执行第一个 getParameter 语句的时候,java 将会按照第一次的编码处理所有提交内容。


因此要解决 get 和 post 的乱码问题需要通过(1)设置 tomcat URIEncoding 来解决 get 问题;(2)设置request.setCharacterEncoding("utf-8"); 来解决 post 问题。


jsp:pageEncoding="GB18030"  jsp页面的编码格式,即jsp会被解析成servlet时,采用的编码格式。如果不配置,默认采用iso-8859-1,当jsp文件保存编码类型和pageEncoding不一致时就会出现jsp内部解析乱码。Eclipse现在默认pageEncoding就是文件的编码格式,修改pageEncoding就会修改文件的编码格式。该参数还有一个功能,就是在JSP中不指定contentType参数,也不使用response.setCharacterEncoding方法时指定对服务器响应进行重新编码(解码)的编码,从而pageEncoding会影响浏览器的编码格式。

jsp:contentType="text/html;charset=UTF-8"  的作用是指定对服务器响应进行重新编码(解码)的编码。设定浏览器的编码格式。也就是说浏览器提交数据就会使用这个编码格式。相当于response.setCharacterEncoding来改变编码,但是改变的只是jsp请求的response编码格式。不能改变里面所有其他的ajax请求的编码格式。在没有设定的情况下默认采用ISO-8859-1格式。

meta中
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
当前面pageEncoding和contentType都没有设置的情况下,被解析成的html页面就会采用这种编码方式。来把byte解析成浏览器显示的信息。

jsp页面中 pageEncoding--->contentType--->meta 默认缺省。pageEncoding写了后面的都可以不用写,默认继承。

jsp页面中的设定编码优先级response.setCharacterEncoding--->contentType--->pageEncoding  层层覆盖。

request.setCharacterEncoding("UTF-8")的作用是设置对客户端请求进行重新编码(解码)的编码。
该方法用来指定对浏览器发送来的数据进行重新编码(或者称为解码)时,使用的编码。对post方法有效。

使用response.setCharacterEncoding方法时,用该参数指定对服务器响应进行重新编码(解码)的编码。

tomcat:默认URIEncoding为iso-8859-1,可以设置。设置之后,会影响get方法和页面链接传递中文的参数字符编码。

关于UTF-8和GBK转化之间的问题
当UTF-8转化成GBK,再从GBK转化成UTF-8的时候,偶数汉字可以在UTF-8,GBK两者中互相转换,而奇数个汉字则不能。

关于BIG5
关于其他的转码问题,如果使用的是简体中文的字符,即使编码和解码都是使用BIG5,部分字符仍然无法解析,是乱码。

关于ISO-8859-1
ISO-8859-1是不支持中文的,所以就算中文字符使用ISO-8859-1进行编码,最后再用ISO-8859-1进行重新编码(解码)的话,拿到的字符也不能显示中文。即换言之,ISO-8859-1不能作为把字符变成byte的编码格式使用。

ajax
xmlHttp.responseText的请求的默认编码是UTF-8 。当然可以重新设置,通过在Request Headers中设置Content-Type:application/x-www-form-urlencoded; charset=utf-8。为什么ajax和之前的不同呢?因为ajax不是使用默认的浏览器跳转提交方式,而是使用httprequest提交方式,默认跳转方式会读取浏览器的编码格式,而httprequest不会,所以ajax就会设置自己的默认的编码格式进行提交,即UTF-8.而使用ajax的post方法提交,无需再设定request的重新编码(解码)格式,因为request不再是默认的null,已经修改为UTF-8,所以不用转换直接拿出即可。而对于get方法的话,需要参考tomcat的URIEncoding重新编码(解码)。



二、返回信息
而response如果没有显式设置的话,不管request的编码是什么,response的编码就是ISO-8859-1。

对于response返回的信息如:response.getWriter().println就可以看到这个编码设置的作用了。而对于使用request.setAttribute等传递数据的话,这个编码格式设置了也没用。

当使用response.getWriter().println打印到浏览器时,在没有设置response的时候默认为null,而在服务器端则默认使用iso-8859-1进行编码成byte,等到了浏览器,发现response的信息header中没有相关编码设置,就会去取window系统的编码格式,中文系统默认为GBK/GB2312。所以,打印出来的页面的浏览器编码格式为GB2312。而如果设置了response的编码格式,那么就算到了浏览器,浏览器解析也会按照设置的编码格式重新编码(解码)。

当使用response.getWriter().println打印到本地文件时,即使设置了response,在发出时,采用的是设置的编码格式编码成byte,等到了客户端,客户端会用系统的编码格式重新编码(解码)文件,对于windows默认就是GB2312/GBK,所以最好再response发出时就设置编码的格式为GBK。

至于为什么打印到浏览器,头文件的信息就写入编码格式,而打印到本地文件,头文件中就没有写入编码格式的问题,还没有得到证实。猜测:后端传送到浏览器时,浏览器使用包含重新编码(解码)格式的。而传送文件时,没有使用重新编码(解码)格式的,使用的是操作系统的编码格式存储文件。

如果设置了response.setCharacterEncoding。那么就会按照这个编码格式传送到前端,浏览器并用这种方式重新编码(解码)。也就是说在传送的页面的文本信息head中的content-type已经设置成了response.setCharacterEncoding定义的编码格式,来用作重新编码(解码)。

ajax
ajax使用的是response.responseText来进行获取信息,也就是说,也是需要使用到response的编码格式的。ajax不会再对该编码格式进行任何修改。只是接受而已。

你可能感兴趣的:(get post 乱码深入分析)