| 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 |
self._raw_post_data = '' |
|---|
| 116 |
self._post, self._files = self.parse_file_upload(self.META, self.environ['wsgi.input']) |
|---|
| 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() |
|---|
| 121 |
|
|---|
| 122 |
def _get_request(self): |
|---|
| 123 |
if not hasattr(self, '_request'): |
|---|
| 124 |
self._request = datastructures.MergeDict(self.POST, self.GET) |
|---|
| 125 |
return self._request |
|---|
| 126 |
|
|---|
| 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 |
|---|
| 132 |
|
|---|
| 133 |
def _set_get(self, get): |
|---|
| 134 |
self._get = get |
|---|
| 135 |
|
|---|
| 136 |
def _get_post(self): |
|---|
| 137 |
if not hasattr(self, '_post'): |
|---|
| 138 |
self._load_post_and_files() |
|---|
| 139 |
return self._post |
|---|
| 140 |
|
|---|
| 141 |
def _set_post(self, post): |
|---|
| 142 |
self._post = post |
|---|
| 143 |
|
|---|
| 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 |
|---|
| 148 |
|
|---|
| 149 |
def _set_cookies(self, cookies): |
|---|
| 150 |
self._cookies = cookies |
|---|
| 151 |
|
|---|
| 152 |
def _get_files(self): |
|---|
| 153 |
if not hasattr(self, '_files'): |
|---|
| 154 |
self._load_post_and_files() |
|---|
| 155 |
return self._files |
|---|
| 156 |
|
|---|
| 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 |
if content_length > 0: |
|---|
| 168 |
safe_copyfileobj(self.environ['wsgi.input'], buf, |
|---|
| 169 |
size=content_length) |
|---|
| 170 |
self._raw_post_data = buf.getvalue() |
|---|
| 171 |
buf.close() |
|---|
| 172 |
return self._raw_post_data |
|---|
| 173 |
|
|---|
| 174 |
GET = property(_get_get, _set_get) |
|---|
| 175 |
POST = property(_get_post, _set_post) |
|---|
| 176 |
COOKIES = property(_get_cookies, _set_cookies) |
|---|
| 177 |
FILES = property(_get_files) |
|---|
| 178 |
REQUEST = property(_get_request) |
|---|
| 179 |
raw_post_data = property(_get_raw_post_data) |
|---|
| 180 |
|
|---|
| 181 |
class WSGIHandler(BaseHandler): |
|---|
| 182 |
initLock = Lock() |
|---|
| 183 |
request_class = WSGIRequest |
|---|
| 184 |
|
|---|
| 185 |
def __call__(self, environ, start_response): |
|---|
| 186 |
from django.conf import settings |
|---|
| 187 |
|
|---|
| 188 |
# Set up middleware if needed. We couldn't do this earlier, because |
|---|
| 189 |
# settings weren't available. |
|---|
| 190 |
if self._request_middleware is None: |
|---|
| 191 |
self.initLock.acquire() |
|---|
| 192 |
# Check that middleware is still uninitialised. |
|---|
| 193 |
if self._request_middleware is None: |
|---|
| 194 |
self.load_middleware() |
|---|
| 195 |
self.initLock.release() |
|---|
| 196 |
|
|---|
| 197 |
dispatcher.send(signal=signals.request_started) |
|---|
| 198 |
try: |
|---|
| 199 |
try: |
|---|
| 200 |
request = self.request_class(environ) |
|---|
| 201 |
except UnicodeDecodeError: |
|---|
| 202 |
response = http.HttpResponseBadRequest() |
|---|
| 203 |
else: |
|---|
| 204 |
response = self.get_response(request) |
|---|
| 205 |
|
|---|
| 206 |
# Apply response middleware |
|---|
| 207 |
for middleware_method in self._response_middleware: |
|---|
| 208 |
response = middleware_method(request, response) |
|---|
| 209 |
response = self.apply_response_fixes(request, response) |
|---|
| 210 |
finally: |
|---|
| 211 |
dispatcher.send(signal=signals.request_finished) |
|---|
| 212 |
|
|---|
| 213 |
try: |
|---|
| 214 |
status_text = STATUS_CODE_TEXT[response.status_code] |
|---|
| 215 |
except KeyError: |
|---|
| 216 |
status_text = 'UNKNOWN STATUS CODE' |
|---|
| 217 |
status = '%s %s' % (response.status_code, status_text) |
|---|
| 218 |
response_headers = [(str(k), str(v)) for k, v in response.items()] |
|---|
| 219 |
for c in response.cookies.values(): |
|---|
| 220 |
response_headers.append(('Set-Cookie', str(c.output(header='')))) |
|---|
| 221 |
start_response(status, response_headers) |
|---|
| 222 |
return response |
|---|