文档章节

SpringMVC源码总结(五)Tomcat的URIEncoding、useBodyEncodingForURI和CharacterEncodingFilter

乒乓狂魔
 乒乓狂魔
发布于 2015/02/07 11:08
字数 2525
阅读 220
收藏 2
继续上一章节的乱码问题。上一篇文章仅仅说了设置Tomcat的URIEncoding可以解决乱码问题,这篇文章便会讲述这一背后的内容。首先说明下,光看是没用的,要多实验实验。

目前我的tomcat版本为:7.0.55,spring所有文章的版本始终为4.0.5

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

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

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title></title>
	</head>
	<body>
		<form action="http://127.0.0.1:8080/string?name=中国" method="post">
			<input type="text" name="user" value="张三"/>
			<input type="submit" value="提交"/>
		</form>
	</body>
</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文件中配置,如下:

<Service name="Catalina">

    <!--The connectors can use a shared executor, you can define one or more named thread pools-->
    <!--
    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
        maxThreads="150" minSpareThreads="4"/>
    -->


    <!-- A "Connector" represents an endpoint by which requests are received
         and responses are returned. Documentation at :
         Java HTTP Connector: /docs/config/http.html (blocking & non-blocking)
         Java AJP  Connector: /docs/config/ajp.html
         APR (HTTP/AJP) Connector: /docs/apr.html
         Define a non-SSL HTTP/1.1 Connector on port 8080
    -->
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" useBodyEncodingForURI='true' URIEncoding='UTF-8' />
    <!-- A "Connector" using the shared thread pool-->

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

<filter>
		<filter-name>encoding</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>

	<filter-mapping>
		<filter-name>encoding</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

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

public class CharacterEncodingFilter extends OncePerRequestFilter {

	private String encoding;

	private boolean forceEncoding = false;


	/**
	 * Set the encoding to use for requests. This encoding will be passed into a
	 * {@link javax.servlet.http.HttpServletRequest#setCharacterEncoding} call.
	 * <p>Whether this encoding will override existing request encodings
	 * (and whether it will be applied as default response encoding as well)
	 * depends on the {@link #setForceEncoding "forceEncoding"} flag.
	 */
	public void setEncoding(String encoding) {
		this.encoding = encoding;
	}

	/**
	 * Set whether the configured {@link #setEncoding encoding} of this filter
	 * is supposed to override existing request and response encodings.
	 * <p>Default is "false", i.e. do not modify the encoding if
	 * {@link javax.servlet.http.HttpServletRequest#getCharacterEncoding()}
	 * returns a non-null value. Switch this to "true" to enforce the specified
	 * encoding in any case, applying it as default response encoding as well.
	 * <p>Note that the response encoding will only be set on Servlet 2.4+
	 * containers, since Servlet 2.3 did not provide a facility for setting
	 * a default response encoding.
	 */
	public void setForceEncoding(boolean forceEncoding) {
		this.forceEncoding = forceEncoding;
	}


	@Override
	protected void doFilterInternal(
			HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) {
			request.setCharacterEncoding(this.encoding);
			if (this.forceEncoding) {
				response.setCharacterEncoding(this.encoding);
			}
		}
		filterChain.doFilter(request, response);
	}

}

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

/**
     * Construct a wrapper for the specified request.
     *
     * @param request The request to be wrapped
     */
    public RequestFacade(Request request) {

        this.request = request;

    }

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


public class MyCharacterEncodingFilter extends CharacterEncodingFilter{

	@Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8");
		String name=request.getParameter("user");
		System.out.println(name);
		request.setCharacterEncoding("UTF-8");
		String name1=request.getParameter("user");
		System.out.println(name1);
		super.doFilterInternal(request, response, filterChain);
	}
}

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

public String getParameter(String name) {

        if (!parametersParsed) {
            parseParameters();
        }

        return coyoteRequest.getParameters().getParameter(name);

    }

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

