再遇org.apache.catalina.connector.ClientAbortException: java.net.SocketException: 断开的管道 (Write failed)
优付商户平台“付款记录”页面,商户操作员点击“下载结算凭证”按钮,系统会将所选条件的交易的回单文件以zip包的形式返回给浏览器页面。
由于程序涉及到复杂计算,同时涉及到读库、网络、磁盘IO,耗时比较长。为了防止重复请求,今天,我用redis分布式锁做了防重复提交控制。
@RequestMapping(value = "/downLoadBill") public void downLoadBill(HttpServletRequest request, HttpServletResponse response) throws Exception { UserVO userVO=(UserVO) request.getSession().getAttribute("userVO"); log.info("==MERCHANT==结算凭证下载,执行开始==企业id={}", userVO.getMERID()); response.setCharacterEncoding(Constant.CHARSET); String lockKey="downLoadBill:"+userVO.getMERID(); String lockValue = UUID.randomUUID().toString(); boolean getLock = JedisUtils.tryGetDistributedLock(lockKey, lockValue,15000); if (!getLock) { log.info("结算凭证下载中,请勿重复提交"); response.getWriter().write("您似乎进行了重复提交操作。请重新发起请求,因数据量大,希望您耐心等待系统响应!"); return; } .... OutputStream responseStream = new BufferedOutputStream(response.getOutputStream()); response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment;filename=" + new String(file.getName().getBytes("UTF-8"),"ISO-8859-1")); responseStream.write(buffer); responseStream.flush(); .... }
那么, 当商户操作人员在页面重复点击时,页面交互如下:
页面交互倒是OK,不过呢,通过监控运行日志,发现程序有报异常:org.apache.catalina.connector.ClientAbortException: java.net.SocketException: 断开的管道 (Write failed)
同样,查看浏览器的网络请求,也发现,重复点击调用了两次接口,不过,第一次的直接爆红,第二次的正常响应。
如下是往response写入字节流的代码
try (BufferedInputStream fis = new BufferedInputStream(new FileInputStream(file.getPath())); OutputStream responseStream = new BufferedOutputStream(response.getOutputStream())) { // 以流的形式下载文件。 byte[] buffer = new byte[fis.available()]; fis.read(buffer); fis.close(); // 清空response response.reset(); response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment;filename=" + new String(file.getName().getBytes("UTF-8"), "ISO-8859-1")); responseStream.write(buffer); responseStream.flush(); responseStream.close(); } catch (IOException ex) { ex.printStackTrace(); }
如下是两次请求的log:
2021-06-23 18:05:46.966 [ INFO] [downLoadBill_1624442477031S903] [com.yft.controller.SettleController:2125] ==MERCHANT==结算凭证下载,执行开始==企业id=89900000222116027420 2021-06-23 18:05:47.001(Timestamp), com.yft.service.impl.TPlatOrderServiceImpl(String), selectPlatOrderPage(String), selectPlatOrderPage(String), 192.168.40.69(String) 2021-06-23 18:05:47.008 [ INFO] [downLoadBill_1624442477031S903] [com.yft.controller.SettleController:2199] ====MERCHANT==结算凭证下载,要下载的结算凭证共有{}==19 2021-06-23 18:05:47.013 [ INFO] [downLoadBill_1624442477031S903] [com.yft.controller.SettleController:2202] ====MERCHANT==结算凭证下载,定义临时文件夹==/home/zipTempPath/89900000222116027420/20210623/ 2021-06-23 18:05:47.014 [ INFO] [downLoadBill_1624442477031S903] [com.yft.controller.SettleController:2208] ====MERCHANT==结算凭证下载,循环处理订单platOrder.orderid:2021061014474300562655,获取结算凭证==/home/zipTempPath/89900000222116027420/20210623/20210610_2021061014474300562655.pdf 2021-06-23 18:05:47.043 [ INFO] [downLoadBill_1624442477021S655] [com.yft.controller.SettleController:2125] ==MERCHANT==结算凭证下载,执行开始==企业id=89900000222116027420 2021-06-23 18:05:47.044 [ INFO] [downLoadBill_1624442477021S655] [com.yft.controller.SettleController:2132] 结算凭证下载中,请勿重复提交 2021-06-23 18:05:47.109 [ INFO] [downLoadBill_1624442477031S903] [com.yft.controller.SettleController:2208] ====MERCHANT==结算凭证下载,循环处理订单platOrder.orderid:2021061014474300562654,获取结算凭证==/home/zipTempPath/89900000222116027420/20210623/20210610_2021061014474300562654.pdf 2021-06-23 18:05:47.117 [ INFO] [downLoadBill_1624442477031S903] [com.yft.controller.SettleController:2208] ====MERCHANT==结算凭证下载,循环处理订单platOrder.orderid:2021061014093100562651,获取结算凭证==/home/zipTempPath/89900000222116027420/20210623/20210610_2021061014093100562651.pdf 2021-06-23 18:05:47.124 [ INFO] [downLoadBill_1624442477031S903] [com.yft.controller.SettleController:2208] ====MERCHANT==结算凭证下载,循环处理订单platOrder.orderid:2021060718400100562574,获取结算凭证==/home/zipTempPath/89900000222116027420/20210623/20210607_2021060718400100562574.pdf .... 2021-06-23 18:05:47.649 [ INFO] [downLoadBill_1624442477031S903] [com.yft.controller.SettleController:2208] ====MERCHANT==结算凭证下载,循环处理订单platOrder.orderid:2021060718380400562573,获取结算凭证==/home/zipTempPath/89900000222116027420/20210623/20210607_2021060718380400562573.pdf 2021-06-23 18:05:47.656 [ INFO] [downLoadBill_1624442477031S903] [com.yft.controller.SettleController:2225] ====MERCHANT==结算凭证下载,定义zip压缩文件路径==20210623180547.zip org.apache.catalina.connector.ClientAbortException: java.net.SocketException: 断开的管道 (Write failed) at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:410) at org.apache.tomcat.util.buf.ByteChunk.append(ByteChunk.java:352) at org.apache.catalina.connector.OutputBuffer.writeBytes(OutputBuffer.java:435) at org.apache.catalina.connector.OutputBuffer.write(OutputBuffer.java:423) at org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:91) at org.springframework.security.web.context.SaveContextOnUpdateOrErrorResponseWrapper$SaveContextServletOutputStream.write(SaveContextOnUpdateOrErrorResponseWrapper.java:457) at java.io.BufferedOutputStream.write(BufferedOutputStream.java:122) at java.io.FilterOutputStream.write(FilterOutputStream.java:97) at com.yft.util.DownloadFileUtil.downloadFile(DownloadFileUtil.java:99) at com.yft.controller.SettleController.downLoadBill(SettleController.java:2236) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 。。。 at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:318) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748) Suppressed: org.apache.catalina.connector.ClientAbortException: java.net.SocketException: 断开的管道 (Write failed) at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:370) at org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:334) at org.apache.catalina.connector.CoyoteOutputStream.flush(CoyoteOutputStream.java:101) at org.springframework.security.web.context.SaveContextOnUpdateOrErrorResponseWrapper$SaveContextServletOutputStream.flush(SaveContextOnUpdateOrErrorResponseWrapper.java:376) at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:141) at java.io.FilterOutputStream.close(FilterOutputStream.java:158) at com.yft.util.DownloadFileUtil.downloadFile(DownloadFileUtil.java:105) ... 77 more Caused by: java.net.SocketException: 断开的管道 (Write failed) at java.net.SocketOutputStream.socketWrite0(Native Method) at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:111) at java.net.SocketOutputStream.write(SocketOutputStream.java:155) at org.apache.coyote.http11.InternalOutputBuffer.realWriteBytes(InternalOutputBuffer.java:216) at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:442) at org.apache.coyote.http11.InternalOutputBuffer.flush(InternalOutputBuffer.java:120) at org.apache.coyote.http11.AbstractHttp11Processor.action(AbstractHttp11Processor.java:849) at org.apache.coyote.Response.action(Response.java:171) at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:366) ... 83 more Caused by: java.net.SocketException: 断开的管道 (Write failed) at java.net.SocketOutputStream.socketWrite0(Native Method) at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:111) at java.net.SocketOutputStream.write(SocketOutputStream.java:155) at org.apache.coyote.http11.InternalOutputBuffer.realWriteBytes(InternalOutputBuffer.java:216) at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:442) at org.apache.tomcat.util.buf.ByteChunk.append(ByteChunk.java:347) at org.apache.coyote.http11.InternalOutputBuffer$OutputStreamOutputBuffer.doWrite(InternalOutputBuffer.java:239) at org.apache.coyote.http11.filters.ChunkedOutputFilter.doWrite(ChunkedOutputFilter.java:119) at org.apache.coyote.http11.AbstractOutputBuffer.doWrite(AbstractOutputBuffer.java:192) at org.apache.coyote.Response.doWrite(Response.java:495) at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:405) ... 85 more 2021-06-23 18:05:48.194 [ INFO] [downLoadBill_1624442477031S903] [com.yft.qrcodeUtil.FileOperateUtil:54] 执行linux命令删除目录,linux cmd=/bin/rm -rf /home/zipTempPath/89900000222116027420/20210623/
如下是谷歌浏览器F12调试器窗口的网络请求截图:
那么,为什么会出现“ClientAbortException: java.net.SocketException: 断开的管道 (Write failed)”异常呢?
原因是:浏览器重复提交时,由于是同步请求,当第二次的请求到达时,浏览器已经关闭了第一次的请求。而此时呢,server端对第一次请求的处理尚未结束(线程仍处于RUNNABLE状态),等到往响应流里写数据时,由于客户端连接已断开,所以出现“断开的管道 (Write failed)”异常,因为是响应异常,故而异常类型是SocketException。
如下图示:
当看到一些不好的代码时,会发现我还算优秀;当看到优秀的代码时,也才意识到持续学习的重要!--buguge
本文来自博客园,转载请注明原文链接:https://www.cnblogs.com/buguge/p/14924197.html