我需要在每个响应中添加标头。我打算做下面的事情
public class MyFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
filterChain.doFilter(request, response);
response.addHeader("Access-Control-Allow-Origin", "*");
}
}
我想在
filterChain.doFilter(request, response)
之后执行此操作,以便一旦控制器处理它,我只需在返回之前添加标头
给客户。正确吗?
但是按照如何编写响应过滤器?
当
回来后,再做什么都晚了 响应。此时,整个响应已发送至 客户端和您的代码无法访问它。chain.doFilter
以上说法在我看来并不正确。我不能在
filterChain.doFilter(request, response)
之后添加标题吗?如果不是为什么?
我正在使用 spring mvc。
在调用
filterChain.doFilter
后,对响应进行任何操作都为时已晚。此时,整个响应已经发送给客户端。
您需要在自己的类中构建一个包装响应,将这些包装器传递到
doFilter
方法并处理包装器中的任何处理。
已经有一个响应包装器:
HttpServletResponseWrapper
,您可以扩展它。例如:
public class MyResponseRequestWrapper extends HttpServletResponseWrapper{
public MyResponseRequestWrapper(HttpServletResponse response) {
super(response);
}
}
您的过滤器:
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HttpServletResponse myResponse = (HttpServletResponse) response;
MyResponseRequestWrapper responseWrapper = new MyResponseRequestWrapper(myResponse);
responseWrapper.addHeader("Access-Control-Allow-Origin", "*");
filterChain.doFilter(request, myResponse);
}
我在我的项目中使用 Spring 3.0.x:
public class MyFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException
{
response.addHeader("headerName", "headerValue");
filterChain.doFilter(request, response);
}
}
工作正常。
修改响应的过滤器通常必须在响应返回给客户端之前捕获响应。为此,您需要将一个替代流传递给生成响应的 servlet。替代流可防止 servlet 在完成时关闭原始响应流,并允许过滤器修改 servlet 的响应。
为了将此替代流传递给 servlet,过滤器创建一个响应包装器,该包装器重写 getWriter 或 getOutputStream 方法以返回此替代流。包装器被传递给过滤器链的 doFilter 方法。包装器方法默认调用包装的请求或响应对象。这种方法遵循设计模式中描述的众所周知的包装器或装饰器模式,
这有点晚了,但下面的内容可能会对一些人有所帮助 因此,如果您确实想将值附加到现有标头,或向现有标头添加新值,最好的方法是编写包装器并在包装器中设置值。
然后将响应链接到过滤器中
HttpServletResponse response = (HttpServletResponse) servletResponse;
ByteArrayPrinter pw = new ByteArrayPrinter();
// Create a wrapper
HttpServletResponse wrappedResp = new HttpServletResponseWrapper(response) {
@Override
public void setContentType(final String type) {
super.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
}
@Override
public PrintWriter getWriter() {
return pw.getWriter();
}
// set the outputstream content type to JSON
@Override
public ServletOutputStream getOutputStream() throws IOException {
ServletResponse response = this.getResponse();
String ct = (response != null) ? response.getContentType() : null;
if (ct != null && ct.contains(APPLICATION_XHTML)) {
response.setContentType(ct + AppConstants.CONSTANT_COMMA + MediaType.APPLICATION_JSON_UTF8_VALUE);
}
return pw.getStream();
}
};
chain.doFilter(httpRequest, wrappedResp);
这是 ByteArrayPrinter.java
public class ByteArrayPrinter {
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
private PrintWriter pw = new PrintWriter(baos);
private ServletOutputStream sos = new ByteArrayServletStream(baos);
public PrintWriter getWriter() {
return pw;
}
public ServletOutputStream getStream() {
return sos;
}
byte[] toByteArray() {
return baos.toByteArray();
}
}
这是 ByteArrayServletOutputStream
public class ByteArrayServletStream extends ServletOutputStream {
ByteArrayOutputStream baos;
ByteArrayServletStream(ByteArrayOutputStream baos) {
this.baos = baos;
}
@Override
public void write(int param) throws IOException {
baos.write(param);
}
@Override
public boolean isReady() {
// TODO Auto-generated method stub
return false;
}
@Override
public void setWriteListener(WriteListener listener) {
// TODO Auto-generated method stub
}
}
TL;博士
我很可能错过了一些明显的东西,但 Spring Boot 2.x 和 Spring Boot 3.x 之间的行为似乎有所不同
通过扩展
OncePerRequestFilter
添加自定义标头
顺便说明一下,
HttpServletRequest
和 HttpServletResponse
的位置在 2.x 和 3.x 之间发生了变化
(参见示例为什么 spring-boot-3 给出 javax.servlet.http.HttpServletRequest ClassNotFoundException)
详情
Spring Boot 2.7.14
过滤器类添加自定义标头。不添加自定义标头。
package com.example.controller;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; // notice the package
import javax.servlet.http.HttpServletResponse; // notice the package
import java.io.IOException;
@Component
public class KeepAliveTimeoutFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
logger.info("Adding custom header"); // to confirm we're executing this
response.addHeader("Foo", "Bar");
filterChain.doFilter(request, response);
}
}
Spring Boot 3.3.1
过滤器类添加自定义标头。添加自定义标头。
package com.example.controller;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; // notice the package
import jakarta.servlet.http.HttpServletResponse; // notice the package
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class KeepAliveTimeoutFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
logger.info("Adding custom header"); // to confirm we're executing this
response.addHeader("Foo", "Bar");
filterChain.doFilter(request, response);
}
}