| 1 |
""" |
|---|
| 2 |
BaseHTTPServer that implements the Python WSGI protocol (PEP 333, rev 1.21). |
|---|
| 3 |
|
|---|
| 4 |
Adapted from wsgiref.simple_server: http://svn.eby-sarna.com/wsgiref/ |
|---|
| 5 |
|
|---|
| 6 |
This is a simple server for use in testing or debugging Django apps. It hasn't |
|---|
| 7 |
been reviewed for security issues. Don't use it for production use. |
|---|
| 8 |
""" |
|---|
| 9 |
|
|---|
| 10 |
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer |
|---|
| 11 |
import mimetypes |
|---|
| 12 |
import os |
|---|
| 13 |
import re |
|---|
| 14 |
import stat |
|---|
| 15 |
import sys |
|---|
| 16 |
import urllib |
|---|
| 17 |
|
|---|
| 18 |
from django.utils.http import http_date |
|---|
| 19 |
|
|---|
| 20 |
__version__ = "0.1" |
|---|
| 21 |
__all__ = ['WSGIServer','WSGIRequestHandler'] |
|---|
| 22 |
|
|---|
| 23 |
server_version = "WSGIServer/" + __version__ |
|---|
| 24 |
sys_version = "Python/" + sys.version.split()[0] |
|---|
| 25 |
software_version = server_version + ' ' + sys_version |
|---|
| 26 |
|
|---|
| 27 |
class WSGIServerException(Exception): |
|---|
| 28 |
pass |
|---|
| 29 |
|
|---|
| 30 |
class FileWrapper(object): |
|---|
| 31 |
"""Wrapper to convert file-like objects to iterables""" |
|---|
| 32 |
|
|---|
| 33 |
def __init__(self, filelike, blksize=8192): |
|---|
| 34 |
self.filelike = filelike |
|---|
| 35 |
self.blksize = blksize |
|---|
| 36 |
if hasattr(filelike,'close'): |
|---|
| 37 |
self.close = filelike.close |
|---|
| 38 |
|
|---|
| 39 |
def __getitem__(self,key): |
|---|
| 40 |
data = self.filelike.read(self.blksize) |
|---|
| 41 |
if data: |
|---|
| 42 |
return data |
|---|
| 43 |
raise IndexError |
|---|
| 44 |
|
|---|
| 45 |
def __iter__(self): |
|---|
| 46 |
return self |
|---|
| 47 |
|
|---|
| 48 |
def next(self): |
|---|
| 49 |
data = self.filelike.read(self.blksize) |
|---|
| 50 |
if data: |
|---|
| 51 |
return data |
|---|
| 52 |
raise StopIteration |
|---|
| 53 |
|
|---|
| 54 |
# Regular expression that matches `special' characters in parameters, the |
|---|
| 55 |
# existence of which force quoting of the parameter value. |
|---|
| 56 |
tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]') |
|---|
| 57 |
|
|---|
| 58 |
def _formatparam(param, value=None, quote=1): |
|---|
| 59 |
"""Convenience function to format and return a key=value pair. |
|---|
| 60 |
|
|---|
| 61 |
This will quote the value if needed or if quote is true. |
|---|
| 62 |
""" |
|---|
| 63 |
if value is not None and len(value) > 0: |
|---|
| 64 |
if quote or tspecials.search(value): |
|---|
| 65 |
value = value.replace('\\', '\\\\').replace('"', r'\"') |
|---|
| 66 |
return '%s="%s"' % (param, value) |
|---|
| 67 |
else: |
|---|
| 68 |
return '%s=%s' % (param, value) |
|---|
| 69 |
else: |
|---|
| 70 |
return param |
|---|
| 71 |
|
|---|
| 72 |
class Headers(object): |
|---|
| 73 |
"""Manage a collection of HTTP response headers""" |
|---|
| 74 |
def __init__(self,headers): |
|---|
| 75 |
if not isinstance(headers, list): |
|---|
| 76 |
raise TypeError("Headers must be a list of name/value tuples") |
|---|
| 77 |
self._headers = headers |
|---|
| 78 |
|
|---|
| 79 |
def __len__(self): |
|---|
| 80 |
"""Return the total number of headers, including duplicates.""" |
|---|
| 81 |
return len(self._headers) |
|---|
| 82 |
|
|---|
| 83 |
def __setitem__(self, name, val): |
|---|
| 84 |
"""Set the value of a header.""" |
|---|
| 85 |
del self[name] |
|---|
| 86 |
self._headers.append((name, val)) |
|---|
| 87 |
|
|---|
| 88 |
def __delitem__(self,name): |
|---|
| 89 |
"""Delete all occurrences of a header, if present. |
|---|
| 90 |
|
|---|
| 91 |
Does *not* raise an exception if the header is missing. |
|---|
| 92 |
""" |
|---|
| 93 |
name = name.lower() |
|---|
| 94 |
self._headers[:] = [kv for kv in self._headers if kv[0].lower()<>name] |
|---|
| 95 |
|
|---|
| 96 |
def __getitem__(self,name): |
|---|
| 97 |
"""Get the first header value for 'name' |
|---|
| 98 |
|
|---|
| 99 |
Return None if the header is missing instead of raising an exception. |
|---|
| 100 |
|
|---|
| 101 |
Note that if the header appeared multiple times, the first exactly which |
|---|
| 102 |
occurrance gets returned is undefined. Use getall() to get all |
|---|
| 103 |
the values matching a header field name. |
|---|
| 104 |
""" |
|---|
| 105 |
return self.get(name) |
|---|
| 106 |
|
|---|
| 107 |
def has_key(self, name): |
|---|
| 108 |
"""Return true if the message contains the header.""" |
|---|
| 109 |
return self.get(name) is not None |
|---|
| 110 |
|
|---|
| 111 |
__contains__ = has_key |
|---|
| 112 |
|
|---|
| 113 |
def get_all(self, name): |
|---|
| 114 |
"""Return a list of all the values for the named field. |
|---|
| 115 |
|
|---|
| 116 |
These will be sorted in the order they appeared in the original header |
|---|
| 117 |
list or were added to this instance, and may contain duplicates. Any |
|---|
| 118 |
fields deleted and re-inserted are always appended to the header list. |
|---|
| 119 |
If no fields exist with the given name, returns an empty list. |
|---|
| 120 |
""" |
|---|
| 121 |
name = name.lower() |
|---|
| 122 |
return [kv[1] for kv in self._headers if kv[0].lower()==name] |
|---|
| 123 |
|
|---|
| 124 |
|
|---|
| 125 |
def get(self,name,default=None): |
|---|
| 126 |
"""Get the first header value for 'name', or return 'default'""" |
|---|
| 127 |
name = name.lower() |
|---|
| 128 |
for k,v in self._headers: |
|---|
| 129 |
if k.lower()==name: |
|---|
| 130 |
return v |
|---|
| 131 |
return default |
|---|
| 132 |
|
|---|
| 133 |
def keys(self): |
|---|
| 134 |
"""Return a list of all the header field names. |
|---|
| 135 |
|
|---|
| 136 |
These will be sorted in the order they appeared in the original header |
|---|
| 137 |
list, or were added to this instance, and may contain duplicates. |
|---|
| 138 |
Any fields deleted and re-inserted are always appended to the header |
|---|
| 139 |
list. |
|---|
| 140 |
""" |
|---|
| 141 |
return [k for k, v in self._headers] |
|---|
| 142 |
|
|---|
| 143 |
def values(self): |
|---|
| 144 |
"""Return a list of all header values. |
|---|
| 145 |
|
|---|
| 146 |
These will be sorted in the order they appeared in the original header |
|---|
| 147 |
list, or were added to this instance, and may contain duplicates. |
|---|
| 148 |
Any fields deleted and re-inserted are always appended to the header |
|---|
| 149 |
list. |
|---|
| 150 |
""" |
|---|
| 151 |
return [v for k, v in self._headers] |
|---|
| 152 |
|
|---|
| 153 |
def items(self): |
|---|
| 154 |
"""Get all the header fields and values. |
|---|
| 155 |
|
|---|
| 156 |
These will be sorted in the order they were in the original header |
|---|
| 157 |
list, or were added to this instance, and may contain duplicates. |
|---|
| 158 |
Any fields deleted and re-inserted are always appended to the header |
|---|
| 159 |
list. |
|---|
| 160 |
""" |
|---|
| 161 |
return self._headers[:] |
|---|
| 162 |
|
|---|
| 163 |
def __repr__(self): |
|---|
| 164 |
return "Headers(%s)" % `self._headers` |
|---|
| 165 |
|
|---|
| 166 |
def __str__(self): |
|---|
| 167 |
"""str() returns the formatted headers, complete with end line, |
|---|
| 168 |
suitable for direct HTTP transmission.""" |
|---|
| 169 |
return '\r\n'.join(["%s: %s" % kv for kv in self._headers]+['','']) |
|---|
| 170 |
|
|---|
| 171 |
def setdefault(self,name,value): |
|---|
| 172 |
"""Return first matching header value for 'name', or 'value' |
|---|
| 173 |
|
|---|
| 174 |
If there is no header named 'name', add a new header with name 'name' |
|---|
| 175 |
and value 'value'.""" |
|---|
| 176 |
result = self.get(name) |
|---|
| 177 |
if result is None: |
|---|
| 178 |
self._headers.append((name,value)) |
|---|
| 179 |
return value |
|---|
| 180 |
else: |
|---|
| 181 |
return result |
|---|
| 182 |
|
|---|
| 183 |
def add_header(self, _name, _value, **_params): |
|---|
| 184 |
"""Extended header setting. |
|---|
| 185 |
|
|---|
| 186 |
_name is the header field to add. keyword arguments can be used to set |
|---|
| 187 |
additional parameters for the header field, with underscores converted |
|---|
| 188 |
to dashes. Normally the parameter will be added as key="value" unless |
|---|
| 189 |
value is None, in which case only the key will be added. |
|---|
| 190 |
|
|---|
| 191 |
Example: |
|---|
| 192 |
|
|---|
| 193 |
h.add_header('content-disposition', 'attachment', filename='bud.gif') |
|---|
| 194 |
|
|---|
| 195 |
Note that unlike the corresponding 'email.Message' method, this does |
|---|
| 196 |
*not* handle '(charset, language, value)' tuples: all values must be |
|---|
| 197 |
strings or None. |
|---|
| 198 |
""" |
|---|
| 199 |
parts = [] |
|---|
| 200 |
if _value is not None: |
|---|
| 201 |
parts.append(_value) |
|---|
| 202 |
for k, v in _params.items(): |
|---|
| 203 |
if v is None: |
|---|
| 204 |
parts.append(k.replace('_', '-')) |
|---|
| 205 |
else: |
|---|
| 206 |
parts.append(_formatparam(k.replace('_', '-'), v)) |
|---|
| 207 |
self._headers.append((_name, "; ".join(parts))) |
|---|
| 208 |
|
|---|
| 209 |
def guess_scheme(environ): |
|---|
| 210 |
"""Return a guess for whether 'wsgi.url_scheme' should be 'http' or 'https' |
|---|
| 211 |
""" |
|---|
| 212 |
if environ.get("HTTPS") in ('yes','on','1'): |
|---|
| 213 |
return 'https' |
|---|
| 214 |
else: |
|---|
| 215 |
return 'http' |
|---|
| 216 |
|
|---|
| 217 |
_hop_headers = { |
|---|
| 218 |
'connection':1, 'keep-alive':1, 'proxy-authenticate':1, |
|---|
| 219 |
'proxy-authorization':1, 'te':1, 'trailers':1, 'transfer-encoding':1, |
|---|
| 220 |
'upgrade':1 |
|---|
| 221 |
} |
|---|
| 222 |
|
|---|
| 223 |
def is_hop_by_hop(header_name): |
|---|
| 224 |
"""Return true if 'header_name' is an HTTP/1.1 "Hop-by-Hop" header""" |
|---|
| 225 |
return header_name.lower() in _hop_headers |
|---|
| 226 |
|
|---|
| 227 |
class ServerHandler(object): |
|---|
| 228 |
"""Manage the invocation of a WSGI application""" |
|---|
| 229 |
|
|---|
| 230 |
# Configuration parameters; can override per-subclass or per-instance |
|---|
| 231 |
wsgi_version = (1,0) |
|---|
| 232 |
wsgi_multithread = True |
|---|
| 233 |
wsgi_multiprocess = True |
|---|
| 234 |
wsgi_run_once = False |
|---|
| 235 |
|
|---|
| 236 |
origin_server = True # We are transmitting direct to client |
|---|
| 237 |
http_version = "1.0" # Version that should be used for response |
|---|
| 238 |
server_software = software_version |
|---|
| 239 |
|
|---|
| 240 |
# os_environ is used to supply configuration from the OS environment: |
|---|
| 241 |
# by default it's a copy of 'os.environ' as of import time, but you can |
|---|
| 242 |
# override this in e.g. your __init__ method. |
|---|
| 243 |
os_environ = dict(os.environ.items()) |
|---|
| 244 |
|
|---|
| 245 |
# Collaborator classes |
|---|
| 246 |
wsgi_file_wrapper = FileWrapper # set to None to disable |
|---|
| 247 |
headers_class = Headers # must be a Headers-like class |
|---|
| 248 |
|
|---|
| 249 |
# Error handling (also per-subclass or per-instance) |
|---|
| 250 |
traceback_limit = None # Print entire traceback to self.get_stderr() |
|---|
| 251 |
error_status = "500 INTERNAL SERVER ERROR" |
|---|
| 252 |
error_headers = [('Content-Type','text/plain')] |
|---|
| 253 |
|
|---|
| 254 |
# State variables (don't mess with these) |
|---|
| 255 |
status = result = None |
|---|
| 256 |
headers_sent = False |
|---|
| 257 |
headers = None |
|---|
| 258 |
bytes_sent = 0 |
|---|
| 259 |
|
|---|
| 260 |
def __init__(self, stdin, stdout, stderr, environ, multithread=True, |
|---|
| 261 |
multiprocess=False): |
|---|
| 262 |
self.stdin = stdin |
|---|
| 263 |
self.stdout = stdout |
|---|
| 264 |
self.stderr = stderr |
|---|
| 265 |
self.base_env = environ |
|---|
| 266 |
self.wsgi_multithread = multithread |
|---|
| 267 |
self.wsgi_multiprocess = multiprocess |
|---|
| 268 |
|
|---|
| 269 |
def run(self, application): |
|---|
| 270 |
"""Invoke the application""" |
|---|
| 271 |
# Note to self: don't move the close()! Asynchronous servers shouldn't |
|---|
| 272 |
# call close() from finish_response(), so if you close() anywhere but |
|---|
| 273 |
# the double-error branch here, you'll break asynchronous servers by |
|---|
| 274 |
# prematurely closing. Async servers must return from 'run()' without |
|---|
| 275 |
# closing if there might still be output to iterate over. |
|---|
| 276 |
try: |
|---|
| 277 |
self.setup_environ() |
|---|
| 278 |
self.result = application(self.environ, self.start_response) |
|---|
| 279 |
self.finish_response() |
|---|
| 280 |
except: |
|---|
| 281 |
try: |
|---|
| 282 |
self.handle_error() |
|---|
| 283 |
except: |
|---|
| 284 |
# If we get an error handling an error, just give up already! |
|---|
| 285 |
self.close() |
|---|
| 286 |
raise # ...and let the actual server figure it out. |
|---|
| 287 |
|
|---|
| 288 |
def setup_environ(self): |
|---|
| 289 |
"""Set up the environment for one request""" |
|---|
| 290 |
|
|---|
| 291 |
env = self.environ = self.os_environ.copy() |
|---|
| 292 |
self.add_cgi_vars() |
|---|
| 293 |
|
|---|
| 294 |
env['wsgi.input'] = self.get_stdin() |
|---|
| 295 |
env['wsgi.errors'] = self.get_stderr() |
|---|
| 296 |
env['wsgi.version'] = self.wsgi_version |
|---|
| 297 |
env['wsgi.run_once'] = self.wsgi_run_once |
|---|
| 298 |
env['wsgi.url_scheme'] = self.get_scheme() |
|---|
| 299 |
env['wsgi.multithread'] = self.wsgi_multithread |
|---|
| 300 |
env['wsgi.multiprocess'] = self.wsgi_multiprocess |
|---|
| 301 |
|
|---|
| 302 |
if self.wsgi_file_wrapper is not None: |
|---|
| 303 |
env['wsgi.file_wrapper'] = self.wsgi_file_wrapper |
|---|
| 304 |
|
|---|
| 305 |
if self.origin_server and self.server_software: |
|---|
| 306 |
env.setdefault('SERVER_SOFTWARE',self.server_software) |
|---|
| 307 |
|
|---|
| 308 |
def finish_response(self): |
|---|
| 309 |
"""Send any iterable data, then close self and the iterable |
|---|
| 310 |
|
|---|
| 311 |
Subclasses intended for use in asynchronous servers will |
|---|
| 312 |
want to redefine this method, such that it sets up callbacks |
|---|
| 313 |
in the event loop to iterate over the data, and to call |
|---|
| 314 |
'self.close()' once the response is finished. |
|---|
| 315 |
""" |
|---|
| 316 |
if not self.result_is_file() and not self.sendfile(): |
|---|
| 317 |
for data in self.result: |
|---|
| 318 |
self.write(data) |
|---|
| 319 |
self.finish_content() |
|---|
| 320 |
self.close() |
|---|
| 321 |
|
|---|
| 322 |
def get_scheme(self): |
|---|
| 323 |
"""Return the URL scheme being used""" |
|---|
| 324 |
return guess_scheme(self.environ) |
|---|
| 325 |
|
|---|
| 326 |
def set_content_length(self): |
|---|
| 327 |
"""Compute Content-Length or switch to chunked encoding if possible""" |
|---|
| 328 |
try: |
|---|
| 329 |
blocks = len(self.result) |
|---|
| 330 |
except (TypeError, AttributeError, NotImplementedError): |
|---|
| 331 |
pass |
|---|
| 332 |
else: |
|---|
| 333 |
if blocks==1: |
|---|
| 334 |
self.headers['Content-Length'] = str(self.bytes_sent) |
|---|
| 335 |
return |
|---|
| 336 |
# XXX Try for chunked encoding if origin server and client is 1.1 |
|---|
| 337 |
|
|---|
| 338 |
def cleanup_headers(self): |
|---|
| 339 |
"""Make any necessary header changes or defaults |
|---|
| 340 |
|
|---|
| 341 |
Subclasses can extend this to add other defaults. |
|---|
| 342 |
""" |
|---|
| 343 |
if 'Content-Length' not in self.headers: |
|---|
| 344 |
self.set_content_length() |
|---|
| 345 |
|
|---|
| 346 |
def start_response(self, status, headers,exc_info=None): |
|---|
| 347 |
"""'start_response()' callable as specified by PEP 333""" |
|---|
| 348 |
|
|---|
| 349 |
if exc_info: |
|---|
| 350 |
try: |
|---|
| 351 |
if self.headers_sent: |
|---|
| 352 |
# Re-raise original exception if headers sent |
|---|
| 353 |
raise exc_info[0], exc_info[1], exc_info[2] |
|---|
| 354 |
finally: |
|---|
| 355 |
exc_info = None # avoid dangling circular ref |
|---|
| 356 |
elif self.headers is not None: |
|---|
| 357 |
raise AssertionError("Headers already set!") |
|---|
| 358 |
|
|---|
| 359 |
assert isinstance(status, str),"Status must be a string" |
|---|
| 360 |
assert len(status)>=4,"Status must be at least 4 characters" |
|---|
| 361 |
assert int(status[:3]),"Status message must begin w/3-digit code" |
|---|
| 362 |
assert status[3]==" ", "Status message must have a space after code" |
|---|
| 363 |
if __debug__: |
|---|
| 364 |
for name,val in headers: |
|---|
| 365 |
assert isinstance(name, str),"Header names must be strings" |
|---|
| 366 |
assert isinstance(val, str),"Header values must be strings" |
|---|
| 367 |
assert not is_hop_by_hop(name),"Hop-by-hop headers not allowed" |
|---|
| 368 |
self.status = status |
|---|
| 369 |
self.headers = self.headers_class(headers) |
|---|
| 370 |
return self.write |
|---|
| 371 |
|
|---|
| 372 |
def send_preamble(self): |
|---|
| 373 |
"""Transmit version/status/date/server, via self._write()""" |
|---|
| 374 |
if self.origin_server: |
|---|
| 375 |
if self.client_is_modern(): |
|---|
| 376 |
self._write('HTTP/%s %s\r\n' % (self.http_version,self.status)) |
|---|
| 377 |
if 'Date' not in self.headers: |
|---|
| 378 |
self._write( |
|---|
| 379 |
'Date: %s\r\n' % http_date() |
|---|
| 380 |
) |
|---|
| 381 |
if self.server_software and 'Server' not in self.headers: |
|---|
| 382 |
self._write('Server: %s\r\n' % self.server_software) |
|---|
| 383 |
else: |
|---|
| 384 |
self._write('Status: %s\r\n' % self.status) |
|---|
| 385 |
|
|---|
| 386 |
def write(self, data): |
|---|
| 387 |
"""'write()' callable as specified by PEP 333""" |
|---|
| 388 |
|
|---|
| 389 |
assert isinstance(data, str), "write() argument must be string" |
|---|
| 390 |
|
|---|
| 391 |
if not self.status: |
|---|
| 392 |
raise AssertionError("write() before start_response()") |
|---|
| 393 |
|
|---|
| 394 |
elif not self.headers_sent: |
|---|
| 395 |
# Before the first output, send the stored headers |
|---|
| 396 |
self.bytes_sent = len(data) # make sure we know content-length |
|---|
| 397 |
self.send_headers() |
|---|
| 398 |
else: |
|---|
| 399 |
self.bytes_sent += len(data) |
|---|
| 400 |
|
|---|
| 401 |
# XXX check Content-Length and truncate if too many bytes written? |
|---|
| 402 |
|
|---|
| 403 |
# If data is too large, socket will choke, so write chunks no larger |
|---|
| 404 |
# than 32MB at a time. |
|---|
| 405 |
length = len(data) |
|---|
| 406 |
if length > 33554432: |
|---|
| 407 |
offset = 0 |
|---|
| 408 |
while offset < length: |
|---|
| 409 |
chunk_size = min(33554432, length) |
|---|
| 410 |
self._write(data[offset:offset+chunk_size]) |
|---|
| 411 |
self._flush() |
|---|
| 412 |
offset += chunk_size |
|---|
| 413 |
else: |
|---|
| 414 |
self._write(data) |
|---|
| 415 |
self._flush() |
|---|
| 416 |
|
|---|
| 417 |
def sendfile(self): |
|---|
| 418 |
"""Platform-specific file transmission |
|---|
| 419 |
|
|---|
| 420 |
Override this method in subclasses to support platform-specific |
|---|
| 421 |
file transmission. It is only called if the application's |
|---|
| 422 |
return iterable ('self.result') is an instance of |
|---|
| 423 |
'self.wsgi_file_wrapper'. |
|---|
| 424 |
|
|---|
| 425 |
This method should return a true value if it was able to actually |
|---|
| 426 |
transmit the wrapped file-like object using a platform-specific |
|---|
| 427 |
approach. It should return a false value if normal iteration |
|---|
| 428 |
should be used instead. An exception can be raised to indicate |
|---|
| 429 |
that transmission was attempted, but failed. |
|---|
| 430 |
|
|---|
| 431 |
NOTE: this method should call 'self.send_headers()' if |
|---|
| 432 |
'self.headers_sent' is false and it is going to attempt direct |
|---|
| 433 |
transmission of the file1. |
|---|
| 434 |
""" |
|---|
| 435 |
return False # No platform-specific transmission by default |
|---|
| 436 |
|
|---|
| 437 |
def finish_content(self): |
|---|
| 438 |
"""Ensure headers and content have both been sent""" |
|---|
| 439 |
if not self.headers_sent: |
|---|
| 440 |
self.headers['Content-Length'] = "0" |
|---|
| 441 |
self.send_headers() |
|---|
| 442 |
else: |
|---|
| 443 |
pass # XXX check if content-length was too short? |
|---|
| 444 |
|
|---|
| 445 |
def close(self): |
|---|
| 446 |
try: |
|---|
| 447 |
self.request_handler.log_request(self.status.split(' ',1)[0], self.bytes_sent) |
|---|
| 448 |
finally: |
|---|
| 449 |
try: |
|---|
| 450 |
if hasattr(self.result,'close'): |
|---|
| 451 |
self.result.close() |
|---|
| 452 |
finally: |
|---|
| 453 |
self.result = self.headers = self.status = self.environ = None |
|---|
| 454 |
self.bytes_sent = 0; self.headers_sent = False |
|---|
| 455 |
|
|---|
| 456 |
def send_headers(self): |
|---|
| 457 |
"""Transmit headers to the client, via self._write()""" |
|---|
| 458 |
self.cleanup_headers() |
|---|
| 459 |
self.headers_sent = True |
|---|
| 460 |
if not self.origin_server or self.client_is_modern(): |
|---|
| 461 |
self.send_preamble() |
|---|
| 462 |
self._write(str(self.headers)) |
|---|
| 463 |
|
|---|
| 464 |
def result_is_file(self): |
|---|
| 465 |
"""True if 'self.result' is an instance of 'self.wsgi_file_wrapper'""" |
|---|
| 466 |
wrapper = self.wsgi_file_wrapper |
|---|
| 467 |
return wrapper is not None and isinstance(self.result,wrapper) |
|---|
| 468 |
|
|---|
| 469 |
def client_is_modern(self): |
|---|
| 470 |
"""True if client can accept status and headers""" |
|---|
| 471 |
return self.environ['SERVER_PROTOCOL'].upper() != 'HTTP/0.9' |
|---|
| 472 |
|
|---|
| 473 |
def log_exception(self,exc_info): |
|---|
| 474 |
"""Log the 'exc_info' tuple in the server log |
|---|
| 475 |
|
|---|
| 476 |
Subclasses may override to retarget the output or change its format. |
|---|
| 477 |
""" |
|---|
| 478 |
try: |
|---|
| 479 |
from traceback import print_exception |
|---|
| 480 |
stderr = self.get_stderr() |
|---|
| 481 |
print_exception( |
|---|
| 482 |
exc_info[0], exc_info[1], exc_info[2], |
|---|
| 483 |
self.traceback_limit, stderr |
|---|
| 484 |
) |
|---|
| 485 |
stderr.flush() |
|---|
| 486 |
finally: |
|---|
| 487 |
exc_info = None |
|---|
| 488 |
|
|---|
| 489 |
def handle_error(self): |
|---|
| 490 |
"""Log current error, and send error output to client if possible""" |
|---|
| 491 |
self.log_exception(sys.exc_info()) |
|---|
| 492 |
if not self.headers_sent: |
|---|
| 493 |
self.result = self.error_output(self.environ, self.start_response) |
|---|
| 494 |
self.finish_response() |
|---|
| 495 |
# XXX else: attempt advanced recovery techniques for HTML or text? |
|---|
| 496 |
|
|---|
| 497 |
def error_output(self, environ, start_response): |
|---|
| 498 |
import traceback |
|---|
| 499 |
start_response(self.error_status, self.error_headers[:], sys.exc_info()) |
|---|
| 500 |
return ['\n'.join(traceback.format_exception(*sys.exc_info()))] |
|---|
| 501 |
|
|---|
| 502 |
# Pure abstract methods; *must* be overridden in subclasses |
|---|
| 503 |
|
|---|
| 504 |
def _write(self,data): |
|---|
| 505 |
self.stdout.write(data) |
|---|
| 506 |
self._write = self.stdout.write |
|---|
| 507 |
|
|---|
| 508 |
def _flush(self): |
|---|
| 509 |
self.stdout.flush() |
|---|
| 510 |
self._flush = self.stdout.flush |
|---|
| 511 |
|
|---|
| 512 |
def get_stdin(self): |
|---|
| 513 |
return self.stdin |
|---|
| 514 |
|
|---|
| 515 |
def get_stderr(self): |
|---|
| 516 |
return self.stderr |
|---|
| 517 |
|
|---|
| 518 |
def add_cgi_vars(self): |
|---|
| 519 |
self.environ.update(self.base_env) |
|---|
| 520 |
|
|---|
| 521 |
class WSGIServer(HTTPServer): |
|---|
| 522 |
"""BaseHTTPServer that implements the Python WSGI protocol""" |
|---|
| 523 |
application = None |
|---|
| 524 |
|
|---|
| 525 |
def server_bind(self): |
|---|
| 526 |
"""Override server_bind to store the server name.""" |
|---|
| 527 |
try: |
|---|
| 528 |
HTTPServer.server_bind(self) |
|---|
| 529 |
except Exception, e: |
|---|
| 530 |
raise WSGIServerException, e |
|---|
| 531 |
self.setup_environ() |
|---|
| 532 |
|
|---|
| 533 |
def setup_environ(self): |
|---|
| 534 |
# Set up base environment |
|---|
| 535 |
env = self.base_environ = {} |
|---|
| 536 |
env['SERVER_NAME'] = self.server_name |
|---|
| 537 |
env['GATEWAY_INTERFACE'] = 'CGI/1.1' |
|---|
| 538 |
env['SERVER_PORT'] = str(self.server_port) |
|---|
| 539 |
env['REMOTE_HOST']='' |
|---|
| 540 |
env['CONTENT_LENGTH']='' |
|---|
| 541 |
env['SCRIPT_NAME'] = '' |
|---|
| 542 |
|
|---|
| 543 |
def get_app(self): |
|---|
| 544 |
return self.application |
|---|
| 545 |
|
|---|
| 546 |
def set_app(self,application): |
|---|
| 547 |
self.application = application |
|---|
| 548 |
|
|---|
| 549 |
class WSGIRequestHandler(BaseHTTPRequestHandler): |
|---|
| 550 |
server_version = "WSGIServer/" + __version__ |
|---|
| 551 |
|
|---|
| 552 |
def __init__(self, *args, **kwargs): |
|---|
| 553 |
from django.conf import settings |
|---|
| 554 |
self.admin_media_prefix = settings.ADMIN_MEDIA_PREFIX |
|---|
| 555 |
# We set self.path to avoid crashes in log_message() on unsupported |
|---|
| 556 |
# requests (like "OPTIONS"). |
|---|
| 557 |
self.path = '' |
|---|
| 558 |
BaseHTTPRequestHandler.__init__(self, *args, **kwargs) |
|---|
| 559 |
|
|---|
| 560 |
def get_environ(self): |
|---|
| 561 |
env = self.server.base_environ.copy() |
|---|
| 562 |
env['SERVER_PROTOCOL'] = self.request_version |
|---|
| 563 |
env['REQUEST_METHOD'] = self.command |
|---|
| 564 |
if '?' in self.path: |
|---|
| 565 |
path,query = self.path.split('?',1) |
|---|
| 566 |
else: |
|---|
| 567 |
path,query = self.path,'' |
|---|
| 568 |
|
|---|
| 569 |
env['PATH_INFO'] = urllib.unquote(path) |
|---|
| 570 |
env['QUERY_STRING'] = query |
|---|
| 571 |
env['REMOTE_ADDR'] = self.client_address[0] |
|---|
| 572 |
|
|---|
| 573 |
if self.headers.typeheader is None: |
|---|
| 574 |
< |
|---|