最近在整合和上传下载相关的工具类,做了个小demo,如图
点击 download链接,程序自动下载使用outputStream write 一个那片海.mp4,这个mp4 大概 44.76MB
代码如下:
private int i = 0;
@RequestMapping("/download")
public void download(HttpServletRequest request,HttpServletResponse response) throws IOException{
i++;
log.info("access i:{}", i);
String pathname = null;
pathname = "D:\\Downloads\\那片海.mp4";
pathname = "C:\\Users\\feilong\\Downloads\\那片海.mp4";
//pathname = "D:\\Downloads\\viewfile.png";
//pathname = "D:\\Downloads\\export-飞天奔月.opml";
//int contentLength = inputStream.available();
File file = new File(pathname);
int contentLength = (int) FileUtil.getFileSize(file);
// 以流的形式下载文件。
InputStream inputStream = new FileInputStream(pathname);
String saveFileName = FileUtil.getFileName(pathname);
download(saveFileName, inputStream, contentLength, request, response);
}
普通的IE浏览器下载,不会出现问题,但是如果是迅雷下载,或者是 360极速浏览器开了 “使用迅雷下载加速模块”功能
日志里面瞬间就会出现 10个相同的请求
17:59:45 INFO (DownloadController.java:74) download() - access i:7
17:59:45 INFO (DownloadController.java:74) download() - access i:8
17:59:45 INFO (DownloadController.java:74) download() - access i:9
17:59:45 DEBUG (Browser.java:85) <init>() - the user-agent:[Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36]
17:59:45 INFO (DownloadController.java:74) download() - access i:10
17:59:45 INFO (DownloadController.java:122) download() - begin download~~,saveFileName:[那片海.mp4],contentLength:[44.76MB]
17:59:45 DEBUG (AbstractHandlerExceptionResolver.java:132) resolveException() - Resolving exception from handler [public void com.feilong.controller.DownloadController.download(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws java.io.IOException]: ClientAbortException: java.net.SocketException: Connection reset by peer: socket write error
17:59:45 DEBUG (AbstractHandlerExceptionResolver.java:132) resolveException() - Resolving exception from handler [public void com.feilong.controller.DownloadController.download(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws java.io.IOException]: ClientAbortException: java.net.SocketException: Software caused connection abort: socket write error
17:59:45 INFO (DownloadController.java:122) download() - begin download~~,saveFileName:[那片海.mp4],contentLength:[44.76MB]
17:59:45 INFO (DownloadController.java:122) download() - begin download~~,saveFileName:[那片海.mp4],contentLength:[44.76MB]
17:59:45 INFO (DownloadController.java:122) download() - begin download~~,saveFileName:[那片海.mp4],contentLength:[44.76MB]
17:59:45 INFO (DownloadController.java:122) download() - begin download~~,saveFileName:[那片海.mp4],contentLength:[44.76MB]
17:59:45 INFO (DownloadController.java:74) download() - access i:11
并且 log中会报 异常
17:59:45 DEBUG (FrameworkServlet.java:976) processRequest() - Could not complete request
org.apache.catalina.connector.ClientAbortException: null
at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:413) ~[tomcat-embed-core-7.0.47.jar:7.0.47]
at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:480) ~[tomcat-embed-core-7.0.47.jar:7.0.47]
at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:359) ~[tomcat-embed-core-7.0.47.jar:7.0.47]
at org.apache.catalina.connector.OutputBuffer.close(OutputBuffer.java:309) ~[tomcat-embed-core-7.0.47.jar:7.0.47]
at org.apache.catalina.connector.CoyoteOutputStream.close(CoyoteOutputStream.java:108) ~[tomcat-embed-core-7.0.47.jar:7.0.47]
at com.feilong.commons.core.io.IOWriteUtil.write(IOWriteUtil.java:154) ~[feilong-core-1.0.8-SNAPSHOT.jar:1.0.8-SNAPSHOT]
at com.feilong.commons.core.io.IOWriteUtil.write(IOWriteUtil.java:97) ~[feilong-core-1.0.8-SNAPSHOT.jar:1.0.8-SNAPSHOT]
at com.feilong.controller.DownloadController.download(DownloadController.java:147) ~[DownloadController.class:na]
at com.feilong.controller.DownloadController.download(DownloadController.java:92) ~[DownloadController.class:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_11]
Caused by: java.net.SocketException: Connection reset by peer: socket write error
at java.net.SocketOutputStream.socketWrite0(Native Method) ~[na:1.8.0_11]
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:109) ~[na:1.8.0_11]
at java.net.SocketOutputStream.write(SocketOutputStream.java:153) ~[na:1.8.0_11]
at org.apache.coyote.http11.InternalOutputBuffer.realWriteBytes(InternalOutputBuffer.java:215) ~[tomcat-embed-core-7.0.47.jar:7.0.47]
at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:480) ~[tomcat-embed-core-7.0.47.jar:7.0.47]
at org.apache.tomcat.util.buf.ByteChunk.append(ByteChunk.java:366) ~[tomcat-embed-core-7.0.47.jar:7.0.47]
at org.apache.coyote.http11.InternalOutputBuffer$OutputStreamOutputBuffer.doWrite(InternalOutputBuffer.java:240) ~[tomcat-embed-core-7.0.47.jar:7.0.47]
at org.apache.coyote.http11.filters.IdentityOutputFilter.doWrite(IdentityOutputFilter.java:84) ~[tomcat-embed-core-7.0.47.jar:7.0.47]
at org.apache.coyote.http11.AbstractOutputBuffer.doWrite(AbstractOutputBuffer.java:192) ~[tomcat-embed-core-7.0.47.jar:7.0.47]
at org.apache.coyote.Response.doWrite(Response.java:517) ~[tomcat-embed-core-7.0.47.jar:7.0.47]
at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:408) ~[tomcat-embed-core-7.0.47.jar:7.0.47]
... 52 common frames omitted
究其原因:
/*
* 在写数据的时候, 对于 ClientAbortException 之类的异常, 是因为客户端取消了下载,而服务器端继续向浏览器写入数据时, 抛出这个异常,这个是正常的。
* 尤其是对于迅雷这种吸血的客户端软件, 明明已经有一个线程在读取
* 如果短时间内没有读取完毕,迅雷会再启第二个、第三个。。。线程来读取相同的字节段,
* 直到有一个线程读取完毕,迅雷会 KILL掉其他正在下载同一字节段的线程, 强行中止字节读出,造成服务器抛 ClientAbortException。
*/
这对项目是不小的一笔开销 (从日志中可以看到,每个相同的请求 都会过 下 Interceptor)
对于这种异常,我们可以在代码中忽略掉,避免 log太多了,代码示例如下:
try{
OutputStream outputStream = response.getOutputStream();
//这种 如果文件一大,很容易内存溢出
//inputStream.read(buffer);
//outputStream = new BufferedOutputStream(response.getOutputStream());
//outputStream.write(buffer);
IOWriteUtil.write(inputStream, outputStream);
if (log.isInfoEnabled()){
Date endDate = new Date();
log.info(
"end download,saveFileName:[{}],contentLength:[{}],time use:[{}]",
saveFileName,
FileUtil.formatSize(contentLength),
DateExtensionUtil.getIntervalForView(beginDate, endDate));
}
}catch (IOException e){
//ClientAbortException: java.net.SocketException: Connection reset by peer: socket write error
final String exceptionName = e.getClass().getName();
if (StringUtil.isContain(exceptionName, "ClientAbortException") || StringUtil.isContain(e.getMessage(), "ClientAbortException")){
log.warn(
"[ClientAbortException],maybe user use Thunder soft or abort client soft download,exceptionName:[{}],exception message:[{}] ,request User-Agent:[{}]",
exceptionName,
e.getMessage(),
RequestUtil.getHeaderUserAgent(request));
}else{
log.error("[download exception],exception name: " + exceptionName, e);
throw e;
}
}
网站是时候禁用迅雷下载了