Django

Code

Changeset 6662

Show
Ignore:
Timestamp:
11/10/07 21:55:44 (10 months ago)
Author:
mtredinnick
Message:

Fixed #5898 -- Changed a few response processing paths to make things harder to get wrong and easier to get right. Previous behaviour wasn't buggy, but it was harder to use than necessary.

We now have automatic HEAD processing always (previously required ConditionalGetMiddleware?), middleware benefits from the Location header rewrite, so they can use relative URLs as well, and responses with response codes 1xx, 204 or 304 will always have their content removed, in accordance with the HTTP spec (so it's much harder to indavertently deliver invalid responses).

Based on a patch and diagnosis from regexbot@gmail.com.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/core/handlers/base.py

    r6296 r6662  
    55 
    66class BaseHandler(object): 
     7    # Changes that are always applied to a response (in this order). 
     8    response_fixes = [http.fix_location_header, 
     9            http.conditional_content_removal] 
     10 
    711    def __init__(self): 
    812        self._request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None 
     
    5155    def get_response(self, request): 
    5256        "Returns an HttpResponse object for the given HttpRequest" 
    53         response = self._real_get_response(request) 
    54         return fix_location_header(request, response) 
    55  
    56     def _real_get_response(self, request): 
    5757        from django.core import exceptions, urlresolvers 
    5858        from django.core.mail import mail_admins 
     
    135135        return '\n'.join(traceback.format_exception(*(exc_info or sys.exc_info()))) 
    136136 
    137 def fix_location_header(request, response): 
    138     """ 
    139     Ensure that we always use an absolute URI in any location header in the 
    140     response. This is required by RFC 2616, section 14.30. 
     137    def apply_response_fixes(self, request, response): 
     138        """ 
     139        Applies each of the functions in self.response_fixes to the request and 
     140        response, modifying the response in the process. Returns the new 
     141        response. 
     142        """ 
     143        for func in self.response_fixes: 
     144            response = func(request, response) 
     145        return response 
    141146 
    142     Code constructing response objects is free to insert relative paths and 
    143     this function converts them to absolute paths. 
    144     """ 
    145     if 'Location' in response and request.get_host(): 
    146         response['Location'] = request.build_absolute_uri(response['Location']) 
    147     return response 
    148  
  • django/trunk/django/core/handlers/modpython.py

    r6550 r6662  
    163163                for middleware_method in self._response_middleware: 
    164164                    response = middleware_method(request, response) 
     165                response = self.apply_response_fixes(request, response) 
    165166        finally: 
    166167            dispatcher.send(signal=signals.request_finished) 
  • django/trunk/django/core/handlers/wsgi.py

    r6592 r6662  
    208208                for middleware_method in self._response_middleware: 
    209209                    response = middleware_method(request, response) 
     210                response = self.apply_response_fixes(request, response) 
    210211        finally: 
    211212            dispatcher.send(signal=signals.request_finished) 
     
    221222        start_response(status, response_headers) 
    222223        return response 
     224 
  • django/trunk/django/http/__init__.py

    r6549 r6662  
    66from django.utils.datastructures import MultiValueDict, FileDict 
    77from django.utils.encoding import smart_str, iri_to_uri, force_unicode 
     8from utils import * 
    89 
    910RESERVED_CHARS="!*'();:@&=+$,/?%#[]" 
  • django/trunk/django/middleware/http.py

    r6635 r6662  
    66    Last-Modified header, and the request has If-None-Match or 
    77    If-Modified-Since, the response is replaced by an HttpNotModified. 
    8  
    9     Removes the content from any response to a HEAD request. 
    108 
    119    Also sets the Date and Content-Length response-headers. 
     
    1917            if_none_match = request.META.get('HTTP_IF_NONE_MATCH', None) 
    2018            if if_none_match == response['ETag']: 
    21                 response.status_code = 304 
    22                 response.content = '' 
    23                 response['Content-Length'] = '0' 
     19                # Setting the status is enough here. The response handling path 
     20                # automatically removes content for this status code (in 
     21                # http.conditional_content_removal()). 
     22                response.status = 304 
    2423 
    2524        if response.has_header('Last-Modified'): 
    2625            if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE', None) 
    2726            if if_modified_since == response['Last-Modified']: 
    28                 response.status_code = 304 
    29                 response.content = '' 
    30                 response['Content-Length'] = '0' 
    31  
    32         if request.method == 'HEAD': 
    33             response.content = '' 
     27                # Setting the status code is enough here (same reasons as 
     28                # above). 
     29                response.status = 304 
    3430 
    3531        return response 
  • django/trunk/django/test/client.py

    r6338 r6662  
    4343            for middleware_method in self._response_middleware: 
    4444                response = middleware_method(request, response) 
    45  
     45            response = self.apply_response_fixes(request, response) 
    4646        finally: 
    4747            dispatcher.send(signal=signals.request_finished) 
  • django/trunk/tests/regressiontests/test_client_regress/models.py

    r6183 r6662  
    3232        except AssertionError, e: 
    3333            self.assertEquals(str(e), "Found 1 instances of 'once' in response (expected 2)") 
    34          
     34 
    3535        try: 
    3636            self.assertContains(response, 'twice', 1) 
    3737        except AssertionError, e: 
    3838            self.assertEquals(str(e), "Found 2 instances of 'twice' in response (expected 1)") 
    39          
     39 
    4040        try: 
    4141            self.assertContains(response, 'thrice') 
     
    4747        except AssertionError, e: 
    4848            self.assertEquals(str(e), "Found 0 instances of 'thrice' in response (expected 3)") 
    49          
     49 
    5050class AssertTemplateUsedTests(TestCase): 
    5151    fixtures = ['testdata.json'] 
    52      
     52 
    5353    def test_no_context(self): 
    5454        "Template usage assertions work then templates aren't in use" 
     
    5757        # Check that the no template case doesn't mess with the template assertions 
    5858        self.assertTemplateNotUsed(response, 'GET Template') 
    59          
     59 
    6060        try: 
    6161            self.assertTemplateUsed(response, 'GET Template') 
     
    6363            self.assertEquals(str(e), "No templates used to render the response") 
    6464 
    65     def test_single_context(self):         
     65    def test_single_context(self): 
    6666        "Template assertions work when there is a single context" 
    6767        response = self.client.get('/test_client/post_view/', {}) 
    6868 
    69         #  
     69        # 
    7070        try: 
    7171            self.assertTemplateNotUsed(response, 'Empty GET Template') 
    7272        except AssertionError, e: 
    7373            self.assertEquals(str(e), "Template 'Empty GET Template' was used unexpectedly in rendering the response") 
    74              
    75         try: 
    76             self.assertTemplateUsed(response, 'Empty POST Template')         
     74 
     75        try: 
     76            self.assertTemplateUsed(response, 'Empty POST Template') 
    7777        except AssertionError, e: 
    7878            self.assertEquals(str(e), "Template 'Empty POST Template' was not a template used to render the response. Actual template(s) used: Empty GET Template") 
    79      
     79 
    8080    def test_multiple_context(self): 
    8181        "Template assertions work when there are multiple contexts" 
     
    100100 
    101101        try: 
    102             self.assertTemplateUsed(response, "Valid POST Template")         
     102            self.assertTemplateUsed(response, "Valid POST Template") 
    103103        except AssertionError, e: 
    104104            self.assertEquals(str(e), "Template 'Valid POST Template' was not a template used to render the response. Actual template(s) used: form_view.html, base.html") 
     
    106106class AssertRedirectsTests(TestCase): 
    107107    def test_redirect_page(self): 
    108         "An assertion is raised if the original page couldn't be retrieved as expected"         
     108        "An assertion is raised if the original page couldn't be retrieved as expected" 
    109109        # This page will redirect with code 301, not 302 
    110         response = self.client.get('/test_client/permanent_redirect_view/')         
     110        response = self.client.get('/test_client/permanent_redirect_view/') 
    111111        try: 
    112112            self.assertRedirects(response, '/test_client/get_view/') 
    113113        except AssertionError, e: 
    114114            self.assertEquals(str(e), "Response didn't redirect as expected: Response code was 301 (expected 302)") 
    115      
     115 
    116116    def test_lost_query(self): 
    117117        "An assertion is raised if the redirect location doesn't preserve GET parameters" 
     
    120120            self.assertRedirects(response, '/test_client/get_view/') 
    121121        except AssertionError, e: 
    122             self.assertEquals(str(e), "Response redirected to 'http://testserver/test_client/get_view/?var=value', expected '/test_client/get_view/'") 
     122            self.assertEquals(str(e), "Response redirected to 'http://testserver/test_client/get_view/?var=value', expected 'http://testserver/test_client/get_view/'") 
    123123 
    124124    def test_incorrect_target(self): 
    125125        "An assertion is raised if the response redirects to another target" 
    126         response = self.client.get('/test_client/permanent_redirect_view/')         
     126        response = self.client.get('/test_client/permanent_redirect_view/') 
    127127        try: 
    128128            # Should redirect to get_view 
     
    130130        except AssertionError, e: 
    131131            self.assertEquals(str(e), "Response didn't redirect as expected: Response code was 301 (expected 302)") 
    132          
     132 
    133133    def test_target_page(self): 
    134134        "An assertion is raised if the response redirect target cannot be retrieved as expected" 
     
    139139        except AssertionError, e: 
    140140            self.assertEquals(str(e), "Couldn't retrieve redirection page '/test_client/permanent_redirect_view/': response code was 301 (expected 200)") 
    141              
     141 
    142142class AssertFormErrorTests(TestCase): 
    143143    def test_unknown_form(self): 
     
    158158        except AssertionError, e: 
    159159            self.assertEqual(str(e), "The form 'wrong_form' was not used to render the response") 
    160          
     160 
    161161    def test_unknown_field(self): 
    162162        "An assertion is raised if the field name is unknown" 
     
    176176        except AssertionError, e: 
    177177            self.assertEqual(str(e), "The form 'form' in context 0 does not contain the field 'some_field'") 
    178          
     178 
    179179    def test_noerror_field(self): 
    180180        "An assertion is raised if the field doesn't have any errors" 
     
    194194        except AssertionError, e: 
    195195            self.assertEqual(str(e), "The field 'value' on form 'form' in context 0 contains no errors") 
    196          
     196 
    197197    def test_unknown_error(self): 
    198198        "An assertion is raised if the field doesn't contain the provided error" 
     
    212212        except AssertionError, e: 
    213213            self.assertEqual(str(e), "The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])") 
    214      
     214 
    215215    def test_unknown_nonfield_error(self): 
    216216        """ 
     
    232232            self.assertFormError(response, 'form', None, 'Some error.') 
    233233        except AssertionError, e: 
    234             self.assertEqual(str(e), "The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )")         
     234            self.assertEqual(str(e), "The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )") 
    235235 
    236236class FileUploadTests(TestCase): 
     
    257257        # Get a redirection page with the second client. 
    258258        response = c.get("/test_client_regress/login_protected_redirect_view/") 
    259          
    260         # At this points, the self.client isn't logged in.  
    261         # Check that assertRedirects uses the original client, not the  
     259 
     260        # At this points, the self.client isn't logged in. 
     261        # Check that assertRedirects uses the original client, not the 
    262262        # default client. 
    263263        self.assertRedirects(response, "http://testserver/test_client_regress/get_view/")