Objective
Have always felt the breakpoint continued to be more mysterious, so I want to go to the end, I do not know where to start, think on the line of writing logic, the results of search, but also to understand the relevant HTTP protocol knowledge, and spent a long time to see the HTTP protocol on the breakpoint continued knowledge, Sometimes find things only when you use to see the relevant content will be mastered more firmly, understand more thoroughly, the following we first to fill in the HTTP protocol interrupt the continuation of the knowledge.
HTTP protocol knowledge of the evil complement
When requesting an HTML page we will see the request page as follows:
When I first saw the parameters in the above accept I was crazy, before I looked at cache cookies and other common head information, so take this opportunity to learn this part of the content.
We know that accept refers to the type of content that the client allows the request to return, so why are there so many parameters? When learning Webapi, we can either return the XML when the server is not filtered, or return the JSON, at which point the text/html does not match, then matches the XML type, then the corresponding format content is returned, so the client accepts so many types of content, And for the service side. There is no specific content response configured, and the most appropriate match is made based on the content of the client settings.
So the question is, what's the top Q?
Q (Quality)
The above gives the client can accept the content type of response, naturally there is the most appropriate match, at this time, the use of q this parameter, I will be Q translated into quality that is the meaning of weight, it should be more appropriate, it is used to indicate that we expect to accept the content of the degree of preference is the weight of the. Its range is 0-1, its default value is 1, which is similar to the quality Inspection Department of the product qualified judgment of a medium. For example, when we need to return to a video resource, our client is set to the following:
accept:audio/*; q=0.2, Audio/basic
At this point we will translate the above translation as follows:
Audio/basic; q=1audio/*; q=0.2
We are more looking forward to returning a resource of type audio/basic because it has a weight of 1 greater than the audio/* type, and if matching to the next resource continues to match, audio/* represents all sub-type resources that belong to the audio type.
Next, let's look at one more example:
Accept:text/plain; q=0.5, Text/html,text/x-dvi; q=0.8, Text/x-c
At this point we can translate it as follows:
Accept:text/html;q=1 or Text/x-c;q=1text/x-dvi; Q=0.8text/plain; q=0.5
tends to return a text/html or text/x-c type resource, if none exists, returns a text/x-dvi with a weight of 0.8, or returns Text/plain if none exists.
Accept-ranges
Adding this field to the response header allows the service side to display an acceptance indicating the scope of the resource. If the server receives a request for a byte-scoped resource, it becomes as follows:
Accept-ranges:bytes
If the server does not accept any range of request resources at this point, the response header is added as follows to tell the client not to send a resource for the scope request:
Accept-ranges:none
Content-range
When you add a resource that accepts a byte range in the response header, when the client requests the resource file to be large, that is, it returns only part of the data, then a portion of the status code of 206 is returned, and the progress of the current data is displayed in real time in the Content-range response header information. such as the following:
Start 500 byte data Content-range:bytes 0-499/1234//the second 500 bytes of data content-range:bytes 500-999/1234// In addition to starting 500 bytes of data content-range:bytes 500-1233/1234//the last 500 bytes of data (indicating that the final transmission of data is complete) content-range:bytes 734-1233/1234
A 416 status code is returned if the client requests a resource to reach the bounds of the given resource.
Note: Do not use the Multipart/byteranges type Content-type in the response header when the request resource is a byte range request .
Breakpoint Continuation scene
When downloading for any other reason at this time download interrupted, then download users can only re-download, such an experience must be more painful, the most annoying is if the user is downloading large files on the mobile side, incredibly download interrupted, and then have to re-download, at this time presumably users will abandon the download. At this point the continuation of the breakpoint was born. The continuation of the breakpoint requires the accept-ranges and content-range to be added to the response header. For example, the following:
HEAD http://localhost/api/files/get?filename=blog_backup.zip user-agent:iishost:localhosthttp/1.1 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:iishost: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
Next we will implement the simple download and the breakpoint continuation download comparison to see the effect.
The WEBAPI provides a series of APIs that are convenient for us to invoke, such as contentdispositionheadervalue to set attachments rather than stitching them in the response header manually in WebForm. And the returned mimetype type mediatypeheadervalue. First, let's look at the most common downloads.
Normal download
The normal download is simply to get the identity of the file and open the downloaded folder, and finally get the file stream back to the response Httpcontent object and set the attachment. We have to look at the following code is relatively simple, this relatively simple download presumably we all must be handy.
The MimeType type of the response is Private Const string MimeType = "Application/octet-stream"; The file that is configured in the configuration file is located in the path Private Const string Appsettingdirpath = "Downloaddir"; Assign the path obtained in the configuration file to this variable private readonly string Dirfilepath; This. Dirfilepath = Configurationmanager.appsettings[appsettingdirpath];
The next step is the most important download logic, as follows:
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; }
So the question is, can we get the file stream into the buffer stream before returning to Httpcontent and then return it? As follows:
var bufferstream = new BufferedStream (fileStream); Response. Content = new Streamcontent (bufferstream);
Do we think it would be better to put the file stream first in the buffer stream? At first I also wanted to, but after verifying that the data found that:
For better performance, there is already buffer logic in the file stream that contains buffered streams, and there is no benefit in the case of using buffered streams to wrap the file stream, and there is no one stream in the. NET framework that needs to use the buffered stream, but However, in one case, the buffer stream is required if we customize the implementation flow and do not implement the buffering logic by default, the data is from: Filestream and BufferedStream
The above is also a rise in knowledge. Go back to our topic, and when we download a file we see the following:
Since the continuation of the breakpoint is not implemented, at this point we can see by right click Cannot pause, as follows:
Let's move on, and then we'll take a look at the breakpoint:
Resume Download
The WEBAPI provides a range property whose return object is Rangeheadervalue with a collection of each scope as follows:
Abstract: // Gets the ranges specified from the System.Net.Http.Headers.RangeHeaderValue // object. //Return Result: // Returns system.collections.generic.icollection<t>. The ranges from the System.Net.Http.Headers.RangeHeaderValue // object. Public icollection<rangeitemheadervalue> Ranges {get;}
This is provided for the use of multi-threaded downloads, where we only implement a range of downloads. We implement the continuation of a breakpoint by judging whether the value of the object is 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");//tells the client to accept the resource as a byte response. Content.Headers.ContentLength = sourcestream.length; Response. Content.Headers.ContentType = new Mediatypeheadervalue (MimeType); Response. Content.Headers.ContentDisposition = new Contentdispositionheadervalue ("attachment") { FileName = FileName }; }
Gets the number of bytes currently downloaded, and proceeds to the remaining byte download.
else { var item = Request.Headers.Range.Ranges.FirstOrDefault (); if (item! = NULL && item. From.hasvalue) { response = this. Getpartialcontent (FileName, item. From.value); } }
Number of bytes remaining download
Private Httpresponsemessage getpartialcontent (string fileName, long partial) {var response = NE W 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; }
Next we look at the results:
From the above demonstration we can see that there has been a continuation of the breakpoint, the browser download manager has a paused button, but when the pause after the subsequent download cannot continue, there are problems here, we will follow up the next section. At the same time when the return httpcontent found that there is actually a return to the httpcontent that is pushstreamcontent, at this time we can download the rest of the bytes to modify the following:
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 = Filestre Am. 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;
As the above is also feasible, return streamcontent not OK, why do you still have a pushstreamcontent? This is again a legacy problem!
Summarize
In this section we describe the normal download in the Webapi and download the breakpoint, for the continuation of the breakpoint download when the pause can not continue to download, there are some problems, for the returned content can be either streamcontent or pushstreamcontent, What is the difference between the two? What is the application scenario? This is another question, and we'll talk about this in the next section, Webapi a very light-weight service framework that you deserve, see U.
NET Webapi of breakpoints continue download 1