Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Fixed #20619 -- Refactored HttpRequest and WSGIRequest. #1335

Closed
wants to merge 1 commit into from

2 participants

Loic Bistuer Alex Gaynor
Loic Bistuer
Owner
loic commented July 06, 2013

No description provided.

Alex Gaynor
Owner
alex commented July 06, 2013

Now that Djanog is 100% WSGI, is there any reason to have separate HttpRequest and WSGIRequest classes?

Loic Bistuer
Owner
loic commented July 06, 2013

@alex: good question.

IMO the refactored HttpRequest class is lightweight and makes it easy to hack the public API. I don't mind keeping the hacks required for WSGI support bundled in their own class.

There might be a backward compatibility issue as well; some people may run custom handlers with an HttpRequest subclass in the wild.

Loic Bistuer loic closed this July 16, 2013
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 1 unique commit by 1 author.

Jul 06, 2013
Loic Bistuer Fixed #20619 -- Refactored HttpRequest and WSGIRequest. 3c3f7db
This page is out of date. Refresh to see the latest.
128  django/core/handlers/wsgi.py
@@ -11,7 +11,8 @@
11 11
 from django.core.handlers import base
12 12
 from django.core.urlresolvers import set_script_prefix
13 13
 from django.utils import datastructures
14  
-from django.utils.encoding import force_str, force_text, iri_to_uri
  14
+from django.utils.datastructures import MultiValueDict
  15
+from django.utils.encoding import force_str
15 16
 
16 17
 # For backwards compatibility -- lots of code uses this in the wild!
17 18
 from django.http.response import REASON_PHRASES as STATUS_CODE_TEXT
@@ -72,7 +73,24 @@ def readline(self, size=None):
72 73
 
73 74
 
74 75
 class WSGIRequest(http.HttpRequest):
  76
+    _post_loaded = False
  77
+
  78
+    @property
  79
+    def encoding(self):
  80
+        return self._encoding
  81
+
  82
+    @encoding.setter
  83
+    def encoding(self, val):
  84
+        self._encoding = val
  85
+
  86
+        self._load_get()
  87
+        self._post_loaded = False
  88
+
75 89
     def __init__(self, environ):
  90
+        self.environ = environ
  91
+
  92
+        super(WSGIRequest, self).__init__()
  93
+
76 94
         script_name = base.get_script_name(environ)
77 95
         path_info = base.get_path_info(environ)
78 96
         if not path_info:
@@ -81,14 +99,16 @@ def __init__(self, environ):
81 99
             # operate as if they'd requested '/'. Not amazingly nice to force
82 100
             # the path like this, but should be harmless.
83 101
             path_info = '/'
84  
-        self.environ = environ
85 102
         self.path_info = path_info
86 103
         self.path = '%s/%s' % (script_name.rstrip('/'), path_info.lstrip('/'))
  104
+
87 105
         self.META = environ
88  
-        self.META['PATH_INFO'] = path_info
89 106
         self.META['SCRIPT_NAME'] = script_name
  107
+        self.META['PATH_INFO'] = path_info
  108
+
90 109
         self.method = environ['REQUEST_METHOD'].upper()
91  
-        _, content_params = self._parse_content_type(self.META.get('CONTENT_TYPE', ''))
  110
+
  111
+        content_params = self._parse_content_type(self.META.get('CONTENT_TYPE', ''))[1]
92 112
         if 'charset' in content_params:
93 113
             try:
94 114
                 codecs.lookup(content_params['charset'])
@@ -96,14 +116,69 @@ def __init__(self, environ):
96 116
                 pass
97 117
             else:
98 118
                 self.encoding = content_params['charset']
99  
-        self._post_parse_error = False
  119
+
100 120
         try:
101 121
             content_length = int(self.environ.get('CONTENT_LENGTH'))
102 122
         except (ValueError, TypeError):
103 123
             content_length = 0
104 124
         self._stream = LimitedStream(self.environ['wsgi.input'], content_length)
105 125
         self._read_started = False
106  
-        self.resolver_match = None
  126
+
  127
+        # `self.GET` and `self.COOKIES` get their data from the environ, so we can
  128
+        # safely load them here. `self.POST`, `self.FILES` and `self.REQUEST` need
  129
+        # to consume the stream so we lazy load them in `__getattribute__()`.
  130
+        self._load_get()
  131
+        self._load_cookies()
  132
+
  133
+    def __getattribute__(self, name):
  134
+        _post_loaded = super(WSGIRequest, self).__getattribute__('_post_loaded')
  135
+        if _post_loaded is False and name in set(['POST', 'FILES', 'REQUEST']):
  136
+            self._post_loaded = True
  137
+            self._load_post_and_files()
  138
+            self._load_request()
  139
+        return super(WSGIRequest, self).__getattribute__(name)
  140
+
  141
+    def _load_get(self):
  142
+        # The WSGI spec says 'QUERY_STRING' may be absent.
  143
