Skip to content

Instantly share code, notes, and snippets.

@dcwatson
Last active May 6, 2023 20:10
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dcwatson/cb5d8157a8fa5a4a046e to your computer and use it in GitHub Desktop.
Save dcwatson/cb5d8157a8fa5a4a046e to your computer and use it in GitHub Desktop.
Django streaming view accepting byte range requests
from django.http import StreamingHttpResponse
from wsgiref.util import FileWrapper
import mimetypes
import os
import re
range_re = re.compile(r'bytes\s*=\s*(\d+)\s*-\s*(\d*)', re.I)
class RangeFileWrapper (object):
def __init__(self, filelike, blksize=8192, offset=0, length=None):
self.filelike = filelike
self.filelike.seek(offset, os.SEEK_SET)
self.remaining = length
self.blksize = blksize
def close(self):
if hasattr(self.filelike, 'close'):
self.filelike.close()
def __iter__(self):
return self
def next(self):
if self.remaining is None:
# If remaining is None, we're reading the entire file.
data = self.filelike.read(self.blksize)
if data:
return data
raise StopIteration()
else:
if self.remaining <= 0:
raise StopIteration()
data = self.filelike.read(min(self.remaining, self.blksize))
if not data:
raise StopIteration()
self.remaining -= len(data)
return data
def stream(request, path):
range_header = request.META.get('HTTP_RANGE', '').strip()
range_match = range_re.match(range_header)
size = os.path.getsize(path)
content_type, encoding = mimetypes.guess_type(path)
content_type = content_type or 'application/octet-stream'
if range_match:
first_byte, last_byte = range_match.groups()
first_byte = int(first_byte) if first_byte else 0
last_byte = int(last_byte) if last_byte else size - 1
if last_byte >= size:
last_byte = size - 1
length = last_byte - first_byte + 1
resp = StreamingHttpResponse(RangeFileWrapper(open(path, 'rb'), offset=first_byte, length=length), status=206, content_type=content_type)
resp['Content-Length'] = str(length)
resp['Content-Range'] = 'bytes %s-%s/%s' % (first_byte, last_byte, size)
else:
resp = StreamingHttpResponse(FileWrapper(open(path, 'rb')), content_type=content_type)
resp['Content-Length'] = str(size)
resp['Accept-Ranges'] = 'bytes'
return resp
@tiankonglanyu
Copy link

666

@olivierpons
Copy link

Thanks for your code, but may I ask you to describe how to add this and make it work in a Django project? Thanks a lot!

@saveychauhan
Copy link

i was using the wrong regex
range_re = re.compile(r'bytess*=s*(d+)s*-s*(d*)', re.I)
after this
range_re = re.compile(r'bytes\s*=\s*(\d+)\s*-\s*(\d*)', re.I)
working absolutely fine .
Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment