【问题标题】:How to set default HTTP response headers and override them in the exception handler in Spring Boot Rest Controller如何在 Spring Boot Rest Controller 的异常处理程序中设置默认 HTTP 响应标头并覆盖它们
【发布时间】:2022-01-27 17:21:37
【问题描述】:

我在 Spring Boot (v2.4) 中有一个注释为 @RestController 的控制器。默认情况下,这应该为它处理的所有端点返回一个具有完全静态配置的 HTTP 标头“Cache-Control”。

到目前为止,我通过控制器类中的 @ModelAttribute 注释方法实现了这一点,该方法只需像这样设置标题:

@ModelAttribute
public void setResponseHeader(HttpServletResponse response) {
  response.setHeader(HttpHeaders.CACHE_CONTROL, 
    CacheControl.maxAge(1, TimeUnit.DAYS).cachePrivate().getHeaderValue());
}

这很好用,但我发现它与我的异常处理程序发生了一点冲突——我通过将@RestControllerAdvice 放到另一个类上配置了一个全局处理程序,然后为内部各种可能的异常设置了多个用@ExceptionHandler 注释的方法.

大多数时候异常处理程序设置一个特定的 HTTP 状态为 400 及以上,当然我不希望它被缓存。

即使我在异常处理方法中提供了HttpServletResponse,我的setResponseHeader(HttpServletResponse) 方法已经在其中设置了HTTP 标头,并且接口HttpServletResponse 不允许我再次删除它们。

那么有什么策略可以让默认的缓存标头在一侧,而异常处理程序的异常在另一侧?

如果我不必在每个端点的控制器方法中单独设置缓存标头,我将不胜感激。

另外的挑战是,我需要将大数据流式传输到客户端,所以我真的很想避免服务器端缓存,因为我预计会有很多请求,并希望防止服务器用应该的内容填充它的堆交付给客户。 不幸的是,我不能使用普通资源处理程序机制,因为我不想放入过滤器中需要有关权限(不仅是 OAuth2,还有许可)的复杂查询。

【问题讨论】:

    标签: java spring-boot rest exception response-headers


    【解决方案1】:

    此 servlet 过滤器设置 Cache-Control 标头,除非响应状态为 4xx 错误:

    public class CacheControlFilter extends OncePerRequestFilter {
    
      @Override
      protected void doFilterInternal(
          HttpServletRequest request,
          HttpServletResponse response,
          FilterChain filterChain) throws ServletException, IOException {
    
        var responseWrapper = new ContentCachingResponseWrapper(response);
        filterChain.doFilter(request, responseWrapper);
        if (!HttpStatus.valueOf(responseWrapper.getStatus()).is4xxClientError()) {
          responseWrapper.setHeader(
              HttpHeaders.CACHE_CONTROL,
              CacheControl.maxAge(1, TimeUnit.DAYS).cachePrivate().getHeaderValue());
        }
    
        responseWrapper.copyBodyToResponse();
      }
    }
    

    您还需要删除设置标题的其他代码,因为标题应该只设置在一个位置。

    【讨论】:

    • 很好的解决方案,谢谢。但我实际上想防止服务器端缓存 - 添加了对问题的额外挑战的描述:)
    【解决方案2】:

    经过多次尝试,我找到了适合我特殊情况的解决方案:

    • 我坚持使用@ModelAttribute 填写默认缓存标头。
    • @ExceptionHandler 带注释的方法中,我使用“no-cache”值覆盖缓存标头。
    package test;
    
    import org.springframework.http.CacheControl;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpStatus;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.ResponseStatus;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    import javax.servlet.http.HttpServletResponse;
    
    @RestControllerAdvice
    public class ExHandlers {
      @ExceptionHandler({NullPointerException.class, IllegalStateException.class})
      @ResponseStatus(HttpStatus.BAD_REQUEST)
      public String nullPointer(RuntimeException e, HttpServletResponse response) {
        response.setHeader(HttpHeaders.CACHE_CONTROL, CacheControl.noCache().getHeaderValue())
        return e.getMessage();
      }
    }
    

    重要提示:不要使用org.springframework.http.ResponseEntity,因为这会在响应中添加第二个Cache-Control 标头,而HttpServletResponse.setHeader(...) 会替换默认的标头。 我检查了实现,一旦设置了标头,就无法主动删除它。

    我的解决方案的缺点:当正文开始流式传输并且发生异常时,我无法“恢复”标题 - 可能它们已经通过电线了。但由于希望没有服务器端缓存,我认为这是唯一的方法。

    【讨论】:

      猜你喜欢
      • 2016-06-04
      • 2020-11-27
      • 2015-08-30
      • 2013-12-16
      • 1970-01-01
      • 1970-01-01
      • 2016-06-06
      • 2015-06-22
      相关资源
      最近更新 更多