如何设置默认 HTTP 响应 headers 并在 Spring Boot Rest Controller 的异常处理程序中覆盖它们

How to set default HTTP response headers and override them in the exception handler in Spring Boot Rest Controller

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

直到现在,我通过控制器 class 中的 @ModelAttribute 注释方法实现了这一点,它只是像这样设置 headers:

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

这工作正常,但我发现它与我的异常处理程序有点冲突 - 我通过将 @RestControllerAdvice 放在另一个 class 上配置了一个全局的,然后用 [ 注释了多个方法=16=]里面各种可能的异常。

异常处理程序大部分时间都将特定的 HTTP 状态设置为 400 及以上,当然我不希望将其缓存。

即使我在异常处理方法中提供了 HttpServletResponse,我的 setResponseHeader(HttpServletResponse) 方法和接口 [=17= 已经设置了 HTTP headers ] 不允许我再次删除它们。

那么有没有什么策略可以在一侧使用默认缓存 header,而在另一侧使用异常处理程序的异常?

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

另一个挑战是,我需要将大数据流式传输到客户端,所以我真的很想避免 server-side 缓存,因为我希望有很多请求并希望防止服务器用应交付给客户的内容。 不幸的是,我不能使用简单的资源处理程序机制,因为需要关于权限(不仅是 OAuth2,还包括许可)的复杂查询,我不想将其放入过滤器中。

此 servlet 过滤器设置 Cache-Control header,响应状态为 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();
  }
}

您还需要删除设置 header 的其他代码,因为 header 应该只设置在一个地方。

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

  • 我坚持@ModelAttribute填写默认缓存header。
  • @ExceptionHandler 注释方法中,我用“no-cache”值覆盖缓存 header。
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 header,而 HttpServletResponse.setHeader(...) 会替换默认值。 我检查了实现,一旦设置 header 就不可能主动删除它。

我的解决方案的缺点:当 body 开始流式传输并且发生异常时,我无法“还原”headers - 可能它们已经通过网络。但是由于不希望 server-side 缓存,我认为这是唯一的方法。