Java 實現的斷點下載
阿新 • • 發佈:2019-01-03
該斷點下載可應用於瀏覽器或者迅雷等下載工具的下載,實現方式有多種多樣的,本文只研究了單執行緒的下載,迅雷等下載工具會自動將下載資源分塊並記錄每塊的起始位置,然後根據系統性能,起多執行緒下載。
1. 基本原理
從Request Header的Range資訊裡面獲取已經下載的檔案大小,然後建立response的outputstream 向客戶端(瀏覽器或者迅雷等下載工具)寫,寫的時候又利用header裡面的“Content-Range”, 讓客戶端知道從哪個位置開始寫;
讀取網路資源方面,利用HttpClient模擬request請求,發起post或者get請求,只是這個請求跟一般請求有點不一樣:需要帶上Range資訊,告訴程式該從哪個位置開始讀資料。
2. 需要使用的Java 元件
- HttpServletRequest / Response
- HttpClient
- ServletOutputStream
- BufferedInputStream
3. 程式碼實現
>>>點選這裡下載程式碼/** * @desc 斷點下載工具方法 * @param request * @param response * @param fileLength * @param contentType * @param fileName * @param fileId */ public static void resumeDownload(HttpServletRequest request, HttpServletResponse response, Long fileLength, String contentType, String fileName, String fileId) { ServletOutputStream out = null; response.reset(); // 記錄斷點續傳的開始點 long pos = 0; if (null != request.getHeader("Range")) { response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); try { pos = Long.parseLong(request.getHeader("Range") .replaceAll("bytes=", "").replaceAll("-.*", "")); } catch (NumberFormatException e) { LOGGER.error(e.getMessage(), e); pos = 0; } String contentRange = new StringBuffer("bytes ").append(pos + "") .append("-").append((fileLength.intValue() - 1) + "") .append("/").append(fileLength.intValue() + "").toString(); response.setHeader("Content-Range", contentRange); } response.setHeader("Accept-Ranges", "bytes"); response.setHeader("Content-Length", String.valueOf(fileLength.intValue() - pos)); response.setCharacterEncoding("UTF-8"); response.setContentType(contentType); response.setHeader("Content-disposition", "attachment;filename=\"" + fileName + "\""); try { out = response.getOutputStream(); } catch (IOException e) { LOGGER.error(e.getMessage(), e); } // 斷點下載 CloseableHttpClient httpClient = HttpClients.createDefault(); HttpPost httpPost = new HttpPost(SysConf.getString("fezo.download.url")); List<NameValuePair> nvps = new ArrayList<NameValuePair>(); nvps.add(new BasicNameValuePair(SysConf.getString("fezo.download.param"), fileId)); HttpResponse httpResponse = null; BufferedInputStream input = null; try { httpPost.setEntity(new UrlEncodedFormEntity(nvps)); httpPost.setHeader("Range", "bytes=" + pos + "-"); httpResponse = httpClient.execute(httpPost); input = new BufferedInputStream(httpResponse.getEntity().getContent()); byte[] buffer = new byte[CommonConstants.BUFFER_SIZE]; int len = -1; while ((len = input.read(buffer)) != -1) { out.write(buffer, 0, len); } out.flush(); out.close(); input.close(); } catch (UnsupportedEncodingException e) { LOGGER.error(e.getMessage(), e); } catch (ClientProtocolException e) { LOGGER.error(e.getMessage(), e); } catch (IOException e) { // 可以忽略這個異常,有可能是使用者暫停下載,或者迅雷等下載工具分塊下載 } finally { try { if (httpClient != null) httpClient.close(); } catch(IOException e) { LOGGER.error(e.getMessage(), e); } } }
4. 重點與難點
- 獲取response的輸出流程來向客戶端提供下載功能,而不是簡單的把資料寫入到某個具體的檔案,核心程式碼:out = response.getOutputStream();
- 頭資訊裡面"Range" 和 "Conent-Range" 等資訊的處理;
- 迅雷等多執行緒分塊下載客戶端下載的處理:還是要處理好"Range" 和 "Conent-Range" 等頭部資訊,迅雷會自動將檔案內容分塊、記錄起始位置。