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