日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

ASP.NET WebAPi之断点续传下载(上)

發(fā)布時間:2023/12/1 asp.net 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ASP.NET WebAPi之断点续传下载(上) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

前言

之前一直感覺斷點(diǎn)續(xù)傳比較神秘,于是想去一探究竟,不知從何入手,以為就寫寫邏輯就行,結(jié)果搜索一番,還得了解相關(guān)http協(xié)議知識,又花了許久功夫去看http協(xié)議中有關(guān)斷點(diǎn)續(xù)傳知識,有時候發(fā)覺東西只有當(dāng)你用到再去看相關(guān)內(nèi)容時才會掌握的更加牢固,理解的更加透徹吧,下面我們首先來補(bǔ)補(bǔ)關(guān)于http協(xié)議中斷點(diǎn)續(xù)傳的知識。

http協(xié)議知識惡補(bǔ)

當(dāng)請求一個html頁面時我們會看到請求頁面如下:

第一眼看到上面Accept中的參數(shù)時我是懵逼的,之前也就看看緩存cookie等常見的頭信息,于是借此機(jī)會也學(xué)習(xí)下這部分內(nèi)容。

我們知道Accept是指客戶端允許請求返回的內(nèi)容類型,那為何這里面參數(shù)有如此之多呢?在學(xué)習(xí)WebAPi時,我們在服務(wù)端未進(jìn)行過濾時既可以返回xml,也可以返回json,此時如上圖一樣,text/html未匹配上,接著匹配xml類型,匹配后則進(jìn)行相應(yīng)格式內(nèi)容返回,所以客戶端接受如此多類型內(nèi)容,也是為了服務(wù)端那邊未設(shè)置特定內(nèi)容響應(yīng),此時則根據(jù)客戶端設(shè)置的內(nèi)容進(jìn)行最合適的匹配。

那么問題來了,上面的q是啥玩意?

q(quality)

上面給出了客戶端能夠接受響應(yīng)的內(nèi)容類型,自然就有最合適的匹配,此時就用到了q這個參數(shù),在此我將q翻譯為quality即權(quán)重的意思,應(yīng)該是比較合適的,它用來表示我們期待接受內(nèi)容偏愛的程度即所占的權(quán)重。它的范圍是0-1,其默認(rèn)值為1,這就類似質(zhì)檢部門對產(chǎn)品合格判斷的一種介質(zhì)。例如當(dāng)我們需要返回視頻資源時,我們客戶端設(shè)置為如下:

