Spring Boot get请求 IE 中文报 HTTP 400 错误

1. 错误描述

Spring Boot 项目,请求有中文的时候 IE 链接报 400 错误,只有 IE 报这个错误,其余谷歌浏览器或者谷歌内核浏览器不报这个错误,这很显然是因为中文问题,果然,将中文使用 encodeURI 转码之后,就可以了,因为谷歌内核的浏览器会自动将中文转码,所以不会出这个问题。
那么问题来了,为什么会出这个问题呢?
以前有项目使用的 tomcat 8.0.33 并没有问题,我用 tomcat 8.5.0 也没有问题,为什么现在项目使用 Spring Boot 就有问题了?
好在后端有报错:

java.lang.IllegalArgumentException: Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986
    at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:479) ~[tomcat-embed-core-8.5.31.jar:8.5.31]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:687) [tomcat-embed-core-8.5.31.jar:8.5.31]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.31.jar:8.5.31]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790) [tomcat-embed-core-8.5.31.jar:8.5.31]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1468) [tomcat-embed-core-8.5.31.jar:8.5.31]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.31.jar:8.5.31]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [?:1.8.0_91]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [?:1.8.0_91]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.31.jar:8.5.31]
    at java.lang.Thread.run(Thread.java:745) [?:1.8.0_91]


2. RFC 3986 和 tomcat 代码实现

简单说就是请求必须符合规范 RFC 7230 and RFC 3986 ,查了一下关于 RFC 3986 的规范。

RFC3986文档规定,Url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符。RFC3986文档对Url的编解码问题做出了详细的建议,指出了哪些字符需要被编码才不会引起Url语义的转变,以及对为什么这些字符需要编码做出了相应的解释。


US-ASCII字符集中没有对应的可打印字符:Url中只允许使用可打印字符。US-ASCII码中的10-7F字节全都表示控制字符,这些字符都不能直接出现在Url中。同时,对于80-FF字节(ISO-8859-1),由于已经超出了US-ACII定义的字节范围,因此也不可以放在Url中。


保留字符:Url可以划分成若干个组件,协议、主机、路径等。有一些字符(:/?#[]@)是用作分隔不同组件的。例如:冒号用于分隔协议和主机,/用于分隔主机和路径,?用于分隔路径和查询参数,等等。还有一些字符(!$&’()*+,;=)用于在每个组件中起到分隔作用的,如=用于表示查询参数中的键值对,&符号用于分隔查询多个键值对。当组件中的普通数据包含这些特殊字符时,需要对其进行编码。


RFC3986中指定了以下字符为保留字符:! * ’ ( ) ; : @ & = + $ , / ? # [ ]


不安全字符:还有一些字符,当他们直接放在Url中的时候,可能会引起解析程序的歧义。这些字符被视为不安全字符,原因有很多。


空格:Url在传输的过程,或者用户在排版的过程,或者文本处理程序在处理Url的过程,都有可能引入无关紧要的空格,或者将那些有意义的空格给去掉。


引号以及<>:引号和尖括号通常用于在普通文本中起到分隔Url的作用 井号(#) 通常用于表示书签或者锚点


%:百分号本身用作对不安全字符进行编码时使用的特殊字符,因此本身需要编码 {}|\^[]`~:某一些网关或者传输代理会篡改这些字符

好了,我们的中文在不支持的范围内。
我们看下代码

} else if (parsingRequestLineQPos != -1 && !httpParser.isQueryRelaxed(chr)) {
    // %nn decoding will be checked at the point of decoding
    throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget"));
} 

出错的在这里,
at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:479)
httpParser 了解一下, org.apache.tomcat.util.http.parser.HttpParser,里面有 IS_NOT_REQUEST_TARGET,这就是不支持的字符数组。

if (IS_CONTROL[i] || i > 127 ||
        i == ' ' || i == '\"' || i == '#' || i == '<' || i == '>' || i == '\\' ||
        i == '^' || i == '`'  || i == '{' || i == '|' || i == '}') {
    if (!REQUEST_TARGET_ALLOW[i]) {
        IS_NOT_REQUEST_TARGET[i] = true;
    }
}

这样一看就很明了了,代码里面写死了,如果要增加支持,看下面的代码

String prop = System.getProperty("tomcat.util.http.parser.HttpParser.requestTargetAllow");
if (prop != null) {
    for (int i = 0; i < prop.length(); i++) {
        char c = prop.charAt(i);
        if (c == '{' || c == '}' || c == '|') {
            REQUEST_TARGET_ALLOW[c] = true;
        } else {
            log.warn(sm.getString("http.invalidRequestTargetCharacter",
                    Character.valueOf(c)));
        }
    }
}

在系统环境里面设置 tomcat.util.http.parser.HttpParser.requestTargetAllow 即可。

然后我看了一下自己的 tomcat 8.0.33 和 8.5.0 版本的tomcat,里面确实是没有 REQUEST_TARGET_ALLOW 的。

2. 强制支持 RFC 7230 和 RFC 3986 版本

看下官方文档,到底什么时候开始要求支持 RFC 7230 和 RFC 3986 的。

tomcat 9.x: 9.0.0.M12 (not released 未正式发布)
http://tomcat.apache.org/tomcat-9.0-doc/changelog.html#Tomcat_9.0.0.M12_(markt)

tomcat 8.5: 8.5.7 (not released 未正式发布)
http://tomcat.apache.org/tomcat-8.5-doc/changelog.html#Tomcat_8.5.7_(markt)

tomcat 8.x: 8.0.39
http://tomcat.apache.org/tomcat-8.0-doc/changelog.html#Tomcat_8.0.39_(violetagg)

tomcat 7.x: 7.0.73
http://tomcat.apache.org/tomcat-7.0-doc/changelog.html#Tomcat_7.0.73_(violetagg)

从上面这几个版本开始,都需要强制支持 7230 and RFC 3986, 在 Coyote 最后一行都有下面的日志信息:

Add additional checks for valid characters to the HTTP request line parsing so invalid request lines are rejected sooner. (markt)

翻译: 在HTTP请求行解析中添加额外的有效字符检查,因此无效的请求会被提前拒绝。

你可能感兴趣的:(Spring,tomcat,学习笔记,Java技术)