本文章会从tomcat的源码角度来解析Tomcat的两个参数设置URIEncoding和useBodyEncodingForURI。

对于一个请求,常用的有两种编码方式,如下:

Java代码  
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8" />
  5. <title></title>
  6. </head>
  7. <body>
  8. <form action="http://127.0.0.1:8080/string?name=中国" method="post">
  9. <input type="text" name="user" value="张三"/>
  10. <input type="submit" value="提交"/>
  11. </form>
  12. </body>
  13. </html>

首先说说结论:
上述请求有两处含有中文,一处是请求参数中,即?name='中国',另一处是请求体中,即user='张三'。对于这两处tomcat7是分两种编码方式的。URIEncoding就是针对请求参数的编码设置的,而filter的request.setCharacterEncoding('UTF-8')或者请求header中的content-type中的编码都是针对请求体的。不要把他们搞混了。

useBodyEncodingForURI=true是说,请求参数的编码方式要采用请求体的编码方式。当useBodyEncodingForURI=true时,若请求体采用utf-8解析,则请求参数也要采用utf-8来解析。这两个属性值的设置在tomcat的conf/server.xml文件中配置,如下:

Java代码  
  1. <Service name="Catalina">
  2. <!--The connectors can use a shared executor, you can define one or more named thread pools-->
  3. <!--
  4. <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
  5. maxThreads="150" minSpareThreads="4"/>
  6. -->
  7. <!-- A "Connector" represents an endpoint by which requests are received
  8. and responses are returned. Documentation at :
  9. Java HTTP Connector: /docs/config/http.html (blocking & non-blocking)
  10. Java AJP  Connector: /docs/config/ajp.html
  11. APR (HTTP/AJP) Connector: /docs/apr.html
  12. Define a non-SSL HTTP/1.1 Connector on port 8080
  13. -->
  14. <Connector port="8080" protocol="HTTP/1.1"
  15. connectionTimeout="20000"
  16. redirectPort="8443" useBodyEncodingForURI='true' URIEncoding='UTF-8' />
  17. <!-- A "Connector" using the shared thread pool-->

这样写只是说明这两者的配置位置,并不是两个属性要同时配置,不要理解错了。
继续说说CharacterEncodingFilter的作用。
使用方式,将如下代码加入web.xml文件中:

Java代码  
  1. <filter>
  2. <filter-name>encoding</filter-name>
  3. <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  4. <init-param>
  5. <param-name>encoding</param-name>
  6. <param-value>UTF-8</param-value>
  7. </init-param>
  8. <init-param>
  9. <param-name>forceEncoding</param-name>
  10. <param-value>true</param-value>
  11. </init-param>
  12. </filter>
  13. <filter-mapping>
  14. <filter-name>encoding</filter-name>
  15. <url-pattern>/*</url-pattern>
  16. </filter-mapping>

作用是,当forceEncoding为false的前提下(默认为false),当request没有指定content-type或content-type不含编码时,该filter将会为这个request设定请求体的编码为filter的encoding值。
当forceEncoding为true的前提下,就会为request的请求体和response都设定为这个filter的encoding值。
CharacterEncodingFilter源码如下:

Java代码  
  1. public class CharacterEncodingFilter extends OncePerRequestFilter {
  2. private String encoding;
  3. private boolean forceEncoding = false;
  4. /**
  5. * Set the encoding to use for requests. This encoding will be passed into a
  6. * {@link javax.servlet.http.HttpServletRequest#setCharacterEncoding} call.
  7. * <p>Whether this encoding will override existing request encodings
  8. * (and whether it will be applied as default response encoding as well)
  9. * depends on the {@link #setForceEncoding "forceEncoding"} flag.
  10. */
  11. public void setEncoding(String encoding) {
  12. this.encoding = encoding;
  13. }
  14. /**
  15. * Set whether the configured {@link #setEncoding encoding} of this filter
  16. * is supposed to override existing request and response encodings.
  17. * <p>Default is "false", i.e. do not modify the encoding if
  18. * {@link javax.servlet.http.HttpServletRequest#getCharacterEncoding()}
  19. * returns a non-null value. Switch this to "true" to enforce the specified
  20. * encoding in any case, applying it as default response encoding as well.
  21. * <p>Note that the response encoding will only be set on Servlet 2.4+
  22. * containers, since Servlet 2.3 did not provide a facility for setting
  23. * a default response encoding.
  24. */
  25. public void setForceEncoding(boolean forceEncoding) {
  26. this.forceEncoding = forceEncoding;
  27. }
  28. @Override
  29. protected void doFilterInternal(
  30. HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
  31. throws ServletException, IOException {
  32. if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) {
  33. request.setCharacterEncoding(this.encoding);
  34. if (this.forceEncoding) {
  35. response.setCharacterEncoding(this.encoding);
  36. }
  37. }
  38. filterChain.doFilter(request, response);
  39. }
  40. }