+        self.GET = http.QueryDict(self.environ.get('QUERY_STRING', ''), encoding=self.encoding)
  144
+
  145
+    def _load_post_and_files(self):
  146
+        """Populate self.POST and self.FILES if the content-type is a form type"""
  147
+
  148
+        if self.method != 'POST':
  149
+            self.POST, self.FILES = http.QueryDict('', encoding=self.encoding), MultiValueDict()
  150
+            return
  151
+        if self._read_started and not hasattr(self, '_body'):
  152
+            self._post_parse_error = True
  153
+            self.POST, self.FILES = http.QueryDict(''), MultiValueDict()
  154
+            return
  155
+
  156
+        if self.META.get('CONTENT_TYPE', '').startswith('multipart/form-data'):
  157
+            if hasattr(self, '_body'):
  158
+                # Use already read data
  159
+                data = BytesIO(self._body)
  160
+            else:
  161
+                data = self
  162
+            try:
  163
+                self.POST, self.FILES = self.parse_file_upload(self.META, data)
  164
+            except:
  165
+                # An error occured while parsing POST data.
  166
+                # Mark that an error occured. This allows self.__repr__ to
  167
+                # be explicit about it instead of simply representing an
  168
+                # empty POST
  169
+                self._post_parse_error = True
  170
+                self.POST, self.FILES = http.QueryDict(''), MultiValueDict()
  171
+                raise
  172
+        elif self.META.get('CONTENT_TYPE', '').startswith('application/x-www-form-urlencoded'):
  173
+            self.POST, self.FILES = http.QueryDict(self.body, encoding=self.encoding), MultiValueDict()
  174
+        else:
  175
+            self.POST, self.FILES = http.QueryDict('', encoding=self.encoding), MultiValueDict()
  176
+
  177
+    def _load_request(self):
  178
+        self.REQUEST = datastructures.MergeDict(self.POST, self.GET)
  179
+
  180
+    def _load_cookies(self):
  181
+        self.COOKIES = http.parse_cookie(self.environ.get('HTTP_COOKIE', ''))
107 182
 
108 183
     def _is_secure(self):
109 184
         return 'wsgi.url_scheme' in self.environ and self.environ['wsgi.url_scheme'] == 'https'
@@ -123,47 +198,6 @@ def _parse_content_type(self, ctype):
123 198
             content_params[k] = v
124 199
         return content_type, content_params
125 200
 
126  
-    def _get_request(self):
127  
-        if not hasattr(self, '_request'):
128  
-            self._request = datastructures.MergeDict(self.POST, self.GET)
129  
-        return self._request
130  
-
131  
-    def _get_get(self):
132  
-        if not hasattr(self, '_get'):
133  
-            # The WSGI spec says 'QUERY_STRING' may be absent.
134  
-            self._get = http.QueryDict(self.environ.get('QUERY_STRING', ''), encoding=self._encoding)
135  
-        return self._get
136  
-
137  
-    def _set_get(self, get):
138  
-        self._get = get
139  
-
140  
-    def _get_post(self):
141  
-        if not hasattr(self, '_post'):
142  
-            self._load_post_and_files()
143  
-        return self._post
144  
-
145  
-    def _set_post(self, post):
146  
-        self._post = post
147  
-
148  
-    def _get_cookies(self):
149  
-        if not hasattr(self, '_cookies'):
150  
-            self._cookies = http.parse_cookie(self.environ.get('HTTP_COOKIE', ''))
151  
-        return self._cookies
152  
-
153  
-    def _set_cookies(self, cookies):
154  
-        self._cookies = cookies
155  
-
156  
-    def _get_files(self):
157  
-        if not hasattr(self, '_files'):
158  
-            self._load_post_and_files()
159  
-        return self._files
160  
-
161  
-    GET = property(_get_get, _set_get)
162  
-    POST = property(_get_post, _set_post)
163  
-    COOKIES = property(_get_cookies, _set_cookies)
164  
-    FILES = property(_get_files)
165  
-    REQUEST = property(_get_request)
166  
-
167 201
 
168 202
 class WSGIHandler(base.BaseHandler):
169 203
     initLock = Lock()
86  django/http/request.py
@@ -34,20 +34,19 @@ class UnreadablePostError(IOError):
34 34
 class HttpRequest(object):
35 35
     """A basic HTTP request."""
36 36
 
37  
-    # The encoding used in GET/POST dicts. None means use default setting.
38  
-    _encoding = None
39  
-    _upload_handlers = []
40  
-
41 37
     def __init__(self):
42  
-        # WARNING: The `WSGIRequest` subclass doesn't call `super`.
43  
-        # Any variable assignment made here should also happen in
44  
-        # `WSGIRequest.__init__()`.
  38
+        self.META = {}
  39
+
  40
+        self.GET, self.POST, self.FILES, self.REQUEST, self.COOKIES = {}, {}, {}, {}, {}
45 41
 
46  
-        self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {}
47 42
         self.path = ''
48 43
         self.path_info = ''
