再遇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。

如下圖示:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM