28

Is there an existing implementation of HTTP byte ranges in Django? I.e. when the client sends a Range: header, I want my Django app to accept that and reply with a HTTP "206 Partial Content" response.

I could write something from scratch, but surely someone has already done this?

There are various parts to this:

  1. Parsing and sanity checking the Range header
  2. Actually generating the ranges
  3. Support for returning a single range in the response
  4. Support for returning multiple ranges in a single response, appropriately MIME encoded

Surely at least (1) and (4) have library support somewhere?

Mp0int
  • 18,172
  • 15
  • 83
  • 114
user9876
  • 10,954
  • 6
  • 44
  • 66
  • 2
    Here is a link: http://stackoverflow.com/questions/720419/how-can-i-find-out-whether-a-server-supportsthe-range-header – catherine Jan 26 '13 at 01:12
  • look at this question http://stackoverflow.com/questions/4538810/html5-video-element-non-seekable-when-using-django-development-server – Sergey Lyapustin Jan 28 '13 at 10:31
  • http://stackoverflow.com/questions/4538810/html5-video-element-non-seekable-when-using-django-development-server suggests serving files from a normal web server, not Django, if you want to use byte range requests. But my files are dynamically generated, I have to serve them from Django. – user9876 Jan 28 '13 at 12:03
  • 1
    http://stackoverflow.com/questions/720419/how-can-i-find-out-whether-a-server-supports-the-range-header is about checking if a server supports Range, not implementing Range in the server. – user9876 Jan 28 '13 at 12:04
  • 3
    For (1) a (very simple) Google query indicates that the werkzeug libraries contain a Range header parser (as does a library called httpheader). "Actually generating the ranges" is really an application-dependent problem (to avoid it being stunningly inefficient), although you could write either a view decorator or middleware that just excerpts appropriately. For (4), generating multipart/byteranges can be done using email.mime.multipart.MIMEMultipart pretty easily; so to your "surely" question, the answer is "yes".If anyone packages the solution, it'd be great to see. – James Aylett Sep 25 '14 at 15:49
  • 2
    is this solved in the new version of django? – Tiger developer Jul 10 '17 at 21:14

4 Answers4

13

The are two relevant feature requests (one is open, another is a duplicate of the first):

Both of the issues are based on the Google Group discussion.

The ticket is in a "hanging" state due to architectural concerns and since there is no consensus on whether this is really something that Django should support. Mostly because web-servers are capable of byte serving.

If you are still interested in the implementation, there is a not-yet-reviewed patch sent implementing a special RangedFileReader class for returning the response in chunks using StreamingHttpResponse, there is parsing and sanity checking for HTTP_RANGE header:

You can try out the fork or use the solution as a base of your own.

FYI, there was an another attempt made here - it was not finished, but can be useful to review.


To parse Range header, see:

alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195
  • This is great information for those looking to implement `Range` headers for static asset related reasons, I was looking for something that addressed [one of the many other use cases](http://stackoverflow.com/q/1434647/359284). Any information on parsing the `Range` header would be a start. – Kevin Brown-Silva Dec 27 '14 at 03:11
  • @KevinBrown even though the django tickets are static-files specific, the general ideas brought by both PRs can be used as a starting point. I've also posted few links on parsing Range header topic. Personally, I think you should start by looking into what `werkzeug.http` has to offer about byte serving more closely since `werkzeug` is definitely something solid and mature. Thanks. – alecxe Dec 27 '14 at 04:48
  • 1
    6 years old, I still need this. After a blind copy/paste from the answer of Collin Anderson, it works, but I really dont like those kind of "patches". Do you know if, in 6 years, this has been implemented or not? I cant find anything about this in the web. – Olivier Pons May 27 '20 at 19:21
13

Here's some basic middleware code that should work for Django 1.11+. It only handles a single range, but that's all I personally need.

import os

from django.utils.deprecation import MiddlewareMixin


class RangesMiddleware(MiddlewareMixin):
    def process_response(self, request, response):
        if response.status_code != 200 or not hasattr(response, 'file_to_stream'):
            return response
        http_range = request.META.get('HTTP_RANGE')
        if not (http_range and http_range.startswith('bytes=') and http_range.count('-') == 1):
            return response
        if_range = request.META.get('HTTP_IF_RANGE')
        if if_range and if_range != response.get('Last-Modified') and if_range != response.get('ETag'):
            return response
        f = response.file_to_stream
        statobj = os.fstat(f.fileno())
        start, end = http_range.split('=')[1].split('-')
        if not start:  # requesting the last N bytes
            start = max(0, statobj.st_size - int(end))
            end = ''
        start, end = int(start or 0), int(end or statobj.st_size - 1)
        assert 0 <= start < statobj.st_size, (start, statobj.st_size)
        end = min(end, statobj.st_size - 1)
        f.seek(start)
        old_read = f.read
        f.read = lambda n: old_read(min(n, end + 1 - f.tell()))
        response.status_code = 206
        response['Content-Length'] = end + 1 - start
        response['Content-Range'] = 'bytes %d-%d/%d' % (start, end, statobj.st_size)
        return response

Install it in settings.py like so:

MIDDLEWARE_CLASSES = [
    'path.to.RangesMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
]
Collin Anderson
  • 14,787
  • 6
  • 68
  • 57
  • Thanks for the tip. I made it a middleware which should make it more clear how to use it. – Collin Anderson Mar 22 '16 at 15:08
  • 1
    It worked for me. In my case, there is a django site with a form for uploading videos, and an iOS app that scans qr codes and displays videos. Thanks !!!!! – Michael Marinos Likouras Mar 28 '18 at 07:54
  • After a blind copy/paste from your answer, it works, but I really dont like those kind of "patches" - nothing about you, I just dont like when I dont master what I'm doing. Do you know if, in 6 years, this has been implemented or not? I cant find anything about this in the web. – Olivier Pons May 27 '20 at 19:22
  • Hi Olivier Pon, I feel the same way about not mastering what I'm doing (hence implementing it myself!). Here's the official Django ticket regarding range requests. You could be the one to implement it for Django! https://code.djangoproject.com/ticket/22479 – Collin Anderson May 27 '20 at 23:28
  • This just saved my final project of my course. Thank you! – GiodoAldeima Jul 03 '21 at 12:56
3

Three approches about content range

Choose proper one by your situation.

1. Support all dynamic views and static files

The WhiteNoise provides a middleware to support global settings.

2. Support static files only

Nginx can support static and media files.

See more details on Setting up Django and your web server with uWSGI and nginx. If some views need redirect to static or media files, see the stackoverflow answer.

3. Support specific dynamic views only

Django Ranged Response supports responses with content range. Use this response for every views you wanted. The views are mostly designed for media API, like speech synthesis, video generators.


declare: I am a contributer of Django Ranged Response. But I think using WhiteNoise is easiest to maintain.

sih4sing5hog5
  • 180
  • 11
  • I found that your `django-ranged-response` package was the best solution - WhiteNoise didn't seem to properly handle ranged responses (it didn't error but sent the whole file in response to `Range: bytes=0-1`?!) – Pierz Dec 12 '17 at 12:36
  • I found code about 206 response in whitenoise. I tried and got whole file. It needs some time to trace code of whitenoise. – sih4sing5hog5 Dec 20 '17 at 06:49
0

If you just need this in testing and don't mind running nginx in front of your django (which has plenty of other advantages, like chunking, etc.), then you can add proxy_force_ranges on; to http/server/location, which, for example, fixes the issues like Chrome requiring this to be able to seek in videos (by setting video.currentTime), etc.

AntonOfTheWoods
  • 809
  • 13
  • 17
  • Unfortunately, that's not very efficient. If I'm serving a 1GB video file in 4kB chunks via range requests. I don't fancy sending 125TB of data to nginx to send just 1GB to the client. (1000000kB/4kB = 250,000 requests from the client for different 4kB chunks; 250,000 * 1GB response = 250TB, but nginx could close the connection after reading the 4kB it wants so on average that halves the traffic, so 125TB going from Django to nginx). – user9876 May 05 '21 at 00:41