Fixed #15281 -- Made the static view use an iterator when serving a file, 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
This commit is contained in:
Jannis Leidel 2011-03-01 22:49:10 +00:00
parent 8f19878263
commit be4a2e3f3e
3 changed files with 49 additions and 38 deletions

View File

@ -19,6 +19,7 @@ from django.core.management.color import color_style
from django.utils.http import http_date from django.utils.http import http_date
from django.utils._os import safe_join from django.utils._os import safe_join
from django.views import static from django.views import static
from django.views.static import FileWrapper # for backwards compatibility, #15281
from django.contrib.staticfiles import handlers from django.contrib.staticfiles import handlers
@ -32,30 +33,6 @@ software_version = server_version + ' ' + sys_version
class WSGIServerException(Exception): class WSGIServerException(Exception):
pass 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 # Regular expression that matches `special' characters in parameters, the
# existence of which force quoting of the parameter value. # existence of which force quoting of the parameter value.
tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]') tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]')

View File

@ -7,14 +7,41 @@ import mimetypes
import os import os
import posixpath import posixpath
import re import re
import stat
import urllib import urllib
from django.template import loader from django.template import loader
from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified
from django.template import Template, Context, TemplateDoesNotExist from django.template import Template, Context, TemplateDoesNotExist
from django.utils.http import http_date, parse_http_date 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): def serve(request, path, document_root=None, show_indexes=False):
""" """
Serve static files below a given point in the directory structure. Serve static files below a given point in the directory structure.
@ -56,12 +83,11 @@ def serve(request, path, document_root=None, show_indexes=False):
mimetype, encoding = mimetypes.guess_type(fullpath) mimetype, encoding = mimetypes.guess_type(fullpath)
mimetype = mimetype or 'application/octet-stream' mimetype = mimetype or 'application/octet-stream'
if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'), 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) return HttpResponseNotModified(mimetype=mimetype)
contents = open(fullpath, 'rb').read() response = HttpResponse(FileWrapper(open(fullpath, 'rb')), mimetype=mimetype)
response = HttpResponse(contents, mimetype=mimetype) response["Last-Modified"] = http_date(statobj.st_mtime)
response["Last-Modified"] = http_date(statobj[stat.ST_MTIME]) response["Content-Length"] = statobj.st_size
response["Content-Length"] = len(contents)
if encoding: if encoding:
response["Content-Encoding"] = encoding response["Content-Encoding"] = encoding
return response return response

View File

@ -26,10 +26,18 @@ class StaticTests(TestCase):
for filename in media_files: for filename in media_files:
response = self.client.get('/views/%s/%s' % (self.prefix, filename)) response = self.client.get('/views/%s/%s' % (self.prefix, filename))
file_path = path.join(media_dir, filename) file_path = path.join(media_dir, filename)
self.assertEquals(open(file_path).read(), response.content) content = str(response.content)
self.assertEquals(len(response.content), int(response['Content-Length'])) 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)) 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): def test_unknown_mime_type(self):
response = self.client.get('/views/%s/file.unknown' % self.prefix) response = self.client.get('/views/%s/file.unknown' % self.prefix)
self.assertEquals('application/octet-stream', response['Content-Type']) self.assertEquals('application/octet-stream', response['Content-Type'])
@ -68,9 +76,9 @@ class StaticTests(TestCase):
response = self.client.get('/views/%s/%s' % (self.prefix, file_name), response = self.client.get('/views/%s/%s' % (self.prefix, file_name),
HTTP_IF_MODIFIED_SINCE=invalid_date) HTTP_IF_MODIFIED_SINCE=invalid_date)
file = open(path.join(media_dir, file_name)) file = open(path.join(media_dir, file_name))
self.assertEquals(file.read(), response.content) content = str(response.content)
self.assertEquals(len(response.content), self.assertEquals(file.read(), content)
int(response['Content-Length'])) self.assertEquals(len(content), int(response['Content-Length']))
def test_invalid_if_modified_since2(self): def test_invalid_if_modified_since2(self):
"""Handle even more bogus If-Modified-Since values gracefully """Handle even more bogus If-Modified-Since values gracefully
@ -82,10 +90,10 @@ class StaticTests(TestCase):
invalid_date = ': 1291108438, Wed, 20 Oct 2010 14:05:00 GMT' invalid_date = ': 1291108438, Wed, 20 Oct 2010 14:05:00 GMT'
response = self.client.get('/views/%s/%s' % (self.prefix, file_name), response = self.client.get('/views/%s/%s' % (self.prefix, file_name),
HTTP_IF_MODIFIED_SINCE=invalid_date) HTTP_IF_MODIFIED_SINCE=invalid_date)
content = str(response.content)
file = open(path.join(media_dir, file_name)) file = open(path.join(media_dir, file_name))
self.assertEquals(file.read(), response.content) self.assertEquals(file.read(), content)
self.assertEquals(len(response.content), self.assertEquals(len(content), int(response['Content-Length']))
int(response['Content-Length']))
class StaticHelperTest(StaticTests): class StaticHelperTest(StaticTests):