这个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,构造函数为:

Java代码  
  1. /**
  2. * Construct a wrapper for the specified request.
  3. *
  4. * @param request The request to be wrapped
  5. */
  6. public RequestFacade(Request request) {
  7. this.request = request;
  8. }

该构造函数将一个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的两个编码上

Java代码  
  1. public class MyCharacterEncodingFilter extends CharacterEncodingFilter{
  2. @Override
  3. protected void doFilterInternal(HttpServletRequest request,
  4. HttpServletResponse response, FilterChain filterChain)
  5. throws ServletException, IOException {
  6. request.setCharacterEncoding("UTF-8");
  7. String name=request.getParameter("user");
  8. System.out.println(name);
  9. request.setCharacterEncoding("UTF-8");
  10. String name1=request.getParameter("user");
  11. System.out.println(name1);
  12. super.doFilterInternal(request, response, filterChain);
  13. }
  14. }

传给过滤器filter的HttpServletRequest request其实是org.apache.catalina.connector.RequestFacade类型的,我们看下获取参数的具体过程:
requestFacade.getParameter("user")会传递到org.apache.catalina.connector.Request的相应方法,如下:

Java代码  
  1. public String getParameter(String name) {
  2. if (!parametersParsed) {
  3. parseParameters();
  4. }
  5. return coyoteRequest.getParameters().getParameter(name);
  6. }

parametersParsed是org.apache.catalina.connector.Request的属性,用于标示是否已经解析过参数,如果解析过,便不再解析,直接从coyoteRequest的Parameters参数中取出。所以当已经解析过后,你再去设置编码,会无效的,因为它会直接返回第一次的解析结果。并且解析过程仅仅发生在第一次获取参数的时候。
我们来看下parseParameters()这个解析参数的过程:

