asp.net做的音樂網(wǎng)站杭州最專業(yè)的seo公司
什么是斷點續(xù)傳
上圖是我們平時在瀏覽器下載文件的場景,下載的本質(zhì)是數(shù)據(jù)的傳輸。當出現(xiàn)網(wǎng)絡異常,瀏覽器異常,或者文件源的服務器異常,下載都可能會終止。而當異常解除后,重新下載文件,我們希望從上一次下載的位置開始下載,而不是從頭開始下載。這就是斷點續(xù)傳
斷點續(xù)傳的實現(xiàn)
ETag頭部字段
ETag是用來標識文件的頭部字段,由用戶自己設定,其目的是表示文件的唯一性,修改過的文件和原文件是不同的。
ETag由服務端設置
static void download(const httplib::Request& req, httplib::Response& resp)
{//......//服務端設置ETag頭部字段resp.set_header("ETag", "......");//......
}
瀏覽器解析響應,發(fā)現(xiàn)有ETag字段,保存并在下次發(fā)送GET請求時包含。ETag搭配Range字段實現(xiàn)斷點續(xù)傳
Range
當服務端返回的響應中有Accept-Ranges頭部字段,代表服務端允許斷點續(xù)傳
客戶端此時發(fā)送的請求可以攜帶Range頭部字段,形式如下:
//服務端響應設置允許斷點續(xù)傳
static void download(const httplib::Request& req, httplib::Response& resp)
{//......//bytes表示客戶端數(shù)據(jù)請求區(qū)間的單位resp.set_header("Accept-Ranges", "bytes");//......
}//客戶端請求斷點續(xù)傳區(qū)間
static void download(const httplib::Request& req, httplib::Response& resp)
{//......//val是服務端上一次發(fā)送的ETagres.set_header("If-Range", ETag);//val的bytes是服務端返回的斷點區(qū)間的單位//start-end代表重傳start到end區(qū)間的數(shù)據(jù),比如5430-66758res.set_header("Range", "bytes=start-end");//......
}//服務端返回響應
static void download(const httplib::Request& req, httplib::Response& resp)
{//......//響應的文件數(shù)據(jù)范圍是start-end,文件總大小為fsizeresp.set_header("Content-Range, "start-end/fsize");//重新設置ETagresp.set_header("ETag", "......");//......
}
- 服務端設置Accept-Range字段,val值一般為bytes。該字段表示服務端支持斷點續(xù)傳,以字節(jié)單位傳輸數(shù)據(jù)
- ETag表示服務端上某一版本資源的唯一標識,如果資源被改動,則ETag改變??蛻舳耸盏紼Tag表示會保存
當下載中斷時,瀏覽器重新下載文件,則第二次的http請求需要包含If-Range字段和Range字段
- If-Range字段:保存服務端響應的ETag字段的信息。用于服務端判斷是否和上一次請求的資源一致,一致則斷點續(xù)傳,不一致則從頭重新下載
- Range字段:記錄斷點請求的數(shù)據(jù)區(qū)間,bytes start-end,表示請求服務器資源從第start字節(jié)開始到第end個字節(jié)的數(shù)據(jù)
收到客戶端發(fā)送的斷點續(xù)傳的http響應,需要包含Content-Range字段和ETag字段4
- Content-Range: start-end/文件大小,表示http響應包含文件數(shù)據(jù)從start開始到end結(jié)束的文件數(shù)據(jù),文件大小表示文件總大小
- ETag:服務端資源的唯一標識
當服務端返回的數(shù)據(jù)是斷點續(xù)傳的數(shù)據(jù)區(qū)間時,狀態(tài)碼是206
示例斷點續(xù)傳的請求&響應如下:
GET /download/a.txt http/1.1
If-Range: "文件唯一標識"
Range: bytes=89-999
-------------------------------------------
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Range: bytes 89-999/100000
Content-Type: application/octet-stream
ETag: "文件唯一標識"對應文件從89到999字節(jié)的數(shù)據(jù)。
編碼實現(xiàn)
使用httplib實現(xiàn)斷點續(xù)傳。
基本邏輯:
- 查看客戶端請求是否包含If-Range字段,有則匹配請求文件的ETag,沒有則是正常下載文件
- 若相同,說明客戶端請求斷點續(xù)傳,解析Range字段,將start-end的數(shù)據(jù)填入響應的正文部分。并設置頭部字段
- 若不同,說明客戶端請求的數(shù)據(jù)被修改了,則需要從頭下載,返回文件全部數(shù)據(jù)。并設置頭部字段
以上邏輯,我們需要手動解析客戶端響應的Range字段,但httplib已經(jīng)幫我們解析了,以下源碼是httplib做的部分處理
這部分表示,httplib解析客戶端請求,如果包含Range字段,會自動設置響應的狀態(tài)碼為206
這部分是httplib解析Range字段,在返回文件數(shù)據(jù)時,會根據(jù)Range字段的解析結(jié)果,截斷文件數(shù)據(jù)。所以我們在代碼編寫時不管是正常下載,還是斷點續(xù)傳都直接響應文件全部數(shù)據(jù)即可,?如果是斷點續(xù)傳,httplib會幫我們進行數(shù)據(jù)截斷,如果start-end是5430-66347,就截出這部分數(shù)據(jù)返回給客戶端
示例代碼如下:
//生成ETag
//ETag = 文件名 + 文件大小 + 文件最后一次修改時間
static std::string getETag(const std::string& filename, const struct stat st)
{std::string ETag;ETag = filename + std::to_string(st.st_size) + std::to_string(st.m_tim);return ETag;
} //返回客戶端要下載的文件
static void download(const httplib::Request& req, httplib::Response& resp)
{//1. 獲取客戶端請求的資源路徑:req.path。截取最后一個/后的字符串,為文件名//2. 根據(jù)資源路徑,獲取文件信息auto pos = req.path.find_last_of('/');std::string filenameif(pos == std::string::npos)filename = req.path;elsefilename = req.path.substr(pos + 1);struct stat st;stat(filename.c_str(), &st);//查看請求是否有If-Range(記錄之前服務器響應的ETag)bool retrans = true;std::string old_etag;//有If-Range,兩種可能:有修改,全部重傳;沒有修改,斷點續(xù)傳if(req.has_header("If-Range")){old_etag = req.get_header_value("If-Range");if(old_etag == getETag(info, st))retrans = false;}//4. 讀取文件信息到響應中std::ifstream ifs(filename.c_str(), std::ios::binary);//讀取文件內(nèi)容body.resize(st.s_size);ifs.read(&body[0], st.s_size);//5. 設置頭部字段resp.set_header("ETag", getETag(info, st));resp.set_header("Accept-Ranges", "bytes");//提供斷點續(xù)傳resp.set_header("Content-Type", "application/octet-stream");//下載文件if(retrans){//文件有修改,需要重傳文件//ETag Accept-Ranges bytes(斷點續(xù)傳)resp.status = 200;}else{//斷點續(xù)傳實現(xiàn):獲取頭部字段中Range:bytes start-end,解析請求文件的起始和結(jié)束//再返回響應時,對文件內(nèi)容進行截斷//但httplib都實現(xiàn)了,他檢測到req中有Range,然后進行處理,甚至會設置resp的狀態(tài)碼//我們只要返回完整的文件,httplib會對文件進行截斷,最后響應正文中只有start-end的文件內(nèi)容//resp.set_header("Content-Range", "bytes start-end/fsize");//原本還要設置這個頭部字段,httplib幫我們做了resp.status = 206;//斷點續(xù)傳的狀態(tài)碼}
}