49 44
         self.method = None
50 45
         self.resolver_match = None
  46
+        self.encoding = None
  47
+        self.upload_handlers = [uploadhandler.load_handler(handler, self)
  48
+                                for handler in settings.FILE_UPLOAD_HANDLERS]
  49
+
51 50
         self._post_parse_error = False
52 51
 
53 52
     def __repr__(self):
@@ -140,40 +139,6 @@ def is_secure(self):
140 139
     def is_ajax(self):
141 140
         return self.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
142 141
 
143  
-    @property
144  
-    def encoding(self):
145  
-        return self._encoding
146  
-
147  
-    @encoding.setter
148  
-    def encoding(self, val):
149  
-        """
150  
-        Sets the encoding used for GET/POST accesses. If the GET or POST
151  
-        dictionary has already been created, it is removed and recreated on the
152  
-        next access (so that it is decoded correctly).
153  
-        """
154  
-        self._encoding = val
155  
-        if hasattr(self, '_get'):
156  
-            del self._get
157  
-        if hasattr(self, '_post'):
158  
-            del self._post
159  
-
160  
-    def _initialize_handlers(self):
161  
-        self._upload_handlers = [uploadhandler.load_handler(handler, self)
162  
-                                 for handler in settings.FILE_UPLOAD_HANDLERS]
163  
-
164  
-    @property
165  
-    def upload_handlers(self):
166  
-        if not self._upload_handlers:
167  
-            # If there are no upload handlers defined, initialize them from settings.
168  
-            self._initialize_handlers()
169  
-        return self._upload_handlers
170  
-
171  
-    @upload_handlers.setter
172  
-    def upload_handlers(self, upload_handlers):
173  
-        if hasattr(self, '_files'):
174  
-            raise AttributeError("You cannot set the upload handlers after the upload has been processed.")
175  
-        self._upload_handlers = upload_handlers
176  
-
177 142
     def parse_file_upload(self, META, post_data):
178 143
         """Returns a tuple of (POST QueryDict, FILES MultiValueDict)."""
179 144
         self.upload_handlers = ImmutableList(
@@ -195,43 +160,6 @@ def body(self):
195 160
             self._stream = BytesIO(self._body)
196 161
         return self._body
197 162
 
198  
-    def _mark_post_parse_error(self):
199  
-        self._post = QueryDict('')
200  
-        self._files = MultiValueDict()
201  
-        self._post_parse_error = True
202  
-
203  
-    def _load_post_and_files(self):
204  
-        """Populate self._post and self._files if the content-type is a form type"""
205  
-        if self.method != 'POST':
206  
-            self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict()
207  
-            return
208  
-        if self._read_started and not hasattr(self, '_body'):
209  
-            self._mark_post_parse_error()
210  
-            return
211  
-
212  
-        if self.META.get('CONTENT_TYPE', '').startswith('multipart/form-data'):
213  
-            if hasattr(self, '_body'):
214  
-                # Use already read data
215  
-                data = BytesIO(self._body)
216  
-            else:
217  
-                data = self
218  
-            try:
219  
-                self._post, self._files = self.parse_file_upload(self.META, data)
220  
-            except:
221  
-                # An error occured while parsing POST data. Since when
222  
-                # formatting the error the request handler might access
223  
-                # self.POST, set self._post and self._file to prevent
224  
-                # attempts to parse POST data again.
225  
-                # Mark that an error occured. This allows self.__repr__ to
226  
-                # be explicit about it instead of simply representing an
227  
-                # empty POST
228  
-                self._mark_post_parse_error()
229  
-                raise
230  
-        elif self.META.get('CONTENT_TYPE', '').startswith('application/x-www-form-urlencoded'):
231  
-            self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict()
232  
-        else:
233  
-            self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict()
234  
-
235 163
     ## File-like and iterator interface.
236 164
     ##
237 165
     ## Expects self._stream to be set to an appropriate source of bytes by
1  tests/requests/tests.py
@@ -94,6 +94,7 @@ def test_wsgirequest_repr(self):
94 94
         request.POST = {'post-key': 'post-value'}
95 95
         request.COOKIES = {'post-key': 'post-value'}
96 96
         request.META = {'post-key': 'post-value'}
  97
+        request._post_loaded = True
97 98
         self.assertEqual(repr(request), str_prefix("<WSGIRequest\npath:/somepath/,\nGET:{%(_)s'get-key': %(_)s'get-value'},\nPOST:{%(_)s'post-key': %(_)s'post-value'},\nCOOKIES:{%(_)s'post-key': %(_)s'post-value'},\nMETA:{%(_)s'post-key': %(_)s'post-value'}>"))
98 99
         self.assertEqual(build_request_repr(request), repr(request))
99 100
         self.assertEqual(build_request_repr(request, path_override='/otherpath/', GET_override={'a': 'b'}, POST_override={'c': 'd'}, COOKIES_override={'e': 'f'}, META_override={'g': 'h'}),
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.