Home | Created on - November 2005
Has this ever happened to you (I bet the answer is "Yes!")? You are
downloading a really large file. When it is almost complete, there is a glitch
of some sort, and the download aborts. Then you have to go back and start from
the beginning — annoying enough on a high-speed Internet connection and really
maddening for the many people who still use a modem and phone line.
But
sometimes an interrupted download does not resume at the beginning; rather, it
picks up where it left off, turning a major hassle into a minor inconvenience.
This is a great feature, one you should implement on your Web site if you make
large files available for download.
The most common way to provide a file
for download is to put it on your Web site and insert a standard hyperlink to
the file on one of your pages. When the user clicks a link to a file whose type
is not recognized by the browser, such as a ZIP file, they are given the option
of downloading it. Simple and effective, right? Well, simple to be sure, but
resumability is not supported with this technique.
ASP.Net developers
have gotten more sophisticated with file downloads, abandoning the default link
as described above for use of the Response.WriteFile() and
Response.End() methods. This permits you to create an ASPX page for
downloads and provides the ability to require a login and also to generate the
file on the fly, so that all your files for download do not clog up your server.
But WriteFile() can be a memory hog to boot, reading the entire
file into memory before sending it to the client. This was addressed with a
hotfix, part of .Net Framework 1.1 Service Pack. And still, there is no
resumability.
The solution lies in two seemingly unrelated features. I learned about the first
in a KnowledgeBase article that dealt with the memory hogging problems
of the original WriteFile(). The solution involved performing the
download in sections rather than all at once. The file to be downloaded is
broken into more manageable chunks; before each is sent, the server uses the
Response.IsClientConnected property to verify that the client is
still connected and available to receive more data. Only then is the next chunk
sent.
From the perspective of the server, this provides two advantages when and if
the connection is broken:
For example, the headers returned by IIS to a client at the start of a resumable download might look like this:
HTTP/1.1 200 OK Connection: close Date: Wed, 7 Sep 2005 21:10:19 GMT Accept-Ranges: bytes Last-Modified: Sun, 4 Sep 2005 05:34:51 GMT ETag: "21accd5ffa11d90:3099" Cache-Control: private Content-Type: application/x-zip-compressed Content-Length: 6511789
Having received these headers, Internet Explorer knows that the server supports resuming downloads. Then, if the download is interrupted, IE sends the ETag value and a range value (which indicates how much has been downloaded so far) back to the server to try to resume the download. Here's an example of some of the headers that would be sent in this situation:
GET http://204.181.112.112/data.zip HTTP/1.0 Range: bytes=1254660- Unless-Modified-Since: Wed, 7 Sep 2005 21:10:19 GMT If-Range: "21accd5ffa11d90:3099"
You can see that these headers include the URL of the file that was being downloaded as well as the date and time the original download was commenced. The server uses this value to determine if the file has changed and, if so, begins a new download. If the file has not changed, IIS resumes the download with a response that includes headers such as these:
HTTP/1.1 206 Partial Content Content-Range: bytes 1254660-6511788/6511789 Accept-Ranges: bytes Last-Modified: Wed, 7 Sep 2005 21:10:19 GMT ETag: "21accd5ffa11d90:3099" Cache-Control: private Content-Type: application/x-zip-compressed Content-Length: 5257329
Note that the Content-Length value now specifies the remaining bytes to be downloaded. In other words, the total file size minus the Range value that IE sent to the server when the download was interrupted.