Accept: audio/*; q=0.2, audio/basic

此時我們將上述翻譯如下:

audio/basic; q=1 audio/*; q=0.2

我們更加期待返回的是audio/basic類型的資源,因為其權(quán)重為1大于audio/*類型的資源,若為匹配到則繼續(xù)匹配下一個資源,audio/*則表示屬于audio類型的所有子類型資源。

接下來,我們再來看一個例子:

Accept: text/plain; q=0.5, text/html,text/x-dvi; q=0.8, text/x-c

此時我們則可以翻譯為如下:

Accept: text/html;q=1或者 text/x-c;q=1 text/x-dvi; q=0.8 text/plain; q=0.5

傾向于返回text/html或者text/x-c類型資源,若都不存在,則返回權(quán)重為0.8的text/x-dvi,最終還是不存在則返回text/plain。

Accept-Ranges

在響應(yīng)頭中添加此字段允許服務(wù)端來顯示表明對資源范圍的接受。如果服務(wù)端接受一個字節(jié)范圍的資源的請求則此時變成如下:

Accept-Ranges: bytes

如果服務(wù)端不接受任何范圍的請求資源此時則在響應(yīng)頭添加如下來告訴客戶端不要發(fā)送范圍請求的資源:

Accept-Ranges: none

Content-Range

當(dāng)在響應(yīng)頭中添加接受字節(jié)范圍的資源時,此時若客戶端請求資源文件比較大時即只是返回部分?jǐn)?shù)據(jù)時,此時則返回狀態(tài)碼為206的部分內(nèi)容,在Content-Range響應(yīng)頭信息中實時顯示當(dāng)前數(shù)據(jù)的進(jìn)度。比如如下:

//開始500個字節(jié)數(shù)據(jù) Content-Range: bytes 0-499/1234//第二個500個字節(jié)數(shù)據(jù) Content-Range: bytes 500-999/1234//除了開始500個字節(jié)之外的數(shù)據(jù) Content-Range: bytes 500-1233/1234//最后500個字節(jié)數(shù)據(jù)(表示數(shù)據(jù)最終傳輸完畢) Content-Range: bytes 734-1233/1234

如果客戶端請求資源到達(dá)所給資源的界限此時則返回416的狀態(tài)碼。

注意:當(dāng)請求資源為字節(jié)范圍請求時,不要在響應(yīng)頭中使用?multipart/byteranges?類型的content-type。?

斷點(diǎn)續(xù)傳場景

當(dāng)正在下載時出于其他任何原因此時下載中斷,那么下載用戶只能重新下載,這樣的體驗想必是比較痛苦的,最煩躁的是如果用戶是在移動端下載大文件時,居然下載中斷了,接下來又得重新下載,此時想必用戶會放棄下載。此時斷點(diǎn)續(xù)傳則應(yīng)運(yùn)而生。 斷點(diǎn)續(xù)傳則需要用到上述Accept-Ranges和Content-Range將其添加到響應(yīng)頭中。例如如下:

HEAD http://localhost/api/files/get?filename=blog_backup.zip User-Agent: IIS Host: localhostHTTP/1.1 200 OK Content-Length: 1182367743 Content-Type: application/octet-stream Accept-Ranges: bytes Server: Microsoft-IIS/10.0 Content-Disposition: attachment; filename=blog_backup.zip HEAD http://localhost/api/files/get?filename=blog_backup.zip User-Agent: IIS Host: localhost Range: bytes=0-999HTTP/1.1 206 Partial Content Content-Length: 1000 Content-Type: application/octet-stream Content-Range: bytes 0-999/1182367743 Accept-Ranges: bytes Server: Microsoft-IIS/10.0 Content-Disposition: attachment; filename=blog_backup.zip

接下來我們來實現(xiàn)簡單的下載以及斷點(diǎn)續(xù)傳下載對比看看效果。?

在webapi中提供了一系列方便我們調(diào)用的api,比如?ContentDispositionHeaderValue?來設(shè)置附件而不像在webform中手動在響應(yīng)頭中進(jìn)行拼接。以及返回的MimeType類型?MediaTypeHeaderValue?。首先我們看看最普通的下載。

普通下載

普通的下載無非就是獲取到文件的標(biāo)識再打開下載的文件夾,最后得到文件流返回到響應(yīng)的HttpContent對象中以及設(shè)置附件即可。我們看看如下代碼還是比較簡單的,這種相對比較簡單的下載想必我們大家定是信手拈來。

//響應(yīng)的MimeType類型private const string MimeType = "application/octet-stream";//配置文件中配置的文件所在路徑private const string AppSettingDirPath = "DownloadDir";//將配置文件中取得的路徑賦給此變量private readonly string DirFilePath;this.DirFilePath = ConfigurationManager.AppSettings[AppSettingDirPath];

接下來就是最重要的下載邏輯了,如下:

public HttpResponseMessage Download(string fileName){var fullFilePath = Path.Combine(this.DirFilePath, fileName);if (!File.Exists(fullFilePath)){throw new HttpResponseException(HttpStatusCode.NotFound);}FileStream fileStream = File.Open(fullFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);var response = new HttpResponseMessage();response.Content = new StreamContent(fileStream);response.Content.Headers.ContentDisposition= new ContentDispositionHeaderValue("attachment") { FileName = fileName };response.Content.Headers.ContentType= new MediaTypeHeaderValue(MimeType);response.Content.Headers.ContentLength= fileStream.Length;return response;}

那么問題來了,我們可不可以在獲取文件流返回到HttpContent之前是不是應(yīng)該首先將文件流放入到緩沖流中然后再返回呢?如下:

var bufferStream = new BufferedStream(fileStream);response.Content = new StreamContent(bufferStream);

我們想著是不是將文件流率先放入到緩沖流中效果是否更佳呢?剛開始我也是這樣想來著,但是經(jīng)過查證資料發(fā)現(xiàn):

為了得到更好的性能,在文件流中已經(jīng)包含有緩沖流的緩沖邏輯,對于用緩沖流來包裹文件流的情況沒有任何好處,還有一點(diǎn)就是在.NET Framework中沒有任何一個流需要用到緩沖流,但是,但是有一種情況除外則是若我們自定義實現(xiàn)流且默認(rèn)沒有實現(xiàn)緩沖的邏輯情況下需要用到緩沖流,資料來源于:Filestream and BufferedStream

上述也算是漲知識了。繼續(xù)回到我們的話題,此時我們下載一個文件則看到如下圖所示:

?

因為未實現(xiàn)斷點(diǎn)續(xù)傳,此時我們通過右鍵可以看到無法暫停,如下:

我們繼續(xù)往下走,接下來來實現(xiàn)斷點(diǎn)續(xù)傳看看:

斷點(diǎn)續(xù)傳下載

在WebAPi提供了Range屬性其返回對象為?RangeHeaderValue?里面有存在每個范圍的集合如下:

// 摘要: // Gets the ranges specified from the System.Net.Http.Headers.RangeHeaderValue// object.//// 返回結(jié)果: // Returns System.Collections.Generic.ICollection<T>.The ranges from the System.Net.Http.Headers.RangeHeaderValue// object.public ICollection<RangeItemHeaderValue> Ranges { get; }

這是為利用多線程下載而提供,這里我們僅僅實現(xiàn)一個范圍的下載。我們通過判斷這個對象的值是否為null來實現(xiàn)斷點(diǎn)續(xù)傳。

if (Request.Headers.Range == null || Request.Headers.Range.Ranges.Count == 0 || Request.Headers.Range.Ranges.FirstOrDefault().From.Value == 0){var sourceStream = File.Open(fullFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);response = new HttpResponseMessage(HttpStatusCode.OK);response.Content = new StreamContent(sourceStream);response.Headers.AcceptRanges.Add("bytes");//告訴客戶端接受資源為字節(jié)response.Content.Headers.ContentLength = sourceStream.Length;response.Content.Headers.ContentType = new MediaTypeHeaderValue(MimeType);response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment"){FileName = fileName};}

獲取當(dāng)前已經(jīng)下載字節(jié)數(shù),接著繼續(xù)進(jìn)行剩下字節(jié)下載。

else{var item = Request.Headers.Range.Ranges.FirstOrDefault();if (item != null && item.From.HasValue){response = this.GetPartialContent(fileName, item.From.Value);}}

剩余字節(jié)數(shù)下載

private HttpResponseMessage GetPartialContent(string fileName, long partial){var response = new HttpResponseMessage();var fullFilePath = Path.Combine(this.DirFilePath, fileName);FileInfo fileInfo = new FileInfo(fullFilePath);long startByte = partial;var memoryStream = new MemoryStream();var buffer = new byte[65536];using (var fileStream = File.Open(fullFilePath, FileMode.Open, FileAccess.Read, FileShare.Read)){var bytesRead = 0;fileStream.Seek(startByte, SeekOrigin.Begin);int length = Convert.ToInt32((fileInfo.Length - 1) - startByte) + 1;while (length > 0 && bytesRead > 0){bytesRead = fileStream.Read(buffer, 0, Math.Min(length, buffer.Length));memoryStream.Write(buffer, 0, bytesRead);length -= bytesRead;}response.Content = new StreamContent(memoryStream); }response.Headers.AcceptRanges.Add("bytes");response.StatusCode = HttpStatusCode.PartialContent;response.Content.Headers.ContentType = new MediaTypeHeaderValue(MimeType);response.Content.Headers.ContentLength = File.Open(fullFilePath, FileMode.Open, FileAccess.Read, FileShare.Read).Length;response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment"){FileName = fileName};return response;}

接下來我們看看演示結(jié)果:

從上面演示我們看出目前已經(jīng)實現(xiàn)了斷點(diǎn)續(xù)傳,瀏覽器下載管理器出現(xiàn)了暫停的按鈕,但是當(dāng)暫停后無法繼續(xù)進(jìn)行后續(xù)下載,在這里存在問題,我們下節(jié)再進(jìn)行后續(xù)講解。同時當(dāng)返回HttpContent發(fā)現(xiàn)居然還有一個可以返回的HttpContent即?PushStreamContent?,此時我們可以將剩余部分字節(jié)下載進(jìn)行如下修改:

Action<Stream, HttpContent, TransportContext> pushContentAction = (outputStream, content, context) =>{try{var buffer = new byte[65536];using (var fileStream = File.Open(fullFilePath, FileMode.Open, FileAccess.Read, FileShare.Read)){var bytesRead = 0;fileStream.Seek(startByte, SeekOrigin.Begin);int length = Convert.ToInt32((fileInfo.Length - 1) - startByte) + 1;while (length > 0 && bytesRead > 0){bytesRead = fileStream.Read(buffer, 0, Math.Min(length, buffer.Length));outputStream.Write(buffer, 0, bytesRead);length -= bytesRead;}}}catch (HttpException ex){throw ex;}finally{outputStream.Close();}}; response.Content = new PushStreamContent(pushContentAction, new MediaTypeHeaderValue(MimeType));response.StatusCode = HttpStatusCode.PartialContent;response.Headers.AcceptRanges.Add("bytes");response.Content.Headers.ContentType = new MediaTypeHeaderValue(MimeType);response.Content.Headers.ContentLength = File.Open(fullFilePath, FileMode.Open, FileAccess.Read, FileShare.Read).Length;response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment"){FileName = fileName};return response;

如上所做也可行,返回StreamContent不就ok了嗎,為何還出現(xiàn)一個PushStreamContent呢?這又是一個遺留問題!

總結(jié)

本節(jié)我們講述了在webapi中普通下載以及斷點(diǎn)續(xù)傳下載,對于斷點(diǎn)續(xù)傳下載當(dāng)暫停后無法繼續(xù)進(jìn)行下載,暫時還存在一定問題,對于返回的內(nèi)容既可以為StreamContent,也可以是PushStreamContent,這二者有何區(qū)別呢?二者的應(yīng)用場景是什么呢?這又是一個問題,關(guān)于此二者我們下節(jié)再講,webapi一個很輕量的服務(wù)框架,你值得擁有,see u。

轉(zhuǎn)載于:https://www.cnblogs.com/CreateMyself/p/6063646.html

總結(jié)

以上是生活随笔為你收集整理的ASP.NET WebAPi之断点续传下载(上)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。