架构师

您现在的位置是:首页 > 程序人生 > 程序人生

程序人生

http请求参数中加号或%号被替换为空格及请求参数被URLDeCode的记录

架构师小跟班 2019-12-30 程序人生
http请求参数中加号或%号被替换为空格及请求参数被URLDeCode的记录今天遇到两个有关tomcat的比较有意思的问题1.用postman模拟https请求的时候,如果请求参数是name=jay+love

http请求参数中加号或%号被替换为空格及请求参数被URLDeCode的记录

今天遇到两个有关tomcat的比较有意思的问题

1.用postman模拟https请求的时候,如果请求参数是name=jay+love。最后服务端用request.getParameter("name")收到的是【jay love】

2.遇到上诉问题之后随即想到对jay+love进行URLEnCode处理,即name=jay%2blove。最后服务端用request.getParameter("name")收到的是【jay+love】,这里被自动URLDeCode解码了。

关于第一个问题:

加号变空

在网上找到一个说法:

html中因为一些非标准的做法,将+等同于空格进行处理(当Html的表单被提交时, 每个表单域都会被Url编码之后才在被发送。由于历史的原因,表单使用的Url编码实现并不符合最新的标准。例如对于空格使用 的编码并不是%20,而是+号,如果表单使用的是Post方法提交的,我们可以在HTTP头中看到有一个Content-Type的header ,值为application/x-www-form-urlencoded,大部分应用程序均能处理这种非标准实现的Url编码)。

这样就解释了为什么【jay+love】变成了【jay love】。

其次我还想到了,为什么我们在html进行form表单提交的时候并没有注意到这样的问题。因为当Html的表单被提交时, 每个表单域都会被Url编码之后才在被发送。所以这个问题就被隐藏了。

关于第二个问题:

自动DeCode

我首先是想到了是调用getParameter这个方法的时候,方法内部对参数进行了URLDeCode。点进去发现方法属于这个接口javax.servlet.ServletRequest,位于javax.servlet-api.jar中。

那么源码就位于WEB容器中,也就是tomcat的源码。下载tomcat源码后,我下载的是apache-tomcat-8.5.32-src。

愉快的找到这个接口。发现有很多实现类。

getParameter的实现类

那我到底用的是哪个呢,我也不知道,那我就在调用的地方打印一下呗。System.out.println(request.getClass().getName());

输出:org.apache.catalina.connector.RequestFacade。

那我们看一下RequestFacade里面怎么写的。【Facade模式对Request对象进行包装】

RequestFacade.getParameter()

可以看到他又调用了org.apache.catalina.connector.Request.getParameter,那么这个方法干什么了呢,如图:

org.apache.catalina.connector.Request.getParameter

不难看出,这个方法解析参数parseParameters。并且看注释说明:

获取指定的参数,比如我们这里的name,如果有就返回值,没有就返回null。如果获取的参数不止一个,比如请求参数中有两个name,那么只返回第一个.

 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
            Charset charset = getCharset();
            boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();
            parameters.setCharset(charset);
            if (useBodyEncodingForURI) {
                parameters.setQueryStringCharset(charset);
            }
            // Note: If !useBodyEncodingForURI, the query string encoding is
            //       that set towards the start of CoyoyeAdapter.service()
            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(false);
                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)) {
                    Context context = getContext();
                    if (context != null && context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(
                                sm.getString("coyoteRequest.postTooLarge"));
                    }
                    checkSwallowInput();
                    parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
                    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) {
                        parameters.setParseFailedReason(FailReason.REQUEST_BODY_INCOMPLETE);
                        return;
                    }
                } catch (IOException e) {
                    // Client disconnect
                    Context context = getContext();
                    if (context != null && context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(
                                sm.getString("coyoteRequest.parseParameters"),
                                e);
                    }
                    parameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);
                    return;
                }
                parameters.processParameters(formData, 0, len);
            } else if ("chunked".equalsIgnoreCase(
                    coyoteRequest.getHeader("transfer-encoding"))) {
                byte[] formData = null;
                try {
                    formData = readChunkedPostBody();
                } catch (IllegalStateException ise) {
                    // chunkedPostTooLarge error
                    parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
                    Context context = getContext();
                    if (context != null && context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(
                                sm.getString("coyoteRequest.parseParameters"),
                                ise);
                    }
                    return;
                } catch (IOException e) {
                    // Client disconnect
                    parameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);
                    Context context = getContext();
                    if (context != null && 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.setParseFailedReason(FailReason.UNKNOWN);
            }
        }
    }

可以看到第三行:

Parametersparameters =coyoteRequest.getParameters();

直接获取Parameters。这不就是我们想要的东西嘛。点进去看看。

就是一个简单的get方法。看一下这个成员变量的定义。

接下来看看那些地方调用了他。

我去这一行调用这名字很可疑呀,去看看。

恍然大悟,原来是构造方法的时候就调用了URLDecoder了。至此,我大概知道了这个流程。

今天有幸遇到这个问题,特此记录。

文章评论