ASP.NET WebAPi之斷點續傳下載(上)

前言

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

http協議知識惡補

當請求一個html頁面時我們會看到請求頁面如下:

第一眼看到上面Accept中的參數時我是懵逼的,之前也就看看緩存cookie等常見的頭信息,于是借此機會也學習下這部分內容。

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

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

q(quality)

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

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

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

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

我們更加期待返回的是audio/basic類型的資源,因為其權重為1大于audio/*類型的資源,若為匹配到則繼續匹配下一個資源,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類型資源,若都不存在,則返回權重為0.8的text/x-dvi,最終還是不存在則返回text/plain。

Accept-Ranges

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

Accept-Ranges: bytes

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

Accept-Ranges: none

Content-Range

當在響應頭中添加接受字節范圍的資源時,此時若客戶端請求資源文件比較大時即只是返回部分數據時,此時則返回狀態碼為206的部分內容,在Content-Range響應頭信息中實時顯示當前數據的進度。比如如下:

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

如果客戶端請求資源到達所給資源的界限此時則返回416的狀態碼。

注意:當請求資源為字節范圍請求時,不要在響應頭中使用?multipart/byteranges?類型的content-type。?

斷點續傳場景

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

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

接下來我們來實現簡單的下載以及斷點續傳下載對比看看效果。?

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

普通下載

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

        //響應的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之前是不是應該首先將文件流放入到緩沖流中然后再返回呢?如下:

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

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

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

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

?

因為未實現斷點續傳,此時我們通過右鍵可以看到無法暫停,如下:

我們繼續往下走,接下來來實現斷點續傳看看:

斷點續傳下載

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

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

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

            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");//告訴客戶端接受資源為字節response.Content.Headers.ContentLength = sourceStream.Length;response.Content.Headers.ContentType = new MediaTypeHeaderValue(MimeType);response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment"){FileName = fileName};}

獲取當前已經下載字節數,接著繼續進行剩下字節下載。

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

剩余字節數下載

        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;}

接下來我們看看演示結果:

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

            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了嗎,為何還出現一個PushStreamContent呢?這又是一個遺留問題!

總結

本節我們講述了在webapi中普通下載以及斷點續傳下載,對于斷點續傳下載當暫停后無法繼續進行下載,暫時還存在一定問題,對于返回的內容既可以為StreamContent,也可以是PushStreamContent,這二者有何區別呢?二者的應用場景是什么呢?這又是一個問題,關于此二者我們下節再講,webapi一個很輕量的服務框架,你值得擁有,see u。

轉載于:https://www.cnblogs.com/CreateMyself/p/6063646.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/373451.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/373451.shtml
英文地址,請注明出處:http://en.pswp.cn/news/373451.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

貪吃蛇(C++實現,VC6.0編譯,使用了EasyX圖形庫)

程序效果&#xff1a; 代碼&#xff1a; //main.cpp 1 #include <iostream>2 #include<fstream>3 #include <graphics.h>4 #include <conio.h>5 #include<ctime>6 #include<windows.h>7 #include<mmsystem.h>8 #pragma comment(lib…

3.0 C++遠征:is a

4-4is_a 0.派生類Soldier繼承自基類Person //Person.h class Person { public:Person(string name "Jim");~Person();void play(); protected:string m_strName; };//Soldier.h class Soldier : public Person { public:Soldier(string name "James", in…

python中sorted的用法append_python sorted()排序詳解

排序&#xff0c;在編程中經常遇到的算法&#xff0c;我也在幾篇文章中介紹了一些關于排序的算法。有的高級語言內置了一些排序函數。本文講述Python在這方面的工作。供使用內置函數sorted()/list.sort()的使用簡單應用python對list有一個內置函數&#xff1a;>>> a[5…

云上的播放框架變得簡單:Openshift模塊

僅僅幾年前&#xff0c;找到一個負擔得起的Java Web應用程序托管解決方案是一項艱巨的任務&#xff0c;而尋找免費的托管解決方案是一項不可能的任務。 更不用說甚至考慮自動縮放&#xff0c;單命令部署&#xff0c;持續集成等事情&#xff0c;這都是科幻小說。 去年見證了云計…

C#中的yield return與Unity中的Coroutine(協程)(下)

Unity中的Coroutine&#xff08;協程&#xff09; 估計熟悉Unity的人看過或者用過StartCoroutine() 假設我們在場景中有一個UGUI組件&#xff0c; Image&#xff1a; 將以下代碼綁定到Image 1 using UnityEngine;2 using System.Collections;3 using System.Threading;4 using …

字節流轉化為文件流_C#文件轉換為字節流及字節流轉換為文件

本文講解了C#實現文件轉換為字節流的方法。文件轉換為字節流的步驟如下1、通過文件流打開指定文件(FileStream fs)&#xff1b;2、定義字節流(byte[] fileBytenew byte[fs.Length])&#xff1b;3、把文件讀取到字節流(fs.Read(fileByte,0,fileByte.Length))&#xff1b;4、關閉…

Spring和JSF集成:導航

我希望這是有關我在Spring和JavaServer Faces之間提供深度集成的努力的一系列博客中的第一篇。 這里提到的所有內容都是“正在進行中的工作”&#xff0c;因此&#xff0c;如果您簽出代碼&#xff0c;請注意它是一個不斷變化的目標。 期待一些粗糙的邊緣&#xff0c;如果有時會…

【CSS3動畫】transform對文字及圖片的旋轉、縮放、傾斜和移動

前言&#xff1a;之前我有寫過CSS3的transform這一這特性&#xff0c;對于它的用法&#xff0c;還不是很透徹&#xff0c;今天補充補充&#xff0c;呵呵 你懂的&#xff0c;小司機準備開車了。 a)再提一提transform的四個屬性 ①旋轉--->rotate(參數a)&#xff0c;單位deg&a…

宏的用法與簡介

預處理指令&#xff1a;例如&#xff1a;#include<stdio.h> #include<stdlib.h> #define MAX 20 ............. 因為他們由預處理器解釋的&#xff0c;所以稱作預處理指令。預處理器讀取源代碼&#xff0c;然后對其修改&#xff0c;并把修改過的…

django 日志寫入mysql_如何將django orm模型 寫入數據庫

1、指定連接pymysql(python3.x)先配置_init_.pyimport pymysqlpymysql.install_as_MySQLdb()2、配置連接mysql文件信息settings.pyDATABASES {default: {ENGINE: django.db.backends.mysql,NAME: django_orm, #你的數據庫名稱USER: root, #你的數據庫用戶名PASSWORD: , #你的數…

ORM的問題第2部分–查詢

在我以前關于對象關系映射工具&#xff08;ORM&#xff09;的帖子中&#xff0c;我討論了在處理當今常見的ORM&#xff08;包括Hibernate&#xff09;時遇到的各種問題。 其中包括與從POJO生成架構有關的問題&#xff0c;實際性能和不斷出現的維護問題。 本質上&#xff0c;結論…

【轉】如何減少接口響應時間

Premature optimization is the root of all evil. — Donald Knuth 對于程序優化&#xff0c;我一直采取保守的態度&#xff0c;除非萬不得已。但是隨著業務的不斷發展&#xff0c;程序越來越復雜&#xff0c;代碼越寫越多&#xff0c;優化似乎是終有一天會到來的事情。 那么對…

數據庫行轉列在現實需求中的用法

select t.客戶姓名,sum(case when t.收款類型首款 then t.金額 else 0 end as 首款),sum(case when t.收款類型尾款 then t.金額 else 0 end as 尾款) from table t group by t.客戶姓名 這段sql的意思 是 查詢出所有客戶收款信息 然后按客戶分組 分組后 然后將這個客戶的所…

mysql生產環境加索引_【生產篇】_MySQL環境下如何查看基于表的索引定義

【引言】今天中午項目組來一需求&#xff0c;欲在MySQL環境的某張表下創建幾個BTREE索引。要創建索引&#xff0c;首先需要了解基表的表結構&#xff0c;以及已經包含的索引。Oracle的表結構大家都很熟悉&#xff0c;但MySQL表結構和已創建索引的查看怎么操作&#xff0c;本文將…

Hadoop模式介紹-獨立,偽分布式,分布式

了解了什么是Hadoop之后&#xff0c;讓我們在單機上啟動Hadoop&#xff1a; 這篇文章包含在ubuntu上安裝Hadoop的說明。 這是Hadoop安裝的快速分步教程。 在這里&#xff0c;您將獲得以獨立模式 &#xff08;單節點集群&#xff09;安裝Hadoop所需的所有命令及其說明&#xff0…

apk反編譯方式

一、Apk反編譯得到Java源代碼 下載上述反編譯工具包&#xff0c;打開apk2java目錄下的dex2jar-0.0.9.9文件夾&#xff0c;內含apk反編譯成java源碼工具&#xff0c;以及源碼查看工具。 apk反編譯工具dex2jar&#xff0c;是將apk中的classes.dex轉化成jar文件 源碼查看工具jdgui…

優化Hibernate所鼓勵的7大措施

優化Hibernate所鼓勵的7大措施&#xff1a; 1.盡量使用many-to-one&#xff0c;避免使用單項one-to-many2.靈活使用單向one-to-many3.不用一對一&#xff0c;使用多對一代替一對一4.配置對象緩存&#xff0c;不使用集合緩存5.一對多使用Bag 多對一使用Set6.繼承使用顯示多態 HQ…

如何用c 控制mysql數據庫_用C語言操作MySQL數據庫

函數描述mysql_affected_rows()返回上次UPDATE、DELETE或INSERT查詢更改&#xff0f;刪除&#xff0f;插入的行數。mysql_autocommit()切換autocommit模式&#xff0c;ON/OFFmysql_change_user()更改打開連接上的用戶和數據庫。mysql_charset_name()返回用于連接的默認字符集的…

數據結構(RMQ):POJ 3624 Balanced Lineup

Balanced LineupDescription For the daily milking, Farmer Johns N cows (1 ≤ N ≤ 50,000) always line up in the same order. One day Farmer John decides to organize a game of Ultimate Frisbee with some of the cows. To keep things simple, he will take a conti…

Apache Thrift快速入門教程

Thrift是一種跨語言RPC框架&#xff0c;最初是在Facebook上開發的&#xff0c;現在作為Apache項目開源。 這篇文章將描述如何以不同的模式&#xff08;例如阻塞&#xff0c;非阻塞和異步&#xff09;編寫Thrift服務和客戶端。 &#xff08;我覺得后兩種模式的文檔較少&#xff…