用户从浏览器发起一个HTTP请求,存在编码的地方是URL、Cookie、Paramiter。服务器端接收到HTTP请求后要解析HTTP协议,其中URL、Cookie和POST表单参数要解码,服务器端可能还需要读取硬盘数据(数据库、文件),这些数据都可能存在编码问题。当Servlet处理完所有请求的数据后,需要将这些数据再编码通过Socket发送到用户请求的浏览器里,再经过浏览器解码成为文本。这些过程用图表示如下:
1.URL的编解码
为了验证浏览器是怎么编码URL的,我们选择FireFox浏览器并通过HTTPFox插件观察请求的URL的实际内容:
从结果上看,PathInfo是UTF-8编码,而QueryString是GBK编码。至于为什么有%,是由URL的编码规范FRC3986规定:浏览器编码URL将非ASCII字符按照某种编码格式编码成16进制数字后将每个16进制表示的字节前加上“%”。
从上面的测试结果可知,浏览器对PathInfo和QueryString的编码是不一样的,不同的浏览器对PathInfo的编码也可能不一样。如Chrome会对请求“http://localhost:8080/中国?中国”转变为“http://localhost:8080/%E4%B8%AD%E5%9B%BD?%E4%B8%AD%E5%9B%BD”,这里PathInfo和QueryString的编码是一样的,都是UTF-8编码。
2.HTTP Header的编解码
当客户端发起一个HTTP请求时,除了上面的URL外还可能会在Header中传递其他的参数,如Cookie、redirectPath等,这些用户设置的值很可能也会存在编码问题。
在Tomcat中,对Header中的项进行解码是在调用request.getHeader时进行的,如果请求的Header项没有解码则调用MessageBytes的toString方法,这个方法将从byte从char的转化使用的默认编码是ISO-8859-1,而我们也不能设置Header的其他解码格式,所以如果你设置的Header中非ASCII字符解码肯定会有乱码。
我们在添加Header时也是同样的道理,不要在Header中传递非ASCII字符,如果一定要传递可以先将字符用org.apache.catalina.util.URLEncoder编码,然后再添加到Header中,这样在浏览器到服务器的传递中就不会丢失信息了,我们在访问这些项时再按照相应的字符集解码就好了。
3.POST表单的编解码
POST表单参数传递方式与QueryString不同,它是通过HTTP的BODY传递到服务端的。当我们在页面上点击提交按钮时浏览器首先将根据页面的ContentType的Charset编码格式对表单填的参数进行编码,然后提交到服务器端。在服务器端同样也是用ContentType中的字符集进行解码。所以通过POST表单提交的参数一般不会出现问题,而且这个字符集编码是我们自己设置的。
另外,针对multipart/form-data类型的参数,也就是上传的文件编码,同样也使用ContentType定义的字符集编码。值得注意的地方是,上传文件是用字节流的方式传输到服务器的本地临时目录,这个过程并没有涉及字符编码,而真正编码是在将文件内容添加到parameters中时,如果用这个不能编码将会用默认编码ISO-8859-1来编码。
4.HTTP BODY的编解码
当用户请求的资源服务端已经成功获取后,这些内容将通过Response返回给客户端浏览器,这个过程先要经过编码再到浏览器进行解码,浏览器根据HTML的<meta HTTP-equiv=“Content-Type” content=”text/html; charset=GBK”>中的charset来解码。如果没有定义,那么浏览器将会使用默认的编码来解码。
访问数据库都是通过客户端JDBC驱动来完成的,用JDBC来存取数据要和数据的内置编码保持一致,可以通过设置JDBC URL来指定。
5.JS中的编解码
html文件本身中的js的编码和当前页面中的Content-Type保持一致。
对于采用<script src=”script.js”/>类型引入的js文件,浏览器就会以当前这个页面的默认字符集解析这个JS文件,如果外部的JS文件的编码格式与当前页面的编码格式一致,那么可以不设置这个charset。但是如果script.js文件的编码格式与当前页面的不一致,就必须要指定对应的字符集,要不然对于非ASCII字符就会出现乱码。
6.其他需要编码的地方
除了URL和参数编码问题外,在服务端还有很多地方可能存在编码,如可能需要读取XML、Velocity模板引擎、JSP或者从数据库读取数据等。
参考资料:《深入分析Java Web技术》
Java Web中涉及的编解码