转自:http://blog.sina.com.cn/s/blog_667ac0360102eckm.html
package com.ipan.core.controller.web.result; import java.io.InputStream; import java.io.OutputStream; import java.net.SocketException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.catalina.connector.ClientAbortException; import org.apache.commons.lang.StringUtils; import org.apache.struts2.dispatcher.StreamResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.opensymphony.xwork2.ActionInvocation; /// // 支持断点续传文件输出流 // // 对StreamResult做了增强,支持断点续传方式(多线程)下载同时也支持普通方式(单线程)下载; // // @author iPan // @version 2014-3-19 // public class MulitStreamResult extends StreamResult { private static final long serialVersionUID = -256643510497634924L; protected static Logger LOG = LoggerFactory.getLogger(MulitStreamResult.class); @Override protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception { // Find the Response in context HttpServletResponse response = (HttpServletResponse) invocation.getInvocationContext().get(HTTP_RESPONSE); HttpServletRequest request = (HttpServletRequest) invocation.getInvocationContext().get(HTTP_REQUEST); // Override any parameters using values on the stack resolveParamsFromStack(invocation.getStack(), invocation); String rangeStr = ""; // 范围字符串(比如“bytes=27000-”或者“bytes=27000-39000”的内容) long fromLength = 0; // 起点长度(比如bytes=27000-39000,则这个值为27000) long toLength = 0; // 终点长度(比如bytes=27000-39000,则这个值为39000) long rangeLength = 0; // 响应的字节总量(toLength - fromLength + 1) OutputStream out = null; // 输出流 if (inputStream == null) { // Find the inputstream from the invocation variable stack inputStream = (InputStream) invocation.getStack().findValue(conditionalParse(inputName, invocation)); } if (inputStream == null) { String msg = ("Can not find a java.io.InputStream with the name [" + inputName + "] in the invocation stack. " + "Check the tag specified for this action."); LOG.error(msg); throw new IllegalArgumentException(msg); } if (contentLength == null || contentLength.length() < 1) { throw new IllegalArgumentException("支持断点续传时,[Content-Length]不能为空."); } // Set the content type if (contentCharSet != null && !contentCharSet.equals("")) { response.setContentType(conditionalParse(contentType, invocation) + ";charset=" + contentCharSet); } else { response.setContentType(conditionalParse(contentType, invocation)); } // Set the content-disposition if (contentDisposition != null) { response.addHeader("Content-Disposition", conditionalParse(contentDisposition, invocation)); } // Set the cache control headers if neccessary if (!allowCaching) { response.addHeader("Pragma", "no-cache"); response.addHeader("Cache-Control", "no-cache"); } // Set the content length String _contentLength = conditionalParse(contentLength, invocation); // 文件长度 int fileLength = Integer.parseInt(_contentLength); response.setContentLength(fileLength); // 需要使用断点续传下载 if (isHeadOfRange(request)) { LOG.debug("断点续传下载."); // 设置状态 HTTP/1.1 206 Partial Content response.setStatus(javax.servlet.http.HttpServletResponse.SC_PARTIAL_CONTENT); // 表示使用了断点续传(默认是“none”,可以不指定) response.setHeader("Accept-Ranges", "bytes"); // 设置Content-Range StringBuilder crBuf = new StringBuilder("bytes "); rangeStr = request.getHeader("Range").replaceAll("bytes=", "").trim(); if (rangeStr.endsWith("-")) { LOG.debug("开区间下载."); rangeStr = StringUtils.substringBefore(rangeStr, "-"); fromLength = Long.parseLong(rangeStr); rangeLength = fileLength - fromLength + 1; crBuf.append(rangeStr).append("-").append(fromLength - 1).append("/").append(fileLength); } else { LOG.debug("闭区间下载."); String num1 = StringUtils.substringBefore(rangeStr, "-"); String num2 = StringUtils.substringAfter(rangeStr, "-"); fromLength = Long.parseLong(num1); toLength = Long.parseLong(num2); rangeLength = toLength - fromLength + 1; crBuf.append(rangeStr).append("/").append(fileLength); } // Content-Range: bytes [文件块的开始字节]-[文件的总大小 - 1]/[文件的总大小] response.setHeader("Content-Range", crBuf.toString()); // 普通下载 } else { LOG.debug("普通下载."); // 默认返回 HTTP/1.1 200 OK rangeLength = fileLength; // 客户端要求全文下载 } // 输出文件操作 try { out = response.getOutputStream(); byte[] outBuff = new byte[bufferSize]; int readLen = 0; // 闭区间处理 if (toLength > 0) { LOG.debug("闭区间下载开始..."); inputStream.skip(fromLength); int readBufSize = (int) Math.min(bufferSize, rangeLength); long pFrom = fromLength; while (pFrom < toLength) { readLen = inputStream.read(outBuff, 0, readBufSize); pFrom += readBufSize; readBufSize = (int) Math.min(readBufSize, toLength - pFrom + 1); out.write(outBuff, 0, readLen); } // 开区间处理 } else { LOG.debug("开区间下载开始..."); inputStream.skip(fromLength); while ((readLen = inputStream.read(outBuff, 0, bufferSize)) != -1) { out.write(outBuff, 0, readLen); } } } catch (ClientAbortException e) { // 忽略(迅雷等下载工具,支持多线程下载,但有些线程会被中途取消,导致异常。) // LOG.debug(e.getMessage(), e); } catch (SocketException e) { // 忽略(迅雷等下载工具,支持多线程下载,但有些线程会被中途取消,导致异常。) // LOG.debug(e.getMessage(), e); } catch (Exception e) { // 其他异常记录日志 LOG.error(e.getMessage(), e); } finally { if (inputStream != null) { try { inputStream.close(); } catch (Exception e) { } } if (out != null) { try { out.flush(); } catch (Exception e1) { } try { out.close(); } catch (Exception e) { } } } } private static boolean isHeadOfRange(HttpServletRequest request) { return request.getHeader("Range") != null; } }
个人补充:功能并未验证是否可行。