在JavaWeb中涉及的编码解码的方面:
用户想服务器发送一个HTTP请求,需要编码的地方有url、cookie、parameter,经过编码后服务器接受HTTP请求,解析HTTP请求,然后对url、cookie、parameter进行解码。在服务器进行业务逻辑处理过程中可能需要读取数据库、磁盘文件或者网络中的其他文件等等,这些过程都需要进行编码解码。当处理完成后,服务器将数据进行编码后发送给客户端,浏览器经过解码后显示给用户。
总结来讲,JavaWeb涉及的编码方面概括来讲有:
- URL请求
- Cookie请求与响应
- 表单Get与Post
- 数据库交互
- 磁盘文件交互(见上)
一、URL编码与解码
对于URL,如果该URL中全部都是英文的没有什么问题,如果有中文就要涉及到编码了。
URL的组成部分:
1)浏览器将会对path和parameter进行编码操作。
将以上地址输入到浏览器URL输入框中,通过查看http 报文头信息我们可以看到浏览器是如何进行编码的。下面是IE、Firefox、Chrome三个浏览器的编码情况:
可以看到各大浏览器对“我是”的编码情况如下:
path部分 |
Query String |
|
Firefox |
E6 88 91 E6 98 AF |
E6 88 91 E6 98 AF |
Chrome |
E6 88 91 E6 98 AF |
E6 88 91 E6 98 AF |
IE |
E6 88 91 E6 98 AF |
CE D2 CA C7 |
可知对于path部分Firefox、chrome、IE都是采用UTF-8编码格式,对于Query String部分Firefox、chrome采用UTF-8,IE采用GBK。至于为什么会加上%,这是因为URL的编码规范规定浏览器将ASCII字符非 ASCII 字符按照某种编码格式编码成 16 进制数字然后将每个 16 进制表示的字节前加上“%”。
当然对于不同的浏览器,相同浏览器不同版本,不同的操作系统等环境都会导致编码结果不同,上表某一种情况,对于URL编码规则下任何结论都是过早的。由于各大浏览器、各个操作系统对URL的URI、QueryString编码都可能存在不同,这样对服务器的解码势必会造成很大的困扰。
2)tomcat对URL进行解码操作的
解析请求的 URL 是在 org.apache.coyote.HTTP11.InternalInputBuffer 的 parseRequestLine 方法中,这个方法把传过来的 URL 的 byte[] 设置到 org.apache.coyote.Request 的相应的属性中。这里的 URL 仍然是 byte 格式,转成 char 是在 org.apache.catalina.connector.CoyoteAdapter 的 convertURI 方法中完成的:
protected void convertURI(MessageBytes uri, Request request) throws Exception { ByteChunk bc = uri.getByteChunk(); int length = bc.getLength(); CharChunk cc = uri.getCharChunk(); cc.allocate(length, -1); String enc = connector.getURIEncoding(); //获取URI解码集 if (enc != null) { B2CConverter conv = request.getURIConverter(); try { if (conv == null) { conv = new B2CConverter(enc); request.setURIConverter(conv); } } catch (IOException e) {...} if (conv != null) { try { conv.convert(bc, cc, cc.getBuffer().length - cc.getEnd()); uri.setChars(cc.getBuffer(), cc.getStart(), cc.getLength()); return; } catch (IOException e) {...} } } // Default encoding: fast conversion byte[] bbuf = bc.getBuffer(); char[] cbuf = cc.getBuffer(); int start = bc.getStart(); for (int i = 0; i < length; i++) { cbuf[i] = (char) (bbuf[i + start] & 0xff); } uri.setChars(cbuf, 0, length); }
从上面的代码可知,对URI的解码操作是首先获取Connector的解码集,该配置在server.xml中:
<Connector URIEncoding="utf-8" />
如果没有定义则会采用默认编码ISO-8859-1来解析。
对于Query String部分,我们知道无论我们是通过get方式还是POST方式提交,所有的参数都是保存在Parameters,然后我们通过request.getParameter,解码工作就是在第一次调用getParameter方法时进行的。在getParameter方法内部它调用org.apache.catalina.connector.Request 的 parseParameters 方法,这个方法将会对传递的参数进行解码。下面代码只是parseParameters方法的一部分:
//获取编码 String enc = getCharacterEncoding(); //获取ContentType 中定义的 Charset boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI(); if (enc != null) { //如果设置编码不为空,则设置编码为enc parameters.setEncoding(enc); if (useBodyEncodingForURI) { //如果设置了Chartset,则设置queryString的解码为ChartSet parameters.setQueryStringEncoding(enc); } } else { //设置默认解码方式 parameters.setEncoding(org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING); if (useBodyEncodingForURI) { parameters.setQueryStringEncoding(org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING); } }
从上面代码可以看出对query String的解码格式要么采用设置的ChartSet要么采用默认的解码格式ISO-8859-1。注意这个设置的ChartSet是在 http Header中定义的ContentType,同时如果我们需要改指定属性生效,还需要进行如下配置:
<Connector URIEncoding="UTF-8" useBodyEncodingForURI="true"/>
二、Cookie的编码与解码
URLDecoder.decode();
URLEncoder.encode()
三、Get与Post的编码与解码
1)Get
当用户点击submit提交表单时,浏览器会用设定的编码(contentType)来编码数据传递给服务器,通过GET方式提交的数据都是拼接在URL后面来提交的,所以tomcat服务器在进行解码过程中URIEncoding就起到作用了。tomcat服务器会根据设置的URIEncoding来进行解码,如果没有设置则会使用默认的ISO-8859-1来解码。假如我们在页面将编码设置为UTF-8,而URIEncoding设置的不是或者没有设置,那么服务器进行解码时就会产生乱码。这个时候我们一般可以通过new String(request.getParameter("name").getBytes("iso-8859-1"),"utf-8") 的形式来获取正确数据。
2)Post
当我通过点击页面的submit按钮来提交表单时,浏览器会用设定的编码(contentType的charset编码格式)来对POST表单的参数进行编码然后提交给服务器,在服务器端同样也是用contentType中设置的字符集来进行解码(这里与get方式就不同了),这就是通过POST表单提交的参数一般而言都不会出现乱码问题。当然这个字符集编码我们是可以自己设定的:request.setCharacterEncoding(charset) 、response.setCharacterEncoding(charset) 或 contentType="text/html;charset=UTF-8"。
四、数据库交互的编码与解码
Java程序与数据库的连接都是通过JDBC驱动程序来连接的,而JDBC驱动程序默认的是ISO-8859-1编码格式的,也就是说我们通过java程序向数据库传递数据时,JDBC首先会将Unicode编码格式的数据转换为ISO-8859-1的编码格式,然后在存储在数据库中,即在数据库保存数据时,默认格式为ISO-8859-1。
故可以通过jdbcurl添加:
useUnicode=true&characterEncoding=utf8
(另提:数据库本身的存储编码则是通过my.ini来配置的,将默认的latin该成utf8,mysql为例)
五、JSP与Servlet的编码与解码过程
1)JSP
我们知道JSP页面是需要转换为servlet的,在转换过程中肯定是要进行编码的。在JSP转换为servlet过程中下面一段代码起到至关重要的作用。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="GBK" %>
在上面代码中有两个地方存在编码:pageEncoding、contentType的charset。其中pageEncoding是jsp文件本身的编码,即经过JSP容器转换为servlet用的编码,而contentType的charset是指服务器发送给客户端时的内容编码。
JSP的转换过程:
第一阶段
JVM将JSP编译为.jsp文件。在这个过程中pageEncoding就起到作用了,JVM首先会获取pageEncoding的值,如果该值存在则采用它设定的编码来编译,否则则采用file.encoding编码来编译。
第二阶段
JVM将.java文件转换为.class文件。在这个过程就与任何编码的设置都没有关系了,不管JSP采用了什么样的编码格式都将无效。经过这个阶段后.jsp文件就转换成了统一的Unicode格式的.class文件了。
第三阶段
后台经过业务逻辑处理后将产生的结果输出到客户端。在这个过程中contentType的charset就发挥了功效。如果设置了charset则浏览器就会使用指定的编码格式进行解码,否则采用默认的ISO-8859-1编码格式进行解码处理。
流程如如下:
2)Servlet
当用户请求Servlet时,WEB容器会调用它的JVM来运行Servlet。首先JVM会把servlet的class加载到内存中去,内存中的servlet代码是Unicode编码格式的。然后JVM在内存中运行该Servlet,在运行过程中如果需要接受从客户端传递过来的数据(如表单和URL传递的数据),则WEB容器会接受传入的数据,在接收过程中如果程序设定了传入参数的的编码则采用设定的编码格式(通过tomcat配置),如果没有设置则采用默认的ISO-8859-1编码格式,接收的数据后JVM会将这些数据进行编码格式转换为Unicode并且存入到内存中。运行Servlet后产生输出结果,同时这些输出结果的编码格式仍然为Unicode。紧接着WEB容器会将产生的Unicode编码格式的字符串直接发送置客户端,如果程序指定了输出时的编码格式,则按照指定的编码格式输出到浏览器,否则采用默认的ISO-8859-1编码格式。整个过程流程图如下:
六、总结
总结在javaweb中出现的所有编码问题的解决方法:
1)URL编码乱码:
在Tomcat中,默认情况下使用ISO- 8859-1对URL提交的数据和表单中GET方式提交的数据进行重新编码(解码),而不使用该参数对URL提交的数据和表单中GET方式提交的数据进行重新编码(解码)。要解决该问题,应该在Tomcat的配置文件的Connector标签中设置useBodyEncodingForURI或者 URIEncoding属性。
<Connector URIEncoding="UTF-8" useBodyEncodingForURI="true"/>
2)Cookie存取编码乱码
URLDecoder.decode()
URLEncoder.encode()
3)Get编码乱码
与URL一致
4)Post编码乱码
request.setCharacterEncoding("UTF-8") //作用是设置对客户端请求进行重新编码的编码。 response.setCharacterEncoding("UTF-8") //作用是指定对服务器响应进行重新编码的编码。
5)数据库交互编码乱码
useUnicode=true&characterEncoding=utf8
6)数据库内部数据乱码
my.ini的latin改成utf8
7)JSP页面与Servlet响应编码乱码
pageEncoding="UTF-8" //作用是设置JSP编译成Servlet时使用的编码。
contentType="text/html;charset=UTF-8" //作用是指定对服务器响应进行重新编码的编码。