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