欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

tomcat源码研究之参数编码格式处理

程序员文章站 2022-06-06 22:20:29
...

一,我们知道tomcat作为web服务器以后,我们编写的Servlet 请求中经常出现 中文乱码问题,而出现这些中文乱码,则是以下三种情况

 1),来自浏览器地址栏uri携带的中文参数

 2),来自页面链接跳转携带的中文参数

 3 ),来自表单form中提交成参数

  而这些提交方式一般以get和Post提交,那么tomcat是怎么按照什么编码格式解析这些请求参数的呢? 下面请看我的分析,

我们知道不管从get还是post来的请求参数  我们都是以reqest.getParameter(xxx) 和request.getParameterValues(xxx) 来的获取值的

所以我们先看request这两个方法:(这里的request对象是org.apache.catalina.connector.Requsest 我们知道tomcat对象我们用HttpServletRequest实现对象时RequestFacade这个对象,而这个对象却是使用设计模式中外观模式对org.apache.catalina.connector.Requsest这个对象进行了一定的封装,本质上还是看org.apache.catalina.connector.Requsest此对象)

 

    public String getParameter(String name) {

        if (!parametersParsed) {
            parseParameters();
        }

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

    }

    public String[] getParameterValues(String name) {

        if (!parametersParsed) {
            parseParameters();
        }

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

    }
// 从上可知,在没有解析之前都是先调用 parseParameters()这个方法,也就是乱码问题可能出现在这儿方法中

 下面是 parseParameters 此对象

 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
            String enc = getCharacterEncoding();

            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);
                }
            }
            //处理uri中参数解析
            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();
            }

            //如果消息体是文件流 则用parseParts()解析
            if ("multipart/form-data".equals(contentType)) {
                parseParts();
                success = true;
                return;
            }

            //处理body中的参数解析
            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 {
                	//如果消息体是form参数 则用ReadPostBody解析
                    if (readPostBody(formData, len) != len) {
                        return;
                    }
                } catch (IOException e) {
                    // Client disconnect
                    if (context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(
                                sm.getString("coyoteRequest.parseParameters"), e);
                    }
                    return;
                }
                //存放到Parametes对象中
                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)从这个方法中 parameters.handleQueryParameters(); 解析uri参数 也就是get方式的参数

2)从这个方法中 parameters.processParameters(formData, 0, len);解析请求post的消息体参数 formdata是一个未解析的字节数组

最终 而parameters最终就是处理这两个方法调用所在(org.apache.tomcat.util.http.Parameters此对象)

(1)下面请看此对象org.apache.tomcat.util.http.Parameters的handlerQueryParameters()方法

 

 public void handleQueryParameters() {
        if( didQueryParameters ) {
            return;
        }

        didQueryParameters=true;

        if( queryMB==null || queryMB.isNull() ) {
            return;
        }

        if(log.isDebugEnabled()) {
            log.debug("Decoding query " + decodedQuery + " " +
                    queryStringEncoding);
        }

        try {
            decodedQuery.duplicate( queryMB );
        } catch (IOException e) {
            // Can't happen, as decodedQuery can't overflow
            e.printStackTrace();
        }
        //在这儿可知我们处理uri的参数编码是用的queryStringEncoding这个属性的编码
        processParameters( decodedQuery, queryStringEncoding );
    }

 (2)下面请看此对象org.apache.tomcat.util.http.Parameters的processParameters()方法

 

   

    public void processParameters( byte bytes[], int start, int len ) {
        //在这儿可知我们处理post请求消息体的bytes字节编码用的是encoding这个属性的编码
        processParameters(bytes, start, len, getCharset(encoding));
    }

 

所以综上所述我们可知处理get和post请求分别是用的org.apache.tomcat.util.http.Parameters对象中queryStringEncoding 和encoding 这两个成员变量存储的编码

 

    String encoding=null;
    String queryStringEncoding=null;

 两个属性

 

