使用 Struts 2 <result type="stream"> 實現文件下載時,用戶在瀏覽器下載對話框點擊「取消」,伺服器拋出 java.lang.IllegalStateException,堆疊追蹤指向 ResponseFacade.sendError()、Dispatcher.serviceAction()、FilterDispatcher.doFilter() 等。
StreamResult 執行流程:配置參數 → 取得輸入流 → 建立輸出流(Socket 連線) → 傳輸資料Response 取得輸出流送錯誤頁面 → 發現流已被佔用/未正確關閉 → 拋出 IllegalStateException<global-results>
<result name="client-abort-exception">/ignored.jsp</result>
</global-results>
<exception-mapping result="client-abort-exception" exception="org.apache.catalina.connector.ClientAbortException"/>
缺點:僅吞掉異常,底層 Socket/流資源仍可能洩漏,治標不治本。
提供一個 4KB 的插件 struts2-sunspoter-stream-1.0.jar,核心類 com.sunspoter.lib.web.struts2.dispatcher.StreamResultX,在用戶取消時主動關閉輸入/輸出流並釋放 Socket。
struts2-sunspoter-stream-1.0.jarWEB-INF/libstruts.xml 註冊新 result type 並替換原 stream<package name="default" namespace="/" extends="struts-default">
<result-types>
<result-type name="streamx" class="com.sunspoter.lib.web.struts2.dispatcher.StreamResultX"/>
</result-types>
<action name="download" class="com.DownloadAction">
<result name="success" type="streamx">
<param name="inputName">download</param>
<param name="bufferSize">4096</param>
<param name="contentDisposition">filename="${filename}"</param>
<param name="contentCharSet">UTF-8</param>
</result>
</action>
</package>
只需將 type="stream" 改為 type="streamx",其他參數完全相容。
啟用 log4j 後,取消下載僅輸出警告,不再拋出異常:
21:23:44,676 WARN StreamResult:45 - StreamResultX Warn : socket write error
含義:Socket 非正常中斷,但流已正確關閉,資源無洩漏。
StreamResult,重寫 execute() 增加 try-finally 確保流關閉ClientAbortException / SocketException 靜默處理附件:struts2-sunspoter-stream-1.0.jar 或訪問 官方下載頁(無需積分)。
Struts 2.0.x ~ 2.5.x,已驗證於 Tomcat 6/7/8、Jetty、JBoss、WebLogic。