Springboot 文件下载异常 IOException: UT010029: Stream is closed
今天在测试项目过程中,发现下载文件时日志中出现了一个异常。虽然异常了,但是文件还是可以正常下载的。
文件下载代码如下
@RequestMapping(value = "/info", method = RequestMethod.GET) public HMResponse info(Long id, Long customerId, HttpServletResponse response) { String info = requestInfoService.getInfo(id, customerId); OutputStream out = null; try { out = response.getOutputStream(); response.addHeader("Content-Disposition", "attachment;filename=" + System.currentTimeMillis() + ".txt"); response.setContentType("application/octet-stream"); out.write(info.getBytes()); } catch (Exception e) { logger.error("下载报文", e); } finally { try { if (out != null) { out.close(); } } catch (IOException e) { logger.error("", e); } } return HMResponse.getInstance(); }
异常信息如下
2022-11-10 14:15:43.363 ERROR [501aad039274c7a0 ◊ 501aad039274c7a0] [XNIO-3 task-6] com.platform.common.configuration.exceptio n.AbstractGeneralGlobalExceptionHandler.printErrorLog @38 :-> method:GET, RequestURL:http://111.106.31.81:8080/data-platform/reque stInfo/info?id=10382&customerId=1001, java.io.IOException: UT010029: Stream is closed at io.undertow.servlet.spec.ServletOutputStreamImpl.write(ServletOutputStreamImpl.java:136) ~[undertow-servlet-1.4.20.Fin al.jar!/:1.4.20.Final] at org.springframework.cloud.sleuth.instrument.web.TraceServletOutputStream.write(TraceServletOutputStream.java:120) ~[sp ring-cloud-sleuth-core-1.2.5.RELEASE.jar!/:1.2.5.RELEASE] at java.io.ByteArrayOutputStream.writeTo(ByteArrayOutputStream.java:167) ~[?:1.8.0_60] at com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter.writeInternal(FastJsonHttpMessageConverter.java:337) ~[fastjson-1.2.83.jar!/:?] at org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:227) ~[spring- web-4.3.11.RELEASE.jar!/:4.3.11.RELEASE] at com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter.write(FastJsonHttpMessageConverter.java:246) ~[fastjs on-1.2.83.jar!/:?] at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverte rs(AbstractMessageConverterMethodProcessor.java:231) ~[spring-webmvc-4.3.11.RELEASE.jar!/:4.3.11.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResp onseBodyMethodProcessor.java:174) ~[spring-webmvc-4.3.11.RELEASE.jar!/:4.3.11.RELEASE] at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnV alueHandlerComposite.java:81) ~[spring-web-4.3.11.RELEASE.jar!/:4.3.11.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHa ndlerMethod.java:113) ~[spring-webmvc-4.3.11.RELEASE.jar!/:4.3.11.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingH andlerAdapter.java:827) ~[spring-webmvc-4.3.11.RELEASE.jar!/:4.3.11.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandle rAdapter.java:738) ~[spring-webmvc-4.3.11.RELEASE.jar!/:4.3.11.RELEASE] at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~ [spring-webmvc-4.3.11.RELEASE.jar!/:4.3.11.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967) [spring-webmvc-4.3.11.RELEASE .jar!/:4.3.11.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901) [spring-webmvc-4.3.11.RELEASE. jar!/:4.3.11.RELEASE] at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) [spring-webmvc-4.3.11.RELEA SE.jar!/:4.3.11.RELEASE] at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861) [spring-webmvc-4.3.11.RELEASE.jar!/: 4.3.11.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:687) [javax.servlet-api-3.1.0.jar!/:3.1.0] at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) [spring-webmvc-4.3.11.RELEASE.jar! /:4.3.11.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:790) [javax.servlet-api-3.1.0.jar!/:3.1.0] at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:85) [undertow-servlet-1.4.20.Final.jar!/ :1.4.20.Final] at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129) [undertow-servlet-1.4.20.F inal.jar!/:1.4.20.Final] at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.jav a:55) [spring-boot-1.5.7.RELEASE.jar!/:1.5.7.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.11.RELEASE .jar!/:4.3.11.RELEASE] at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) [undertow-servlet-1.4.20.Final.jar!/:1.4.20.Fin al] at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) [undertow-servlet-1.4.20.F inal.jar!/:1.4.20.Final] at com.github.xiaoymin.swaggerbootstrapui.filter.SecurityBasicAuthFilter.doFilter(SecurityBasicAuthFilter.java:84) [swagg er-bootstrap-ui-1.9.6.jar!/:?] at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) [undertow-servlet-1.4.20.Final.jar!/:1.4.20.Fin al] at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) [undertow-servlet-1.4.20.F inal.jar!/:1.4.20.Final] at com.github.xiaoymin.swaggerbootstrapui.filter.ProductionSecurityFilter.doFilter(ProductionSecurityFilter.java:53) [swa gger-bootstrap-ui-1.9.6.jar!/:?] at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) [undertow-servlet-1.4.20.Final.jar!/:1.4.20.Fin al]
上网查了一下解决方案,有部分的解决方案是,修改全局的异常捕获处理器注解。个人认为这种方式不是从根本上解决问题,我也没有尝试这个方案。因为没有全局异常处理器。该异常也不会存在。
@ExceptionHandler(Exception.class) 改成 @ExceptionHandler(BindException.class)
与是参考了网上的另一个解决方案
从异常日志和 Debug 跟踪,发现异常是从 Spring 框架层抛出的,下载业务逻辑并未出现异常,就分析猜测:问题可能出在 out.close () 上,把流关闭注释掉后就正常了。
后面进一步测试发现,如果要保留关闭流的习惯,下载方法就不要有任何返回值,这样也是可以的。
总结:
1、使用 Spring框架时,在我们的业务代码中处理完业务逻辑后,不要对 HttpServletResponse 对象的 ServletOutputStream 做流关闭处理;
2、使用 Spring 框架时,对于下载文件的方法请不要有任何返回值,因为我们写完业务逻辑后 Spring 框架层还会做一些额外的工作(可能会用到 ServletOutputStream 对象,所以不能关闭)。
最后修改后的代码
@RequestMapping(value = "/info", method = RequestMethod.GET) public HMResponse info(Long id, Long customerId, HttpServletResponse response) { String info = requestInfoService.getInfo(id, customerId); OutputStream out = null; try { out = response.getOutputStream(); response.addHeader("Content-Disposition", "attachment;filename=" + System.currentTimeMillis() + ".txt"); response.setContentType("application/octet-stream"); out.write(info.getBytes()); } catch (Exception e) { log.error("下载报文", e); } return HMResponse.getInstance(); }