1 此类核心续传servlet2 |||||||||||||||||||
3
4
5 importjava.io.BufferedOutputStream;6 importjava.io.File;7 importjava.io.IOException;8 importjava.io.OutputStream;9 importjava.io.RandomAccessFile;10 importjava.util.concurrent.ConcurrentSkipListMap;11 importjavax.servlet.ServletException;12 importjavax.servlet.http.HttpServlet;13 importjavax.servlet.http.HttpServletRequest;14 importjavax.servlet.http.HttpServletResponse;15 importorg.apache.commons.logging.Log;16 importorg.apache.commons.logging.LogFactory;17 importorg.springframework.util.Assert;18
19 //HTTP 断点续传 demo(客户端测试工具:快车、迅雷)
20 public class MyHttpDownloadServlet extendsHttpServlet {21 private static final long serialVersionUID = 1L;22 final static Log log = LogFactory.getLog(ArcSyncHttpDownloadServlet. class);23
24 @Override25 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throwsServletException, IOException {26 this.doPost(req, resp);27 }28
29 @Override30 protected voiddoPost(HttpServletRequest request, HttpServletResponse response) {31 String name = request. getParameter("filename");32
33 try{34 log.info( "请求下载的连接地址为:" + request.getRequestURL() + "?" +request.getQueryString());35 Assert. hasText(name);36 } catch(IllegalArgumentException e) {37 log.error( "请求下载的文件名参数为空!");38 return;39 }40 String path = "D:/Install/" +name;41 log.info( "文件拼装路径:" +path);42 File downloadFile = newFile(path);43 if(downloadFile.exists()) {44 if(downloadFile.isFile()) {45 if (downloadFile.length() > 0) {46 } else{47 log.info( "请求下载的文件是一个空文件");48 return;49 }50 if (!downloadFile.canRead()) {51 log.info( "请求下载的文件不是一个可读的文件");52 return;53 } else{54 }55 } else{56 log.info( "请求下载的文件是一个文件夹");57 return;58 }59 } else{60 log.info( "请求下载的文件不存在!");61 return;62 }63
64 long fileLength = downloadFile.length(); //记录文件大小
65 long pastLength = 0; //记录已下载文件大小
66 int rangeSwitch = 0; //0:从头开始的全文下载;1:从某字节开始的下载(bytes=27000-);2:从某字节开始到某字节结束的下载(bytes=27000-39000)
67 long toLength = 0; //记录客户端需要下载的字节段的最后一个字节偏移量(比如bytes=27000-39000,则这个值是为39000)
68 long contentLength = 0; //客户端请求的字节总量
69 String rangeBytes = ""; //记录客户端传来的形如“bytes=27000-”或者“bytes=27000-39000”的内容
70 RandomAccessFile raf = null; //负责读取数据
71 OutputStream os = null; //写出数据
72 OutputStream out = null; //缓冲
73 byte b[] = new byte[1024]; //暂存容器
74
75 if (request.getHeader( "Range") != null) { //客户端请求的下载的文件块的开始字节
76 response.setStatus(javax.servlet.http.HttpServletResponse. SC_PARTIAL_CONTENT);77 log.info( "request.getHeader(\"Range\")=" + request.getHeader("Range"));78 rangeBytes = request.getHeader("Range" ).replaceAll("bytes=" , "");79 if (rangeBytes.indexOf( '-') == rangeBytes.length() - 1) {//bytes=969998336-
80 rangeSwitch = 1;81 rangeBytes = rangeBytes.substring(0, rangeBytes.indexOf('-'));82 pastLength =Long.parseLong(rangeBytes.trim());83 contentLength = fileLength - pastLength; //客户端请求的是 969998336 之后的字节
84 } else { //bytes=1275856879-1275877358
85 rangeSwitch = 2;86 String temp0 = rangeBytes.substring(0, rangeBytes.indexOf('-'));87 String temp2 = rangeBytes.substring(rangeBytes.indexOf('-' ) + 1, rangeBytes.length());88 pastLength = Long. parseLong(temp0.trim()); //bytes=1275856879-1275877358,从第 1275856879 个字节开始下载
89 toLength = Long. parseLong(temp2); //bytes=1275856879-1275877358,到第 1275877358 个字节结束
90 contentLength = toLength - pastLength; //客户端请求的是 1275856879-1275877358 之间的字节
91 }92 } else { //从开始进行下载
93 contentLength = fileLength; //客户端要求全文下载
94 }95
96 /**
97 * 如果设设置了Content -Length,则客户端会自动进行多线程下载。如果不希望支持多线程,则不要设置这个参数。 响应的格式是: Content - Length: [文件的总大小] - [客户端请求的下载的文件块的开始字节] ServletActionContext.getResponse().setHeader("Content- Length", new Long(file.length() - p).toString());98 */
99 response.reset(); //告诉客户端允许断点续传多线程连接下载,响应的格式是:Accept-Ranges: bytes
100 response.setHeader( "Accept-Ranges", "bytes" );//如果是第一次下,还没有断点续传,状态是默认的 200,无需显式设置;响应的格式是:HTTP/1.1 200 OK
101 if (pastLength != 0) {102 //不是从最开始下载,103 //响应的格式是:104 //Content-Range: bytes [文件块的开始字节]-[文件的总大小 - 1]/[文件的总大小]
105 log.info( "----------------------------不是从开始进行下载!服务器即将开始断点续传...");106 switch(rangeSwitch) {107 case 1: { //针对 bytes=27000- 的请求
108 String contentRange = new StringBuffer("bytes ").append(new Long(pastLength).toString()).append("-" ).append(new Long(fileLength - 1).toString()).append("/" ).append(newLong(fileLength).toString()).toString();109 response.setHeader( "Content-Range", contentRange);110 break;111 }112 case 2: { //针对 bytes=27000-39000 的请求
113 String contentRange = rangeBytes + "/" + newLong(fileLength).toString();114 response.setHeader( "Content-Range", contentRange);115 break;116 }117 default: {118 break;119 }120 }121 } else{122 //是从开始下载
123 log.info( "----------------------------是从开始进行下载!");124 }125
126 try{127 response.addHeader( "Content-Disposition", "attachment; filename=\"" + downloadFile.getName() + "\"");128 response.setContentType(CommonUtil. setContentType(downloadFile.getName())); //set the MIME type.
129 response.addHeader( "Content-Length", String.valueOf(contentLength));130 os =response.getOutputStream();131 out = newBufferedOutputStream(os);132 raf = new RandomAccessFile(downloadFile, "r");133 try{134 switch(rangeSwitch) {135 case 0: { //普通下载,或者从头开始的下载136 //同1
137 }138 case 1: { //针对 bytes=27000- 的请求
139 raf.seek(pastLength); //形如 bytes=969998336- 的客户端请求,跳过 969998336 个字节
140 int n = 0;141 while ((n = raf.read(b, 0, 1024)) != -1) {142 out.write(b, 0, n);143 }144 break;145 }146 case 2: { //针对 bytes=27000-39000 的请求
147 raf.seek(pastLength); //形如 bytes=1275856879-1275877358 的客户端请求,找到第 1275856879 个字节
148 int n = 0;149 long readLength = 0; //记录已读字节数
150 while (readLength <= contentLength - 1024) {//大部分字节在这里读取
151 n = raf.read(b, 0, 1024);152 readLength += 1024;153 out.write(b, 0, n);154 }155 if (readLength <= contentLength) { //余下的不足 1024 个字节在这里读取
156 n = raf.read(b, 0, ( int) (contentLength -readLength));157 out.write(b, 0, n);158 }159 break;160 }161 default: {162 break;163 }164 }165 out.flush();166 log.info( "------------------------------下载结束");167 } catch(IOException ie) {168 /**
169 * 在写数据的时候, 对于 ClientAbortException 之类的异常, 是因为客户端取消了下载,而服务器端继续向浏览器写入数据时, 抛出这个异常,这个是正常的。 尤其是对于迅雷这种吸血的客户端软件, 明明已经有一个线程在读取 bytes=1275856879-1275877358, 如果短时间内没有读取完毕,迅雷会再启第二个、第三个。。。线程来读取相同的字节段, 直到有一个线程读取完毕,迅雷会 KILL 掉其他正在下载同一字节段的线程, 强行中止字节读出,造成服务器抛 ClientAbortException。 所以,我们忽略这种异常170 */
171 //ignore
172 log.info( "#提醒# 向客户端传输时出现IO异常,但此异常是允许的的,有可能客户端取消了下载,导致此异常,不用关心!");173 }174 } catch(Exception e) {175 log.error(e.getMessage(), e);176 } finally{177 if (out != null) {178 try{179 out.close();180 } catch(IOException e) {181 log.error(e.getMessage(), e);182 }183 }184 if (raf != null) {185 try{186 raf.close();187 } catch(IOException e) {188 log.error(e.getMessage(), e);189 }190 }191 }192 }193 }