1. 问题
今天测试突然说一个5年前的功能,报400错误,并提示如下错误信息:
HTTP Status 400 – Bad Request
Type Exception Report
Message Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986
Description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).
Exception
java.lang.IllegalArgumentException: Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986
org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:468)
org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:260)
org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:860)
org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1598)
org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
java.base/java.lang.Thread.run(Thread.java:830)
Note The full stack trace of the root cause is available in the server logs.
2. 解决方案
问题非常明显,提示已经足够清楚了。
经过复现测试提供的问题,发现部分老的js代码没有遵循规范,在url的请求参数中携带了原始的没有经过url encode 转码的json字符串。json字符串中的{}显然不属于 RFC 7230 和 RFC 3986 允许的特殊字符。
2.1. 彻底的解决方案
彻底的解决方案显然应该是重构代码,让代码遵循规范。
- 所有的url参数都应该经过url encode,确保不会出现特殊字符
- 复杂请求参数应当通过 post,放在Request Body中传递,而不是 get 的url中显式传递。
2.2. 临时解决方案
问题已经出现。而且经过初步检查,发现存在类似问题的不规范老代码太多,一时半会不能保证能全部修改完毕。那么彻底的解决方案就不可行的。对应急处理,还是有办法的:
2.2.1. 对tomcat6、tomcat7、tomcat8:
在 ${tomcat.dir}/conf/catalina.properties 文件中,找到被注释调的参数 tomcat.util.http.parser.HttpParser.requestTargetAllow
,取消备注,将该参数的值指定为:tomcat.util.http.parser.HttpParser.requestTargetAllow=|{}
。这样,“|{}”这三个特殊字符就不再被拦截了。
2.2.2. 对tomcat9:
tomcat9不支持参数 tomcat.util.http.parser.HttpParser.requestTargetAllow
,但有类似的配置项。
在 ${tomcat.dir}/conf/server.xml 文件中,找到你使用的Connector,添加参数 relaxedPathChars="|{}" relaxedQueryChars="|{}"
。
2.2.3. 对spring-boot的内嵌tomcat9以前版本:
如果是内嵌的 tomcat9以前版本,可以通过如下代码配置:
@SpringBootApplication
public class XxApplication {
public static void main(String[] args) {
System.setProperty("tomcat.util.http.parser.HttpParser.requestTargetAllow","|{}");
SpringApplication.run(XxApplication.class, args);
}
...
}
2.2.4. 对spring-boot内嵌的tomcat9:
可以通过类似如下代码添加Connector的自定义参数:
spring-boot-1.*可以这样:
@Component
public class MyEmbeddedServletContainerCustomizer implements EmbeddedServletContainerCustomizer {
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
TomcatEmbeddedServletContainerFactory factory = (TomcatEmbeddedServletContainerFactory) container;
factory.addConnectorCustomizers((connector) -> {
connector.setAttribute("relaxedPathChars", "|{}");
connector.setAttribute("relaxedQueryChars", "|{}");
});
}
}
spring-boot-2.*可以这样
@Component
public class MyTomcatServletWebServerFactoryCustomizer extends TomcatServletWebServerFactoryCustomizer {
/**
* 构造函数
* @param serverProperties
*/
public MyTomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
super(serverProperties);
}
@Override
public void customize(TomcatServletWebServerFactory factory) {
super.customize(factory);
factory.addConnectorCustomizers((connector) -> {
connector.setAttribute("relaxedPathChars", "|{}");
connector.setAttribute("relaxedQueryChars", "|{}");
});
}
}
2.3. 最后忠告
- 当然,你可以根据自己的需要,添加其他参数,但要注意如果添加了类似 “<>”这样的参数,可能会引发其他问题,导致服务器无法启动,或无法正常工作。
- 再次强调,这个方法,只是临时解决方案,用于问题发生后给自己争取时间来实施彻底解决方案,不能作为常规解决方案。