在过滤器中添加响应头?

25

我需要在每个响应中添加页眉。我计划执行以下操作:

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。


1
@fps HandlerInterceptor 不允许在 postHandle 中修改响应,该方法会在控制器处理请求后触发。OP(以及我自己)希望在响应发送到客户端之前添加一个响应头。 - shashwat
@fps,即使在这种情况下,我们希望在请求通过控制器后添加标题的过滤器中,我也无法使其正常工作。我使用了implements ResponseBodyAdvice<Object>中的beforeBodyWrite方法。 - shashwat
@shashwat 那个被接受的答案怎么样了?我真的不知道有什么注意事项,我还没有回答。 - fps
@shashwat 或许这个答案会有所帮助。 - fps
4个回答

28

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

4
为什么在你的示例中要使用包装器(wrapper),而不是直接向容器传递原始实现并添加标题?我认为只有在实现某些逻辑以确保您的标题不会在过滤器链的某个地方被删除/覆盖时,包装器才有意义。 - troy
2
@Luan - 在你上面的回答中,你应该将responseWrapper传递给filterChain.doFilter吗? - Harshad Vyawahare
2
谢谢你的回答,Luan!在我的情况下,我只想为404类型的响应设置标题,所以根据答案中提到的,我在我的包装类中添加了以下代码:@Override public void sendError(int sc) throws IOException { if (sc == 404) { this.setHeader("X-ServiceFabric", "ResourceNotFound"); } super.sendError(sc); } - Harshad Vyawahare
5
为什么要进行不必要的类型转换?为什么需要额外的变量myResponse?为什么要使用包装器?这些都可以通过以下简单的代码实现:response.addHeader("headerName", "headerValue"); filterChain.doFilter(request, response);请注意,此代码块没有修改原意。 - David Balažic

5

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

运作良好。


请提供一些关于如何/在哪里使用“doFilterInternal”的额外提示。它是自动使用的,是OncePerRequestFilter接口的一部分。 - David Balažic
它会在请求发送到控制器之前还是之后添加头部? - shashwat
@shashwat 之前 - David Balažic

2

来自Java EE教程

修改响应的过滤器通常需要在返回给客户端之前捕获响应。为此,您可以向生成响应的Servlet传递一个替代流。该替代流可以防止Servlet在完成后关闭原始响应流,并允许过滤器修改Servlet的响应。

为了将这个替代流传递给Servlet,过滤器创建了一个响应包装器,覆盖了getWriter或getOutputStream方法以返回该替代流。该包装器被传递到过滤器链的doFilter方法中。包装器方法默认调用包装的请求或响应对象。这种方法遵循了《设计模式》中描述的著名的Wrapper或Decorator模式。


0
这可能有点晚了,但以下内容可能会对一些人有所帮助。如果您真的想将值附加到现有标头,或向现有标头添加新值,则最好的方法是编写一个包装器并在包装器中设置该值。
然后在过滤器中链接响应。
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

    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接