app/django/core/servers/basehttp.py
changeset 54 03e267d67478
child 323 ff1a9aa48cfd
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     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()