SpringBoot 2 - OncePerRequestFilter - 在控制器处理后修改响应 Headers
SpringBoot 2 - OncePerRequestFilter - modify response Headers after processing of Controller
你好,我想在完成处理(执行逻辑)并以 HTTP 状态代码结束后修改我的 API 的一些响应 Headers。
例如,如果响应是 404,则包括特定的 Cache-Control
Headers 示例不缓存,或类似的东西。
我已经注册了 2 个 OncePerRequestFilter
,它们工作正常 - 但显然我不能做逻辑 - 一旦处理完成。 CacheControlFilter
已经具有默认添加一些 Cache-Control
headers 的逻辑 - 例如缓存 15 秒等。似乎发生了这种情况(添加 headers响应)在调度的早期阶段,当它到达执行实际 Controller/Endpoint
的阶段并且有一个异常或错误显然将由建议等处理时,我不能 mutate
这些已经存在的 headers- 已由过滤器添加。
@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
,如 post here and here on the CacheControlFilter
but it does not seem to work. I have also seen a similar S.O thread 所示。
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");
}
};
但是代码根本没有执行!所以我想在处理完成并且我准备好 return 响应后才在第二个过滤器上操作 Cache-Control headers。
我不确定我也有,做一些清理和设置错误响应 - 把事情搞混了!
@ControllerAdvice
@Slf4j
public class GlobalErrorHandler
更新:请注意,当我的控制器抛出异常或错误时,会调用上面的 GlobalErrorHandler
并在那里执行特殊处理,returning 一个 error response
.但我看到的是 magically
响应已经由过滤器 (CacheControlFilter) 填充了 default
header。所以它最终有点奇怪,我添加了额外的逻辑来更改控件 header 并且我最终得到一个具有相同 header 2 次的响应(1 具有由 CacheControlFilter
然后是我试图在 ControllerAdvice
上覆盖的任何特殊值
感谢任何提示或帮助!我正在使用 Spring Boot 2.1.2
和 Undertow
作为我的底层 servlet 容器。
我猜你正在使用 spring-mvc
(正如你在标签中提到的);如果是这样,您可以绑定到 HttpServletResponse
以添加您的 headers。您可以像这样在方法处理程序中执行此操作:
@RestController
class HelloWordController{
@GetMapping("/hello")
public String test(HttpServletResponse response){
response.addHeader("test", "123");
return "hola";
}
}
另一个解决方案(时尚)是return一个ResponseEntity
代替:
@RestController
class HelloWorkController{
@GetMapping("/hello")
public ResponseEntity<String> test(HttpServletResponse response){
return ResponseEntity.status(HttpStatus.OK)
.header("test", "4567")
.body("hello world");
}
}
你提到的link说的无法获取状态码或修改ResponseBodyAdvice
中的headers是不正确的。如果将 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");
}
}
}
有多种方法可以将 return 之前的 HttpServletResponse
更改为 Spring 中的客户端并将响应注入处理程序方法或利用 ControllerAdvice
是有效的解决方案。但是,我不明白你的问题的基本前提是过滤器不能完成这项工作:
I have already 2 OncePerRequestFilter registered, which work fine -
but obviously I can not do logic - once the processing is complete.
就修改 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);
}
你好,我想在完成处理(执行逻辑)并以 HTTP 状态代码结束后修改我的 API 的一些响应 Headers。
例如,如果响应是 404,则包括特定的 Cache-Control
Headers 示例不缓存,或类似的东西。
我已经注册了 2 个 OncePerRequestFilter
,它们工作正常 - 但显然我不能做逻辑 - 一旦处理完成。 CacheControlFilter
已经具有默认添加一些 Cache-Control
headers 的逻辑 - 例如缓存 15 秒等。似乎发生了这种情况(添加 headers响应)在调度的早期阶段,当它到达执行实际 Controller/Endpoint
的阶段并且有一个异常或错误显然将由建议等处理时,我不能 mutate
这些已经存在的 headers- 已由过滤器添加。
@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
,如 post here and here on the CacheControlFilter
but it does not seem to work. I have also seen a similar S.O thread
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");
}
};
但是代码根本没有执行!所以我想在处理完成并且我准备好 return 响应后才在第二个过滤器上操作 Cache-Control headers。
我不确定我也有,做一些清理和设置错误响应 - 把事情搞混了!
@ControllerAdvice
@Slf4j
public class GlobalErrorHandler
更新:请注意,当我的控制器抛出异常或错误时,会调用上面的 GlobalErrorHandler
并在那里执行特殊处理,returning 一个 error response
.但我看到的是 magically
响应已经由过滤器 (CacheControlFilter) 填充了 default
header。所以它最终有点奇怪,我添加了额外的逻辑来更改控件 header 并且我最终得到一个具有相同 header 2 次的响应(1 具有由 CacheControlFilter
然后是我试图在 ControllerAdvice
感谢任何提示或帮助!我正在使用 Spring Boot 2.1.2
和 Undertow
作为我的底层 servlet 容器。
我猜你正在使用 spring-mvc
(正如你在标签中提到的);如果是这样,您可以绑定到 HttpServletResponse
以添加您的 headers。您可以像这样在方法处理程序中执行此操作:
@RestController
class HelloWordController{
@GetMapping("/hello")
public String test(HttpServletResponse response){
response.addHeader("test", "123");
return "hola";
}
}
另一个解决方案(时尚)是return一个ResponseEntity
代替:
@RestController
class HelloWorkController{
@GetMapping("/hello")
public ResponseEntity<String> test(HttpServletResponse response){
return ResponseEntity.status(HttpStatus.OK)
.header("test", "4567")
.body("hello world");
}
}
你提到的link说的无法获取状态码或修改ResponseBodyAdvice
中的headers是不正确的。如果将 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");
}
}
}
有多种方法可以将 return 之前的 HttpServletResponse
更改为 Spring 中的客户端并将响应注入处理程序方法或利用 ControllerAdvice
是有效的解决方案。但是,我不明白你的问题的基本前提是过滤器不能完成这项工作:
I have already 2 OncePerRequestFilter registered, which work fine - but obviously I can not do logic - once the processing is complete.
就修改 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);
}