|
1 from threading import Lock |
|
2 from pprint import pformat |
|
3 try: |
|
4 from cStringIO import StringIO |
|
5 except ImportError: |
|
6 from StringIO import StringIO |
|
7 |
|
8 from django import http |
|
9 from django.core import signals |
|
10 from django.core.handlers.base import BaseHandler |
|
11 from django.dispatch import dispatcher |
|
12 from django.utils import datastructures |
|
13 from django.utils.encoding import force_unicode |
|
14 |
|
15 # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html |
|
16 STATUS_CODE_TEXT = { |
|
17 100: 'CONTINUE', |
|
18 101: 'SWITCHING PROTOCOLS', |
|
19 200: 'OK', |
|
20 201: 'CREATED', |
|
21 202: 'ACCEPTED', |
|
22 203: 'NON-AUTHORITATIVE INFORMATION', |
|
23 204: 'NO CONTENT', |
|
24 205: 'RESET CONTENT', |
|
25 206: 'PARTIAL CONTENT', |
|
26 300: 'MULTIPLE CHOICES', |
|
27 301: 'MOVED PERMANENTLY', |
|
28 302: 'FOUND', |
|
29 303: 'SEE OTHER', |
|
30 304: 'NOT MODIFIED', |
|
31 305: 'USE PROXY', |
|
32 306: 'RESERVED', |
|
33 307: 'TEMPORARY REDIRECT', |
|
34 400: 'BAD REQUEST', |
|
35 401: 'UNAUTHORIZED', |
|
36 402: 'PAYMENT REQUIRED', |
|
37 403: 'FORBIDDEN', |
|
38 404: 'NOT FOUND', |
|
39 405: 'METHOD NOT ALLOWED', |
|
40 406: 'NOT ACCEPTABLE', |
|
41 407: 'PROXY AUTHENTICATION REQUIRED', |
|
42 408: 'REQUEST TIMEOUT', |
|
43 409: 'CONFLICT', |
|
44 410: 'GONE', |
|
45 411: 'LENGTH REQUIRED', |
|
46 412: 'PRECONDITION FAILED', |
|
47 413: 'REQUEST ENTITY TOO LARGE', |
|
48 414: 'REQUEST-URI TOO LONG', |
|
49 415: 'UNSUPPORTED MEDIA TYPE', |
|
50 416: 'REQUESTED RANGE NOT SATISFIABLE', |
|
51 417: 'EXPECTATION FAILED', |
|
52 500: 'INTERNAL SERVER ERROR', |
|
53 501: 'NOT IMPLEMENTED', |
|
54 502: 'BAD GATEWAY', |
|
55 503: 'SERVICE UNAVAILABLE', |
|
56 504: 'GATEWAY TIMEOUT', |
|
57 505: 'HTTP VERSION NOT SUPPORTED', |
|
58 } |
|
59 |
|
60 def safe_copyfileobj(fsrc, fdst, length=16*1024, size=0): |
|
61 """ |
|
62 A version of shutil.copyfileobj that will not read more than 'size' bytes. |
|
63 This makes it safe from clients sending more than CONTENT_LENGTH bytes of |
|
64 data in the body. |
|
65 """ |
|
66 if not size: |
|
67 return |
|
68 while size > 0: |
|
69 buf = fsrc.read(min(length, size)) |
|
70 if not buf: |
|
71 break |
|
72 fdst.write(buf) |
|
73 size -= len(buf) |
|
74 |
|
75 class WSGIRequest(http.HttpRequest): |
|
76 def __init__(self, environ): |
|
77 self.environ = environ |
|
78 self.path = force_unicode(environ['PATH_INFO']) |
|
79 self.META = environ |
|
80 self.method = environ['REQUEST_METHOD'].upper() |
|
81 |
|
82 def __repr__(self): |
|
83 # Since this is called as part of error handling, we need to be very |
|
84 # robust against potentially malformed input. |
|
85 try: |
|
86 get = pformat(self.GET) |
|
87 except: |
|
88 get = '<could not parse>' |
|
89 try: |
|
90 post = pformat(self.POST) |
|
91 except: |
|
92 post = '<could not parse>' |
|
93 try: |
|
94 cookies = pformat(self.COOKIES) |
|
95 except: |
|
96 cookies = '<could not parse>' |
|
97 try: |
|
98 meta = pformat(self.META) |
|
99 except: |
|
100 meta = '<could not parse>' |
|
101 return '<WSGIRequest\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \ |
|
102 (get, post, cookies, meta) |
|
103 |
|
104 def get_full_path(self): |
|
105 return '%s%s' % (self.path, self.environ.get('QUERY_STRING', '') and ('?' + self.environ.get('QUERY_STRING', '')) or '') |
|
106 |
|
107 def is_secure(self): |
|
108 return 'wsgi.url_scheme' in self.environ \ |
|
109 and self.environ['wsgi.url_scheme'] == 'https' |
|
110 |
|
111 def _load_post_and_files(self): |
|
112 # Populates self._post and self._files |
|
113 if self.method == 'POST': |
|
114 if self.environ.get('CONTENT_TYPE', '').startswith('multipart'): |
|
115 header_dict = dict([(k, v) for k, v in self.environ.items() if k.startswith('HTTP_')]) |
|
116 header_dict['Content-Type'] = self.environ.get('CONTENT_TYPE', '') |
|
117 self._post, self._files = http.parse_file_upload(header_dict, self.raw_post_data) |
|
118 else: |
|
119 self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict() |
|
120 else: |
|
121 self._post, self._files = http.QueryDict('', encoding=self._encoding), datastructures.MultiValueDict() |
|
122 |
|
123 def _get_request(self): |
|
124 if not hasattr(self, '_request'): |
|
125 self._request = datastructures.MergeDict(self.POST, self.GET) |
|
126 return self._request |
|
127 |
|
128 def _get_get(self): |
|
129 if not hasattr(self, '_get'): |
|
130 # The WSGI spec says 'QUERY_STRING' may be absent. |
|
131 self._get = http.QueryDict(self.environ.get('QUERY_STRING', ''), encoding=self._encoding) |
|
132 return self._get |
|
133 |
|
134 def _set_get(self, get): |
|
135 self._get = get |
|
136 |
|
137 def _get_post(self): |
|
138 if not hasattr(self, '_post'): |
|
139 self._load_post_and_files() |
|
140 return self._post |
|
141 |
|
142 def _set_post(self, post): |
|
143 self._post = post |
|
144 |
|
145 def _get_cookies(self): |
|
146 if not hasattr(self, '_cookies'): |
|
147 self._cookies = http.parse_cookie(self.environ.get('HTTP_COOKIE', '')) |
|
148 return self._cookies |
|
149 |
|
150 def _set_cookies(self, cookies): |
|
151 self._cookies = cookies |
|
152 |
|
153 def _get_files(self): |
|
154 if not hasattr(self, '_files'): |
|
155 self._load_post_and_files() |
|
156 return self._files |
|
157 |
|
158 def _get_raw_post_data(self): |
|
159 try: |
|
160 return self._raw_post_data |
|
161 except AttributeError: |
|
162 buf = StringIO() |
|
163 try: |
|
164 # CONTENT_LENGTH might be absent if POST doesn't have content at all (lighttpd) |
|
165 content_length = int(self.environ.get('CONTENT_LENGTH', 0)) |
|
166 except ValueError: # if CONTENT_LENGTH was empty string or not an integer |
|
167 content_length = 0 |
|
168 if content_length > 0: |
|
169 safe_copyfileobj(self.environ['wsgi.input'], buf, |
|
170 size=content_length) |
|
171 self._raw_post_data = buf.getvalue() |
|
172 buf.close() |
|
173 return self._raw_post_data |
|
174 |
|
175 GET = property(_get_get, _set_get) |
|
176 POST = property(_get_post, _set_post) |
|
177 COOKIES = property(_get_cookies, _set_cookies) |
|
178 FILES = property(_get_files) |
|
179 REQUEST = property(_get_request) |
|
180 raw_post_data = property(_get_raw_post_data) |
|
181 |
|
182 class WSGIHandler(BaseHandler): |
|
183 initLock = Lock() |
|
184 request_class = WSGIRequest |
|
185 |
|
186 def __call__(self, environ, start_response): |
|
187 from django.conf import settings |
|
188 |
|
189 # Set up middleware if needed. We couldn't do this earlier, because |
|
190 # settings weren't available. |
|
191 if self._request_middleware is None: |
|
192 self.initLock.acquire() |
|
193 # Check that middleware is still uninitialised. |
|
194 if self._request_middleware is None: |
|
195 self.load_middleware() |
|
196 self.initLock.release() |
|
197 |
|
198 dispatcher.send(signal=signals.request_started) |
|
199 try: |
|
200 try: |
|
201 request = self.request_class(environ) |
|
202 except UnicodeDecodeError: |
|
203 response = http.HttpResponseBadRequest() |
|
204 else: |
|
205 response = self.get_response(request) |
|
206 |
|
207 # Apply response middleware |
|
208 for middleware_method in self._response_middleware: |
|
209 response = middleware_method(request, response) |
|
210 response = self.apply_response_fixes(request, response) |
|
211 finally: |
|
212 dispatcher.send(signal=signals.request_finished) |
|
213 |
|
214 try: |
|
215 status_text = STATUS_CODE_TEXT[response.status_code] |
|
216 except KeyError: |
|
217 status_text = 'UNKNOWN STATUS CODE' |
|
218 status = '%s %s' % (response.status_code, status_text) |
|
219 response_headers = [(str(k), str(v)) for k, v in response.items()] |
|
220 for c in response.cookies.values(): |
|
221 response_headers.append(('Set-Cookie', str(c.output(header='')))) |
|
222 start_response(status, response_headers) |
|
223 return response |
|
224 |