SpringBoot 2-OncePerRequestFilter-处理控制器后修改响应头

时间:2019-02-02 20:23:16

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

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

例如,如果该响应是404,则包括具体例如Cache-Control集管例如不要高速缓存,或类似的东西。

我已经注册了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;
  }

我已经尝试过按照这些帖子hereHttpServletResponseWrapper上的here中的说明添加一个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响应已具有default被过滤器(CacheControlFilter)填充头。因此,最终结果有点怪异,我添加了额外的逻辑来更改控制标头,最后得到一个响应,该响应具有相同的标头2次(1由CacheControlFilter设置,然后是特殊的我试图在ControllerAdvice

上覆盖的值

任何提示或帮助表示感谢!我将Spring Boot 2.1.2Undertow用作基础servlet容器。

3 个答案:

答案 0 :(得分:1)

您提到的link说无法获取状态代码或修改ResponseBodyAdvice中的标题不是true。如果将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");
        }       
    }
}

答案 1 :(得分:1)

有很多方法可以有效地解决方法,这些方法是在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);
}

答案 2 :(得分:0)

我认为您正在使用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");
    }
}