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.

using the Static File Middleware to return partial HTTP response to client

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.

using the FileStreamResult to return partial http response

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.