Long gone are the days when internet speed was a problem for most technology workers. Anecdotally, I downloaded a video for this post, misplaced it, and rather than search my local machine, I decided to download it again. It’s a perk of living in the future. While consumers of the internet may take bandwidth and data transfer for granted, developers might want to consider what repeated complete downloads will do to budget expenditure and user experience.
In this post, we’ll see three ways to process HTTP range requests, what benefits we’ll see from using them, and how they work in our web browser.
Let’s get started.
The Complete Download Problem
The internet’s evolution has brought us to a point where we commonly transmit gigabytes of information from a server to a client. Platforms like Netflix, HBO Max, and TikTok will stream video files as their core experience. While most consumers might watch a video from start to finish, interruptions do happen, having to pause or abandon a viewing session.
How do we resume a file from a particular point?
Well, there’s a solution for this problem directly in the HTTP specification. It’s called HTTP range requests.
What’s an HTTP Range Request?
**HTTP range requests allow a client to ask the server to send a portion of a response to the client. ** Partial HTTP range requests are most beneficial when used with large file downloads and have the added benefit of supporting pause and resume functionality on the client.
To initiate a partial range request, the client must initiate a request to the server and will potentially receive two headers: Accept-Ranges
and Content-Length
. If the server supports partial range requests, the value for Accept-Ranges
will be bytes
; otherwise, the value will be none
. The Content-Length
is the total file size the client can expect when retrieving the resource.
HTTP/1.1 200 OK
...
Accept-Ranges: bytes
Content-Length: 100000
In some cases, the server might support HEAD
requests, allowing us to receive only the resource’s header information without transmitting the entire payload.
Assuming the server can handle partial range requests, we can now add a Range
header to our HTTP request with the format of bytes=[start]-[end]
.
GET /puppies.mp4 HTTP/1.1
Host: localhost
Range: bytes=0-100
When successful, we’ll receive an HTTP status code of 206 Partial Content
along with additional headers of Content-Range
and Content-Length
.
HTTP/1.1 206 Partial Content
Content-Range: bytes 0-100/100000
Content-Length: 100
...
(binary content)
It is important to note that Content-Length
is the response payload’s length, not the total file size. To retrieve the entire file size, we can parse the Content-Range
header and use the value after the /
.
In general, we can expect three different status code responses from a server:
-
206 Partial Content
: The server successfully handled a partial request and sending a partial payload. -
416 Requested Range Not Satisfiable
: Something is wrong with the partial request, likely out of bounds, and the response reflects a failure. -
200 OK
: No support for partial requests and the server is returning the complete resource.
To read a more complete details about HTTP Range requests, I recommend reading the Mozilla documentation.
Great, so how do we use Partial request requests and responses with ASP.NET Core?
Static File Middleware and Partial Requests
The first and most straightforward way of using HTTP range requests is to let ASP.NET Core do all the work. The StaticFileMiddleware
component of ASP.NET Core supports partial responses. Adding files into our wwwroot
folder will automatically allow ASP.NET Core and the server to return larger media files in parts.
In our Startup
class, we need to add the StaticFileMiddleware
registration in our Configure
method.
app.UseStaticFiles();
Within our wwwroot
folder, we’ll drop a video.mp4
file, which will allow the server to send the file to a client.
Finally, within a Razor pages files, we can add a video
tag that will retrieve our video file and render it to the browser.
<div>
<h2>Static</h2>
<video
src="video.mp4"
width="400"
controls>
</video>
</div>
Let’s see what happens in our web browser developer tools.
As we can see, we see a status code of 206 Partial Content
, along with headers for Accept-Ranges
and Content-Range
. In this particular example, the byte range is small that the payload returned is the complete video.
ASP.NET Core MVC and Partial Requests
Sometimes we need to hide files behind a layer of business logic, and the StaticFileMiddleware
might not be the right fit for our problem. Luckily for ASP.NET Core MVC developers, this functionality is exposed through the FileStreamResult
type, which implements the IActionResult
interface. The FileStreamResult
type can take any stream and attempt to handle an HTTP range request.
In this example, we’ll use the same video file but create a readable stream to pass as a parameter to the Controller’s File
method.
public class VideoController : Controller
{
private readonly IWebHostEnvironment environment;
public VideoController(IWebHostEnvironment environment)
{
this.environment = environment;
}
// GET
[HttpGet, Route("videos/video.mp4")]
public IActionResult Index()
{
var video = environment
.WebRootFileProvider
.GetFileInfo("video.mp4")
.CreateReadStream();
return File(video, "video/mp4", enableRangeProcessing: true);
}
}
We’ll also notice we need to set the optional parameter of enableRangeProcessing
to true
to get the expected functionality.
Let’s update the Razor page to call the controller action.
<div>
<h2>Partial</h2>
<video
src="@Url.Action("Index", "Video")"
width="400" controls>
</video>
</div>
Let’s see what’s happening in our browser’s development tools.
Similar to the StaticFileMiddleware
, we’ll see all the hallmarks of a range request and a partial response.
We have the added benefit of supporting all streams, not just streams reading from a local disk.
Conclusion
The HTTP range request happens transparently for most browser users, but knowing that partial responses are possible opens a world of new pause/resume features for developers building native clients. Luckily, we can continue to use ASP.NET Core as a proxy to large media files while getting the benefits of this particular HTTP specification feature.
If we need a different behavior from ASP.NET Core, we can access the header values directly from the HttpRequest
class and create our responses according to our needs.
It is important to note that the underlying host server, IIS and Kestrel, must have support for this feature; otherwise, it won’t work.
I hope you enjoyed this blog post and let me know on Twitter, @buhakmeh, if you use this particular feature and how.
As always, thanks for reading.