在 Spring Boot 中处理嵌入式 Tomcat 异常

Posted

技术标签:

【中文标题】在 Spring Boot 中处理嵌入式 Tomcat 异常【英文标题】:Handle Embedded Tomcat Exception in Spring Boot 【发布时间】:2017-01-15 12:57:58 【问题描述】:

我们遇到了嵌入式 Tomcat 从 LegacyCookieProcessor 抛出 IllegalArgumentException 的问题。它会抛出一个 500 HTTP 响应代码。

我们需要处理异常并对其进行处理(具体而言,将其作为 400 发送)。

典型的@ExceptionHandler(IllegalArgumentException.class) 似乎没有被触发,Google 似乎只给出了处理 Spring Boot 特定异常的结果。


示例:

这是重现该行为的示例。您可以通过下载 2.1.5.RELEASE 版本中包含 spring-web (https://start.spring.io/) 的初始项目来执行该示例。然后将以下两个类添加到您的项目中。

DemoControllerAdvice.java

package com.example.demo;

import java.util.HashMap;
import java.util.Map;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class DemoControllerAdvice 

    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public Map<String, String> forbiddenHandler() 
        Map<String, String> map = new HashMap<>();
        map.put("error", "An error occurred.");
        map.put("status", HttpStatus.FORBIDDEN.value() + " " + HttpStatus.FORBIDDEN.name());
        return map;
    



DemoRestController.java

package com.example.demo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoRestController 

    @GetMapping(value = "/working")
    public void working() 
        throw new java.lang.IllegalArgumentException();
    

    @GetMapping(value = "/not-working")
    public String notWorking(@RequestParam String demo) 
        return "You need to pass e.g. the character ^ as a request param to test this.";
    



然后,启动服务器并在浏览器中请求以下 URL:

http://localhost:8080/working IllegalArgumentException 在控制器中手动抛出。然后它被 ControllerAdvice 捕获,因此将生成一个 JSON 字符串,其中包含DemoControllerAdvice 中定义的信息 http://localhost:8080/not-working?demo=test^123 Tomcat 抛出 IllegalArgumentException,因为请求参数无法解析(因为无效字符 ^)。但是,ControllerAdvice 不会捕获该异常。它显示了 Tomcat 提供的默认 html 页面。它还提供了不同于DemoControllerAdvice 中定义的错误代码。

在日志中显示以下消息:

o.apache.coyote.http11.Http11Processor:解析 HTTP 请求标头时出错 注意:进一步出现的 HTTP 请求解析错误将记录在 DEBUG 级别。

java.lang.IllegalArgumentException:在请求目标中发现无效字符。有效字符在 RFC 7230 和 RFC 3986 中定义 在 org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:467) ~[tomcat-embed-core-9.0.19.jar:9.0.19]

【问题讨论】:

@Imran 字符串不是这样比较的! 也许你没有找到答案,因为你没有提供简单的MCVE 让其他人重现问题。 你不能拦截这个。这个异常在被任何与 Servlet API 相关的东西处理之前就被抛出了,所以这里甚至没有 servlet 过滤器、监听器或DispatcherServlet 参与其中。只是tomcat在内部很早就炸了。 @ssc-hrep3 奇怪的是,我无法为http://localhost:8080/not-working?demo=test^123 复制我这边的相同行为,它给了我 200。你能告诉我你正在使用哪个版本的 Java 以及任何特殊的吗属性启用?一个完整的 github repo 示例有助于尝试一些选项。 @ssc-hrep3,看看 MCVE 有多大用处?新人——不一定是我,因为我是 AOP 专家但对容器了解不多,所以首先要求 MCVE 的人——参与讨论并提供有价值的信息,重新提出一个 2.5 年前的问题。 :-) 【参考方案1】:

这是this 回答中提到的Tomcat 本身的一个功能。

但是,您可以通过允许将您期望的特殊字符作为请求的一部分并自己处理它们来执行此类操作。

首先,您需要通过像这样设置 relaxedQueryChars 来允许您需要处理的特殊字符。

import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;

@Component
public class TomcatCustomizer implements 
WebServerFactoryCustomizer<TomcatServletWebServerFactory> 

@Override
public void customize(TomcatServletWebServerFactory factory) 
    factory.addConnectorCustomizers((connector) -> 
        connector.setAttribute("relaxedQueryChars", "^");
    );
 

然后处理每个请求中的特殊字符,或者创建一个拦截器并在一个公共位置处理它。

要在请求中单独处理它,您可以执行以下操作。

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoRestController 

@GetMapping(value = "/working")
public void working() 
    throw new java.lang.IllegalArgumentException();


@GetMapping(value = "/not-working")
public String notWorking(@RequestParam String demo) 

    if (demo.contains("^")) 
        throw new java.lang.IllegalArgumentException("^");
    
    return "You need to pass e.g. the character ^ as a request param to test this.";



您可能还想参考this 的答案来决定您是否真的需要此修复程序。

【讨论】:

感谢您的回复,这很有趣。但是,我不想允许这些字符。我只想以相同的方式响应所有异常:通过返回一个包含 JSON(和自定义标头)的 HTTP 包。允许这些字符然后在每个控制器中再次处理它们似乎不是一个好的解决方案。而且它实际上并不能阻止 Tomcat 抛出其他异常(我假设还有其他情况,Tomcat 会抛出除特殊字符之外的异常)。 在每个控制器中允许这些字符只是此场景的一个示例。您可以做的是构建一个 HTTP 拦截器并检查具有特殊字符的参数并从拦截器中抛出 IllegalArgumentException 以获得通用实现。 虽然问题已经演变为讨论查询参数,但最初的问题实际上是关于 cookie 中的一个奇怪值,而不是查询参数。这两种情况都适用吗?

以上是关于在 Spring Boot 中处理嵌入式 Tomcat 异常的主要内容,如果未能解决你的问题,请参考以下文章

嵌入式 servlet 容器不处理 Spring Boot 中的 META-INF/资源

无法在 Spring Boot 中启动嵌入式 Tomcat [重复]

spring boot 2 - webrestfulCrud实验错误处理机制配置嵌入式Servlet容器

如何在spring boot嵌入式tomcat中获取当前客户端请求线程数?

Spring Boot使用嵌入式容器,自定义Filter如何配置?

Spring-boot简单的理解