Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Added 'django-admin.py runserver', which starts a lightweight develop…

…ment server running Django on a local port

git-svn-id: http://code.djangoproject.com/svn/django/trunk@174 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit b68c478aa5d890e76aae6e2f695220505618c8e0 1 parent fbfa3d6
Adrian Holovaty authored July 18, 2005
31  django/bin/django-admin.py
@@ -362,6 +362,30 @@ def startapp(app_name, directory):
362 362
 startapp.help_doc = "Creates a Django app directory structure for the given app name in the current directory."
363 363
 startapp.args = "[appname]"
364 364
 
  365
+def runserver(port):
  366
+    "Starts a lightweight Web server for development."
  367
+    from django.core.servers.basehttp import run, WSGIServerException
  368
+    from django.core.handlers.wsgi import WSGIHandler
  369
+    if not port.isdigit():
  370
+        sys.stderr.write("Error: %r is not a valid port number.\n" % port)
  371
+        sys.exit(1)
  372
+    print "Starting server on port %s. Go to http://127.0.0.1:%s/ for Django." % (port, port)
  373
+    try:
  374
+        run(int(port), WSGIHandler())
  375
+    except WSGIServerException, e:
  376
+        # Use helpful error messages instead of ugly tracebacks.
  377
+        ERRORS = {
  378
+            13: "You don't have permission to access that port.",
  379
+            98: "That port is already in use.",
  380
+        }
  381
+        try:
  382
+            error_text = ERRORS[e.args[0].args[0]]
  383
+        except (AttributeError, KeyError):
  384
+            error_text = str(e)
  385
+        sys.stderr.write("Error: %s\n" % error_text)
  386
+        sys.exit(1)
  387
+runserver.args = '[optional port number]'
  388
+
365 389
 def usage():
366 390
     sys.stderr.write("Usage: %s [action]\n" % sys.argv[0])
367 391
 
@@ -376,6 +400,7 @@ def usage():
376 400
 ACTION_MAPPING = {
377 401
     'adminindex': get_admin_index,
378 402
 #     'dbcheck': database_check,
  403
+    'runserver': runserver,
379 404
     'sql': get_sql_create,
380 405
     'sqlall': get_sql_all,
381 406
     'sqlclear': get_sql_delete,
@@ -406,6 +431,12 @@ def usage():
406 431
             usage()
407 432
         ACTION_MAPPING[action](name, os.getcwd())
408 433
         sys.exit(0)
  434
+    elif action == 'runserver':
  435
+        if len(sys.argv) < 3:
  436
+            port = '8000'
  437
+        else:
  438
+            port = sys.argv[2]
  439
+        ACTION_MAPPING[action](port)
409 440
     elif action == 'dbcheck':
410 441
         from django.core import meta
411 442
         mod_list = meta.get_all_installed_modules()
0  django/core/servers/__init__.py
No changes.
586  django/core/servers/basehttp.py
... ...
@@ -0,0 +1,586 @@
  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, TupleType
  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:
  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
+# existance 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:
  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:
  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 Dude, this is whack!"
  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
+    def get_environ(self):
  534
+        env = self.server.base_environ.copy()
  535
+        env['SERVER_PROTOCOL'] = self.request_version
  536
+        env['REQUEST_METHOD'] = self.command
  537
+        if '?' in self.path:
  538
+            path,query = self.path.split('?',1)
  539
+        else:
  540
+            path,query = self.path,''
  541
+
  542
+        env['PATH_INFO'] = urllib.unquote(path)
  543
+        env['QUERY_STRING'] = query
  544
+
  545
+        host = self.address_string()
  546
+        if host != self.client_address[0]:
  547
+            env['REMOTE_HOST'] = host
  548
+        env['REMOTE_ADDR'] = self.client_address[0]
  549
+
  550
+        if self.headers.typeheader is None:
  551
+            env['CONTENT_TYPE'] = self.headers.type
  552
+        else:
  553
+            env['CONTENT_TYPE'] = self.headers.typeheader
  554
+
  555
+        length = self.headers.getheader('content-length')
  556
+        if length:
  557
+            env['CONTENT_LENGTH'] = length
  558
+
  559
+        for h in self.headers.headers:
  560
+            k,v = h.split(':',1)
  561
+            k=k.replace('-','_').upper(); v=v.strip()
  562
+            if k in env:
  563
+                continue                    # skip content length, type,etc.
  564
+            if 'HTTP_'+k in env:
  565
+                env['HTTP_'+k] += ','+v     # comma-separate multiple headers
  566
+            else:
  567
+                env['HTTP_'+k] = v
  568
+        return env
  569
+
  570
+    def get_stderr(self):
  571
+        return sys.stderr
  572
+
  573
+    def handle(self):
  574
+        """Handle a single HTTP request"""
  575
+        self.raw_requestline = self.rfile.readline()
  576
+        if not self.parse_request(): # An error code has been sent, just exit
  577
+            return
  578
+        handler = ServerHandler(self.rfile, self.wfile, self.get_stderr(), self.get_environ())
  579
+        handler.request_handler = self      # backpointer for logging
  580
+        handler.run(self.server.get_app())
  581
+
  582
+def run(port, wsgi_handler):
  583
+    server_address = ('', port)
  584
+    httpd = WSGIServer(server_address, WSGIRequestHandler)
  585
+    httpd.set_app(wsgi_handler)
  586
+    httpd.serve_forever()

0 notes on commit b68c478

Please sign in to comment.
Something went wrong with that request. Please try again.