Ticket #14903: 14903.patch

File 14903.patch, 21.7 KB (added by Aymeric Augustin, 9 years ago)
  • django/core/servers/basehttp.py

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