本文章会从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. Road Construction(无向图的双连通分量)

    http://poj.org/problem?id=3352 题意:给出一个有n个顶点m条边的无向连通图,问至少添加几条边,使删除任意一条边原图仍连通. 思路:一个边双连通图删除任意一条边仍为连通图. ...

  2. MyEclipse找不到install new software

    Window->Preferences->Capabilities-> classic update(勾选即可) 勾选后会出现software updates,下面按照help-&g ...

  3. jsp中session执行机制

  4. ios的认识

    刚进了ios兴趣班,第一次使用苹果电脑,因为苹果电脑和windows电脑使用的区别很大.所以老师教我们苹果电脑的基本使用,以及关于苹果产品的一些认识.我听得热血沸腾,对苹果开发越来越感兴趣,相信下次上 ...

  5. 图解TCP/IP笔记(1)——TCP/IP协议群

    转载请注明:https://www.cnblogs.com/igoslly/p/9167916.html TCP/IP制定  制定:IETF 记录:RFC - Request for comment ...

  6. C# windform自定义控件的属性小知识

    word中的加粗变斜之类的一直让我以为是button,直到我接触了自定义控件,才发现实现这种机能最好的是CheckBox,然后我们在做一个系统的时候,这种控件有可能要用好多次,总不能在用一次的时候,就 ...

  7. UICollectionView框架总结

    一.UIcollectionView介绍 1.1.简介 首先看苹果官方文档 UICollectionView Class Reference 的介绍: The UICollectionView cla ...

  8. yum进程被占用

    使用yum安装软件的时候出现,/var/run/yum.pid 已被锁定,PID 为 6503 的另一个程序正在运行的问题 [root@localhost mysql]# yum install gc ...

  9. spring IOC bean中注入集合

    建立一个实体 package com.java.test4; import java.util.*; /** * @author nidegui * @create 2019-06-22 14:45 ...

  10. 数据库操作(二)SOQL

    1.SOQL SOQL是对象查询语言.它可以在单个sObject中在给定标准上搜索记录. 2.SELECT语句 [格式]SELECT 列名称 FROM 表名称 [示例] 3.SELECT...WHER ...