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



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

例如,如果响应是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;
}

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

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响应已经包含了由Filter(CacheControlFilter)填充的default标头。因此,这最终有点奇怪,我添加了额外的逻辑来更改控制标头,结果得到了两次具有相同标头的响应(1次由CacheControlFilter设置,然后是我试图在ControllerAdvice上覆盖的任何特殊值

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

您提到的无法在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");
}       
}
}

我假设您使用的是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");
}
}

在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);
}

相关内容

  • 没有找到相关文章

最新更新