问题描述:
前后端分离项目,前端使用查询时参数中带有[]{}等特殊字符时,前端直接返回400异常问题。
单体模式下get请求带有路径参数,这个参数中有[]或者/这个特殊字符,前端已经转移成了%2F,后端用的是springboot,并没有收到这个请求,直接返回了400的错误。(注意是后端根本没有收到请求就直接报400了);
这个问题是由于Tomcat的新版本中增加了一个新特性,就是严格按照 RFC 3986规范进行访问解析,而 RFC 3986规范定义了Url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符(RFC3986中指定了以下字符为保留字符:! * ’ ( ) ; : @ & = + $ , / ? # [ ])。
单体模式解决办法:
①将get请求换成post请求;
②前端处理请求时URL encode处理特殊字段
③重写tomcat的TomcatConnectorCustomizer类
@Configuration
public class Config {
@Bean
public TomcatServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
@Override
public void customize(Connector connector) {
connector.setProperty("relaxedPathChars", "\"<>[\\]^`{|}");
connector.setProperty("relaxedQueryChars", "\"<>[\\]^`{|}");
connector.setProperty("rejectIllegalHeader", "false");
}
});
return factory;
}
}
微服务模式下的处理方式:
①将get请求换成post请求;
②在gateway服务下实现WebServerFactoryCustomizer接口
/**
* @Author MrYang
* @Description 解决通过网关调用服务时,url中含有 [] {}等字符时浏览器400异常
* @Version 1.0
*/
@Component
@Slf4j
public class EncodeQueryNettyWebServerCustomizer
implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
/**
* 需要encode的特殊字符
*/
private final List<Character> charList = new ArrayList<Character>() {
{
this.add('{');
this.add('}');
this.add('[');
this.add(']');
}
};
@Override
public void customize(NettyReactiveWebServerFactory factory) {
factory.addServerCustomizers(httpServer ->
httpServer.observe((conn, state) -> {
if (state == ConnectionObserver.State.CONNECTED) {
conn.channel().pipeline().addAfter(NettyPipeline.HttpCodec, "", new QueryHandler());
}
}));
}
class QueryHandler extends ChannelInboundHandlerAdapter {
public QueryHandler() {
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException {
if (msg instanceof HttpRequest) {
HttpRequest request = (HttpRequest) msg;
String url = request.uri();
// fix url
String[] split = url.split("\\?");
StringBuilder fixUrl = new StringBuilder(split[0]);
if (split.length > 1) {
fixUrl.append("?");
char[] chars = split[1].toCharArray();
for (char aChar : chars) {
if (charList.contains(aChar)) {
fixUrl.append(URLEncoder.encode(String.valueOf(aChar), "UTF-8"));
}else {
fixUrl.append(aChar);
}
}
}
request.setUri(fixUrl.toString());
}
ctx.fireChannelRead(msg);
}
}
}