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已经处理过了。

导致 response 状态变为 committed 的原因:

send***这类方法:向客户端发送状态码或重定向会直接提交响应。

刷新缓存:当response对象缓存区满时,或者使用response对象的flushbuffer方法会刷新response对象的缓存导致响应提交。

转发:将未提交的response通过forward转发可能会在转发目标的处理流程内被提交(include转发不会)。

forward指令和include指令很相似,它们都采用方法来导入目标。

执行forward指令时,response必须未提交,目标获得的response与原Servlet中同一个(ResponseFacade对象)。原先存放在response对象中的内容将会自动被清除,目标可以直接发出响应,之后程序流程回到原Servlet转发处继续执行,但是原Servlet似乎连页面内容都不可输出了。

而执行include指令时,目标获得的response与原Servlet中不是同一个(被换成了一个ApplicationHttpResponse对象,让目标无法对源请求做出实质响应,但是该对象进行提交操作后会导致原Servlet中的response对象也变为已提交,但仍然可以进行页面输出)。原Servlet把目标产生的响应的内容部分包含到自身响应的内容中,目标改变响应消息的状态码和响应头的语句执行结果将被忽略(即被调用的Servlet的响应只有内容部分会并入原Servlet的响应的内容部分中)。

关于forward和include的详细分析不在此处深究。

对于当前页面中已经committed(提交)的response:

就不能再使用这个response向缓冲区写任何东西  。(原文这里可能有错误

不可以再进行send***这类发送响应内容的操作(因为响应已经提交给客户端),

可以使用set***这类设置响应内容的函数(设置后无效,因为响应已经提交给客户端),

实测可以继续进行页面内容的输出(--此处存疑--不能理解--实测(执行response.getWriter().close()后会导致后续输出无效,但不会爆异常)),

实测可以进行request.getRequestDispatcher(“”).include(request, response);,

实测不可以进行request.getRequestDispatcher(“”).forward(request, response);(会抛出IllegalStateException异常Cannot forward after response has been committed),

(注:以为JSP中,response是一个JSP页面的内置对象,所以同一个页面中的response.XXX()是同一个response的不同方法,只要其中一个已经导致了committed,那么其它类似方式的调用都会导致 IllegalStateException异常)。

源码展示

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

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

 

参考:

https://blog.csdn.net/woslx/article/details/100540958

https://www.cnblogs.com/Leroscox/p/8305141.html

 

posted @ 2022-11-23 20:08  Boblim  阅读(1194)  评论(0编辑  收藏  举报