Ticket #2131: httpresponsesendfile-no-default-content_bypass-middleware_with-header_with-docs-and-tests.diff

File httpresponsesendfile-no-default-content_bypass-middleware_with-header_with-docs-and-tests.diff, 12.3 KB (added by mrts, 6 years ago)

Added the requested header, as well as docs and tests.

  • django/http/__init__.py

     
    393393            raise Exception("This %s instance cannot tell its position" % self.__class__)
    394394        return sum([len(chunk) for chunk in self._container])
    395395
     396class HttpResponseSendFile(HttpResponse):
     397    def __init__(self, path_to_file, content_type=None, block_size=8192):
     398        if not content_type:
     399            from mimetypes import guess_type
     400            content_type = guess_type(path_to_file)[0]
     401            if content_type is None:
     402                content_type = "application/octet-stream"
     403        super(HttpResponseSendFile, self).__init__(None,
     404                content_type=content_type)
     405        self.sendfile_filename = path_to_file
     406        self.block_size = block_size
     407        self['Content-Length'] = os.path.getsize(path_to_file)
     408        self['Content-Disposition'] = ('attachment; filename=%s' %
     409                os.path.basename(path_to_file))
     410        self[settings.HTTPRESPONSE_SENDFILE_HEADER] = path_to_file
     411
    396412class HttpResponseRedirect(HttpResponse):
    397413    status_code = 302
    398414
  • django/conf/global_settings.py

     
    239239# Example: "http://media.lawrence.com"
    240240MEDIA_URL = ''
    241241
     242# Header to use in HttpResponseSendFile to inform the handler to serve the
     243# file with efficient handler-specific routines.
     244HTTPRESPONSE_SENDFILE_HEADER = 'X-Sendfile'
     245
    242246# List of upload handler classes to be applied in order.
    243247FILE_UPLOAD_HANDLERS = (
    244248    'django.core.files.uploadhandler.MemoryFileUploadHandler',
  • django/core/servers/basehttp.py

     
    313313        in the event loop to iterate over the data, and to call
    314314        'self.close()' once the response is finished.
    315315        """
    316         if not self.result_is_file() and not self.sendfile():
    317             for data in self.result:
    318                 self.write(data)
    319             self.finish_content()
     316        for data in self.result:
     317            self.write(data)
     318        self.finish_content()
    320319        self.close()
    321320
    322321    def get_scheme(self):
  • django/core/handlers/wsgi.py

     
    231231            self.initLock.release()
    232232
    233233        set_script_prefix(base.get_script_name(environ))
    234         signals.request_started.send(sender=self.__class__)
    235         try:
    236             try:
    237                 request = self.request_class(environ)
    238             except UnicodeDecodeError:
    239                 response = http.HttpResponseBadRequest()
    240             else:
    241                 response = self.get_response(request)
    242234
    243                 # Apply response middleware
    244                 for middleware_method in self._response_middleware:
    245                     response = middleware_method(request, response)
    246                 response = self.apply_response_fixes(request, response)
    247         finally:
    248             signals.request_finished.send(sender=self.__class__)
     235        response = self.process_request(environ)
    249236
    250237        try:
    251238            status_text = STATUS_CODE_TEXT[response.status_code]
    252239        except KeyError:
    253240            status_text = 'UNKNOWN STATUS CODE'
    254241        status = '%s %s' % (response.status_code, status_text)
     242
    255243        response_headers = [(str(k), str(v)) for k, v in response.items()]
    256244        for c in response.cookies.values():
    257245            response_headers.append(('Set-Cookie', str(c.output(header=''))))
     246
    258247        start_response(status, response_headers)
     248
     249        if isinstance(response, http.HttpResponseSendFile):
     250            filelike = open(response.sendfile_filename, 'rb')
     251            if 'wsgi.file_wrapper' in environ:
     252                return environ['wsgi.file_wrapper'](filelike,
     253                        response.block_size)
     254            else:
     255                # wraps close() as well
     256                from django.core.servers.basehttp import FileWrapper
     257                return FileWrapper(filelike, response.block_size)
     258
    259259        return response
    260260
  • django/core/handlers/base.py

     
    6363        # as a flag for initialization being complete.
    6464        self._request_middleware = request_middleware
    6565
     66    def process_request(self, request_env):
     67        signals.request_started.send(sender=self.__class__)
     68        try:
     69            try:
     70                request = self.request_class(request_env)
     71            except UnicodeDecodeError:
     72                response = http.HttpResponseBadRequest()
     73            else:
     74                response = self.get_response(request)
     75
     76                # Apply response middleware
     77                if not isinstance(response, http.HttpResponseSendFile):
     78                    for middleware_method in self._response_middleware:
     79                        response = middleware_method(request, response)
     80                    response = self.apply_response_fixes(request, response)
     81        finally:
     82            signals.request_finished.send(sender=self.__class__)
     83
     84        return response
     85
    6686    def get_response(self, request):
    6787        "Returns an HttpResponse object for the given HttpRequest"
    6888        from django.core import exceptions, urlresolvers
  • django/core/handlers/modpython.py

     
    22from pprint import pformat
    33
    44from django import http
    5 from django.core import signals
    65from django.core.handlers.base import BaseHandler
    76from django.core.urlresolvers import set_script_prefix
    87from django.utils import datastructures
     
    191190            self.load_middleware()
    192191
    193192        set_script_prefix(req.get_options().get('django.root', ''))
    194         signals.request_started.send(sender=self.__class__)
    195         try:
    196             try:
    197                 request = self.request_class(req)
    198             except UnicodeDecodeError:
    199                 response = http.HttpResponseBadRequest()
    200             else:
    201                 response = self.get_response(request)
    202193
    203                 # Apply response middleware
    204                 for middleware_method in self._response_middleware:
    205                     response = middleware_method(request, response)
    206                 response = self.apply_response_fixes(request, response)
    207         finally:
    208             signals.request_finished.send(sender=self.__class__)
     194        response = self.process_request(req)
    209195
    210196        # Convert our custom HttpResponse object back into the mod_python req.
    211197        req.content_type = response['Content-Type']
     
    215201        for c in response.cookies.values():
    216202            req.headers_out.add('Set-Cookie', c.output(header=''))
    217203        req.status = response.status_code
    218         try:
    219             for chunk in response:
    220                 req.write(chunk)
    221         finally:
    222             response.close()
    223204
     205        if isinstance(response, http.HttpResponseSendFile):
     206            req.sendfile(response.sendfile_filename)
     207        else:
     208            try:
     209                for chunk in response:
     210                    req.write(chunk)
     211            finally:
     212                response.close()
     213
    224214        return 0 # mod_python.apache.OK
    225215
    226216def handler(req):
  • tests/regressiontests/sendfile/views.py

     
     1import urllib
     2
     3from django.http import HttpResponseSendFile
     4
     5def serve_file(request, filename):
     6    filename = urllib.unquote(filename)
     7    return HttpResponseSendFile(filename)
  • tests/regressiontests/sendfile/tests.py

     
     1import urllib, os
     2
     3from django.test import TestCase
     4from django.conf import settings
     5from django.core.files import temp as tempfile
     6
     7FILE_SIZE = 2 ** 10
     8CONTENT = 'a' * FILE_SIZE
     9
     10class SendFileTests(TestCase):
     11    def test_sendfile(self):
     12        tdir = tempfile.gettempdir()
     13
     14        file1 = tempfile.NamedTemporaryFile(suffix=".pdf", dir=tdir)
     15        file1.write(CONTENT)
     16        file1.seek(0)
     17
     18        response = self.client.get('/sendfile/serve_file/%s/' %
     19                urllib.quote(file1.name))
     20
     21        file1.close()
     22
     23        self.assertEqual(response.status_code, 200)
     24        self.assertEqual(response[settings.HTTPRESPONSE_SENDFILE_HEADER],
     25                file1.name)
     26        self.assertEqual(response['Content-Disposition'],
     27                'attachment; filename=%s' % os.path.basename(file1.name))
     28        self.assertEqual(response['Content-Length'], str(FILE_SIZE))
     29        self.assertEqual(response['Content-Type'], 'application/pdf')
     30
     31        # *if* the degraded case is to be supported, add this instead:
     32        # self.assertEqual(response.content, CONTENT)
     33        get_content = lambda: response.content
     34        self.assertRaises(TypeError, get_content)
     35
     36        # TODO: test middleware bypass etc
  • tests/regressiontests/sendfile/urls.py

     
     1from django.conf.urls.defaults import patterns
     2
     3import views
     4
     5urlpatterns = patterns('',
     6    (r'^serve_file/(?P<filename>.*)/$', views.serve_file),
     7)
  • tests/urls.py

     
    2020
    2121    # test urlconf for middleware tests
    2222    (r'^middleware/', include('regressiontests.middleware.urls')),
    23    
     23
    2424    # admin view tests
    2525    (r'^test_admin/', include('regressiontests.admin_views.urls')),
    2626    (r'^generic_inline_admin/', include('regressiontests.generic_inline_admin.urls')),
    27    
     27
    2828    # admin widget tests
    2929    (r'widget_admin/', include('regressiontests.admin_widgets.urls')),
    3030
     
    3232
    3333    # test urlconf for syndication tests
    3434    (r'^syndication/', include('regressiontests.syndication.urls')),
     35
     36    # HttpResponseSendfile tests
     37    (r'^sendfile/', include('regressiontests.sendfile.urls')),
    3538)
  • docs/ref/request-response.txt

     
    538538HttpResponse subclasses
    539539-----------------------
    540540
    541 Django includes a number of ``HttpResponse`` subclasses that handle different
    542 types of HTTP responses. Like ``HttpResponse``, these subclasses live in
    543 :mod:`django.http`.
     541Django includes a number of :class:`HttpResponse` subclasses that handle
     542different types of HTTP responses. Like :class:`HttpResponse`, these subclasses
     543live in :mod:`django.http`.
    544544
     545.. class:: HttpResponseSendFile
     546
     547    .. versionadded:: 1.1
     548
     549    A special response class for efficient file serving. It informs the HTTP
     550    protocol handler to use platform-specific file serving mechanism (if
     551    available). The constructor takes three arguments -- the file path and,
     552    optionally, the file's content type and block size hint for handlers that
     553    need it.
     554
     555    Note that response middleware will be bypassed if you use
     556    :class:`HttpResponseSendFile`.
     557
    545558.. class:: HttpResponseRedirect
    546559
    547560    The constructor takes a single argument -- the path to redirect to. This
Back to Top