本文是在使用过滤器添加动态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中的过滤器:
其中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();
这一行。
此时的position是419,这标识了缓冲区中有效数据的位置。
在commit方法中,缓冲区的数据被写入到了NioSocketWrapper对象中:
socketWrapper.write(isBlocking(), headerBuffer);