How to work with HTTP Range Headers in WebAPI

Range requests enable you to retrieve partial content in lieu of the entire content for improved performance when working with WebAPI over HTTP.

HTTP 1.1 provides support for range headers – it allows you to specify range headers to enable the client to retrieve partial response. This can be used to retrieve partial content when working over HTTP. It's a technique that's widely used when streaming video files, for instance.

With this approach, rather than retrieving the entire data at one go, your application can retrieve the data partially, in chunks. This feature can be particularly helpful in scenarios when the client needs to recover the data from interrupted connections that might occur due to cancelled requests, or connections to the server that have been dropped.

The HTTP 206 Partial Content status code and its associated headers enable the clients or the web browsers to retrieve partial content in lieu of the complete content when a request is sent. Note that most modern-day web servers provide support for range requests. The ByteRangeStreamContent class that has been added to WebAPI makes it easier to work with range requests. The ByteRangeStreamContent class works similarly to how, let's say, StreamContent works – with the exception that it can provide a view over a stream that is seekable. Note that a MemoryStream or a FileStream is seekable, i.e., they can be used to do ranges over them.

Let’s now implement a simple WebAPI with support for HTTP Range Header using the ByteRangeStreamContent class. Here's a quick glance at how this all would work.

  1. The client connects to the server and requests for partial content by specifying the values in the Range header.
  2.  The server responds by sending partial content if range is available with the status code as HTTP 206.
  3. If the requested range is not available, the server responds with HTTP 416.

And, now the implementation.

The following code snippet illustrates a WebAPI controller method with support for range headers. For the sake of simplicity I have hard-coded a string as data that would be retrieved as partial response by the client. You can change this implementation to work with data of much larger sizes, like files, large arrays, lists, etc.

public HttpResponseMessage GetData ()

        {

            try

            {

                MemoryStream memoryStream = new MemoryStream(data);

                if (Request.Headers.Range != null)

                {

                    HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.PartialContent);

                    response.Content = new ByteRangeStreamContent(memoryStream,

                    Request.Headers.Range, MediaTypeHeaderValue.Parse("application/json"));

                    return response;

                }

                else

                {

                    HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);

                    response.Content = new StreamContent(memoryStream);

                    response.Content.Headers.ContentType =  

                    MediaTypeHeaderValue.Parse("application/json");

                    return response;

                }

            }

            catch (Exception ex)

            {

                return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);

            }

        }

Refer to the GetData method shown above. Please note that you should mention the appropriate media type specific to your needs. The GetData method checks if a request for partial content was made and depending on whether partial content was requested for, the content is generated and sent back as response. To be more precise, if the request is a range request, we create a ByteRangeStreamContentRequest and return it as part of the response. If the request is not a range request, an instance of StreamContent is created and returned as part of the response. Note that the response to a byte range request is HTTP 206 (Partial Content). If no range was requested, an HTTP status code of 200 (OK) is sent back as response. Note how the ByteRangeStreamContent class has been used to build the partial response. The constructor of this class accepts three arguments, i.e., the memory stream instance, the range and the media type.

Here’s how the controller looks like. As I mentioned earlier, I’ve used a hardcoded string as data here for demonstration purposes only.

public class DefaultController : ApiController

 { 

        private static readonly byte[] data =

            Encoding.ASCII.GetBytes("This is a test string to demonstrate range requests");

     public HttpResponseMessage GetData()

     {

       //The complete source code of the GetData method is given earlier.

     }

 }

The following code snippet illustrates how we can call the GetData method using HttpClient.

using (var httpclient = new HttpClient())

   {

        httpclient.DefaultRequestHeaders.Range = new RangeHeaderValue(0, 5);

        var response = await httpclient.GetAsync(url, HttpCompletionOption.ResponseContentRead);

        using (var stream = await response.Content.ReadAsStreamAsync())

          {

              var data = new byte[5];

              int count = stream.Read(data, 0, data.Length);

              MemoryStream memoryStream = new MemoryStream(data);

              StreamReader reader = new StreamReader(memoryStream);

              string text = reader.ReadToEnd();

          }

    }

And, that’s it! When the above piece of code is executed, the string variable "text" would contain the first 5 characters of the entire response.

Copyright © 2017 IDG Communications, Inc.