Skip to content

Commit

Permalink
Fixed #15281 -- Made the static view use an iterator when serving a f…
Browse files Browse the repository at this point in the history
…ile, effectively making this less of a memory hog. Also use the appropriate attributes of the stat object instead of indexes. Thanks for the initial patch, FunkyBob and aaugustin.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@15701 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
jezdez committed Mar 1, 2011
1 parent 8f19878 commit be4a2e3
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 38 deletions.
25 changes: 1 addition & 24 deletions django/core/servers/basehttp.py
Expand Up @@ -19,6 +19,7 @@
from django.utils.http import http_date
from django.utils._os import safe_join
from django.views import static
from django.views.static import FileWrapper # for backwards compatibility, #15281

from django.contrib.staticfiles import handlers

Expand All @@ -32,30 +33,6 @@
class WSGIServerException(Exception):
pass

class FileWrapper(object):
"""Wrapper to convert file-like objects to iterables"""

def __init__(self, filelike, blksize=8192):
self.filelike = filelike
self.blksize = blksize
if hasattr(filelike,'close'):
self.close = filelike.close

def __getitem__(self,key):
data = self.filelike.read(self.blksize)
if data:
return data
raise IndexError

def __iter__(self):
return self

def next(self):
data = self.filelike.read(self.blksize)
if data:
return data
raise StopIteration

# Regular expression that matches `special' characters in parameters, the
# existence of which force quoting of the parameter value.
tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]')
Expand Down
38 changes: 32 additions & 6 deletions django/views/static.py
Expand Up @@ -7,14 +7,41 @@
import os
import posixpath
import re
import stat
import urllib

from django.template import loader
from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified

from django.template import Template, Context, TemplateDoesNotExist
from django.utils.http import http_date, parse_http_date


class FileWrapper(object):
"""
Wrapper to convert file-like objects to iterables
"""
def __init__(self, filelike, blksize=8192):
self.filelike = filelike
self.blksize = blksize
if hasattr(filelike,'close'):
self.close = filelike.close

def __getitem__(self,key):
data = self.filelike.read(self.blksize)
if data:
return data
raise IndexError

def __iter__(self):
return self

def next(self):
data = self.filelike.read(self.blksize)
if data:
return data
raise StopIteration


def serve(request, path, document_root=None, show_indexes=False):
"""
Serve static files below a given point in the directory structure.
Expand Down Expand Up @@ -56,12 +83,11 @@ def serve(request, path, document_root=None, show_indexes=False):
mimetype, encoding = mimetypes.guess_type(fullpath)
mimetype = mimetype or 'application/octet-stream'
if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]):
statobj.st_mtime, statobj.st_size):
return HttpResponseNotModified(mimetype=mimetype)
contents = open(fullpath, 'rb').read()
response = HttpResponse(contents, mimetype=mimetype)
response["Last-Modified"] = http_date(statobj[stat.ST_MTIME])
response["Content-Length"] = len(contents)
response = HttpResponse(FileWrapper(open(fullpath, 'rb')), mimetype=mimetype)
response["Last-Modified"] = http_date(statobj.st_mtime)
response["Content-Length"] = statobj.st_size
if encoding:
response["Content-Encoding"] = encoding
return response
Expand Down
24 changes: 16 additions & 8 deletions tests/regressiontests/views/tests/static.py
Expand Up @@ -26,10 +26,18 @@ def test_serve(self):
for filename in media_files:
response = self.client.get('/views/%s/%s' % (self.prefix, filename))
file_path = path.join(media_dir, filename)
self.assertEquals(open(file_path).read(), response.content)
self.assertEquals(len(response.content), int(response['Content-Length']))
content = str(response.content)
self.assertEquals(open(file_path).read(), content)
self.assertEquals(len(content), int(response['Content-Length']))
self.assertEquals(mimetypes.guess_type(file_path)[1], response.get('Content-Encoding', None))

def test_serve_does_not_buffer_files(self):
"The static view uses an iterator - see #15281"
response = self.client.get('/views/%s/file.txt' % self.prefix)
# response objects can't be used as file-like objects when they are
# initialized with an iterator
self.assertRaises(Exception, response.write, 'more text')

def test_unknown_mime_type(self):
response = self.client.get('/views/%s/file.unknown' % self.prefix)
self.assertEquals('application/octet-stream', response['Content-Type'])
Expand Down Expand Up @@ -68,9 +76,9 @@ def test_invalid_if_modified_since(self):
response = self.client.get('/views/%s/%s' % (self.prefix, file_name),
HTTP_IF_MODIFIED_SINCE=invalid_date)
file = open(path.join(media_dir, file_name))
self.assertEquals(file.read(), response.content)
self.assertEquals(len(response.content),
int(response['Content-Length']))
content = str(response.content)
self.assertEquals(file.read(), content)
self.assertEquals(len(content), int(response['Content-Length']))

def test_invalid_if_modified_since2(self):
"""Handle even more bogus If-Modified-Since values gracefully
Expand All @@ -82,10 +90,10 @@ def test_invalid_if_modified_since2(self):
invalid_date = ': 1291108438, Wed, 20 Oct 2010 14:05:00 GMT'
response = self.client.get('/views/%s/%s' % (self.prefix, file_name),
HTTP_IF_MODIFIED_SINCE=invalid_date)
content = str(response.content)
file = open(path.join(media_dir, file_name))
self.assertEquals(file.read(), response.content)
self.assertEquals(len(response.content),
int(response['Content-Length']))
self.assertEquals(file.read(), content)
self.assertEquals(len(content), int(response['Content-Length']))


class StaticHelperTest(StaticTests):
Expand Down

0 comments on commit be4a2e3

Please sign in to comment.