1. 程式人生 > >Java 實現的斷點下載

Java 實現的斷點下載

該斷點下載可應用於瀏覽器或者迅雷等下載工具的下載,實現方式有多種多樣的,本文只研究了單執行緒的下載,迅雷等下載工具會自動將下載資源分塊並記錄每塊的起始位置,然後根據系統性能,起多執行緒下載。

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" 等頭部資訊,迅雷會自動將檔案內容分塊、記錄起始位置。