继续上述方法 

 

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
            String enc = getCharacterEncoding();

            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);
                }
            }

 

 

有上述代码可知 getCharacterEncoding()和useBodyEncodingForURI 这两个会覆盖 encoding和queryStringEncoding两属性的值 从而影响解码

若使用默认编码格式 则是"ISO-8859-1”格式

 

public final class Constants {


    // -------------------------------------------------------------- Constants

    public static final String Package = "org.apache.coyote";
    
    public static final String DEFAULT_CHARACTER_ENCODING="ISO-8859-1";

    public static final int MAX_NOTES = 32;

 

 

若不是采用默认格式编码 ,那么我们有哪些方式可以设置呢

1)可以从 getCharacterEncoding()(也就是 org.apache.coyote.Request.getCharacterEncoding() 注意最终到了org.apache.coyote.Request.getCharacterEncoding()此方法)

 

    public String getCharacterEncoding() {

        if (charEncoding != null)
            return charEncoding;

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

    }

 上述代码可知  如果charEncoding 为空 ,那么我们采取的是页面http头中的ContentType:text/html;charset=xxx 这样的编码格式, 所有我们要是设置好charEncoding就会采用我们自己的编码格式

 

 而设置charEncoding属性却是org.apache.catalina.connector.Requsest

 

    public void setCharacterEncoding(String enc)
        throws UnsupportedEncodingException {

        if (usingReader) {
            return;
        }

        // Ensure that the specified encoding is valid
        byte buffer[] = new byte[1];
        buffer[0] = (byte) 'a';

        // Confirm that the encoding name is valid
        B2CConverter.getCharset(enc);

        // Save the validated encoding
        //org.apache.coyote.Request.setCharacterEncoding(xxx)方法
        coyoteRequest.setCharacterEncoding(enc);
    }

  这时回到了 org.apache.catalina.connector.Requsest的setCharacterEncoding此方法 此方法是不是我们很熟悉啊, 它的调用就是我们上面所说的RequestFacde对象的setCharacterEncoding方法 也就是我们应用程序中HttpServletRequest的setCharacterEncoding的方法

 

2)从org.apache.tomcat.util.http.Parameters的queryStringEncoding属性设置

而此处设置却是来源于connector的U

 public void service(org.apache.coyote.Request req,
                        org.apache.coyote.Response res)
        throws Exception {

        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());

        }
//省略代码

 从最后一行代码可知其实queryStringEncoding的设置还是来源于connector的URIEncoding 属性的设置

3)可以从 connector的useBodyEncodingForURI 

 

二,综上所述 我们可以设置的编码格式地方有以下两点:

(1),connector的URIEncoding和useBodyEncodingForURI属性

 也就是修改tomcat下conf/server.xml中的connector节点 如:

  <Connector port="8080" protocol="HTTP/1.1"

           connectionTimeout="20000"   redirectPort="8443" useBodyEncodingForURI='true' URIEncoding='UTF-8' />

(2)   以及代码中 request.setsetCharacterEncoding 

 

 所以我们再次根据 这段代码可以总结

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
            String enc = getCharacterEncoding();

            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);
                }
            }

 (1)默认情况下get请求 按照 queryStringEncoding 属性解码 即默认为org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING="ISO-8859-1" post 按照 encoding解码 也是"ios-8859-1"

    当设置了 useBodyEncodingForURI=true uri 解析就根据页面http消息头的Content-type解析

(2)如果设置了 request.setsetCharacterEncoding 编码 那么 post请求的encoding一定是根据这个设置的结果,

  get请求 如下

   1,useBodyEncodingForURI=false 还是则看URIEncoding='UTF-8' 属性的设置 如果这个属性还是为空 则使用默认的 "ISO-8859-1" 如果设置了 则用 URIEncoding这个值

        2,如果 useBodyEncodingForURI=true 则根据request.setsetCharacterEncoding 这个设置