Springboot应用中过滤器chain.doFilter后设置header无效

Springboot应用中过滤器chain.doFilter后设置header无效

本文是在使用过滤器添加动态header过程中遇到设置header无效,经过研究源码而产生。
因为特殊需求,自定义的header必须在经过Controller处理之后,才能确定,所以不能在请求处理之前设置,必须在请求处理之后。于是出现了这个坑。

springboot版本:2.1.7

在springboot中添加过滤器后,如果需要在过滤器中给response对象添加header,那么一定要在chain.doFilter(request, httpServletResponse);之前添加,在这个一句后面添加将无效。这和过滤器的处理流程以及对header的处理时机有关。

首先过滤器链的处理流程是:进入到一个过滤器的doFitler方法中,处理一些逻辑,然后调用chain.doFilter(request, httpServletResponse);进入到过滤器链的下一个过滤器的doFilter方法中.当在过滤器链上最后一个过滤器的doFilter方法中调用chain.doFilter(request, httpServletResponse);时,将会把请求转发到Servlet中,再分配到对应的Controller的方法中。当从Controller的方法中退出,再回到最后一个过滤器的doFilter方法中之前,就将会把respone对象上的header写入到headerBuffer中。

所以,在chain.doFilter()方法之后,一方面是给response对象设置header不会成功,因为发现response对象的状态已经是commited状态,就不会再写入到headers里,另一方面,即便通过反射的方式写入了,也不会输出给客户端,因为headers已经处理过了。

原理就是上面的这些。后面部分无需再看,且比较乱。

逻辑起始入口在org.apache.coyote.Response的sendHeaders()方法:

public void sendHeaders() {
    action(ActionCode.COMMIT, this);
    setCommitted(true);
}

在action方法中调用了Http11Processor的action方法。追踪下去,最后发现在Http11Processor的prepareResponse方法中,先是取出原始未处理的MimeHeaders对象,也就是保存了所有header信息的对象。然后设置一些其他Header信息。最后把headers信息进行输出到outputBuffer(Http11OutputBuffer类型):

int size = headers.size();
for (int i = 0; i < size; i++) {
    outputBuffer.sendHeader(headers.getName(i), headers.getValue(i));
}
outputBuffer.endHeaders();

Http11OutputBuffer的sendHeaer方法:

public void sendHeader(MessageBytes name, MessageBytes value) {
    write(name);
    headerBuffer.put(Constants.COLON).put(Constants.SP);
    write(value);
    headerBuffer.put(Constants.CR).put(Constants.LF);
}

现在,来看一下在自定义过滤器中doFilter方法中的ServletResponse对象到底是什么:

Springboot应用中过滤器chain.doFilter后设置header无效_第1张图片

最后,再看一下springboot中的过滤器:

Springboot应用中过滤器chain.doFilter后设置header无效_第2张图片

其中myFilter是自定义的过滤器,其他的5个都是springboot自己的过滤器。

还有一个地方可以操作响应对象:拦截器。
那么在拦截器的postHandle和afterCompletion方法中给response对象设置header会生效吗?不会,因为先调用sendHeaders()方法后,才进入到拦截器的这个两个方法,也就是说,headers处理之后,才进入到拦截器的这两个方法。

可能可以通过反射的方式,改写headerBuffer中的内容,但是太麻烦了,也容易引起底层错误。

所以,不做太多折腾,如果要在Filter或者拦截器中给response对象添加header,一定要在过滤器的chain.doFilter或者是拦截器的preHandle中。

经过一顿操作,设置无效的原因找到了,但是问题没解决,因为本人的需求必须在处理请求之后。在Controller的方法中处理也是不行的,因为此时拿不到某些容器添加到Header,而这些header是在最后要写入到outputBuffer对象时,容器才生产那些header。要想解决自己的需求,尝试过使用AOP,拦截outputBuffer的commit操作。结果是不行。所以,目前自己的这个需求,没法解决。

番外

headers被保存到outputBuffer对象中后,是什么时候从outputBuffer这个NIO缓冲区被读取出去的呢?通过查找outputBuffer被使用的地方,定位到Http11Processor类中outputBuffer对象的commit方法。outputBuffer是Http11OutputBuffer类型。

把断点下在Http11Processor类prepareResponse方法的outputBuffer.commit();这一行。

Springboot应用中过滤器chain.doFilter后设置header无效_第3张图片

此时的position是419,这标识了缓冲区中有效数据的位置。

在commit方法中,缓冲区的数据被写入到了NioSocketWrapper对象中:

socketWrapper.write(isBlocking(), headerBuffer);

你可能感兴趣的:(springboot,Java)