继续上一章节的乱码问题。上一篇文章仅仅说了设置Tomcat的URIEncoding可以解决乱码问题,这篇文章便会讲述这一背后的内容。首先说明下,光看是没用的,要多实验实验。
目前我的tomcat版本为:7.0.55,spring所有文章的版本始终为4.0.5
本文章会从tomcat的源码角度来解析Tomcat的两个参数设置URIEncoding和useBodyEncodingForURI。
对于一个请求,常用的有两种编码方式,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
首先说说结论:
上述请求有两处含有中文,一处是请求参数中,即?name='中国',另一处是请求体中,即user='张三'。对于这两处tomcat7是分两种编码方式的。URIEncoding就是针对请求参数的编码设置的,而filter的request.setCharacterEncoding('UTF-8')或者请求header中的content-type中的编码都是针对请求体的。不要把他们搞混了。
useBodyEncodingForURI=true是说,请求参数的编码方式要采用请求体的编码方式。当useBodyEncodingForURI=true时,若请求体采用utf-8解析,则请求参数也要采用utf-8来解析。这两个属性值的设置在tomcat的conf/server.xml文件中配置,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
这样写只是说明这两者的配置位置,并不是两个属性要同时配置,不要理解错了。
继续说说CharacterEncodingFilter的作用。
使用方式,将如下代码加入web.xml文件中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
作用是,当forceEncoding为false的前提下(默认为false),当request没有指定content-type或content-type不含编码时,该filter将会为这个request设定请求体的编码为filter的encoding值。
当forceEncoding为true的前提下,就会为request的请求体和response都设定为这个filter的encoding值。
CharacterEncodingFilter源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
|
这个filter有两个属性,encoding和forceEncoding,我们可以在web.xml文件中来设定这两个属性值。
每次request请求到来执行方法doFilterInternal,首先调用request.getCharacterEncoding(),本质就是从请求header content-type中获取编码值,如果没有,则调用request.setCharacterEncoding(this.encoding)将该filter的encoding值设置为请求体的编码方式,记住该编码方式只对请求体,不针对请求参数。当forceEncoding=true时,不管请求header content-type有没有编码方式,始终将该filter的encoding值设置到request和response中,同样只针对request的请求体。
以上的结论说完了,下面就要看看源代码了。不想看的就算了不影响使用,想看看原理的请继续:
首先是三个名词:
org.apache.coyote.Request:这是一个最底层的request,包含各种参数信息。暂且称为coyoteRequest。
org.apache.catalina.connector.Request:实现了HttpServletRequest接口,称它为request,同时包含了一个coyoteRequest,一个connector,待会你就会发现这个connector的编码传递作用。
org.apache.catalina.connector.RequestFacade:同样实现了HttpServletRequest接口,它仅仅是一个装饰类,称它为requestFacade,构造函数为:
1 2 3 4 5 6 7 8 9 10 |
|
该构造函数将一个org.apache.catalina.connector.Request传进来,requestFacade的工作全是靠它内部的org.apache.catalina.connector.Request来完成的,org.apache.catalina.connector.Request又是依据它所包含的org.apache.coyote.Request这个最底层的类来完成的。通过org.apache.catalina.connector.Request,我们可以设定org.apache.coyote.Request的一些工作方式,如通过什么编码来解析数据。
org.apache.coyote.Request含有的属性:
String charEncoding:针对请求体的编码(在第一次解析参数时会传递给Parameters的encoding)
Parameters :用于处理和存放请求参数和请求体参数的类
(1)含String encoding:针对请求体的编码
(2)含String queryStringEncoding:针对请求参数的编码
(3)含Map<String,ArrayList<String>> paramHashValues:存放解析后的参数
Parameters的两个编码是最最重要的编码,直接参与解析数据的编码,不像其他对象的编码大部分都是起传递作用,最终作用到Parameters的两个编码上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
传给过滤器filter的HttpServletRequest request其实是org.apache.catalina.connector.RequestFacade类型的,我们看下获取参数的具体过程:
requestFacade.getParameter("user")会传递到org.apache.catalina.connector.Request的相应方法,如下:
1 2 3 4 5 6 7 8 9 |
|
parametersParsed是org.apache.catalina.connector.Request的属性,用于标示是否已经解析过参数,如果解析过,便不再解析,直接从coyoteRequest的Parameters参数中取出。所以当已经解析过后,你再去设置编码,会无效的,因为它会直接返回第一次的解析结果。并且解析过程仅仅发生在第一次获取参数的时候。
我们来看下parseParameters()这个解析参数的过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
|
上面有四处我们需要关注的重点。
重点1:getCharacterEncoding()其实是通过底层的coyoteRequest来获取header content-type中的编码。
如下:
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 7 8 9 |
|
若无,则返回空。
重点2:
boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();这里就是我们在tomcat的server中配置的useBodyEncodingForURI属性的值。
常量值org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING="ISO-8859-1";
当重点1中的enc为空时,则会设置底层coyoteRequest的parameters对象的encoding=s上述"ISO-8859-1",即请求体采用"ISO-8859-1"来解析。当useBodyEncodingForURI=true时,请求参数和请求体的编码设置的都是同一个值,即"ISO-8859-1"。当useBodyEncodingForURI=false时,不改变queryStringEncoding即请求参数的编码,queryStringEncoding默认是为null的,当解析时碰见queryStringEncoding也会采用默认的编码"ISO-8859-1",然而我们可以通过org.apache.catalina.connector.Request所包含的connector配置来给queryStringEncoding赋值。如下:
当你在tomcat的server.xml文件中加入URIEncoding="UTF-8"时,它将会为queryStringEncoding赋值此值。
在tomcat的server.xml中配置此值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
connector将这个值为queryStringEncoding赋值的过程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
connector.getURIEncoding()便是我们配置的URIEncoding值
req.getParameters().setQueryStringEncoding
(connector.getURIEncoding());
这句代码便是将我们在tomcat的server.xml文件中配置的URIEncoding值设置进最重要的Parameters的queryStringEncoding中。
当重点1中的enc不为空时,为Parameters请求体的的编码encoding设置为enc。
至此,Parameters的encoding和queryStringEncoding都有相应的值了,然后便按照对应的编码来解析字节数组。
重点3和4:有个相应的编码方式,分别执行请求参数的解析过程和请求体的解析过程。
总结下一些设置的作用:
request.setCharacterEncoding(encoding) :暴漏给我们的request为requestFacade,最终调用request->调用coyoteRequest->设置到coyoteRequest的charEncoding,所以coyoteRequest的charEncoding有两种来源,一种可能是content-type中的编码,另一种就是调用request.setCharacterEncoding(encoding) 方法。此方法最好在第一次解析参数之前调用,不然就无效。
URIEncoding:直接设置Parameters的queryStringEncoding的值。即针对请求参数的编码。
useBodyEncodingForURI:设置queryStringEncoding的值=encoding的值,即请求参数采用请求体的编码方式。