More Fun with the Range Header

1 minute read

In my previous post about codeless APIs I made use the HTTP Range header to download only a portion of the file from a web server or cache (byte serving).

Because I was working in client side Javascript, I didn’t provide a Ruby example, which would look like:

require 'uri'
require 'net/http'

uri = URI("http://example.com/file-I-want")
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Get.new(uri.path)
request['Range'] = 'bytes=64-1024'
response = http.request(request)

For bonus points, response.code == 206 tells you that you got your “Partial Content”. A code of 200 would tell you that the Range was ignored and you got the whole file.

There a couple of other features of the Range header that are worth mentioning…

Multibyte

In my code I only requested a single byte range. However, the protocol also allows for multiple ranges to be requested at one time:

Range: bytes=500-1000,1500-2000

We’ll put this to use in a future episode. For now, know that Multibyte comes with a caveat, the standards recommend that servers ignore clients that request overlapping or out of order ranges:

Range: bytes=500-1000,900-1500
Range: bytes=500-1000,200-300

These are signs of, at best, a broken client, at worse, a DoS attack. Don’t be a broke client, don’t be ignored.

Rest of the File

You can requests to download everything from a byte offset to the end of a the file by leaving the ending offset off of the request.

Range: bytes=1000-

This can be used to resume a download. For example, if I started to download big-file.tar.gz but was interrupted, I could pull just the remainder down with:

start = File.size? 'big-file.tar.gz'
uri = URI('http://example.com/big-file.tar.gz')
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Get.new(uri.path)
request['Range'] = "bytes=#{start}"-
response = http.request(request)

File.open('big-file.tar.gz', 'a') {|f| f.write(response.body) }

(You might want to be smarter about memory and download it in chunks, but that’s left as an exercise for the reader.)

Backwards!

Last and likely least (the above exclamation mark is a bit gratuitous), you can read the last N bytes of a file by requesting a negative offset:

Range: bytes=-42

Honestly, I’ve never come up with a use case for that.

There you have it. The Range header is powerful, yet under utilized, tool. What will you do with it?

Comments