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]
简单说就是请求必须符合规范 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
的。
看下官方文档,到底什么时候开始要求支持 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请求行解析中添加额外的有效字符检查,因此无效的请求会被提前拒绝。