Fixed #4909 -- Fixed a race condition with middleware initialisation in multi-threade...
[django.git] / django / core / handlers / wsgi.py
blob99d400d1bb5fe96e12f501f3f93f49b325a59128
1 from django.core.handlers.base import BaseHandler
2 from django.core import signals
3 from django.dispatch import dispatcher
4 from django.utils import datastructures
5 from django.utils.encoding import force_unicode
6 from django import http
7 from pprint import pformat
8 from shutil import copyfileobj
9 from threading import Lock
10 try:
11 from cStringIO import StringIO
12 except ImportError:
13 from StringIO import StringIO
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',
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)
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()
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)
104 def get_full_path(self):
105 return '%s%s' % (self.path, self.environ.get('QUERY_STRING', '') and ('?' + self.environ.get('QUERY_STRING', '')) or '')
107 def is_secure(self):
108 return 'HTTPS' in self.environ and self.environ['HTTPS'] == 'on'
110 def _load_post_and_files(self):
111 # Populates self._post and self._files
112 if self.method == 'POST':
113 if self.environ.get('CONTENT_TYPE', '').startswith('multipart'):
114 header_dict = dict([(k, v) for k, v in self.environ.items() if k.startswith('HTTP_')])
115 header_dict['Content-Type'] = self.environ.get('CONTENT_TYPE', '')
116 self._post, self._files = http.parse_file_upload(header_dict, self.raw_post_data)
117 else:
118 self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
119 else:
120 self._post, self._files = http.QueryDict('', encoding=self._encoding), datastructures.MultiValueDict()
122 def _get_request(self):
123 if not hasattr(self, '_request'):
124 self._request = datastructures.MergeDict(self.POST, self.GET)
125 return self._request
127 def _get_get(self):
128 if not hasattr(self, '_get'):
129 # The WSGI spec says 'QUERY_STRING' may be absent.
130 self._get = http.QueryDict(self.environ.get('QUERY_STRING', ''), encoding=self._encoding)
131 return self._get
133 def _set_get(self, get):
134 self._get = get
136 def _get_post(self):
137 if not hasattr(self, '_post'):
138 self._load_post_and_files()
139 return self._post
141 def _set_post(self, post):
142 self._post = post
144 def _get_cookies(self):
145 if not hasattr(self, '_cookies'):
146 self._cookies = http.parse_cookie(self.environ.get('HTTP_COOKIE', ''))
147 return self._cookies
149 def _set_cookies(self, cookies):
150 self._cookies = cookies
152 def _get_files(self):
153 if not hasattr(self, '_files'):
154 self._load_post_and_files()
155 return self._files
157 def _get_raw_post_data(self):
158 try:
159 return self._raw_post_data
160 except AttributeError:
161 buf = StringIO()
162 try:
163 # CONTENT_LENGTH might be absent if POST doesn't have content at all (lighttpd)
164 content_length = int(self.environ.get('CONTENT_LENGTH', 0))
165 except ValueError: # if CONTENT_LENGTH was empty string or not an integer
166 content_length = 0
167 safe_copyfileobj(self.environ['wsgi.input'], buf, size=content_length)
168 self._raw_post_data = buf.getvalue()
169 buf.close()
170 return self._raw_post_data
172 GET = property(_get_get, _set_get)
173 POST = property(_get_post, _set_post)
174 COOKIES = property(_get_cookies, _set_cookies)
175 FILES = property(_get_files)
176 REQUEST = property(_get_request)
177 raw_post_data = property(_get_raw_post_data)
179 class WSGIHandler(BaseHandler):
180 initLock = Lock()
182 def __call__(self, environ, start_response):
183 from django.conf import settings
185 # Set up middleware if needed. We couldn't do this earlier, because
186 # settings weren't available.
187 if self._request_middleware is None:
188 self.initLock.acquire()
189 # Check that middleware is still uninitialised.
190 if self._request_middleware is None:
191 self.load_middleware()
192 self.initLock.release()
194 dispatcher.send(signal=signals.request_started)
195 try:
196 request = WSGIRequest(environ)
197 response = self.get_response(request)
199 # Apply response middleware
200 for middleware_method in self._response_middleware:
201 response = middleware_method(request, response)
203 finally:
204 dispatcher.send(signal=signals.request_finished)
206 try:
207 status_text = STATUS_CODE_TEXT[response.status_code]
208 except KeyError:
209 status_text = 'UNKNOWN STATUS CODE'
210 status = '%s %s' % (response.status_code, status_text)
211 response_headers = [(str(k), str(v)) for k, v in response.headers.items()]
212 for c in response.cookies.values():
213 response_headers.append(('Set-Cookie', str(c.output(header=''))))
214 start_response(status, response_headers)
215 return response