檔案下載是WEB網站提供的最基本服務,然而你知道HTTP的斷點續傳是怎麼實現的嗎?
背景
這兩天在實現一個基於HTML5在線音視頻播放,由於文件是存放於企業網盤中的,HTTP不可達,因此需要用程式來實現文件的讀取和HTTP協議的下載。用Java實作文件下載也不用多說了,讀取文件,透過二進位流的方式往response裡寫就行了。 H5播放器調用也能進行播放了;然而當我控制進度的前進和後退時,問題來了,居然一點效果都沒有!沒有快轉播放器還叫播放器嗎?
分析
首先看到播放器無法取得音視頻檔案的時間長度,很自然想到Content-Length屬性,後台透過file.length()取得檔案長度並設定到Content-Length上(代碼如下),前台播放器裡可以顯示音視頻的長度了,並且可以快進了;然而當我快退的時候,還是無效,同時後台報錯。
response.addHeader("Content-Length", file.length());
換了一個HTTP檔進行比較測試,發現直接HTTP存取的能夠正常快轉快退。仔細分析兩者的request和response頭,發現了差異,請求參數 多瞭如下圖所示屬性,該屬性表名需要從服務端獲取的資源範圍,預設從第一個位元組開始取,快進快退實際上就是透過指定這個Range屬性來確定你所期望的起始點。
然而這個屬性是在請求頭上的,客戶端又是怎麼知道要添加這個屬性呢?繼續尋找發現了Accept-Ranges這個屬性,屬性值是bytes,其表示是否接受取得其某個實體的一部分(例如檔案的一部分)的請求。 bytes:表示接受,none:表示不接受。與此對應的response中另外一個屬性Content-Range,其表名該回應所包含的部分物件為整個物件的哪個部分。完整回應頭如下:
解決
根據上面的分析,我們就知道在服務端該怎麼處理了,首先在響應頭上添加Accept-Ranges。
response.setHeader("Accept-Ranges", "bytes");
接著判斷request中是否存在Range屬性,即是否指定的起點,若存在,則透過stream的skip直接跳到目標起點,最後加入Content-Range屬性表名目前區塊的起始和結束,完整程式碼如下:
stream = new FileInputStream(file); if(request.getHeader("Range") != null) //客户端请求的下载的文件块的开始字节 { //从请求中得到开始的字节 //请求的格式是: //Range: bytes=[文件块的开始字节]- String range = StringUtils.substringBetween(request.getHeader("Range"), "bytes=", "-"); long start = Long.parseLong(range); //下载的文件(或块)长度 //响应的格式是: //Content-Length: [文件的总大小] - [客户端请求的下载的文件块的开始字节] response.setHeader("Content-Length", String.valueOf(fileSize - start)); if (start != 0) { //要设置状态 //响应的格式是: //HTTP/1.1 206 Partial Content response.setStatus(javax.servlet.http.HttpServletResponse.SC_PARTIAL_CONTENT);//206 //不是从最开始下载, //响应的格式是: //Content-Range: bytes [文件块的开始字节]-[文件的总大小 - 1]/[文件的总大小] response.setHeader("Content-Range","bytes " + start + "-" + String.valueOf(fileSize - 1) + "/" + String.valueOf(fileSize)); stream.skip(start); } } responseBinaryStream(response, this.getContentType(FilenameUtils.getExtension(fileName)), stream);