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