【问题标题】:SpringBoot 2 - OncePerRequestFilter - modify response Headers after processing of ControllerSpringBoot 2 - OncePerRequestFilter - 处理控制器后修改响应标头
【发布时间】:2019-06-27 01:46:51
【问题描述】:

您好,我想在完成处理(执行逻辑)并以 HTTP 状态代码结束后修改我的一些 API 响应标头。

例如,如果响应是 404,则包括特定的,例如 Cache-Control Headers example dont cache,或类似的东西。

我已经注册了 2 个OncePerRequestFilter,它们工作正常——但显然我不能做逻辑——一旦处理完成。 CacheControlFilter 已经具有默认添加一些 Cache-Control 标头的逻辑 - 例如缓存 15 秒等。尽管这似乎发生在调度的早期阶段以及当它发生时(在响应中添加标头)到达执行实际Controller/Endpoint 的阶段,并且有一个异常或错误显然将由通知等处理,我不能mutate 这些已经存在的标题 - 已经由过滤器添加。


  @Bean
  public FilterRegistrationBean filterOne() {
    Filter filter = new FilterOne();
    return createFilter(filter, "FilterOne",List.of("/*"));
  }

  @Bean
  public FilterRegistrationBean cacheControlFilter() {
    Filter filter = new CacheControlFilter();
    return createFilter(filter, "CacheControlFilter", List.of("/*"));
  }

  private FilterRegistrationBean createFilter(Filter aFilter, String filterName,
      List<String> urlPatterns) {
    FilterRegistrationBean filterRegBean = new FilterRegistrationBean(aFilter);
    filterRegBean.addUrlPatterns(urlPatterns.toArray(new String[0]));
    filterRegBean.setName(filterName);
    filterRegBean.setEnabled(true);
    filterRegBean.setAsyncSupported(true);
    return filterRegBean;
  }

我已经尝试添加HttpServletResponseWrapper,如herehere 上的CacheControlFilter 上的这些帖子所示,但它似乎不起作用。我还看到了类似的 S.O 线程 here

