震惊!Tomcat的Response对象竟然不能同时使用PrintWriter与SevletOutputStream

帅比萌擦擦* 提交于 2020-11-08 11:10:06

1、背景

前几天项目上线,就是把MVC框架由Spring MVC换成Restlet,然后客户说原有的TXT文件导出功能报500了,而且生产上没有报错日志,而后面更有客户说某些的TXT文件导出功能是可行的。经过场景复现,最终推导出这个TXT功能大文件导出是正常的,而小文件就会失败

针对这种场景,当时就猜测是由于小文件在写入缓冲区后到响应给浏览器时出现了异常,最终经过层层的堆栈分析,发现Restlet框架是用的ServletOutputStream对象去写信息,而TXT导出的功能类用的是PrintWriter对象去写出,而在Response对象的定义中,只能用一种方式去进行写出响应信息。而为什么这样定义,这是因为ServletOutputStream用的是OutputBuffer对象的字节流bb数组,而PrintWriter用的是OutputBuffer对象的cb数组,所以这两种方式不能同时使用。

2、原因查找

2.1 小文件失败

我们先来看Response对象获取PrintWriter与ServletOutputStream的两个方法

获取ServletOutputStream对象,先检查是否已经使用了PrintWriter对象,若使用直接抛出异常

获取PrintWriter对象,先检查是否已经使用了ServletOutputStream对象,若使用直接抛出异常

可以看出,这两个方法是互斥的,同时从上面两个方法可以看出,他们具体的数据处理都委托给了 OutputBuffer对象,具体细节我们可以看看CoyoteOutputStream与CoyoteWriter的write方法。

CoyoteWriter的wrtie方法:

CoyoteWriter的wrtie方法

CoyoteOutputStream的wrtie方法:

CoyoteOutputStream的wrtie方法

这里的ob就是指的OutputBuffer,然后我们追踪到OutputBuffer里,最终发现他对于这两种方式分别用了两个不同的数据来存储

在这里插入图片描述

具体OutputBuffer在写入数据时做了什么处理,我们就不去深究了,至此我们可以清晰的明白,ServletOutputStream与PrintWriter不能同时使用的原因是因为他们所使用的数据缓冲区对象不同。 而在Restlet框架设计中,他是用ServletCall

2.2 大文件可行

出现这个现象是因为OutputBuffer的缓冲区大小问题,若需要写入的数据大于OutputBuffer的缓冲区大小,那么OutputBuffer会提前把缓冲区内容提前通过SocketOutputStream响应给Client

在write方法里可以看到,他会检查cb缓冲区是否已满,若已满则提前把内容刷出去

在write方法里可以看到,他会检查cb缓冲区是否已满,若已满则提前把内容刷出去。所以当所导出的文件大于缓冲区大小时,就可以正常的导出。

结论

所以在使用Response对象的PrintWriter对象与ServletOutputStream对象时,一定需要注意他们是不能同时使用的,避免此类的bug发生

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!