Java代码  
  1. /**
  2. * Parse request parameters.
  3. */
  4. protected void parseParameters() {
  5. //解析发生后,便将是状态置为已解析
  6. parametersParsed = true;
  7. Parameters parameters = coyoteRequest.getParameters();
  8. boolean success = false;
  9. try {
  10. // Set this every time in case limit has been changed via JMX
  11. parameters.setLimit(getConnector().getMaxParameterCount());
  12. // getCharacterEncoding() may have been overridden to search for
  13. // hidden form field containing request encoding
  14. //重点1
  15. String enc = getCharacterEncoding();
  16. //重点2
  17. boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();
  18. if (enc != null) {
  19. parameters.setEncoding(enc);
  20. if (useBodyEncodingForURI) {
  21. parameters.setQueryStringEncoding(enc);
  22. }
  23. } else {
  24. parameters.setEncoding
  25. (org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
  26. if (useBodyEncodingForURI) {
  27. parameters.setQueryStringEncoding
  28. (org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
  29. }
  30. }
  31. //重点3
  32. parameters.handleQueryParameters();
  33. if (usingInputStream || usingReader) {
  34. success = true;
  35. return;
  36. }
  37. if( !getConnector().isParseBodyMethod(getMethod()) ) {
  38. success = true;
  39. return;
  40. }
  41. String contentType = getContentType();
  42. if (contentType == null) {
  43. contentType = "";
  44. }
  45. int semicolon = contentType.indexOf(';');
  46. if (semicolon >= 0) {
  47. contentType = contentType.substring(0, semicolon).trim();
  48. } else {
  49. contentType = contentType.trim();
  50. }
  51. if ("multipart/form-data".equals(contentType)) {
  52. parseParts();
  53. success = true;
  54. return;
  55. }
  56. if (!("application/x-www-form-urlencoded".equals(contentType))) {
  57. success = true;
  58. return;
  59. }
  60. int len = getContentLength();
  61. if (len > 0) {
  62. int maxPostSize = connector.getMaxPostSize();
  63. if ((maxPostSize > 0) && (len > maxPostSize)) {
  64. if (context.getLogger().isDebugEnabled()) {
  65. context.getLogger().debug(
  66. sm.getString("coyoteRequest.postTooLarge"));
  67. }
  68. checkSwallowInput();
  69. return;
  70. }
  71. byte[] formData = null;
  72. if (len < CACHED_POST_LEN) {
  73. if (postData == null) {
  74. postData = new byte[CACHED_POST_LEN];
  75. }
  76. formData = postData;
  77. } else {
  78. formData = new byte[len];
  79. }
  80. try {
  81. if (readPostBody(formData, len) != len) {
  82. return;
  83. }
  84. } catch (IOException e) {
  85. // Client disconnect
  86. if (context.getLogger().isDebugEnabled()) {
  87. context.getLogger().debug(
  88. sm.getString("coyoteRequest.parseParameters"), e);
  89. }
  90. return;
  91. }
  92. //重点4
  93. parameters.processParameters(formData, 0, len);
  94. } else if ("chunked".equalsIgnoreCase(
  95. coyoteRequest.getHeader("transfer-encoding"))) {
  96. byte[] formData = null;
  97. try {
  98. formData = readChunkedPostBody();
  99. } catch (IOException e) {
  100. // Client disconnect or chunkedPostTooLarge error
  101. if (context.getLogger().isDebugEnabled()) {
  102. context.getLogger().debug(
  103. sm.getString("coyoteRequest.parseParameters"), e);
  104. }
  105. return;
  106. }
  107. if (formData != null) {
  108. parameters.processParameters(formData, 0, formData.length);
  109. }
  110. }
  111. success = true;
  112. } finally {
  113. if (!success) {
  114. parameters.setParseFailed(true);
  115. }
  116. }
  117. }

上面有四处我们需要关注的重点。

重点1:getCharacterEncoding()其实是通过底层的coyoteRequest来获取header content-type中的编码。
如下:

Java代码  
  1. /**
  2. * Return the character encoding for this Request.
  3. */
  4. @Override
  5. public String getCharacterEncoding() {
  6. return coyoteRequest.getCharacterEncoding();
  7. }
Java代码  
  1. public String getCharacterEncoding() {
  2. if (charEncoding != null)
  3. return charEncoding;
  4. charEncoding = ContentType.getCharsetFromContentType(getContentType());
  5. return charEncoding;
  6. }

若无,则返回空。

重点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中配置此值

Java代码  
  1. <Service name="Catalina">
  2. <!--The connectors can use a shared executor, you can define one or more named thread pools-->
  3. <!--
  4. <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
  5. maxThreads="150" minSpareThreads="4"/>
  6. -->
  7. <!-- A "Connector" represents an endpoint by which requests are received
  8. and responses are returned. Documentation at :
  9. Java HTTP Connector: /docs/config/http.html (blocking & non-blocking)
  10. Java AJP  Connector: /docs/config/ajp.html
  11. APR (HTTP/AJP) Connector: /docs/apr.html
  12. Define a non-SSL HTTP/1.1 Connector on port 8080
  13. -->
  14. <Connector port="8080" protocol="HTTP/1.1"
  15. connectionTimeout="20000"
  16. redirectPort="8443" URIEncoding='UTF-8'/>

connector将这个值为queryStringEncoding赋值的过程如下:

Java代码  
  1. public void log(org.apache.coyote.Request req,
  2. org.apache.coyote.Response res, long time) {
  3. Request request = (Request) req.getNote(ADAPTER_NOTES);
  4. Response response = (Response) res.getNote(ADAPTER_NOTES);
  5. if (request == null) {
  6. // Create objects
  7. request = connector.createRequest();
  8. request.setCoyoteRequest(req);
  9. response = connector.createResponse();
  10. response.setCoyoteResponse(res);
  11. // Link objects
  12. request.setResponse(response);
  13. response.setRequest(request);
  14. // Set as notes
  15. req.setNote(ADAPTER_NOTES, request);
  16. res.setNote(ADAPTER_NOTES, response);
  17. // Set query string encoding
  18. //重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点
  19. req.getParameters().setQueryStringEncoding
  20. (connector.getURIEncoding());
  21. }

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的值,即请求参数采用请求体的编码方式。

URIEncoding和useBodyEncodingForURI区别的更多相关文章

  1. URIEncoding与useBodyEncodingForURI 在tomcat中文乱码处理上的区别

    大家知道tomcat5.0开始,对网页的中文字符的post或者get,经常会出现乱码现象. 具体是因为Tomcat默认是按ISO-8859-1进行URL解码,ISO-8859-1并未包括中文字符,这样 ...

  2. URIEncoding和useBodyEncodingForURI详解

    之前关于编码的问题已经总结过两次了,有些地方写的很粗略.http://blog.itpub.net/29254281/viewspace-775925/http://blog.itpub.net/29 ...

  3. java web项目get,post请求参数中文乱码解决

    [转载]原文地址:https://www.cnblogs.com/tom-plus/p/6392279.html 在开发过程中,有时候会碰到get,post请求参数中文乱码. 原因: Http请求传输 ...

  4. JSP 中 pageEncoding 和 charset 区别以及中文乱码解决方案

    一.JSP 中 pageEndcodeing 和 charset 的作用 <%@ page contentType="text/html;charset=GB2312"%&g ...

  5. JSP中pageEncoding和charset区别,中文乱码解决方案(转载)

    转载自:JSP中pageEncoding和charset区别,中文乱码解决方案 JSP指令标签中<%@ page contentType="text/html;charset=GB23 ...

  6. 关于JSP页面中的pageEncoding和contentType两种属性的区别

    转自:http://blog.csdn.net/dragon4s/article/details/6604624 JSP指令标签中<%@ page contentType="text/ ...

  7. 浅析tomcat nio 配置

    [尊重原创文章摘自:http://blog.csdn.net/yaerfeng/article/details/7679740] tomcat的运行模式有3种.修改他们的运行模式.3种模式的运行是否成 ...

  8. 编码之JSP乱码涉及问题

    各种编码一栏表 A. JSP/Servlet都有的编码设置 1. request.setCharacterEncoding("UTF-8") 2. response.setChar ...

  9. tomcat相关配置技巧梳理

    tomcat常用架构:1)nginx+tomcat:即前端放一台nginx,然后通过nginx反向代理到tomcat端口(可参考:分享一例测试环境下nginx+tomcat的视频业务部署记录)2)to ...

随机推荐

  1. 【转载】同步与异步--阻塞与非阻塞型I/O

    同步阻塞IO 在这个模型中,应用程序(application)为了执行这个read操作,会调用相应的一个system call,将系统控制权交给kernel,然后就进行等待(这其实就是被阻塞了).ke ...

  2. Ruby 遍历多个数组

    puts("----------------------------------------") puts("             多重指定 test") ...

  3. [App Store Connect帮助]二、 添加、编辑和删除用户(4)更改用户的 App 访问权限

    您可以限制具有“App 管理”.“客户支持”.“开发者”.“营销”或“销售”职能的用户(均不具有“访问报告”职能)拥有哪些 App 的访问权限.如果您不更改他们的用户 App 访问权限,他们将默认拥有 ...

  4. [Swift通天遁地]六、智能布局-(8)布局框架的使用:多分辨率适配和横竖屏布局

    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...

  5. 大数据~说说Hadoop

    Hadoop是一个由Apache基金会所开发的分布式系统基础架构. 用户可以在不了解分布式底层细节的情况下,开发分布式程序.充分利用集群的威力进行高速运算和存储.  Hadoop实现了一个分布式文件系 ...

  6. 网上流行的学生选课相关的50个常用sql语句

    学生表 Student(S#,Sname,Sage,Ssex) 教师表 Teacher(T#,Tname) 课程表 Course(C#,Cname,T#) 学生成绩表 SC(S#,C#,score) ...

  7. Android 检查手机上是否安装了指定的软件(根据包名检测)

    Android检查手机上是否安装了指定的软件(根据包名检测) /** * 检查手机上是否安装了指定的软件 * @param context * @param packageName * @return ...

  8. mysql有关时间是问题

     mysql中有关时间的类型 date/datetime/time/timestamp/year date:表示日期的类型,格式为:“yyyy-MM-dd” dateTime:表示日期时间的类型,格式 ...

  9. struts2.5.2 通配符问题_亲测有用

    学了一段时间struts2,跟着教程做,但发现struts2的版本不同,很多东西的使用是有差异的.例如之前遇到的创建sessionFactory的方式就跟之前版本有着明显的差异.今天又遇到一个问题,那 ...

  10. cesium的学习

    一.学习资料:http://cesiumjs.org/tutorials.html,看完6个教程后对图层加载.控件控制开关.地形数据叠加.模型添加.相机控制.图形绘制有一点了解.这也是cesium的主 ...