HttpServletResponseWrapper wrapper = new HttpServletResponseWrapper(response) {
   @Override
   public void setStatus(int sc) {
      super.setStatus(sc);
      handleStatus(sc);
  }

 @Override
 @SuppressWarnings("deprecation")
 public void setStatus(int sc, String sm) {
     super.setStatus(sc, sm);
     handleStatus(sc);
 }

 @Override
 public void sendError(int sc, String msg) throws IOException {
     super.sendError(sc, msg);
     handleStatus(sc);
  }

 @Override
 public void sendError(int sc) throws IOException {
     super.sendError(sc);
     handleStatus(sc);
 }

 private void handleStatus(int code) {
    if(code == 404)
         addHeader("Cache-Control, "xxx");
 }

};

但是代码根本没有执行!因此,我只想在处理完成并且准备好返回响应之后,才在第二个过滤器上操作 Cache-Control 标头。

我不确定我是否也有,做一些清理并在错误时设置响应 - 把事情搞混了!

@ControllerAdvice
@Slf4j
public class GlobalErrorHandler

更新:请注意,当我的控制器抛出异常或错误时,上面的 GlobalErrorHandler 被调用,我在那里执行特殊处理,返回 error response。我看到的是magically 响应已经具有由过滤器(CacheControlFilter)填充的default 标头。所以它最终有点奇怪,我添加了额外的逻辑,以更改控制标头,我最终得到一个具有相同标头的响应 2 次(1 具有由 CacheControlFilter 设置的值,然后是任何特殊值我我试图覆盖ControllerAdvice

任何提示或帮助表示感谢!我使用 Spring Boot 2.1.2Undertow 作为我的底层 servlet 容器。

【问题讨论】:

  • 您是否使用@RestController(或Controller)来公开您的API?
  • 是的,我愿意。用更多的上下文更新了问题 - 与您下面的 answer 相关

标签: spring spring-boot spring-mvc servlet-filters undertow


【解决方案1】:

您提到的link 说无法获取状态码或修改ResponseBodyAdvice 中的标题是不正确的。如果您将 ServerHttpResponse 转换为 ServletServerHttpResponse ,则两者都可以。所以简单地实现一个ResponseBodyAdvice

@ControllerAdvice
public class CacheControlBodyAdvice implements ResponseBodyAdvice {

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
            Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

        if(response instanceof ServletServerHttpResponse) {
                ServletServerHttpResponse res= (ServletServerHttpResponse)(response);
                if(res.getServletResponse().getStatus() == 400){
                    res.getServletResponse().setHeader("Cache-Control", "XXXXX");
                }           
        }
        return body;
    }
}

还有一点需要注意的是,如果你的控制器方法在正常完成之前抛出异常,取决于如何处理异常,ResponseBodyAdvice可能不会被触发。所以,我建议在GlobalErrorHandler 中为安全防护实现相同的逻辑:

@ControllerAdvice
public class GlobalErrorHandler{

   @ExceptionHandler(value = Exception.class)
   public void handle(HttpServletRequest request, HttpServletResponse response) {

        if(response.getStatus() == 400){
           response.setHeader("Cache-Control", "XXXXX");
        }       
    }
}

【讨论】:

  • 这正是我的问题!第二部分。所以我有一个过滤器,无论如何都会为每个请求添加一些默认的 Cache-Control 标头。这很好并且有效。现在,如果这些控制器中的任何一个出现异常,ControllerAdvice 将执行异常处理并返回自定义错误消息。我看到的是 Cache-Control 标头无论如何都会添加到过滤器的响应中,并且当我尝试执行任何额外的逻辑时-例如,很可能像您在上面显示的那样更改错误处理程序上 Header 的值,我最终具有相同的标题和冲突的值!!!!
  • 我已经更新了我的问题,以便indicate 这个奇怪的行为如上所述
  • @javapapo 原来response.setHeader()response.addHeader() 不同。后者将在同一个标​​头的现有值之上添加标头值,这允许同一个标头具有多个值,因此可能会导致您提到的重复标头问题。我修改了GlobalErrorHandler 中的代码以使用response.setHeader() 以防止此类问题。另外,我建议使用CacheControlBodyAdvice,而不是你的CacheControlFilter ...
  • 正确 - 非常感谢。我想我会这样做,用建议替换过滤器,这似乎是真正拥有完全控制权而不发疯的唯一方法!
【解决方案2】:

我认为您使用的是spring-mvc(正如您在标签中提到的那样);如果是这样,您可以绑定到HttpServletResponse 以添加您的标题。你可以在你的方法处理程序中这样做:

@RestController
class HelloWordController{

    @GetMapping("/hello")
    public String test(HttpServletResponse response){
        response.addHeader("test", "123");
        return "hola";
    }
}

另一种解决方案(时尚)是返回ResponseEntity

@RestController
class HelloWorkController{

    @GetMapping("/hello")
    public ResponseEntity<String> test(HttpServletResponse response){
        return ResponseEntity.status(HttpStatus.OK)
                .header("test", "4567")
                .body("hello world");
    }
}

【讨论】:

  • 也许我的措辞具有误导性 - 这行得通!但似乎过滤器在我们执行任何实际业务逻辑之前添加了响应标头。假设我的系统调用了一个下游服务,它得到了一个 404 响应,它想要返回一个 404,所以只有当我完成任何处理,并且我准备好返回客户端时 - 我想要设置的响应标题而不是之前。至少这是我所看到的正在发生的事情。过滤器 -> 过滤器 -> 控制器/服务
【解决方案3】:

在 Spring 中返回客户端之前更改 HttpServletResponse 并将响应注入处理程序方法或利用 ControllerAdvice 是有效的解决方案,有十几种方法。但是,我不明白您的问题的基本前提是过滤器无法完成这项工作:

我已经注册了 2 个 OncePerRequestFilter,工作正常 - 但显然我不能做逻辑 - 一旦处理完成。

就修改 HttpServletResponse 而言,Filters 对我来说完全没问题,并且至少与任何其他工具一样适用于该工作:

@Bean
public FilterRegistrationBean createFilter() {
    Filter filter = new OncePerRequestFilter() {
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            super.doFilter(request, response, filterChain);
            response.setHeader("Cache-Control", "xxx");
        }
    };
    return new FilterRegistrationBean(filter);
}

【讨论】:

  • 也许我的措辞具有误导性 - 这行得通!但似乎过滤器在我们执行任何实际业务逻辑之前添加了响应标头。假设我的系统调用了一个下游服务,它得到了一个 404 响应,它想要返回一个 404,所以只有当我完成任何处理,并且我准备好返回客户端时 - 我想要设置的响应标题而不是之前。至少这是我所看到的正在发生的事情。过滤器 -> 过滤器 -> 控制器/服务 ...
  • 在一个Filter实现中的doFilterInternal方法中,调用doFilter之前的代码在Controller之前执行,调用doFilter之后的代码在Controller之后执行。
  • doFilter 为时已晚后,响应可能已被刷新/提交,这意味着对此处响应的修改将被忽略。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-02-21
  • 1970-01-01
  • 1970-01-01
  • 2021-12-19
  • 1970-01-01
  • 2011-02-01
  • 1970-01-01
相关资源
最近更新 更多