zoukankan      html  css  js  c++  java
  • 再遇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。

    如下图示:

  • 相关阅读:
    50.Ext_数字输入框_Ext.form.NumberField
    49.Ext.form.TextField()基本用法
    48.EXt.Data.JsonReader()
    47. Ext.form.Field.prototype.msgTarget
    46. Ext中namespace的作用(转)
    45. ExtJS ComboBox 下拉列表详细用法
    44. Ext信息提示对话框
    43. ExtJs控件属性配置详细
    42.extjs Combobox动态加载数据问题,mode:local 还是remote
    堆叠顺序
  • 原文地址:https://www.cnblogs.com/buguge/p/14924197.html
Copyright © 2011-2022 走看看