含%符号的URL无法触发Spring Boot自定义错误页面问题
请求处理过程中发生错误时,能正常获取自定义错误页面响应,但当请求URL末尾带有%符号(如http://localhost:8080/some-path%)时,返回的是Tomcat默认的HTTP 400错误页面。
使用Spring Boot v2.7.2及web starter,自定义ErrorController代码如下:
@Controller public class CustomErrorController implements ErrorController { @RequestMapping("/error") public String handleError() { return "error"; } }
application.yml配置:
spring: web: resources: add-mappings: false mvc: throw-exception-if-no-handler-found: true server: error: path: /error whitelabel: enabled: false
经排查,错误发生在org.apache.catalina.connector.CoyoteAdapter#postParseRequest方法的URI解码阶段:
// URI decoding // %xx decoding of the URL try { req.getURLDecoder().convert(decodedURI.getByteChunk(), connector.getEncodedSolidusHandlingInternal()); } catch (IOException ioe) { response.sendError(400, "Invalid URI: " + ioe.getMessage()); }
疑问:为何自定义错误响应未生效?能否修改该默认响应?
核心原因是错误触发时机早于Spring MVC的请求处理流程。
当URL末尾带单独的%时,Tomcat在请求解析的早期阶段(CoyoteAdapter#postParseRequest)就会执行URI解码操作。单独的%属于非法URL编码(规范要求%后必须跟两位十六进制字符),此时Tomcat会直接调用response.sendError(400)返回错误,整个过程完全绕过了Spring的DispatcherServlet,因此自定义的ErrorController根本不会被触发。
1. 配置Tomcat错误页面映射
通过Spring Boot配置让Tomcat在返回400错误时转发到自定义的/error路径,这样就能被你的ErrorController处理。创建配置类:
@Configuration public class TomcatConfig { @Bean public TomcatServletWebServerFactory tomcatServletWebServerFactory() { TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(); // 为400错误添加自定义页面映射 factory.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/error")); return factory; } }
2. 自定义Tomcat ErrorValve
如果需要更灵活的错误逻辑控制,可以自定义一个Valve替换Tomcat默认的错误处理阀,在里面拦截并处理URI解码导致的400错误:
首先实现自定义Valve:
public class CustomErrorValve extends ErrorReportValve { @Override protected void report(Request request, Response response, Throwable throwable) { // 判断是否是URI解码失败导致的400错误 if (response.getStatus() == HttpStatus.BAD_REQUEST.value() && throwable instanceof IOException) { try { // 转发到自定义错误页面 request.getRequestDispatcher("/error").forward(request, response); } catch (ServletException | IOException e) { // 转发失败时 fallback 到默认处理 super.report(request, response, throwable); } } else { // 其他错误沿用默认处理逻辑 super.report(request, response, throwable); } } }
然后在配置类中注册这个Valve:
@Configuration public class TomcatConfig { @Bean public TomcatServletWebServerFactory tomcatServletWebServerFactory() { TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(); factory.addContextCustomizers(context -> { // 将自定义Valve添加到Tomcat的处理管道中 context.getPipeline().addValve(new CustomErrorValve()); }); return factory; } }
3. 调整Tomcat URI解码规则(不推荐)
可以通过配置放宽Tomcat对URI字符的限制,避免因单独%触发400错误,但这种方式可能违反URL规范,带来安全风险,不建议生产环境使用。在application.yml中添加:
server: tomcat: relaxed-path-chars: "'{\"}[]|" relaxed-query-chars: "'{\"}[]|"
内容的提问来源于stack exchange,提问作者Andrii Petrov