/**
     * Parse request parameters.
     */
    protected void parseParameters() {

        //解析发生后,便将是状态置为已解析
        parametersParsed = true;

        Parameters parameters = coyoteRequest.getParameters();
        boolean success = false;
        try {
            // Set this every time in case limit has been changed via JMX
            parameters.setLimit(getConnector().getMaxParameterCount());

            // getCharacterEncoding() may have been overridden to search for
            // hidden form field containing request encoding
            //重点1
            String enc = getCharacterEncoding();
            //重点2
            boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();
            if (enc != null) {
                parameters.setEncoding(enc);
                if (useBodyEncodingForURI) {
                    parameters.setQueryStringEncoding(enc);
                }
            } else {
                parameters.setEncoding
                    (org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
                if (useBodyEncodingForURI) {
                    parameters.setQueryStringEncoding
                        (org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
                }
            }
            //重点3
            parameters.handleQueryParameters();

            if (usingInputStream || usingReader) {
                success = true;
                return;
            }

            if( !getConnector().isParseBodyMethod(getMethod()) ) {
                success = true;
                return;
            }

            String contentType = getContentType();
            if (contentType == null) {
                contentType = "";
            }
            int semicolon = contentType.indexOf(';');
            if (semicolon >= 0) {
                contentType = contentType.substring(0, semicolon).trim();
            } else {
                contentType = contentType.trim();
            }

            if ("multipart/form-data".equals(contentType)) {
                parseParts();
                success = true;
                return;
            }

            if (!("application/x-www-form-urlencoded".equals(contentType))) {
                success = true;
                return;
            }

            int len = getContentLength();

            if (len > 0) {
                int maxPostSize = connector.getMaxPostSize();
                if ((maxPostSize > 0) && (len > maxPostSize)) {
                    if (context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(
                                sm.getString("coyoteRequest.postTooLarge"));
                    }
                    checkSwallowInput();
                    return;
                }
                byte[] formData = null;
                if (len < CACHED_POST_LEN) {
                    if (postData == null) {
                        postData = new byte[CACHED_POST_LEN];
                    }
                    formData = postData;
                } else {
                    formData = new byte[len];
                }
                try {
                    if (readPostBody(formData, len) != len) {
                        return;
                    }
                } catch (IOException e) {
                    // Client disconnect
                    if (context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(
                                sm.getString("coyoteRequest.parseParameters"), e);
                    }
                    return;
                }
               //重点4
                parameters.processParameters(formData, 0, len);
            } else if ("chunked".equalsIgnoreCase(
                    coyoteRequest.getHeader("transfer-encoding"))) {
                byte[] formData = null;
                try {
                    formData = readChunkedPostBody();
                } catch (IOException e) {
                    // Client disconnect or chunkedPostTooLarge error
                    if (context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(
                                sm.getString("coyoteRequest.parseParameters"), e);
                    }
                    return;
                }
                if (formData != null) {
                    parameters.processParameters(formData, 0, formData.length);
                }
            }
            success = true;
        } finally {
            if (!success) {
                parameters.setParseFailed(true);
            }
        }

    }

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

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

/**
     * Return the character encoding for this Request.
     */
    @Override
    public String getCharacterEncoding() {
      return coyoteRequest.getCharacterEncoding();
    }

public String getCharacterEncoding() {

        if (charEncoding != null)
            return charEncoding;

        charEncoding = ContentType.getCharsetFromContentType(getContentType());
        return charEncoding;

    }

若无,则返回空。

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

<Service name="Catalina">

    <!--The connectors can use a shared executor, you can define one or more named thread pools-->
    <!--
    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
        maxThreads="150" minSpareThreads="4"/>
    -->


    <!-- A "Connector" represents an endpoint by which requests are received
         and responses are returned. Documentation at :
         Java HTTP Connector: /docs/config/http.html (blocking & non-blocking)
         Java AJP  Connector: /docs/config/ajp.html
         APR (HTTP/AJP) Connector: /docs/apr.html
         Define a non-SSL HTTP/1.1 Connector on port 8080
    -->
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" URIEncoding='UTF-8'/>

connector将这个值为queryStringEncoding赋值的过程如下:
public void log(org.apache.coyote.Request req,
            org.apache.coyote.Response res, long time) {

        Request request = (Request) req.getNote(ADAPTER_NOTES);
        Response response = (Response) res.getNote(ADAPTER_NOTES);

        if (request == null) {
            // Create objects
            request = connector.createRequest();
            request.setCoyoteRequest(req);
            response = connector.createResponse();
            response.setCoyoteResponse(res);

            // Link objects
            request.setResponse(response);
            response.setRequest(request);

            // Set as notes
            req.setNote(ADAPTER_NOTES, request);
            res.setNote(ADAPTER_NOTES, response);

            // Set query string encoding
            //重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点
            req.getParameters().setQueryStringEncoding
                (connector.getURIEncoding());
        }

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

© 著作权归作者所有

共有 人打赏支持
乒乓狂魔
粉丝 996
博文 105
码字总数 271356
作品 0
长宁
程序员
Tomcat中文乱码处理:URIEncoding,useBodyEncodingForURI

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

xjcyxyx
2014/05/08
0
0
SpringMVC 解决GET请求时中文乱码的问题

CharacterEncoding org.springframework.web.filter.CharacterEncodingFilter encoding UTF-

yuanyi860829
2017/02/20
0
0
Java Web中文乱码问题解决

一、为什么会出现中文乱码问题: java内核和class文件是基于unicode码的,这使Java程序具有良好的跨平台性,但也导致在Java和JSP文件在编译时以及Java程序与其他媒介交互时等情况下产生中文乱...

ws199358
2016/09/13
13
0
springmvc get请求参数乱码解决方法

今天在写代码的时候,有一个查询接口,用的是http的get请求方式,在本地测试通过后,发到测试环境,发现测试环境调用查询的结果有点让我意外,经过在代码里面输出数据调试,发现是因为乱码造...

脚踝程序员
2017/10/18
0
0
关于Tomcat上请求的编解码问题

最近翻阅《深入分析 Java Web 技术内幕》(作者:许令波),关于Tomcat上Web请求的编解码问题,少了一个小点,可能影响了部分读者的理解,我特意查证了一下,特总结如下: 1. 请求的PathInfo部...

anranran
2017/08/04
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Web系统大规模并发:电商秒杀与抢购

一、大规模并发带来的挑战 在过去的工作中,我曾经面对过5w每秒的高并发秒杀功能,在这个过程中,整个Web系统遇到了很多的问题和挑战。如果Web系统不做针对性的优化,会轻而易举地陷入到异常...

xtof
今天
1
0
代码质量管理平台-sonarqube

在工作中,往往开发的时候会不怎么注重代码质量的人很多,存在着很多的漏洞和隐患等问题,sonarqube可以进行代码质量的审核,而且十分的残酷。。。。。接下来我们说下怎么安装 进入官网下载:...

落叶清风
今天
6
0
在Ubuntu安装和配置Sphinx

Ubuntu系统默认是配置有sphinx的,先检查一下,别多此一举。。。。。 在开始本指南之前,您需要: 一个Ubuntu 16.04服务器。 sudo的一个非root用户,您可以通过以下设置本教程 。 安装在服务...

阿锋zxf
今天
1
0
Qt编写输入法V2018超级终结版

对于qt嵌入式linux开发人员来说,输入法一直是个鸡肋问题,要么不支持实体键盘同步,要么不能汉字输入,要么不支持网页输入等,这几年通过陆续接触大量的各种输入法应用场景客户,得到真实需...

飞扬青云
今天
2
0
TypeScript基础入门之高级类型的多态的 this类型

转发 TypeScript基础入门之高级类型的多态的 this类型 高级类型 多态的this类型 多态的this类型表示的是某个包含类或接口的子类型。 这被称做F-bounded多态性。 它能很容易的表现连贯接口间的...

durban
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部